Revision: 9725 https://osdn.net/projects/ttssh2/scm/svn/commits/9725 Author: zmatsuo Date: 2022-02-06 02:46:17 +0900 (Sun, 06 Feb 2022) Log Message: ----------- cygterm Unicode化 - Unicodeフォルダのttermpro.exeを起動できるようになった - 設定に関する部分を cygterm_cfg.cc,h に分離した - cygterm.cfgの読み書きを集める - teraterm/common/ の関数を sub.cpp へコピー - cyterm を単体でビルドすることを考慮するため - cmakeビルド時に tar.gz を生成するようにした - cygterm.rc は生成されるので svn から削除 - ポータブル版仮対応 - 無効化 Modified Paths: -------------- trunk/.editorconfig trunk/cygwin/cygterm/CMakeLists.txt trunk/cygwin/cygterm/Makefile trunk/cygwin/cygterm/README-j trunk/cygwin/cygterm/cygterm.cc trunk/doc/ja/html/setup/folder.md Added Paths: ----------- trunk/cygwin/cygterm/cygterm_cfg.cc trunk/cygwin/cygterm/cygterm_cfg.h trunk/cygwin/cygterm/sub.cpp trunk/cygwin/cygterm/sub.h Removed Paths: ------------- trunk/cygwin/cygterm/cygterm.rc -------------- next part -------------- Modified: trunk/.editorconfig =================================================================== --- trunk/.editorconfig 2022-02-05 17:46:06 UTC (rev 9724) +++ trunk/.editorconfig 2022-02-05 17:46:17 UTC (rev 9725) @@ -4,7 +4,7 @@ indent_style = tab indent_size = 4 -[*.{cpp,c,h}] +[*.{cpp,c,h,cc}] indent_style = tab indent_size = 4 end_of_line = crlf Modified: trunk/cygwin/cygterm/CMakeLists.txt =================================================================== --- trunk/cygwin/cygterm/CMakeLists.txt 2022-02-05 17:46:06 UTC (rev 9724) +++ trunk/cygwin/cygterm/CMakeLists.txt 2022-02-05 17:46:17 UTC (rev 9725) @@ -1,8 +1,11 @@ cmake_minimum_required(VERSION 3.11) +option(UNICODE "use Unicode Win32 API" ON) + message("CMAKE_COMMAND=${CMAKE_COMMAND}") message("CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") message("CMAKE_HOST_SYSTEM_NAME=${CMAKE_HOST_SYSTEM_NAME}") +message("UNICODE=${UNICODE}") if(${CMAKE_HOST_SYSTEM_NAME} MATCHES "MSYS") message("MSYS2TERM=ON") set(MSYS2TERM ON) @@ -17,10 +20,16 @@ project(${PACKAGE_NAME}) ENABLE_LANGUAGE(RC) +file(WRITE "${CMAKE_CURRENT_LIST_DIR}/cygterm.rc" "icon ICON cygterm.ico") + add_executable( ${PACKAGE_NAME} cygterm.cc - cygterm.rc + cygterm_cfg.cc + cygterm_cfg.h + sub.cpp + sub.h + ${CMAKE_CURRENT_LIST_DIR}/cygterm.rc ) if (MSYS2TERM) @@ -42,8 +51,18 @@ PRIVATE -D_GNU_SOURCE -fno-exceptions + -Wall -Wextra ) +if(UNICODE) + target_compile_options( + ${PACKAGE_NAME} + PRIVATE + -DUNICODE=1 + -D_UNICODE=1 + ) +endif() + target_link_options( ${PACKAGE_NAME} PRIVATE @@ -50,6 +69,13 @@ -mwindows ) +target_link_libraries( + ${PACKAGE_NAME} + PRIVATE + shell32 + ole32 + ) + install( TARGETS ${PACKAGE_NAME} DESTINATION . @@ -58,3 +84,31 @@ FILES ${PACKAGE_NAME}.cfg DESTINATION . ) + + +set(ARCHIVE "cygterm+.tar.gz") + +set(SRC + cygterm.cc + cygterm_cfg.cc + cygterm_cfg.h + sub.cpp + sub.h + # + cygterm.ico + # + cygterm.cfg + msys2term.cfg + ) + +add_custom_target( + tar ALL + SOURCES ${CMAKE_CURRENT_LIST_DIR}/${ARCHIVE} + ) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_LIST_DIR}/${ARCHIVE} + DEPENDS ${SRC} + COMMAND ${CMAKE_COMMAND} -E tar cvz ${CMAKE_CURRENT_LIST_DIR}/${ARCHIVE} COPYING README README-j Makefile CMakeLists.txt ${SRC} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + ) Modified: trunk/cygwin/cygterm/Makefile =================================================================== --- trunk/cygwin/cygterm/Makefile 2022-02-05 17:46:06 UTC (rev 9724) +++ trunk/cygwin/cygterm/Makefile 2022-02-05 17:46:17 UTC (rev 9725) @@ -3,12 +3,18 @@ BINDIR = $(HOME)/bin CC = gcc -CFLAGS = -D_GNU_SOURCE -O2 -fno-exceptions -#CFLAGS = -g -fno-exceptions +CFLAGS = -D_GNU_SOURCE -O2 -fno-exceptions -DUNICODE -D_UNICODE +#CFLAGS = -D_GNU_SOURCE -O2 -fno-exceptions +CXXFLAGS = $(CFLAGS) LDFLAGS = -mwindows EXE = cygterm.exe -SRC = $(EXE:.exe=.cc) +SRC = \ + $(EXE:.exe=.cc) \ + cygterm_cfg.cc \ + cygterm_cfg.h \ + sub.cpp \ + sub.h CFG = $(EXE:.exe=.cfg) RES = $(EXE:.exe=.res) ICO = $(EXE:.exe=.ico) @@ -19,20 +25,18 @@ all : $(EXE) $(ARCHIVE) -$(EXE) : $(SRC) $(ICO) $(RC) - windres -O coff -o $(RES) $(RC) - ifeq (0, $(shell nm /usr/lib/crt0.o | grep -c WinMainCRTStartup)) - $(CC) $(CFLAGS) $(LDFLAGS) -DNO_WIN_MAIN -o $(EXE) $(SRC) $(RES) - else - $(CC) $(CFLAGS) $(LDFLAGS) -o $(EXE) $(SRC) $(RES) - endif +$(EXE) : cygterm.o cygterm_cfg.o sub.o $(RES) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(EXE) $^ -lole32 strip $(EXE) +$(RES): $(RC) + windres -O coff -o $(RES) $(RC) + $(RC): echo 'icon ICON $(ICO)' > $(RC) clean : - rm -f $(EXE) $(RC) $(RES) $(ARCHIVE) + rm -f $(EXE) $(RC) $(RES) $(ARCHIVE) *.o *.obj install : $(EXE) @ install -v $(EXE) $(BINDIR)/$(EXE) @@ -44,5 +48,17 @@ rm -f $(BINDIR)/$(EXE) rm -f $(BINDIR)/$(CFG) -$(ARCHIVE) : $(SRC) $(ICO) $(CFG) README README-j Makefile - tar cf - $(SRC) $(ICO) $(CFG) COPYING README README-j Makefile | gzip > $(ARCHIVE) +$(ARCHIVE) : $(SRC) $(ICO) $(CFG) README README-j Makefile CMakeLists.txt + tar cf - $(SRC) $(ICO) $(CFG) COPYING README README-j Makefile CMakeLists.txt msys2term.cfg | gzip > $(ARCHIVE) + +cygterm.o: + ifeq (0, $(shell nm /usr/lib/crt0.o | grep -c WinMainCRTStartup)) + $(CXX) $(CXXFLAGS) -DNO_WIN_MAIN cygterm.cc -c -o $@ + else + $(CXX) $(CXXFLAGS) cygterm.cc -c -o $@ + endif + +# cc -M *.cc *.cpp +cygterm.o: cygterm.cc sub.h cygterm_cfg.h +cygterm_cfg.o: cygterm_cfg.cc cygterm_cfg.h +sub.o: sub.cpp sub.h Modified: trunk/cygwin/cygterm/README-j =================================================================== --- trunk/cygwin/cygterm/README-j 2022-02-05 17:46:06 UTC (rev 9724) +++ trunk/cygwin/cygterm/README-j 2022-02-05 17:46:17 UTC (rev 9725) @@ -91,18 +91,31 @@ BINDIR $B$K(B cygterm.exe $B$H(B cygterm.cfg $B$,%$%s%9%H!<%k$5$l$^$9!#(B cygterm.cfg $B$O>e=q$-$5$l$^$;$s!#(B +// $BF0:n%b!<%I(B // + + cygterm.exe $B$HF1$8%U%)%k%@$K(B portable.ini $B$,$"$k>l9g%]!<%?%V%kHG$H(B + $B$7$FF0:n$7$^$9!#(Bportable.ini $B$,$J$$>l9g$ODL>oHG$H$7$FF0:n$7$^$9!#(B + // $B @ _(B $BDj(B $B%U(B $B%!(B $B%$(B $B%k(B // cygterm.cfg $B$O @ _Dj%U%!%$%k$G$9!#(B $B;HMQ$9$kC<Kv%(%_%e%l!<%?$N%3%^%s%I%i%$%s(B $B$d5/F0$9$k%7%'%k$N%3%^%s%I%i%$%sEy$r @ _Dj$7$^$9!#(B - cygterm.cfg $B$O<!$N=g=x$GFI$_9~$^$l$^$9!#(B - - $B%3%^%s%I%i%$%s0z?t(B - - exe $B$HF1$8%U%)%k%@(B - - /etc/cygterm.conf (cygwin$B$N(B/etc/) - - $APPDATA/teraterm5/cygterm.cfg - - ~/.cygtermrc + cygterm.cfg $B$O<!$N=g=x$GFI$_$^$9!#(B + - exe$B$HF1$8%U%)%k%@$N(B cygterm.cfg + - /etc/cygterm.conf + - $APPDATA/teraterm5/cygterm.cfg + - ~/.cygtermrc + + $SHELL $B$H(B $USER $B$O!"@_Dj%U%!%$%kFI$_9~$_A0$K @ _Dj$5$l$^$9!#(B + $B8e$+$iFI$_9~$s$@CM$G>e=q$-$5$l$^$9!#(B + $B @ _Dj%U%!%$%k$rFI$_9~$s$@$N$A%3%^%s%I%i%$%s%*%W%7%g%s$G;XDj$G>e=q$-$5$l$^$9!#(B + + $B%]!<%?%V%kHG$N$H$-$O!"<!$N%U%!%$%k$N$_$rFI$_9~$_$^$9!#(B + + - $APPDATA/teraterm5/cygterm.cfg + cygterm.cfg $B$NNc(B +----------------------------------------------------------------------- | TERM = C:\program files\ttermpro\ttermpro.exe %s %d /KR=SJIS /KT=SJIS @@ -110,10 +123,10 @@ | PORT_START = 20000 | PORT_RANGE = 40 | SHELL = /bin/bash - | ENV_1 = MAKE_MODE=unix - | ENV_2 = HOME=/home - | : : + $B @ _Dj%U%!%$%k$NJ8;z%3!<%I$O(BUTF-8$B$G$9!#(B + (Cygwin 1.5$B4D6-$G$O(B Shift_JIS $B$G$9!#(B) + TERM ---- $B%?!<%_%J%k!&%(%_%e%l!<%?$N5/F0%3%^%s%I%i%$%s$G$9!#(B @@ -268,9 +281,11 @@ TCP/IP Port# $B$K(B 23 $B$r;XDj$7$F @ _DjJ]B8$7D>$;$P85$KLa$j$^$9!#(B // $BJQ(B $B99(B $BMz(B $BNr(B // -v1.07_30(beta) 2021/11/14 +v1.07_30 2022/02/06 * $BFI$_9~$`@_Dj%U%!%$%k$rDI2C(B $APPDATA/teraterm5/cygterm.cfg + * Unicode$BBP1~(B + * $B%]!<%?%V%kHGBP1~(B($B2>(B) v1.07_29 2016/11/26 (by maya) * $B%"%$%3%s$rJQ99$7$?!#(B Modified: trunk/cygwin/cygterm/cygterm.cc =================================================================== --- trunk/cygwin/cygterm/cygterm.cc 2022-02-05 17:46:06 UTC (rev 9724) +++ trunk/cygwin/cygterm/cygterm.cc 2022-02-05 17:46:17 UTC (rev 9725) @@ -1,1539 +1,1434 @@ -///////////////////////////////////////////////////////////////////////////// -// CygTerm+ - yet another Cygwin console -// Copyright (C) 2000-2006 NSym. -// (C) 2006-2016 TeraTerm Project -//--------------------------------------------------------------------------- -// This program is free software; you can redistribute it and/or modify it -// under the terms of the GNU General Public License (GPL) as published by -// the Free Software Foundation; either version 2 of the License, or (at -// your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -//--------------------------------------------------------------------------- - -///////////////////////////////////////////////////////////////////////////// -// CygTerm+ - yet another Cygwin console -// -// Using Cygwin with a terminal emulator. -// -// Writtern by TeraTerm Project. -// https://ttssh2.osdn.jp/ -// -// Original written by NSym. -// *** Web Pages *** -// (English) http://www.dd.iij4u.or.jp/~nsym/cygwin/cygterm/index-e.html -// (Japanese) http://www.dd.iij4u.or.jp/~nsym/cygwin/cygterm/index.html -// - -static char Program[] = "CygTerm+"; -static char Version[] = "version 1.07_30_beta (2021/11/14)"; - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <ctype.h> -#include <fcntl.h> -#include <unistd.h> -#include <signal.h> -#include <errno.h> -#include <sys/types.h> -#include <sys/ioctl.h> -#include <termios.h> -#include <sys/time.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <sys/wait.h> -#include <arpa/inet.h> -#include <windows.h> -#include <shlobj.h> -#include <pwd.h> -#include <sys/select.h> -#include <wchar.h> - -// pageant support (ssh-agent proxy) -//---------------------------------- -#define AGENT_COPYDATA_ID 0x804e50ba -#define AGENT_MAX_MSGLEN 8192 -char sockdir[] = "/tmp/ssh-XXXXXXXXXX"; -char sockname[256]; - -// PTY device name -//---------------- -#define DEVPTY "/dev/ptmx" - -// TCP port for TELNET -//-------------------- -int port_start = 20000; // default lowest port number -int port_range = 40; // default number of ports - -// command lines of a terminal-emulator and a shell -//------------------------------------------------- -char cmd_term[256] = ""; -char cmd_termopt[256] = ""; -char cmd_shell[128] = ""; -char pw_shell[128] = ""; -char change_dir[256] = ""; - -// TCP port for connection to another terminal application -//-------------------------------------------------------- -int cl_port = 0; - -// telnet socket timeout -//---------------------- -int telsock_timeout = 5; // timeout 5 sec - -// dumb terminal flag -//------------------- -bool dumb = false; - -// chdir to HOME -//-------------- -bool home_chdir = false; - -// login shell flag -//----------------- -bool enable_loginshell = false; - -// ssh agent proxy -//---------------- -bool enable_agent_proxy = false; - -// terminal type & size -//--------------------- -char term_type[41] = ""; -struct winsize win_size = {0,0,0,0}; - -// debug mode -//----------- -bool debug_flag = false; - -// additional env vars given to a shell -//------------------------------------- -struct sh_env_t { - struct sh_env_t* next; - char env[1]; -} sh_env = {NULL, ""}; - -sh_env_t* sh_envp = &sh_env; - -int add_env(sh_env_t** envp, const char* str, const char* str2) -{ - int len; - sh_env_t* e; - - len = strlen(str); - if (str2) { - len += strlen(str2) + 1; - } - - e = (sh_env_t*)malloc(sizeof(sh_env_t) + len); - if (e) { - if (str2) { - snprintf(e->env, len + 1, "%s=%s", str, str2); - } - else { - strcpy(e->env, str); - } - e->next = NULL; - *envp = ((*envp)->next = e); - return 1; - } - else { - return 0; - } -} - -//================// -// message output // -//----------------// -void msg_print(const char* msg) -{ - MessageBox(NULL, msg, Program, MB_OK | MB_ICONINFORMATION | MB_TOPMOST); -} - -//=========================// -// Win32-API error message // -//-------------------------// -void api_error(const char* string = NULL) -{ - char msg[1024]; - char *ptr = msg; - if (string != NULL) - ptr += snprintf(ptr, sizeof(msg), "%s\n\n", string); - FormatMessage( - FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - ptr, sizeof(msg)-(ptr-msg), NULL - ); - msg_print(msg); -} - -//=========================// -// C-runtime error message // -//-------------------------// -void c_error(const char* string = NULL) -{ - char msg[1024]; - char *ptr = msg; - if (string != NULL) - ptr += snprintf(ptr, sizeof(msg), "%s\n\n", string); - snprintf(ptr, sizeof(msg)-(ptr-msg), "%s\n", strerror(errno)); - msg_print(msg); -} - -//======================// -// debug message output // -//======================// -void debug_msg_print(const char* msg) -{ - if (debug_flag) { - msg_print(msg); - } -} - -//==================================// -// parse line in configuration file // -//----------------------------------// -void parse_cfg_line(char *buf) -{ - // "KEY = VALUE" format in each line. - // skip leading/trailing blanks. KEY is not case-sensitive. - char* p1; - for (p1 = buf; isspace(*p1); ++p1); - if (!isalpha(*p1)) { - return; // comment line with non-alphabet 1st char - } - char* name = p1; - for (++p1; isalnum(*p1) || *p1 == '_'; ++p1); - char* p2; - for (p2 = p1; isspace(*p2); ++p2); - if (*p2 != '=') { - return; // igonore line without '=' - } - for (++p2; isspace(*p2); ++p2); - char* val = p2; - for (p2 += strlen(p2); isspace(*(p2-1)); --p2); - *p1 = *p2 = 0; - - if (!strcasecmp(name, "TERM")) { - // terminal emulator command line (host:%s, port#:%d) - strncpy(cmd_term, val, sizeof(cmd_term)-1); - cmd_term[sizeof(cmd_term)-1] = 0; - } - else if (!strcasecmp(name, "SHELL")) { - // shell command line - if (strcasecmp(val, "AUTO") != 0) { - strncpy(cmd_shell, val, sizeof(cmd_shell)-1); - } - else { - strncpy(cmd_shell, pw_shell, sizeof(cmd_shell)-1); - } - cmd_shell[sizeof(cmd_shell)-1] = 0; - } - else if (!strcasecmp(name, "PORT_START")) { - // minimum port# for TELNET - port_start = atoi(val); - } - else if (!strcasecmp(name, "PORT_RANGE")) { - // number of ports for TELNET - port_range = atoi(val); - } - else if (!strcasecmp(name, "TERM_TYPE")) { - // terminal type name (maybe overridden by TELNET negotiation.) - strncpy(term_type, val, sizeof(term_type)-1); - term_type[sizeof(term_type)-1] = 0; - } - else if (!strncasecmp(name, "ENV_", 4)) { - // additional env vars given to a shell - add_env(&sh_envp, val, NULL); - } - else if (!strcasecmp(name, "HOME_CHDIR")) { - // change directory to home - if (strchr("YyTt", *val) != NULL || atoi(val) > 0) { - home_chdir = true; - } - } - else if (!strcasecmp(name, "LOGIN_SHELL")) { - // execute a shell as a login shell - if (strchr("YyTt", *val) != NULL || atoi(val) > 0) { - enable_loginshell = true; - } - } - else if (!strcasecmp(name, "SOCKET_TIMEOUT")) { - // telnet socket timeout - telsock_timeout = atoi(val); - } - else if (!strcasecmp(name, "SSH_AGENT_PROXY")) { - // ssh-agent proxy - if (strchr("YyTt", *val) != NULL || atoi(val) > 0) { - enable_agent_proxy = true; - } - } - else if (!strcasecmp(name, "DEBUG")) { - // debug mode - if (strchr("YyTt", *val) != NULL || atoi(val) > 0) { - debug_flag = true; - } - } - - return; -} - -// '\\' -> '/' -void convert_bs(char *path) -{ - char *p = path; - while(*p != 0) { - if (*p == '\\') { - *p = '/'; - } - p++; - } -} - -// L'\\' -> L'/' -void convert_bsW(wchar_t *path) -{ - wchar_t *p = path; - while(*p != 0) { - if (*p == L'\\') { - *p = L'/'; - } - p++; - } -} - -// wchar -> utf8 -char *convert_utf8_from_wchar(const wchar_t *strW) -{ - size_t mb_len = ::WideCharToMultiByte(CP_UTF8, 0, strW, -1, NULL, 0, NULL, NULL); - char *u8 = (char *)malloc(sizeof(wchar_t) * mb_len); - ::WideCharToMultiByte(CP_UTF8, 0, strW, -1, u8, mb_len, NULL, NULL); - return u8; -} - -// $APPDATA -char *get_appdata_dir() -{ -#if 0 - // link error :-( - wchar_t *home_pathW; - SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &home_pathW); - convert_bsW(home_pathW); - char *home_pathU8 = convert_utf8_from_wchar(home_pathW); - CoTaskMemFree(home_pathW); - return home_pathU8; -#endif -#if 1 - char *appdata = strdup(getenv("APPDATA")); - convert_bs(appdata); - return appdata; -#endif -} - -void get_cfg_filenames(char **cfg_exe_full, char **cfg_appdata_full, char **cfg) -{ - wchar_t win_conf[MAX_PATH]; - - // get cfg path from exe path - if (GetModuleFileNameW(NULL, win_conf, MAX_PATH) <= 0) { - *cfg_exe_full = NULL; - *cfg = NULL; - return; - } - - convert_bsW(win_conf); - - wchar_t* bcW = wcsrchr(win_conf, '/'); - if (bcW != NULL) { - wchar_t* dot = wcsrchr(bcW, '.'); - if (dot == NULL) { - wcscat(bcW, L".cfg"); - } else { - wcscpy(dot, L".cfg"); - } - } - char *u8 = convert_utf8_from_wchar(win_conf); - *cfg_exe_full = u8; - - char *bs = strrchr(u8, '/'); - *cfg = strdup(bs+1); - - char *appdata = get_appdata_dir(); - const char *teraterm = "/teraterm5/"; - size_t len = strlen(appdata) + strlen(teraterm) + strlen(*cfg) + 1; - *cfg_appdata_full = (char *)malloc(sizeof(wchar_t) * len); - strcpy(*cfg_appdata_full, appdata); - strcat(*cfg_appdata_full, teraterm); - strcat(*cfg_appdata_full, *cfg); - free(appdata); -} - -//====================// -// load configuration // -//--------------------// -void load_cfg() -{ - // configuration file (.cfg) path - char *conf_exe_full; - char *conf_appdata_full; - char *conf_base; - get_cfg_filenames(&conf_exe_full, &conf_appdata_full, &conf_base); - - char sys_conf[] = "/etc/cygterm.conf"; - - // user configuration file (~/.*rc) path - char usr_conf[MAX_PATH] = ""; - - // auto generated configuration file path - char tmp_conf[MAX_PATH] = "/tmp/cygtermrc.XXXXXX"; - - // get user name from getlogin(). if it fails, use $USERNAME instead. - // and get /etc/passwd information by getpwnam(3) with user name, - // and generate temporary configuration file by mktemp(3). - const char* username = getlogin(); - if (username == NULL) - username = getenv("USERNAME"); - if (username != NULL) { - struct passwd* pw_ent = getpwnam(username); - if (pw_ent != NULL) { - strncpy(pw_shell, pw_ent->pw_shell, sizeof(pw_shell)-1); - pw_shell[sizeof(pw_shell)-1] = 0; - - strcpy(usr_conf, pw_ent->pw_dir); - strcat(usr_conf, "/."); - strcat(usr_conf, conf_base); - char* dot = strrchr(usr_conf, '.'); - if (dot == NULL) { - strcat(usr_conf, "rc"); - } else { - strcpy(dot, "rc"); - } - } - int fd = mkstemp(tmp_conf); - FILE* fp = fdopen(fd, "w"); - if (fp != NULL) { - if (pw_ent != NULL) { - fprintf(fp, "ENV_1=USER=%s\n", pw_ent->pw_name); - fprintf(fp, "ENV_2=SHELL=%s\n", pw_ent->pw_shell); - fprintf(fp, "SHELL=%s\n", pw_ent->pw_shell); - } else { - fprintf(fp, "ENV_1=USER=%s\n", username); - } - fclose(fp); - } - } - - if (strcmp(usr_conf, "") == 0) { - strcpy(usr_conf, ""); - strcpy(tmp_conf, ""); - } - - char const *conf_path[] = { - tmp_conf, - conf_exe_full, // [exe directory]/cygterm.cfg - sys_conf, // /etc/cygterm.conf - conf_appdata_full, // $APPDATA/teraterm5/cygterm.cfg - usr_conf // ~/cygtermrc - }; - for (int i = 0; i < sizeof(conf_path)/sizeof(conf_path[0]); i++) { - // ignore empty configuration file path - if (strcmp(conf_path[i], "") == 0) { - continue; - } - // read each setting parameter - FILE* fp; - if ((fp = fopen(conf_path[i], "r")) == NULL) { - continue; - } - char buf[BUFSIZ]; - while (fgets(buf, sizeof(buf), fp) != NULL) { - parse_cfg_line(buf); - } - fclose(fp); - } - - // remove temporary configuration file, if it was generated. - if (strcmp(tmp_conf, "") != 0) { - unlink(tmp_conf); - } - - free(conf_base); - free(conf_exe_full); - free(conf_appdata_full); -} - -void quote_cut(char *dst, size_t len, char *src) { - while (*src && len > 1) { - if (*src != '"') { - *dst++ = *src; - } - src++; - } - *dst = 0; -} - -//=======================// -// commandline arguments // -//-----------------------// -void get_args(int argc, char** argv) -{ - char tmp[sizeof(cmd_termopt)]; - - for (++argv; *argv != NULL; ++argv) { - if (!strcmp(*argv, "-t")) { // -t <terminal emulator> - if (*++argv == NULL) - break; - strncpy(cmd_term, *argv, sizeof(cmd_term)-1); - cmd_term[sizeof(cmd_term)-1] = '\0'; - } - else if (!strcmp(*argv, "-p")) { // -p <port#> - if (*(argv+1) != NULL) { - ++argv, cl_port = atoi(*argv); - } - } - else if (!strcmp(*argv, "-dumb")) { // -dumb - dumb = true; - strcpy(term_type, "dumb"); - } - else if (!strcmp(*argv, "-s")) { // -s <shell> - if (*++argv == NULL) - break; - if (strcasecmp(*argv, "AUTO") != 0) { - strncpy(cmd_shell, *argv, sizeof(cmd_shell)-1); - } - else { - strncpy(cmd_shell, pw_shell, sizeof(cmd_shell)-1); - } - cmd_shell[sizeof(cmd_shell)-1] = '\0'; - } - else if (!strcmp(*argv, "-cd")) { // -cd - home_chdir = true; - } - else if (!strcmp(*argv, "-nocd")) { // -nocd - home_chdir = false; - } - else if (!strcmp(*argv, "+cd")) { // +cd - home_chdir = false; - } - else if (!strcmp(*argv, "-ls")) { // -ls - enable_loginshell = true; - } - else if (!strcmp(*argv, "-nols")) { // -nols - enable_loginshell = false; - } - else if (!strcmp(*argv, "+ls")) { // +ls - enable_loginshell = false; - } - else if (!strcmp(*argv, "-A")) { // -A - enable_agent_proxy = true; - } - else if (!strcmp(*argv, "-a")) { // -a - enable_agent_proxy = false; - } - else if (!strcmp(*argv, "-v")) { // -v <additional env var> - if (*(argv+1) != NULL) { - ++argv; - add_env(&sh_envp, *argv, NULL); - } - } - else if (!strcmp(*argv, "-d")) { // -d <exec directory> - if (*++argv == NULL) - break; - quote_cut(change_dir, sizeof(change_dir), *argv); - } - else if (!strcmp(*argv, "-o")) { // -o <additional option for terminal> - if (*++argv == NULL) - break; - if (cmd_termopt[0] == '\0') { - strncpy(cmd_termopt, *argv, sizeof(cmd_termopt)-1); - cmd_termopt[sizeof(cmd_termopt)-1] = '\0'; - } - else { - snprintf(tmp, sizeof(tmp), "%s %s", cmd_termopt, *argv); - strncpy(cmd_termopt, tmp, sizeof(cmd_termopt)-1); - cmd_termopt[sizeof(cmd_termopt)-1] = '\0'; - } - } - else if (!strcmp(*argv, "-debug")) { // -debug - debug_flag = true; - } - } -} - -//===================================// -// pageant support (ssh-agent proxy) // -//-----------------------------------// -unsigned long get_uint32(unsigned char *buff) -{ - return ((unsigned long)buff[0] << 24) + - ((unsigned long)buff[1] << 16) + - ((unsigned long)buff[2] << 8) + - ((unsigned long)buff[3]); -} - -void set_uint32(unsigned char *buff, unsigned long v) -{ - buff[0] = (unsigned char)(v >> 24); - buff[1] = (unsigned char)(v >> 16); - buff[2] = (unsigned char)(v >> 8); - buff[3] = (unsigned char)v; - return; -} - -unsigned long agent_request(unsigned char *out, unsigned long out_size, unsigned char *in) -{ - HWND hwnd; - char mapname[25]; - HANDLE fmap = NULL; - unsigned char *p = NULL; - COPYDATASTRUCT cds; - unsigned long len; - unsigned long ret = 0; - - if (out_size < 5) { - return 0; - } - if ((len = get_uint32(in)) > AGENT_MAX_MSGLEN) { - goto agent_error; - } - - hwnd = FindWindow("Pageant", "Pageant"); - if (!hwnd) { - goto agent_error; - } - - sprintf(mapname, "PageantRequest%08x", (unsigned)GetCurrentThreadId()); - fmap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, - 0, AGENT_MAX_MSGLEN, mapname); - if (!fmap) { - goto agent_error; - } - - if ((p = (unsigned char *)MapViewOfFile(fmap, FILE_MAP_WRITE, 0, 0, 0)) == NULL) { - goto agent_error; - } - - cds.dwData = AGENT_COPYDATA_ID; - cds.cbData = strlen(mapname) + 1; - cds.lpData = mapname; - - memcpy(p, in, len + 4); - if (SendMessage(hwnd, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&cds) > 0) { - len = get_uint32(p); - if (out_size >= len + 4) { - memcpy(out, p, len + 4); - ret = len + 4; - } - } - -agent_error: - if (p) { - UnmapViewOfFile(p); - } - if (fmap) { - CloseHandle(fmap); - } - if (ret == 0) { - set_uint32(out, 1); - out[4] = 5; // SSH_AGENT_FAILURE - } - - return ret; -} - -void sighandler(int sig) { - unlink(sockname); - rmdir(sockdir); - exit(0); -}; - -struct connList { - int sock; - int recvlen; - int sendlen; - struct connList *next; - unsigned char ibuff[AGENT_MAX_MSGLEN]; - unsigned char obuff[AGENT_MAX_MSGLEN]; -}; - -int proc_recvd(struct connList *conn) -{ - int reqlen, len; - - if (conn->sendlen > 0) { - return 0; - } - - if (conn->recvlen < 4) { - return 0; - } - - reqlen = get_uint32(conn->ibuff) + 4; - if (conn->recvlen < reqlen) { - return 0; - } - - len = agent_request(conn->obuff, sizeof(conn->obuff), conn->ibuff); - - if (len > 0) { - conn->sendlen = len; - } - else { - set_uint32(conn->obuff, 1); - conn->obuff[4] = 5; // SSH_AGENT_FAILURE - conn->sendlen = 1; - } - - if (conn->recvlen == reqlen) { - conn->recvlen = 0; - } - else { - conn->recvlen -= reqlen; - memmove(conn->ibuff, conn->ibuff + reqlen, conn->recvlen); - } - - return 1; -} - -void agent_proxy() -{ - int sock, asock, ret; - long len; - unsigned long reqlen; - struct sockaddr_un addr; - unsigned char tmpbuff[AGENT_MAX_MSGLEN]; - struct connList connections, *new_conn, *prev, *cur; - fd_set readfds, writefds, rfds, wfds; - struct sigaction act; - sigset_t blk; - - connections.next = NULL; - - if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { - c_error("agent_proxy: socket failed."); - exit(0); - } - memset(&addr, 0, sizeof(addr)); - addr.sun_family = AF_UNIX; - strlcpy(addr.sun_path, sockname, sizeof(addr.sun_path)); - - if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - goto agent_thread_cleanup; - } - if (listen(sock, -1) < 0) { - goto agent_thread_cleanup; - } - - sigfillset(&blk); - sigdelset(&blk, SIGKILL); - sigdelset(&blk, SIGSTOP); - - memset(&act, 0, sizeof(act)); - act.sa_handler = sighandler; - act.sa_mask = blk; - sigaction(SIGINT, &act, NULL); - sigaction(SIGTERM, &act, NULL); - sigaction(SIGHUP, &act, NULL); - sigaction(SIGQUIT, &act, NULL); - - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_SET(sock, &readfds); - - while (1) { - memcpy(&rfds, &readfds, sizeof(fd_set)); - memcpy(&wfds, &writefds, sizeof(fd_set)); - - select(FD_SETSIZE, &rfds, &wfds, NULL, NULL); - - if (FD_ISSET(sock, &rfds)) { - asock = accept(sock, NULL, NULL); - if (asock < 0) { - if (!(errno == EINTR || errno == ECONNABORTED)) { - break; - } - } - else { - new_conn = (struct connList *)malloc(sizeof(struct connList)); - if (new_conn == NULL) { - // no memory - close(sock); - } - else { - new_conn->sock = asock; - new_conn->recvlen = 0; - new_conn->sendlen = 0; - new_conn->next = connections.next; - connections.next = new_conn; - FD_SET(asock, &readfds); - } - } - } - - prev = &connections; - for (cur=connections.next; cur != NULL; cur = cur->next) { - if (FD_ISSET(cur->sock, &wfds)) { - if (cur->sendlen > 0) { - len = send(cur->sock, cur->obuff, cur->sendlen, 0); - if (len < 0) { - // write error - prev->next = cur->next; - shutdown(cur->sock, SHUT_RDWR); - close(cur->sock); - FD_CLR(cur->sock, &writefds); - FD_CLR(cur->sock, &readfds); - free(cur); - cur = prev; - continue; - } - else if (len >= cur->sendlen) { - cur->sendlen = 0; - - sigprocmask(SIG_BLOCK, &blk, NULL); - ret = proc_recvd(cur); - sigprocmask(SIG_UNBLOCK, &blk, NULL); - - if (ret) { - FD_SET(cur->sock, &writefds); - FD_CLR(cur->sock, &readfds); - } - else { - FD_CLR(cur->sock, &writefds); - FD_SET(cur->sock, &readfds); - } - } - else if (len > 0) { - cur->sendlen -= len; - memmove(cur->obuff, cur->obuff+len, cur->sendlen); - } - } - else { - FD_CLR(cur->sock, &writefds); - } - } - - if (FD_ISSET(cur->sock, &rfds)) { - len = recv(cur->sock, cur->ibuff + cur->recvlen, sizeof(cur->ibuff) - cur->recvlen, 0); - if (len > 0) { - cur->recvlen += len; - - sigprocmask(SIG_BLOCK, &blk, NULL); - ret = proc_recvd(cur); - sigprocmask(SIG_UNBLOCK, &blk, NULL); - - if (ret) { - FD_SET(cur->sock, &writefds); - FD_CLR(cur->sock, &readfds); - } - else { - FD_CLR(cur->sock, &writefds); - FD_SET(cur->sock, &readfds); - } - } - else if (len <= 0) { - // read error - prev->next = cur->next; - shutdown(cur->sock, SHUT_RDWR); - close(cur->sock); - FD_CLR(cur->sock, &readfds); - FD_CLR(cur->sock, &writefds); - free(cur); - cur = prev; - continue; - } - } - } - } - -agent_thread_cleanup: - shutdown(sock, SHUT_RDWR); - close(sock); - - unlink(sockname); - rmdir(sockdir); - - exit(0); -} - -int exec_agent_proxy() -{ - int pid; - int malloc_size; - - if (mkdtemp(sockdir) == NULL) { - return -1; - } - snprintf(sockname, sizeof(sockname), "%s/agent.%ld", sockdir, getpid()); - - if (!add_env(&sh_envp, "SSH_AUTH_SOCK", sockname)) { - return -1; - } - - if ((pid = fork()) < 0) { - return -1; - } - if (pid == 0) { - setsid(); - agent_proxy(); - } - return pid; -} - -//=============================// -// terminal emulator execution // -//-----------------------------// -DWORD WINAPI term_thread(LPVOID) -{ - STARTUPINFO si; - PROCESS_INFORMATION pi; - FillMemory(&si, sizeof(si), 0); - si.cb = sizeof(si); - si.dwFlags = STARTF_USESHOWWINDOW; - si.wShowWindow = SW_SHOW; - DWORD flag = 0; - if (!CreateProcess( - NULL, cmd_term, NULL, NULL, FALSE, flag, NULL, NULL, &si, &pi)) - { - api_error(cmd_term); - return 0; - } - WaitForSingleObject(pi.hProcess, INFINITE); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - return 0; -} - -//============================-==========// -// thread creation for terminal emulator // -//---------------------------------------// -HANDLE exec_term() -{ - DWORD id; - return CreateThread(NULL, 0, term_thread, NULL, 0, &id); -} - -//=======================================// -// listener socket for TELNET connection // -//---------------------------------------// -int listen_telnet(u_short* port) -{ - int lsock; - if ((lsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { - return -1; - } - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - int i; - for (i = 0; i < port_range; ++i) { // find an unused port# - addr.sin_port = htons(port_start + i); - if (bind(lsock, (struct sockaddr *)&addr, sizeof(addr)) == 0) { - break; - } - } - if (i == port_range) { - shutdown(lsock, 2); - close(lsock); - return -1; - } - if (listen(lsock, 1) != 0) { - shutdown(lsock, 2); - close(lsock); - return -1; - } - *port = addr.sin_port; - return lsock; -} - -//=============================// -// accept of TELNET connection // -//-----------------------------// -int accept_telnet(int lsock) -{ - fd_set rbits; - FD_ZERO(&rbits); - FD_SET(lsock, &rbits); - struct timeval tm; - tm.tv_sec = telsock_timeout; - tm.tv_usec = 0; - if (select(FD_SETSIZE, &rbits, 0, 0, &tm) <= 0) { - c_error("accept_telnet: select failed"); - return -1; - } - if (!FD_ISSET(lsock, &rbits)) { - c_error("accept_telnet: FD_ISSET failed"); - return -1; - } - int asock; - struct sockaddr_in addr; - int len = sizeof(addr); - if ((asock = accept(lsock, (struct sockaddr *)&addr, &len)) < 0) { - c_error("accept_telnet: accept failed"); - return -1; - } - if (getpeername(asock, (struct sockaddr *)&addr, &len) != 0) { - c_error("accept_telnet: getpeername failed"); - shutdown(asock, 2); - close(asock); - return -1; - } - if (addr.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) { - // reject it except local connection - msg_print("not local connection"); - shutdown(asock, 2); - close(asock); - return -1; - } - return asock; -} - -//============================// -// connect to specified port# // -//----------------------------// -int connect_client() -{ - int csock; - if ((csock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { - return -1; - } - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - addr.sin_port = htons(cl_port); - if (connect(csock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - close(csock); - return -1; - } - return csock; -} - -//========================================// -// setup *argv[] from a string for exec() // -//----------------------------------------// -void get_argv(char **argv, int maxc, char *s) -{ - int esc, sq, dq; // recognize (\) (') (") and tokenize - int c, argc; - char *p; - esc = sq = dq = 0; - for (argc = 0; argc < maxc-1; ++argc) { - for ( ; isascii(*s) && isspace(*s); ++s); - if (*s == 0) { - break; - } - argv[argc] = p = s; - while ((c = *s) != 0) { - ++s; - if (isspace(c) && !esc && !sq && !dq) { - break; - } - if (c == '\'' && !esc && !dq) { - sq ^= 1; - } else if (c == '"' && !esc && !sq) { - dq ^= 1; - } else if (c == '\\' && !esc) { - esc = 1; - } else { - esc = 0; - *p++ = c; - } - } - *p = 0; - } - // not to judge syntax errors - // if (dq || sq || esc) { syntax error } - // if (argc == maxc) { overflow } - argv[argc] = NULL; -} - -//=================// -// shell execution // -//-----------------// -int exec_shell(int* sh_pid) -{ - char env_term[64]; - // open pty master - int master; - if ((master = open(DEVPTY, O_RDWR)) < 0) { - c_error("exec_shell: master pty open error"); - return -1; - } - int pid; - if ((pid = fork()) < 0) { - c_error("exec_shell: fork failed"); - return -1; - } - if (pid == 0) { - // detach from control tty - setsid(); - // open pty slave - int slave; - if ((slave = open(ptsname(master), O_RDWR)) < 0) { - c_error("exec_shell: slave pty open error"); - exit(0); - } - // stdio redirection - while (slave <= 2) { - if ((slave = dup(slave)) < 0) { - exit(0); - } - } - int fd; - for (fd = 0; fd < 3; ++fd) { - close(fd); - dup(slave); - fcntl(fd, F_SETFD, 0); - } - for (fd = 3; fd < getdtablesize(); ++fd) { - if (fcntl(fd, F_GETFD) == 0) { - close(fd); - } - } - // set env vars - if (*term_type != 0) { - // set terminal type to $TERM - sprintf(env_term, "TERM=%s", term_type); - putenv(env_term); - } - // set other additional env vars - sh_env_t* e; - for (e = sh_env.next; e != NULL; e = e->next) { - putenv(e->env); - } - // change directory - if (change_dir[0] != 0) { - if (chdir(change_dir) < 0) { - char tmp[256]; - snprintf(tmp, 256, "exec_shell: Can't chdir to \"%s\".", change_dir); - tmp[255] = 0; - c_error(tmp); - } - } - else if (home_chdir) { - // chdir to home directory - const char *home_dir = getenv("HOME"); - // ignore chdir(2) system-call error. - chdir(home_dir); - } - // execute a shell - char *argv[32]; - get_argv(argv, 32, cmd_shell); - if (enable_loginshell) { - char shell_path[128]; - char *pos; - strcpy(shell_path, argv[0]); - if ((pos = strrchr(argv[0], '/')) != NULL) { - *pos = '-'; - argv[0] = pos; - } - debug_msg_print(shell_path); - execv(shell_path, argv); - } - else { - debug_msg_print(argv[0]); - execv(argv[0], argv); - } - // no error, exec() doesn't return - c_error(argv[0]); - exit(0); - } - *sh_pid = pid; - return master; -} - -//==================// -// i/o buffer class // -//------------------// -class IOBuf -{ -private: - int fd; - u_char i_buf[4096]; - u_char o_buf[4096]; - int i_pos, i_len, o_pos; -public: - IOBuf(int channel) : fd(channel), i_pos(0), i_len(0), o_pos(0) {} - operator int() { return fd; } - void ungetc() { --i_pos; } - bool flush_in(); - bool getc(u_char*); - bool nextc(u_char*); - bool putc(u_char); - bool flush_out(); -}; - -// read bytes into input buffer -//----------------------------- -bool IOBuf::flush_in() -{ - if ((i_len = read(fd, i_buf, sizeof(i_buf))) <= 0) - return false; - i_pos = 0; - return true; -} - -// get 1 char from input buffer -//----------------------------- -inline bool IOBuf::getc(u_char* c) -{ - if (i_pos == i_len) return false; - *c = i_buf[i_pos++]; - return true; -} - -// get next 1 char from input buffer -//---------------------------------- -inline bool IOBuf::nextc(u_char* c) -{ - if (i_pos == i_len) - if (!flush_in()) return false; - *c = i_buf[i_pos++]; - return true; -} - -// put 1 char to output buffer -//---------------------------- -inline bool IOBuf::putc(u_char c) -{ - if (o_pos == sizeof(o_buf)) - if (!flush_out()) return false; - o_buf[o_pos++] = c; - return true; -} - -// write bytes from output buffer -//------------------------------- -bool IOBuf::flush_out() -{ - int n; - for (int i = 0; i < o_pos; i += n) { - if ((n = write(fd, o_buf+i, o_pos-i)) <= 0) return false; - } - o_pos = 0; - return true; -} - -//=========================// -// TELNET command handling // (see RFC854 TELNET PROTOCOL SPECIFICATION) -//-------------------------// -enum { nIAC=255, nWILL=251, nWONT=252, nDO=253, nDONT=254 }; -enum { sSEND=1, sIS=0, sSB=250, sSE=240 }; -enum { oECHO=1, oSGA=3, oTERM=24, oNAWS=31 }; - -bool c_will_term = false; -bool c_will_naws = false; - -u_char telnet_cmd(IOBuf* te) -{ - u_char cmd, c; - te->nextc(&cmd); - if (cmd == sSB) { - te->nextc(&c); - // accept terminal type request - if (c == oTERM) { // "SB TERM - te->nextc(&c); // IS - u_char* p = (u_char*)term_type; - te->nextc(p); // TERMINAL-TYPE - while (*p != nIAC) { - if (isupper(*p)) *p = _tolower(*p); - ++p; te->nextc(p); - } - *p = 0; - te->nextc(&c); // IAC SE" - return (u_char)oTERM; - } - // accept terminal size request - if (c == oNAWS) { // "SB NAWS - u_short col, row; - te->nextc((u_char*)&col); - te->nextc((u_char*)&col+1); // 00 00 (cols) - te->nextc((u_char*)&row); - te->nextc((u_char*)&row+1); // 00 00 (rows) - te->nextc(&c); - te->nextc(&c); // TAC SE" - win_size.ws_col = ntohs(col); - win_size.ws_row = ntohs(row); - return (u_char)oNAWS; - } - while (c != nIAC) te->nextc(&c); // "... IAC SE" - te->nextc(&c); - } - else if (cmd == nWILL || cmd == nWONT || cmd == nDO || cmd == nDONT) { - u_char c; - te->nextc(&c); - if (cmd == nWILL && c == oTERM) // "WILL TERM" - c_will_term = true; - else if (cmd == nWILL && c == oNAWS) // "WILL NAWS" - c_will_naws = true; - } - return cmd; -} - -//============================// -// TELNET initial negotiation // -//----------------------------// -void telnet_nego(int te_sock) -{ - IOBuf te = te_sock; - u_char c; - - // start terminal type negotiation - // IAC DO TERMINAL-TYPE - te.putc(nIAC); te.putc(nDO); te.putc(oTERM); - te.flush_out(); - te.nextc(&c); - if (c != nIAC) { - te.ungetc(); - return; - } - (void)telnet_cmd(&te); - if (c_will_term) { - // terminal type sub-negotiation - // IAC SB TERMINAL-TYPE SEND IAC SE - te.putc(nIAC); te.putc(sSB); te.putc(oTERM); - te.putc(sSEND); te.putc(nIAC); te.putc(sSE); - te.flush_out(); - // accept terminal type response - te.nextc(&c); - if (c != nIAC) { - te.ungetc(); - return; - } - (void)telnet_cmd(&te); - } - - // start terminal size negotiation - // IAC DO WINDOW-SIZE - te.putc(nIAC); te.putc(nDO); te.putc(oNAWS); - te.flush_out(); - te.nextc(&c); - if (c != nIAC) { - te.ungetc(); - return; - } - (void)telnet_cmd(&te); - if (c_will_naws) { - // accept terminal size response - te.nextc(&c); - if (c != nIAC) { - te.ungetc(); - return; - } - (void)telnet_cmd(&te); - } - - // SGA/ECHO - te.putc(nIAC); te.putc(nWILL); te.putc(oSGA); - te.putc(nIAC); te.putc(nDO); te.putc(oSGA); - te.putc(nIAC); te.putc(nWILL); te.putc(oECHO); - te.flush_out(); -} - -//=============================================// -// relaying of a terminal emulator and a shell // -//---------------------------------------------// -void telnet_session(int te_sock, int sh_pty) -{ - IOBuf te = te_sock; - IOBuf sh = sh_pty; - fd_set rtmp, rbits; - FD_ZERO(&rtmp); - FD_SET(te, &rtmp); - FD_SET(sh, &rtmp); - u_char c; - int cr = 0; - int cnt = 0; - for (;;) { - rbits = rtmp; - if (select(FD_SETSIZE, &rbits, 0, 0, 0) <= 0) { - break; - } - if (FD_ISSET(sh, &rbits)) { - // send data from a shell to a terminal - if (sh.flush_in() == false) { - break; - } - while (sh.getc(&c) == true) { - if (c == nIAC) { - // escape a TELNET IAC char - te.putc(c); - } - te.putc(c); - } - if (te.flush_out() == false) { - break; - } - if (cnt++ < 20) { - continue; // give priority to data from a shell - } - cnt = 0; - } - if (FD_ISSET(te, &rbits)) { - // send data from a terminal to a shell - if (te.flush_in() == false) { - break; - } - while (te.getc(&c) == true) { - if (c == nIAC && !dumb) { - u_char cmd = telnet_cmd(&te) ; - if (cmd == oNAWS) { - // resize pty by terminal size change notice - ioctl(sh_pty, TIOCSWINSZ, &win_size); - continue; - } - if (cmd != nIAC) { - continue; - } - } else if (c == '\r') { - cr = 1; - } else if (c == '\n' || c == '\0') { - if (cr) { // do not send LF or NUL just after CR - cr = 0; - continue; - } - } else { - cr = 0; - } - sh.putc(c); - } - if (sh.flush_out() == false) { - break; - } - } - } -} - -//=========================================================// -// connection of TELNET terminal emulator and Cygwin shell // -//---------------------------------------------------------// -int main(int argc, char** argv) -{ - int listen_sock = -1; - u_short listen_port; - int te_sock = -1; - int sh_pty = -1; - HANDLE hTerm = NULL; - int sh_pid, agent_pid = 0; - - // load configuration - load_cfg(); - - // read commandline arguments - get_args(argc, argv); - - if (cmd_shell[0] == 0) { - msg_print("missing shell"); - return 0; - } - if (cmd_term[0] == 0 && cl_port <= 0) { - msg_print("missing terminal"); - return 0; - } - - if (change_dir[0] != 0) { - home_chdir = false; - if (enable_loginshell) { - add_env(&sh_envp, "CHERE_INVOKING=y", NULL); - } - } - - // terminal side connection - if (cl_port > 0) { - // connect to the specified TCP port - if ((te_sock = connect_client()) < 0) { - goto cleanup; - } - } else { - // prepare a TELNET listener socket - if ((listen_sock = listen_telnet(&listen_port)) < 0) { - goto cleanup; - } - in_addr addr; - addr.s_addr = htonl(INADDR_LOOPBACK); - char tmp[256]; - debug_msg_print("execute terminal"); - snprintf(tmp, sizeof(tmp), cmd_term, inet_ntoa(addr), (int)ntohs(listen_port)); - snprintf(cmd_term, sizeof(cmd_term), "%s %s", tmp, cmd_termopt); - - // execute a terminal emulator - if ((hTerm = exec_term()) == NULL) { - api_error("exec_term failed"); - goto cleanup; - } - // accept connection from the terminal emulator - if ((te_sock = accept_telnet(listen_sock)) < 0) { - goto cleanup; - } - shutdown(listen_sock, 2); - close(listen_sock); - listen_sock = -1; - } - // TELNET negotiation - if (!dumb) { - telnet_nego(te_sock); - } - - // execute ssh-agent proxy - if (enable_agent_proxy) { - agent_pid = exec_agent_proxy(); - } - - // execute a shell - debug_msg_print("execute shell"); - if ((sh_pty = exec_shell(&sh_pid)) < 0) { - debug_msg_print("exec_shell failed"); - goto cleanup; - } - // set initial pty window size - if (!dumb && c_will_naws && win_size.ws_col != 0) { - ioctl(sh_pty, TIOCSWINSZ, &win_size); - } - - debug_msg_print("entering telnet session"); - // relay the terminal emulator and the shell - telnet_session(te_sock, sh_pty); - - cleanup: - if (agent_pid > 0) { - kill(agent_pid, SIGTERM); - } - if (sh_pty >= 0) { - close(sh_pty); - kill(sh_pid, SIGKILL); - } - if (agent_pid > 0 || sh_pty >= 0) { - wait((int*)NULL); - } - if (listen_sock >= 0) { - shutdown(listen_sock, 2); - close(listen_sock); - } - if (te_sock >= 0) { - shutdown(te_sock, 2); - close(te_sock); - } - if (hTerm != NULL) { - WaitForSingleObject(hTerm, INFINITE); - CloseHandle(hTerm); - } - return 0; -} - -#ifdef NO_WIN_MAIN -// This program is an Win32 application but, start as Cygwin main(). -//------------------------------------------------------------------ -extern "C" { - void mainCRTStartup(void); - void WinMainCRTStartup(void) { mainCRTStartup(); } -}; -#endif - -//EOF +///////////////////////////////////////////////////////////////////////////// +// CygTerm+ - yet another Cygwin console +// Copyright (C) 2000-2006 NSym. +// (C) 2006- TeraTerm Project +//--------------------------------------------------------------------------- +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License (GPL) as published by +// the Free Software Foundation; either version 2 of the License, or (at +// your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +//--------------------------------------------------------------------------- + +///////////////////////////////////////////////////////////////////////////// +// CygTerm+ - yet another Cygwin console +// +// Using Cygwin with a terminal emulator. +// +// Writtern by TeraTerm Project. +// https://ttssh2.osdn.jp/ +// +// Original written by NSym. +// + +#if !defined(__CYGWIN__) +#error check compiler +#endif + +// MessageBox\x82̃^\x83C\x83g\x83\x8B\x82Ŏg\x97p TODO exe\x83t\x83@\x83C\x83\x8B\x96\xBC\x82ɕύX +static char Program[] = "CygTerm+"; +//static char Version[] = "version 1.07_30_beta (2021/11/14)"; + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <fcntl.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <termios.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <arpa/inet.h> +#include <windows.h> +#include <shlobj.h> +#include <pwd.h> +#include <sys/select.h> +#include <wchar.h> + +#include "sub.h" + +#include "cygterm_cfg.h" + +// pageant support (ssh-agent proxy) +//---------------------------------- +#define AGENT_COPYDATA_ID 0x804e50ba +#define AGENT_MAX_MSGLEN 8192 +char sockdir[] = "/tmp/ssh-XXXXXXXXXX"; +char sockname[256]; + +// PTY device name +//---------------- +#define DEVPTY "/dev/ptmx" + +// TCP port for TELNET +//-------------------- +#define PORT_START_DEFAULT 20000 // default lowest port number +#define PORT_RANGE_DEFAULT 40 // default number of ports + +// TCP port for connection to another terminal application +//-------------------------------------------------------- +int cl_port = 0; +u_short listen_port; + +// telnet socket timeout +//---------------------- +#define TELSOCK_TIMEOUT_DEFAULT 5 // timeout 5 sec + +// chdir to HOME +//-------------- +#define HOME_CHDIR_DEFAULT false + +// login shell flag +//----------------- +#define ENABLE_LOGINSHELL_DEFAULT false + +// ssh agent proxy +//---------------- +#define ENABLE_AGENT_PROXY_DEFAULT false + +// debug mode +//----------- +#define DEBUG_FLAG_DEFAULT false; +bool debug_flag = DEBUG_FLAG_DEFAULT; + +// "cygterm.cfg" +static char *cfg_base; // "cygterm.cfg" +static char *cfg_exe; // [exe directory]/cygterm.cfg +static char *conf_appdata_full; // $APPDATA/teraterm5/cygterm.cfg +static char *sys_conf; // /etc/cygterm.conf +static char *usr_conf; // ~/cygtermrc $HOME/cygtermrc + +//================// +// message output // +//----------------// +// msg \x82\xCD ANSI\x95\xB6\x8E\x9A\x83R\x81[\x83h (UTF8\x82͉\xBB\x82\xAF\x82\xE9) +void msg_print(const char* msg) +{ + OutputDebugStringA(msg); + MessageBoxA(NULL, msg, Program, MB_OK | MB_ICONINFORMATION | MB_TOPMOST); +} + +//=========================// +// Win32-API error message // +//-------------------------// +void api_error(const char* string = NULL) +{ + char msg[1024]; + char *ptr = msg; + if (string != NULL) + ptr += snprintf(ptr, sizeof(msg), "%s\n\n", string); + FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + ptr, sizeof(msg)-(ptr-msg), NULL + ); + msg_print(msg); +} + +//=========================// +// C-runtime error message // +//-------------------------// +void c_error(const char* string = NULL) +{ + char msg[1024]; + char *ptr = msg; + if (string != NULL) + ptr += snprintf(ptr, sizeof(msg), "%s\n\n", string); + snprintf(ptr, sizeof(msg)-(ptr-msg), "%s\n", strerror(errno)); + msg_print(msg); +} + +//======================// +// debug message output // +//======================// +void debug_msg_print(const char* msg, ...) +{ + if (debug_flag) { + char *tmp1; + va_list arg; + va_start(arg, msg); + vasprintf(&tmp1, msg, arg); + va_end(arg); + + char *tmp2; + unsigned long pid = GetCurrentProcessId(); + asprintf(&tmp2, "dbg %lu: %s\n", pid, tmp1); + OutputDebugStringA(tmp2); + // printf("%s", tmp2); + free(tmp2); + free(tmp1); + } +} + +static void get_cfg_filenames() +{ + char *argv0 = GetModuleFileNameU8(); + // cfg base filename "cygterm.cfg" + char *p = strrchr(argv0, '.'); + *p = 0; // cut ".exe" + p = strrchr(argv0, '/') + 1; + cfg_base = (char *)malloc(strlen(p) + 5); + strcpy(cfg_base, p); + strcat(cfg_base, ".cfg"); + + // exe path + cfg_exe = (char *)malloc(strlen(argv0) + strlen(cfg_base)); + strcpy(cfg_exe, argv0); + p = strrchr(cfg_exe, '/') + 1; + strcpy(p, cfg_base); + free(argv0); + argv0 = NULL; + + // home $HOME/cygtermrc + const char *home = getenv("HOME"); + usr_conf = (char *)malloc(strlen(home) + strlen(cfg_base) + 2); + strcpy(usr_conf, home); + strcat(usr_conf, "/."); + strcat(usr_conf, cfg_base); + p = strrchr(usr_conf, '.'); // ".cfg" -> "rc" + strcpy(p, "rc"); + + // system + sys_conf = (char *)malloc(sizeof("/etc/") + strlen(cfg_base) + 2); + strcpy(sys_conf, "/etc/"); + strcat(sys_conf, cfg_base); + p = strrchr(sys_conf, '.'); + strcpy(p, ".conf"); // ".cfg" -> ".conf" + + // $APPDATA/teraterm5/cygterm.cfg + char *appdata = GetAppDataDirU8(); + const char *teraterm = "/teraterm5/"; + size_t len = strlen(appdata) + strlen(teraterm) + strlen(cfg_base) + 1; + conf_appdata_full = (char *)malloc(sizeof(char) * len); + strcpy(conf_appdata_full, appdata); + strcat(conf_appdata_full, teraterm); + strcat(conf_appdata_full, cfg_base); + free(appdata); +} + +/** + * read /etc/passwd + * get user name from getlogin(). if it fails, use $USERNAME instead. + * and get /etc/passwd information by getpwnam(3) with user name, + */ +static void get_username_and_shell(cfg_data_t *cfg) +{ + const char* username = getlogin(); + if (username == NULL) + username = getenv("USERNAME"); + if (username != NULL) { + struct passwd* pw_ent = getpwnam(username); + if (pw_ent != NULL) { + free(cfg->shell); + cfg->shell = strdup(pw_ent->pw_shell); + free(cfg->username); + cfg->username = strdup(pw_ent->pw_name); + } + else { + free(cfg->username); + cfg->username = strdup(username); + } + } +} + +#define _countof(_Array) (sizeof(_Array) / sizeof(_Array[0])) + +//====================// +// load configuration // +//--------------------// +static void load_cfg(cfg_data_t *cfg) +{ + // \x90ݒ\xE8\x83t\x83@\x83C\x83\x8B\x93ǂݍ\x9E\x82ݏ\x87 + // \x83\x8A\x83X\x83g\x82̏\xE3\x82̂ق\xA4\x82\xA9\x82\xE7\x90\xE6\x82ɓǂݍ\x9E\x82܂\xEA\x82\xE9 + // \x83\x8A\x83X\x83g\x82̉\xBA\x82̂ق\xA4\x82\xA9\x82\xE7\x8C\xE3\x82ɓǂݍ\x9E\x82܂\xEA\x82\xE9(\x82\xA0\x82Ə\x9F\x82\xBF) + // \x89\xBA\x82̕\xFB\x82\xAA\x97D\x90揇\x88ʂ\xAA\x8D\x82\x82\xA2 + // \x92ʏ펞 + char *conf_order_normal_list[] = { + cfg_exe, // [exe directory]/cygterm.cfg + sys_conf, // /etc/cygterm.conf + conf_appdata_full, // $APPDATA/teraterm5/cygterm.cfg + usr_conf // ~/cygtermrc + }; + const int conf_order_normal_count = (int)_countof(conf_order_normal_list); + + // \x83|\x81[\x83^\x83u\x83\x8B\x8E\x9E + char *conf_order_portable_list[] = { + cfg_exe, // [exe directory]/cygterm.cfg + }; + const int conf_order_portable_count = (int)_countof(conf_order_portable_list); + + char **conf_order_list; + int conf_order_count; + if (IsPortableMode()) { + // \x83|\x81[\x83^\x83u\x83\x8B\x8E\x9E + conf_order_list = conf_order_portable_list; + conf_order_count = conf_order_portable_count; + } + else { + // \x92ʏ펞 + conf_order_list = conf_order_normal_list; + conf_order_count = conf_order_normal_count; + } + + // \x8E\xC0\x8Dۂɓǂݍ\x9E\x82\xDE + for (int i = 0; i < conf_order_count; i++) { + const char *fname = conf_order_list[i]; + debug_msg_print("load %s", fname); + // ignore empty configuration file path + if (fname == NULL || strcmp(fname, "") == 0) { + debug_msg_print(" pass"); + continue; + } + + bool r = cfg->load(cfg, fname); + debug_msg_print(" %s", r ? "ok" : "ng"); + cfg->dump(cfg, debug_msg_print); + } +} + +void quote_cut(char *dst, size_t len, char *src) { + while (*src && len > 1) { + if (*src != '"') { + *dst++ = *src; + } + src++; + } + *dst = 0; +} + +//=======================// +// commandline arguments // +//-----------------------// +void get_args(char** argv, cfg_data_t *cfg) +{ + for (++argv; *argv != NULL; ++argv) { + if (!strcmp(*argv, "-t")) { // -t <terminal emulator> + if (*++argv == NULL) + break; + free(cfg->term); + cfg->term = strdup(*argv); + } + else if (!strcmp(*argv, "-p")) { // -p <port#> + if (*(argv+1) != NULL) { + ++argv; + cfg->cl_port = atoi(*argv); + } + } + else if (!strcmp(*argv, "-dumb")) { // -dumb + cfg->dumb = 1; + free(cfg->term_type); + cfg->term_type = strdup("dumb"); + } + else if (!strcmp(*argv, "-s")) { // -s <shell> + if (*++argv == NULL) + break; + if (strcasecmp(*argv, "AUTO") != 0) { + free(cfg->shell); + cfg->shell = strdup(*argv); + } + } + else if (!strcmp(*argv, "-cd")) { // -cd + cfg->home_chdir = true; + } + else if (!strcmp(*argv, "-nocd")) { // -nocd + cfg->home_chdir = false; + } + else if (!strcmp(*argv, "+cd")) { // +cd + cfg->home_chdir = false; + } + else if (!strcmp(*argv, "-ls")) { // -ls + cfg->enable_loginshell = true; + } + else if (!strcmp(*argv, "-nols")) { // -nols + cfg->enable_loginshell = false; + } + else if (!strcmp(*argv, "+ls")) { // +ls + cfg->enable_loginshell = false; + } + else if (!strcmp(*argv, "-A")) { // -A + cfg->enable_agent_proxy = true; + } + else if (!strcmp(*argv, "-a")) { // -a + cfg->enable_agent_proxy = false; + } + else if (!strcmp(*argv, "-v")) { // -v <additional env var> + if (*(argv+1) != NULL) { + sh_env_t *sh_env = cfg->sh_env; + ++argv; + sh_env->add1(sh_env, *argv); + } + } + else if (!strcmp(*argv, "-d")) { // -d <exec directory> + if (*++argv == NULL) + break; + char change_dir[256] = ""; + quote_cut(change_dir, sizeof(change_dir), *argv); + cfg->change_dir = strdup(change_dir); + } + else if (!strcmp(*argv, "-o")) { // -o <additional option for terminal> + if (*++argv == NULL) + break; + free(cfg->termopt); + cfg->termopt = strdup(*argv); + } + else if (!strcmp(*argv, "-debug")) { // -debug + cfg->debug_flag = true; + } + } +} + +//===================================// +// pageant support (ssh-agent proxy) // +//-----------------------------------// +unsigned long get_uint32(unsigned char *buff) +{ + return ((unsigned long)buff[0] << 24) + + ((unsigned long)buff[1] << 16) + + ((unsigned long)buff[2] << 8) + + ((unsigned long)buff[3]); +} + +void set_uint32(unsigned char *buff, unsigned long v) +{ + buff[0] = (unsigned char)(v >> 24); + buff[1] = (unsigned char)(v >> 16); + buff[2] = (unsigned char)(v >> 8); + buff[3] = (unsigned char)v; + return; +} + +unsigned long agent_request(unsigned char *out, unsigned long out_size, unsigned char *in) +{ + HWND hwnd; + char mapname[25]; + HANDLE fmap = NULL; + unsigned char *p = NULL; + COPYDATASTRUCT cds; + unsigned long len; + unsigned long ret = 0; + + if (out_size < 5) { + return 0; + } + if ((len = get_uint32(in)) > AGENT_MAX_MSGLEN) { + goto agent_error; + } + + hwnd = FindWindowA("Pageant", "Pageant"); + if (!hwnd) { + goto agent_error; + } + + sprintf(mapname, "PageantRequest%08x", (unsigned)GetCurrentThreadId()); + fmap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, + 0, AGENT_MAX_MSGLEN, mapname); + if (!fmap) { + goto agent_error; + } + + if ((p = (unsigned char *)MapViewOfFile(fmap, FILE_MAP_WRITE, 0, 0, 0)) == NULL) { + goto agent_error; + } + + cds.dwData = AGENT_COPYDATA_ID; + cds.cbData = strlen(mapname) + 1; + cds.lpData = mapname; + + memcpy(p, in, len + 4); + if (SendMessageA(hwnd, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&cds) > 0) { + len = get_uint32(p); + if (out_size >= len + 4) { + memcpy(out, p, len + 4); + ret = len + 4; + } + } + +agent_error: + if (p) { + UnmapViewOfFile(p); + } + if (fmap) { + CloseHandle(fmap); + } + if (ret == 0) { + set_uint32(out, 1); + out[4] = 5; // SSH_AGENT_FAILURE + } + + return ret; +} + +void sighandler(int sig) { + (void)sig; + unlink(sockname); + rmdir(sockdir); + exit(0); +}; + +struct connList { + int sock; + int recvlen; + int sendlen; + struct connList *next; + unsigned char ibuff[AGENT_MAX_MSGLEN]; + unsigned char obuff[AGENT_MAX_MSGLEN]; +}; + +int proc_recvd(struct connList *conn) +{ + int reqlen, len; + + if (conn->sendlen > 0) { + return 0; + } + + if (conn->recvlen < 4) { + return 0; + } + + reqlen = get_uint32(conn->ibuff) + 4; + if (conn->recvlen < reqlen) { + return 0; + } + + len = agent_request(conn->obuff, sizeof(conn->obuff), conn->ibuff); + + if (len > 0) { + conn->sendlen = len; + } + else { + set_uint32(conn->obuff, 1); + conn->obuff[4] = 5; // SSH_AGENT_FAILURE + conn->sendlen = 1; + } + + if (conn->recvlen == reqlen) { + conn->recvlen = 0; + } + else { + conn->recvlen -= reqlen; + memmove(conn->ibuff, conn->ibuff + reqlen, conn->recvlen); + } + + return 1; +} + +void agent_proxy() +{ + int sock, asock, ret; + long len; + struct sockaddr_un addr; + struct connList connections, *new_conn, *prev, *cur; + fd_set readfds, writefds, rfds, wfds; + struct sigaction act; + sigset_t blk; + + connections.next = NULL; + + if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { + c_error("agent_proxy: socket failed."); + exit(0); + } + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strlcpy(addr.sun_path, sockname, sizeof(addr.sun_path)); + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + goto agent_thread_cleanup; + } + if (listen(sock, -1) < 0) { + goto agent_thread_cleanup; + } + + sigfillset(&blk); + sigdelset(&blk, SIGKILL); + sigdelset(&blk, SIGSTOP); + + memset(&act, 0, sizeof(act)); + act.sa_handler = sighandler; + act.sa_mask = blk; + sigaction(SIGINT, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGQUIT, &act, NULL); + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_SET(sock, &readfds); + + while (1) { + memcpy(&rfds, &readfds, sizeof(fd_set)); + memcpy(&wfds, &writefds, sizeof(fd_set)); + + select(FD_SETSIZE, &rfds, &wfds, NULL, NULL); + + if (FD_ISSET(sock, &rfds)) { + asock = accept(sock, NULL, NULL); + if (asock < 0) { + if (!(errno == EINTR || errno == ECONNABORTED)) { + break; + } + } + else { + new_conn = (struct connList *)malloc(sizeof(struct connList)); + if (new_conn == NULL) { + // no memory + close(sock); + } + else { + new_conn->sock = asock; + new_conn->recvlen = 0; + new_conn->sendlen = 0; + new_conn->next = connections.next; + connections.next = new_conn; + FD_SET(asock, &readfds); + } + } + } + + prev = &connections; + for (cur=connections.next; cur != NULL; cur = cur->next) { + if (FD_ISSET(cur->sock, &wfds)) { + if (cur->sendlen > 0) { + len = send(cur->sock, cur->obuff, cur->sendlen, 0); + if (len < 0) { + // write error + prev->next = cur->next; + shutdown(cur->sock, SHUT_RDWR); + close(cur->sock); + FD_CLR(cur->sock, &writefds); + FD_CLR(cur->sock, &readfds); + free(cur); + cur = prev; + continue; + } + else if (len >= cur->sendlen) { + cur->sendlen = 0; + + sigprocmask(SIG_BLOCK, &blk, NULL); + ret = proc_recvd(cur); + sigprocmask(SIG_UNBLOCK, &blk, NULL); + + if (ret) { + FD_SET(cur->sock, &writefds); + FD_CLR(cur->sock, &readfds); + } + else { + FD_CLR(cur->sock, &writefds); + FD_SET(cur->sock, &readfds); + } + } + else if (len > 0) { + cur->sendlen -= len; + memmove(cur->obuff, cur->obuff+len, cur->sendlen); + } + } + else { + FD_CLR(cur->sock, &writefds); + } + } + + if (FD_ISSET(cur->sock, &rfds)) { + len = recv(cur->sock, cur->ibuff + cur->recvlen, sizeof(cur->ibuff) - cur->recvlen, 0); + if (len > 0) { + cur->recvlen += len; + + sigprocmask(SIG_BLOCK, &blk, NULL); + ret = proc_recvd(cur); + sigprocmask(SIG_UNBLOCK, &blk, NULL); + + if (ret) { + FD_SET(cur->sock, &writefds); + FD_CLR(cur->sock, &readfds); + } + else { + FD_CLR(cur->sock, &writefds); + FD_SET(cur->sock, &readfds); + } + } + else if (len <= 0) { + // read error + prev->next = cur->next; + shutdown(cur->sock, SHUT_RDWR); + close(cur->sock); + FD_CLR(cur->sock, &readfds); + FD_CLR(cur->sock, &writefds); + free(cur); + cur = prev; + continue; + } + } + } + } + +agent_thread_cleanup: + shutdown(sock, SHUT_RDWR); + close(sock); + + unlink(sockname); + rmdir(sockdir); + + exit(0); +} + +static int exec_agent_proxy(sh_env_t *sh_env) +{ + int pid; + + if (mkdtemp(sockdir) == NULL) { + return -1; + } + snprintf(sockname, sizeof(sockname), "%s/agent.%ld", sockdir, (long)getpid()); + + if (sh_env->add(sh_env, "SSH_AUTH_SOCK", sockname)) { + return -1; + } + + if ((pid = fork()) < 0) { + return -1; + } + if (pid == 0) { + setsid(); + agent_proxy(); + } + return pid; +} + +//=============================// +// terminal emulator execution // +//-----------------------------// +DWORD WINAPI term_thread(LPVOID param) +{ + cfg_data_t *cfg = (cfg_data_t *)param; + + in_addr addr; + addr.s_addr = htonl(INADDR_LOOPBACK); + char *term; + asprintf(&term, cfg->term, inet_ntoa(addr), (int)ntohs(listen_port)); + if (cfg->termopt != NULL) { + char *tmp; + asprintf(&tmp, "%s %s", tmp, cfg->termopt); + free(term); + term = tmp; + } + + debug_msg_print("CreateProcess '%s'", term); + + STARTUPINFO si; + PROCESS_INFORMATION pi; + FillMemory(&si, sizeof(si), 0); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_SHOW; + DWORD flag = 0; + +#if defined(UNICODE) + wchar_t *termT = ToWcharU8(term); +#else + char *termT = strdup(term); +#endif + + BOOL r = + CreateProcess( + NULL, termT, NULL, NULL, FALSE, flag, NULL, NULL, &si, &pi); + free(termT); + if (!r) { + api_error(term); + return 0; + } + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return 0; +} + +//=======================================// +// thread creation for terminal emulator // +//---------------------------------------// +HANDLE exec_term(cfg_data_t *cfg) +{ + DWORD id; + return CreateThread(NULL, 0, term_thread, cfg, 0, &id); +} + +//=======================================// +// listener socket for TELNET connection // +//---------------------------------------// +int listen_telnet(u_short* port, cfg_data_t *cfg) +{ + int lsock; + if ((lsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + return -1; + } + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + int i; + for (i = 0; i < cfg->port_range; ++i) { // find an unused port# + addr.sin_port = htons(cfg->port_start + i); + if (bind(lsock, (struct sockaddr *)&addr, sizeof(addr)) == 0) { + break; + } + } + if (i == cfg->port_range) { + shutdown(lsock, 2); + close(lsock); + return -1; + } + if (listen(lsock, 1) != 0) { + shutdown(lsock, 2); + close(lsock); + return -1; + } + *port = addr.sin_port; + return lsock; +} + +//=============================// +// accept of TELNET connection // +//-----------------------------// +int accept_telnet(int lsock, cfg_data_t *cfg) +{ + fd_set rbits; + FD_ZERO(&rbits); + FD_SET(lsock, &rbits); + struct timeval tm; + tm.tv_sec = cfg->telsock_timeout; + tm.tv_usec = 0; + if (select(FD_SETSIZE, &rbits, 0, 0, &tm) <= 0) { + c_error("accept_telnet: select failed"); + return -1; + } + if (!FD_ISSET(lsock, &rbits)) { + c_error("accept_telnet: FD_ISSET failed"); + return -1; + } + int asock; + struct sockaddr_in addr; + int len = sizeof(addr); + if ((asock = accept(lsock, (struct sockaddr *)&addr, &len)) < 0) { + c_error("accept_telnet: accept failed"); + return -1; + } + if (getpeername(asock, (struct sockaddr *)&addr, &len) != 0) { + c_error("accept_telnet: getpeername failed"); + shutdown(asock, 2); + close(asock); + return -1; + } + if (addr.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) { + // reject it except local connection + msg_print("not local connection"); + shutdown(asock, 2); + close(asock); + return -1; + } + return asock; +} + +//============================// +// connect to specified port# // +//----------------------------// +int connect_client() +{ + int csock; + if ((csock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + return -1; + } + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = htons(cl_port); + if (connect(csock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(csock); + return -1; + } + return csock; +} + +//========================================// +// setup *argv[] from a string for exec() // +//----------------------------------------// +void get_argv(char **argv, int maxc, char *s) +{ + int esc, sq, dq; // recognize (\) (') (") and tokenize + int c, argc; + char *p; + esc = sq = dq = 0; + for (argc = 0; argc < maxc-1; ++argc) { + for ( ; isascii(*s) && isspace(*s); ++s); + if (*s == 0) { + break; + } + argv[argc] = p = s; + while ((c = *s) != 0) { + ++s; + if (isspace(c) && !esc && !sq && !dq) { + break; + } + if (c == '\'' && !esc && !dq) { + sq ^= 1; + } else if (c == '"' && !esc && !sq) { + dq ^= 1; + } else if (c == '\\' && !esc) { + esc = 1; + } else { + esc = 0; + *p++ = c; + } + } + *p = 0; + } + // not to judge syntax errors + // if (dq || sq || esc) { syntax error } + // if (argc == maxc) { overflow } + argv[argc] = NULL; +} + +//=================// +// shell execution // +//-----------------// +static int exec_shell(int* sh_pid, cfg_data_t *cfg) +{ + // open pty master + int master; + if ((master = open(DEVPTY, O_RDWR)) < 0) { + c_error("exec_shell: master pty open error"); + return -1; + } + int pid; + if ((pid = fork()) < 0) { + c_error("exec_shell: fork failed"); + return -1; + } + if (pid == 0) { + // detach from control tty + setsid(); + // open pty slave + int slave; + if ((slave = open(ptsname(master), O_RDWR)) < 0) { + c_error("exec_shell: slave pty open error"); + exit(0); + } + // stdio redirection + while (slave <= 2) { + if ((slave = dup(slave)) < 0) { + exit(0); + } + } + int fd; + for (fd = 0; fd < 3; ++fd) { + close(fd); + dup(slave); + fcntl(fd, F_SETFD, 0); + } + for (fd = 3; fd < getdtablesize(); ++fd) { + if (fcntl(fd, F_GETFD) == 0) { + close(fd); + } + } + // set env vars + if (cfg->term_type != NULL) { + // set terminal type to $TERM + setenv("TERM", cfg->term_type, 1); + } + // set other additional env vars + int i = 0; + sh_env_t *sh_env = cfg->sh_env; + while(1) { + char *value; + const char *env = sh_env->get(sh_env, i++, &value); + if (env == NULL) { + break; + } + setenv(env, value, 1); + } + // change directory + if (cfg->change_dir != NULL) { + if (chdir(cfg->change_dir) < 0) { + char tmp[256]; + snprintf(tmp, 256, "exec_shell: Can't chdir to \"%s\".", cfg->change_dir); + tmp[255] = 0; + c_error(tmp); + } + } + else if (cfg->home_chdir) { + // chdir to home directory + const char *home_dir = getenv("HOME"); + // ignore chdir(2) system-call error. + chdir(home_dir); + } + // execute a shell + char *argv[32]; + char *cmd_shell = cfg->shell; + get_argv(argv, 32, cmd_shell); + cfg->shell = strdup(cmd_shell); + if (cfg->enable_loginshell) { + char shell_path[128]; + char *pos; + strcpy(shell_path, argv[0]); + if ((pos = strrchr(argv[0], '/')) != NULL) { + *pos = '-'; + argv[0] = pos; + } + debug_msg_print("execv '%s' (login shell)", shell_path); + execv(shell_path, argv); + } + else { + debug_msg_print("execv '%s'", argv[0]); + execv(argv[0], argv); + } + // no error, exec() doesn't return + c_error(argv[0]); + exit(0); + } + *sh_pid = pid; + return master; +} + +//==================// +// i/o buffer class // +//------------------// +class IOBuf +{ +private: + int fd; + u_char i_buf[4096]; + u_char o_buf[4096]; + int i_pos, i_len, o_pos; +public: + IOBuf(int channel) : fd(channel), i_pos(0), i_len(0), o_pos(0) {} + operator int() { return fd; } + void ungetc() { --i_pos; } + bool flush_in(); + bool getc(u_char*); + bool nextc(u_char*); + bool putc(u_char); + bool flush_out(); +}; + +// read bytes into input buffer +//----------------------------- +bool IOBuf::flush_in() +{ + if ((i_len = read(fd, i_buf, sizeof(i_buf))) <= 0) + return false; + i_pos = 0; + return true; +} + +// get 1 char from input buffer +//----------------------------- +inline bool IOBuf::getc(u_char* c) +{ + if (i_pos == i_len) return false; + *c = i_buf[i_pos++]; + return true; +} + +// get next 1 char from input buffer +//---------------------------------- +inline bool IOBuf::nextc(u_char* c) +{ + if (i_pos == i_len) + if (!flush_in()) return false; + *c = i_buf[i_pos++]; + return true; +} + +// put 1 char to output buffer +//---------------------------- +inline bool IOBuf::putc(u_char c) +{ + if (o_pos == sizeof(o_buf)) + if (!flush_out()) return false; + o_buf[o_pos++] = c; + return true; +} + +// write bytes from output buffer +//------------------------------- +bool IOBuf::flush_out() +{ + int n; + for (int i = 0; i < o_pos; i += n) { + if ((n = write(fd, o_buf+i, o_pos-i)) <= 0) return false; + } + o_pos = 0; + return true; +} + +//=========================// +// TELNET command handling // (see RFC854 TELNET PROTOCOL SPECIFICATION) +//-------------------------// +enum { nIAC=255, nWILL=251, nWONT=252, nDO=253, nDONT=254 }; +enum { sSEND=1, sIS=0, sSB=250, sSE=240 }; +enum { oECHO=1, oSGA=3, oTERM=24, oNAWS=31 }; + +bool c_will_term = false; +bool c_will_naws = false; + +// terminal type & size +//--------------------- +char *term_type; +struct winsize win_size = {0,0,0,0}; + +// dumb terminal flag +//------------------- +bool dumb = false; + +u_char telnet_cmd(IOBuf* te) +{ + u_char cmd, c; + te->nextc(&cmd); + if (cmd == sSB) { + te->nextc(&c); + // accept terminal type request + if (c == oTERM) { // "SB TERM + te->nextc(&c); // IS + u_char* p = (u_char*)term_type; + te->nextc(p); // TERMINAL-TYPE + while (*p != nIAC) { + if (isupper(*p)) *p = _tolower(*p); + ++p; te->nextc(p); + } + *p = 0; + te->nextc(&c); // IAC SE" + return (u_char)oTERM; + } + // accept terminal size request + if (c == oNAWS) { // "SB NAWS + u_short col, row; + te->nextc((u_char*)&col); + te->nextc((u_char*)&col+1); // 00 00 (cols) + te->nextc((u_char*)&row); + te->nextc((u_char*)&row+1); // 00 00 (rows) + te->nextc(&c); + te->nextc(&c); // TAC SE" + win_size.ws_col = ntohs(col); + win_size.ws_row = ntohs(row); + return (u_char)oNAWS; + } + while (c != nIAC) te->nextc(&c); // "... IAC SE" + te->nextc(&c); + } + else if (cmd == nWILL || cmd == nWONT || cmd == nDO || cmd == nDONT) { + u_char c; + te->nextc(&c); + if (cmd == nWILL && c == oTERM) // "WILL TERM" + c_will_term = true; + else if (cmd == nWILL && c == oNAWS) // "WILL NAWS" + c_will_naws = true; + } + return cmd; +} + +//============================// +// TELNET initial negotiation // +//----------------------------// +void telnet_nego(int te_sock) +{ + IOBuf te = te_sock; + u_char c; + + // start terminal type negotiation + // IAC DO TERMINAL-TYPE + te.putc(nIAC); te.putc(nDO); te.putc(oTERM); + te.flush_out(); + te.nextc(&c); + if (c != nIAC) { + te.ungetc(); + return; + } + (void)telnet_cmd(&te); + if (c_will_term) { + // terminal type sub-negotiation + // IAC SB TERMINAL-TYPE SEND IAC SE + te.putc(nIAC); te.putc(sSB); te.putc(oTERM); + te.putc(sSEND); te.putc(nIAC); te.putc(sSE); + te.flush_out(); + // accept terminal type response + te.nextc(&c); + if (c != nIAC) { + te.ungetc(); + return; + } + (void)telnet_cmd(&te); + } + + // start terminal size negotiation + // IAC DO WINDOW-SIZE + te.putc(nIAC); te.putc(nDO); te.putc(oNAWS); + te.flush_out(); + te.nextc(&c); + if (c != nIAC) { + te.ungetc(); + return; + } + (void)telnet_cmd(&te); + if (c_will_naws) { + // accept terminal size response + te.nextc(&c); + if (c != nIAC) { + te.ungetc(); + return; + } + (void)telnet_cmd(&te); + } + + // SGA/ECHO + te.putc(nIAC); te.putc(nWILL); te.putc(oSGA); + te.putc(nIAC); te.putc(nDO); te.putc(oSGA); + te.putc(nIAC); te.putc(nWILL); te.putc(oECHO); + te.flush_out(); +} + +//=============================================// +// relaying of a terminal emulator and a shell // +//---------------------------------------------// +void telnet_session(int te_sock, int sh_pty) +{ + IOBuf te = te_sock; + IOBuf sh = sh_pty; + fd_set rtmp, rbits; + FD_ZERO(&rtmp); + FD_SET(te, &rtmp); + FD_SET(sh, &rtmp); + u_char c; + int cr = 0; + int cnt = 0; + for (;;) { + rbits = rtmp; + if (select(FD_SETSIZE, &rbits, 0, 0, 0) <= 0) { + break; + } + if (FD_ISSET(sh, &rbits)) { + // send data from a shell to a terminal + if (sh.flush_in() == false) { + break; + } + while (sh.getc(&c) == true) { + if (c == nIAC) { + // escape a TELNET IAC char + te.putc(c); + } + te.putc(c); + } + if (te.flush_out() == false) { + break; + } + if (cnt++ < 20) { + continue; // give priority to data from a shell + } + cnt = 0; + } + if (FD_ISSET(te, &rbits)) { + // send data from a terminal to a shell + if (te.flush_in() == false) { + break; + } + while (te.getc(&c) == true) { + if (c == nIAC && !dumb) { + u_char cmd = telnet_cmd(&te) ; + if (cmd == oNAWS) { + // resize pty by terminal size change notice + ioctl(sh_pty, TIOCSWINSZ, &win_size); + continue; + } + if (cmd != nIAC) { + continue; + } + } else if (c == '\r') { + cr = 1; + } else if (c == '\n' || c == '\0') { + if (cr) { // do not send LF or NUL just after CR + cr = 0; + continue; + } + } else { + cr = 0; + } + sh.putc(c); + } + if (sh.flush_out() == false) { + break; + } + } + } +} + +//=========================================================// +// connection of TELNET terminal emulator and Cygwin shell // +//---------------------------------------------------------// +int main(int argc, char** argv) +{ + (void)argc; + int listen_sock = -1; + int te_sock = -1; + int sh_pty = -1; + HANDLE hTerm = NULL; + int sh_pid, agent_pid = 0; + + + // configuration file (.cfg) path + get_cfg_filenames(); + debug_msg_print("cfg_base %s", cfg_base); + debug_msg_print("cfg_exe %s", cfg_exe); + debug_msg_print("conf_appdata_full %s", conf_appdata_full); + debug_msg_print("sys_conf %s", sys_conf); + debug_msg_print("usr_conf %s", usr_conf); + + + // set default values + cfg_data_t *cfg = create_cfg(); + cfg->port_start = PORT_START_DEFAULT; + cfg->port_range = PORT_RANGE_DEFAULT; + cfg->telsock_timeout = TELSOCK_TIMEOUT_DEFAULT; + cfg->home_chdir = HOME_CHDIR_DEFAULT; + cfg->enable_loginshell = ENABLE_LOGINSHELL_DEFAULT; + cfg->enable_agent_proxy = ENABLE_AGENT_PROXY_DEFAULT; + cfg->debug_flag = DEBUG_FLAG_DEFAULT; + + // load configuration + get_username_and_shell(cfg); // from /etc/passwd + cfg->dump(cfg, debug_msg_print); + load_cfg(cfg); + sh_env_t *sh_env = cfg->sh_env; + sh_env->add(sh_env, "SHELL", cfg->shell); + sh_env->add(sh_env, "USER", cfg->username); + debug_msg_print("loginshell %d", cfg->enable_loginshell); + cfg->dump(cfg, debug_msg_print); + + // read commandline arguments + get_args(argv, cfg); + cfg->dump(cfg, debug_msg_print); + + // restore values + debug_flag = cfg->debug_flag; + + if (cfg->shell == NULL) { + msg_print("missing shell"); + return 0; + } + if (cfg->term == NULL && cl_port <= 0) { + msg_print("missing terminal"); + return 0; + } + + if (cfg->change_dir != NULL) { + cfg->home_chdir = false; + if (cfg->enable_loginshell) { + sh_env->add(sh_env, "CHERE_INVOKING", "y"); + } + } + + // terminal side connection + if (cfg->cl_port > 0) { + // connect to the specified TCP port + cl_port = cfg->cl_port; + if ((te_sock = connect_client()) < 0) { + goto cleanup; + } + } else { + // prepare a TELNET listener socket + if ((listen_sock = listen_telnet(&listen_port, cfg)) < 0) { + goto cleanup; + } + debug_msg_print("execute terminal"); + + // execute a terminal emulator + if ((hTerm = exec_term(cfg)) == NULL) { + api_error("exec_term failed"); + goto cleanup; + } + // accept connection from the terminal emulator + if ((te_sock = accept_telnet(listen_sock, cfg)) < 0) { + goto cleanup; + } + shutdown(listen_sock, 2); + close(listen_sock); + listen_sock = -1; + } + // TELNET negotiation + term_type = cfg->term_type; + dumb = cfg->dumb; + if (!dumb) { + telnet_nego(te_sock); + } + + // execute ssh-agent proxy + if (cfg->enable_agent_proxy) { + agent_pid = exec_agent_proxy(sh_env); + } + + // execute a shell + debug_msg_print("execute shell"); + if ((sh_pty = exec_shell(&sh_pid, cfg)) < 0) { + debug_msg_print("exec_shell failed"); + goto cleanup; + } + // set initial pty window size + if (!dumb && c_will_naws && win_size.ws_col != 0) { + ioctl(sh_pty, TIOCSWINSZ, &win_size); + } + + debug_msg_print("entering telnet session"); + // relay the terminal emulator and the shell + telnet_session(te_sock, sh_pty); + + cleanup: + if (agent_pid > 0) { + kill(agent_pid, SIGTERM); + } + if (sh_pty >= 0) { + close(sh_pty); + kill(sh_pid, SIGKILL); + } + if (agent_pid > 0 || sh_pty >= 0) { + wait((int*)NULL); + } + if (listen_sock >= 0) { + shutdown(listen_sock, 2); + close(listen_sock); + } + if (te_sock >= 0) { + shutdown(te_sock, 2); + close(te_sock); + } + if (hTerm != NULL) { + WaitForSingleObject(hTerm, INFINITE); + CloseHandle(hTerm); + } + return 0; +} + +#ifdef NO_WIN_MAIN +// \x83\x8A\x83\x93\x83N\x8E\x9E\x82\xC9 -mwindows \x82\xF0\x8Ew\x92肵\x82Ă\xA2\x82\xE9\x82̂\xC5 +// \x8E\xC0\x8Ds\x83t\x83@\x83C\x83\x8B\x82\xCD subsystem=windows \x82Ő\xB6\x90\xAC\x82\xB3\x82\xEA\x82Ă\xA2\x82\xE9 +// \x83v\x83\x8D\x83O\x83\x89\x83\x80\x82̃G\x83\x93\x83g\x83\x8A\x82\xCD WinMainCRTStartup() \x82ƂȂ\xE9 +// cygwin\x82ŃC\x83\x93\x83X\x83g\x81[\x83\x8B\x82\xB3\x82\xEA\x82\xE9gcc 11.2 \x82ł͂Ƃ\xAD\x82Ɏw\x92肵\x82Ȃ\xAD\x82Ă\xE0 main() \x82\xAA\x83R\x81[\x83\x8B\x82\xB3\x82\xEA\x82\xE9 +// +// \x88ȑO\x82\xCCcygwin\x82\xCCgcc\x82ł͎\x9F\x82̃R\x81[\x83h\x82\xAA\x95K\x97v\x82\xBE\x82\xC1\x82\xBD\x82̂\xA9\x82\xE0\x82\xB5\x82\xEA\x82Ȃ\xA2 +// This program is an Win32 application but, start as Cygwin main(). +//------------------------------------------------------------------ +extern "C" { + void mainCRTStartup(void); + void WinMainCRTStartup(void) { mainCRTStartup(); } +}; +#endif + +//EOF Deleted: trunk/cygwin/cygterm/cygterm.rc =================================================================== --- trunk/cygwin/cygterm/cygterm.rc 2022-02-05 17:46:06 UTC (rev 9724) +++ trunk/cygwin/cygterm/cygterm.rc 2022-02-05 17:46:17 UTC (rev 9725) @@ -1 +0,0 @@ -icon ICON cygterm.ico Added: trunk/cygwin/cygterm/cygterm_cfg.cc =================================================================== --- trunk/cygwin/cygterm/cygterm_cfg.cc (rev 0) +++ trunk/cygwin/cygterm/cygterm_cfg.cc 2022-02-05 17:46:17 UTC (rev 9725) @@ -0,0 +1,406 @@ +/* + * (C) 2022- TeraTerm Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> + +#include "cygterm_cfg.h" + +#define DUMP_ENABLE 1 + +// additional env vars given to a shell +//------------------------------------- + +typedef struct sh_env_data_t { + struct sh_env_data_t* next; + char *name; + char *value; +} sh_env_data_t; + +typedef struct sh_env_private_tag { + sh_env_data_t *envp; +} sh_env_private_t; + +static bool env_add(sh_env_t *envp, const char* name, const char* value) +{ + sh_env_private_t *pr_data = (sh_env_private_t *)envp->private_data; + sh_env_data_t* e; + + if (name[0] == 0) { + return true; + } + e = (sh_env_data_t*)malloc(sizeof(*e)); + if (e == NULL) { + return false; + } + + e->name = strdup(name); + e->value = strdup(value); + e->next = NULL; + + if (pr_data->envp == NULL) { + pr_data->envp = e; + return true; + } + sh_env_data_t* env_data = pr_data->envp; + sh_env_data_t* prev_env = NULL; + while(1) { + if (strcmp(env_data->name, name) == 0) { + // \x93\xAF\x82\xB6\x96\xBC\x91O -> \x93\xFC\x82\xEA\x91ւ\xA6 + if (prev_env == NULL) { + pr_data->envp = e; + } else { + prev_env->next = e; + e->next = env_data->next; + } + free(env_data->name); + free(env_data->value); + free(env_data); + break; + } + if (env_data->next == NULL) { + // \x8DŌ\xE3\x82܂ŗ\x88\x82\xBD + env_data->next = e; + break; + } + prev_env = env_data; + env_data = env_data->next; + } + + return true; +} + +static bool env_add1(sh_env_t *envp, const char* name_value) +{ + if (name_value[0] == 0) { + return true; + } + char *name = strdup(name_value); + char *p = strchr(name, '='); + char *value; + if (p == NULL) { + value = NULL; + } + else { + *p = 0; + value = strdup(p+1); + } + bool r = env_add(envp, name, value); + free(value); + free(name); + return r; +} + +static char *env_get(sh_env_t *envp, int index, char **value) +{ + sh_env_private_t *pr_data = (sh_env_private_t *)envp->private_data; + sh_env_data_t* e = pr_data->envp; + if (e == NULL) { + return NULL; + } + while(1) { + if (index == 0) { + *value = e->value; + return e->name;; + } + if (e->next == NULL) { + *value = NULL; + return NULL; + } + index--; + e = e->next; + } +} + +static void env_destry_all(sh_env_t *envp) +{ + sh_env_private_t *pr_data = (sh_env_private_t *)envp->private_data; + sh_env_data_t* e = pr_data->envp; + if (e == NULL) { + return; + } + pr_data->envp = NULL; + while(1) { + sh_env_data_t* e_next = e->next; + e->next = NULL; + free(e); + e = e_next; + if (e == NULL) { + break; + } + } +} + +static void env_destry(sh_env_t *envp) +{ + sh_env_private_t *pr_data = (sh_env_private_t *)envp->private_data; + env_destry_all(envp); + free(pr_data); + envp->private_data = NULL; + free(envp); +} + +sh_env_t *create_sh_env(void) +{ + sh_env_t *sh_env = (sh_env_t *)calloc(sizeof(*sh_env), 1); + if (sh_env == NULL) { + return NULL; + } + sh_env_private_t *pr_data = (sh_env_private_t *)calloc(sizeof(*pr_data), 1); + if (pr_data == NULL) { + free(sh_env); + return NULL; + } + sh_env->private_data = pr_data; + sh_env->destroy = env_destry; + sh_env->add = env_add; + sh_env->add1 = env_add1; + sh_env->get = env_get; + + return sh_env; +} + +static bool is_bool_string(const char *s) +{ + if (strchr("YyTt", *s) != NULL) + return true; + if (atoi(s) > 0) + return true; + + return false; +} + +//==================================// +// parse line in configuration file // +//----------------------------------// +static void parse_cfg_line(char *buf, cfg_data_t *cfg) +{ + // "KEY = VALUE" format in each line. + // skip leading/trailing blanks. KEY is not case-sensitive. + char* p1; + for (p1 = buf; isspace(*p1); ++p1); + if (!isalpha(*p1)) { + return; // comment line with non-alphabet 1st char + } + char* name = p1; + for (++p1; isalnum(*p1) || *p1 == '_'; ++p1); + char* p2; + for (p2 = p1; isspace(*p2); ++p2); + if (*p2 != '=') { + return; // igonore line without '=' + } + for (++p2; isspace(*p2); ++p2); + char* val = p2; + for (p2 += strlen(p2); isspace(*(p2-1)); --p2); + *p1 = *p2 = 0; + + if (!strcasecmp(name, "TERM")) { + // terminal emulator command line (host:%s, port#:%d) + free(cfg->term); + cfg->term = strdup(val); + } + else if (!strcasecmp(name, "SHELL")) { + // shell command line + if (strcasecmp(val, "AUTO") != 0) { + free(cfg->shell); + cfg->shell = strdup(val); + } + } + else if (!strcasecmp(name, "PORT_START")) { + // minimum port# for TELNET + cfg->port_start = atoi(val); + } + else if (!strcasecmp(name, "PORT_RANGE")) { + // number of ports for TELNET + cfg->port_range = atoi(val); + } + else if (!strcasecmp(name, "TERM_TYPE")) { + // terminal type name (maybe overridden by TELNET negotiation.) + free(cfg->term_type); + cfg->term_type = strdup(val); + } + else if (!strncasecmp(name, "ENV_", 4)) { + // additional env vars given to a shell + sh_env_t *sh_env = cfg->sh_env; + sh_env->add1(sh_env, val); + } + else if (!strcasecmp(name, "HOME_CHDIR")) { + // change directory to home + if (is_bool_string(val)) { + cfg->home_chdir = true; + } + } + else if (!strcasecmp(name, "LOGIN_SHELL")) { + // execute a shell as a login shell + if (is_bool_string(val)) { + cfg->enable_loginshell = true; + } + } + else if (!strcasecmp(name, "SOCKET_TIMEOUT")) { + // telnet socket timeout + cfg->telsock_timeout = atoi(val); + } + else if (!strcasecmp(name, "SSH_AGENT_PROXY")) { + // ssh-agent proxy + if (is_bool_string(val)) { + cfg->enable_agent_proxy = true; + } + } + else if (!strcasecmp(name, "DEBUG")) { + // debug mode + if (is_bool_string(val)) { + cfg->debug_flag = true; + } + } +} + +typedef struct { + sh_env_data_t *env; +} cfg_private_data_t; + +static void destroy(cfg_data_t *cfg_data) +{ +// env_destry_all(cfg_data); + sh_env_t *sh_env = cfg_data->sh_env; + sh_env->destroy(sh_env); + cfg_private_data_t *pr_data = (cfg_private_data_t *)cfg_data->private_data; + free(pr_data); + free(cfg_data); +} + +// read each setting parameter +// configuration file (.cfg) path +static bool load_cfg(cfg_data_t *cfg_data, const char *conf) +{ + FILE* fp = fopen(conf, "r"); + if (fp == NULL) { + return false; + } + + char buf[BUFSIZ]; + while (fgets(buf, sizeof(buf), fp) != NULL) { + parse_cfg_line(buf, cfg_data); + } + fclose(fp); + + return true; +} + +#if !defined(offsetof) +#define offsetof(s,m) ((size_t)&(((s*)0)->m)) +#endif + +#if DUMP_ENABLE +static void dump(cfg_data_t *cfg_data, void (*print)(const char* msg, ...)) +{ + const static struct { + const char *name; + size_t offset; + char type; + } list[] = { + { "username", offsetof(cfg_data_t, username), 's' }, + { "term", offsetof(cfg_data_t, term), 's' }, + { "termopt", offsetof(cfg_data_t, termopt), 's' }, + { "shell", offsetof(cfg_data_t, shell), 's' }, + { "term_type", offsetof(cfg_data_t, term_type), 's' }, + { "change_dir", offsetof(cfg_data_t, change_dir), 's' }, + { "port_start", offsetof(cfg_data_t, port_start), 'i' }, + { "port_range", offsetof(cfg_data_t, port_range), 'i' }, + { "cl_port", offsetof(cfg_data_t, cl_port), 'i' }, + { "home_chdir", offsetof(cfg_data_t, home_chdir), 'b' }, + { "enable_loginshell", offsetof(cfg_data_t, enable_loginshell), 'b' }, + { "telsock_timeout", offsetof(cfg_data_t, telsock_timeout), 'i' }, + { "enable_agent_proxy", offsetof(cfg_data_t, enable_agent_proxy), 'b' }, + { "dumb", offsetof(cfg_data_t, dumb), 'b' }, + { "debug_flag", offsetof(cfg_data_t, debug_flag), 'b' }, + }; + for (int i = 0; i < (int)(sizeof(list)/sizeof(list[0])); i++) { + uint8_t *p = (uint8_t *)cfg_data + list[i].offset; + switch (list[i].type) { + case 's': { + char *str = *(char **)p; + print("%s=%s", list[i].name, str == NULL ? "NULL" : str); + break; + } + case 'i': { + int i2 = *(int *)p; + print("%s=%d(0x%x)", list[i].name, i2, i2); + break; + } + case 'b': { + bool b = *(bool *)p; + print("%s=%i(%s)", list[i].name, b, b ? "true" : "false"); + break; + } + default: + print("?"); + break; + } + } + + sh_env_t *sh_env = cfg_data->sh_env; + for(int i = 0;;i++) { + char *value; + const char *env = sh_env->get(sh_env, i, &value); + if (env == NULL) { + break; + } + print("env %d %s=%s", i, env, value); + } +} +#else +static void dump(cfg_data_t *cfg_data, void (*print)(const char* msg, ...)) +{ + (void)cfg_data; + (void)print; +} +#endif + +cfg_data_t *create_cfg(void) +{ + cfg_data_t *cfg_data = (cfg_data_t *)calloc(sizeof(*cfg_data), 1); + if (cfg_data == NULL) { + return NULL; + } + cfg_private_data_t *pr_data = (cfg_private_data_t *)calloc(sizeof(*pr_data), 1); + if (pr_data == NULL) { + free(cfg_data); + return NULL; + } + + // data, func + cfg_data->private_data = pr_data; + cfg_data->sh_env = create_sh_env(); + cfg_data->destroy = destroy; + cfg_data->load = load_cfg; + cfg_data->dump = dump; + + return cfg_data; +} Added: trunk/cygwin/cygterm/cygterm_cfg.h =================================================================== --- trunk/cygwin/cygterm/cygterm_cfg.h (rev 0) +++ trunk/cygwin/cygterm/cygterm_cfg.h 2022-02-05 17:46:17 UTC (rev 9725) @@ -0,0 +1,74 @@ +/* + * (C) 2022- TeraTerm Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct sh_env_tag { + void (*destroy)(struct sh_env_tag *sh_env_tag); + bool (*add)(struct sh_env_tag *sh_env_tag, const char *name, const char *value); + bool (*add1)(struct sh_env_tag *sh_env_tag, const char *namevalue); + char *(*get)(struct sh_env_tag *sh_env_tag, int index, char **value); + void *private_data; +} sh_env_t; + +sh_env_t *create_sh_env(void); + +typedef struct cfg_data_tag { + char *username; + char *term; // ex. "ttermpro.exe %s %d" + char *termopt; // ex. "/KR-UTF8" + char *shell; // ex. "/usr/bin/bash" + char *term_type; // terminal type ex. "vt100" + char *change_dir; // cd \x82\xB5\x82\xBD\x8C\xE3\x81A\x83V\x83F\x83\x8B\x8BN\x93\xAE + int port_start; // default lowest port number + int port_range; // default number of ports + int cl_port; + bool home_chdir; // chdir to HOME + bool enable_loginshell; // login shell flag + int telsock_timeout; // telnet socket timeout + bool enable_agent_proxy; // ssh agent proxy + bool dumb; + bool debug_flag; // debug mode + bool (*save)(struct cfg_data_tag *cfg_data_tag, const char *fname); + bool (*load)(struct cfg_data_tag *cfg_data_tag, const char *fname); + void (*destroy)(struct cfg_data_tag *cfg_data_tag); + void (*dump)(struct cfg_data_tag *cfg_data_tag, void (*print)(const char* msg, ...)); + sh_env_t *sh_env; + void *private_data; +} cfg_data_t; + +cfg_data_t *load_cfg(const char *cfg); +cfg_data_t *create_cfg(void); + +#ifdef __cplusplus +} +#endif Added: trunk/cygwin/cygterm/sub.cpp =================================================================== --- trunk/cygwin/cygterm/sub.cpp (rev 0) +++ trunk/cygwin/cygterm/sub.cpp 2022-02-05 17:46:17 UTC (rev 9725) @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2022- TeraTerm Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <windows.h> +#include <wchar.h> +#include <shlobj.h> +#define INITGUID +#include <knownfolders.h> +#include <tchar.h> + +#include "sub.h" + +/** + * \x83}\x83\x8B\x83`\x83o\x83C\x83g\x95\xB6\x8E\x9A\x97\xF1\x82\xF0wchar_t\x95\xB6\x8E\x9A\x97\xF1\x82֕ϊ\xB7 + * @param[in] *str_ptr mb(char)\x95\xB6\x8E\x9A\x97\xF1 + * @param[in] str_len mb(char)\x95\xB6\x8E\x9A\x97\xF1\x92\xB7(0\x82̂Ƃ\xAB\x8E\xA9\x93\xAE\x81A\x8E\xA9\x93\xAE\x82̂Ƃ\xAB\x82\xCD'\0'\x82Ń^\x81[\x83~\x83l\x81[\x83g\x82\xB7\x82邱\x82\xC6) + * @param[in] code_page \x95ϊ\xB7\x8C\xB3\x83R\x81[\x83h\x83y\x81[\x83W + * @param[out] *w_len_ wchar_t\x95\xB6\x8E\x9A\x97\xF1\x92\xB7,wchar_t\x90\x94,'\0'\x82\xF0\x95ϊ\xB7\x82\xB5\x82\xBD\x82\xE7L'\0'\x82\xE0\x8A܂\xDE + * (NULL\x82̂Ƃ\xAB\x95\xB6\x8E\x9A\x97\xF0\x95Ԃ\xB3\x82Ȃ\xA2) + * @retval wchar_t\x95\xB6\x8E\x9A\x97\xF1\x82ւ̃|\x83C\x83\x93\x83^(NULL\x82̎\x9E\x95ϊ\xB7\x83G\x83\x89\x81[) + * \x8Eg\x97p\x8C\xE3 free() \x82\xB7\x82邱\x82\xC6 + */ +static wchar_t *_MultiByteToWideChar(const char *str_ptr, size_t str_len, int code_page, size_t *w_len_) +{ + DWORD flags = MB_ERR_INVALID_CHARS; + if (code_page == CP_ACP) { + code_page = (int)GetACP(); + } + if (code_page == CP_UTF8) { + // CP_UTF8 When this is set, dwFlags must be zero. + flags = 0; + } + if (w_len_ != NULL) { + *w_len_ = 0; + } + if (str_len == 0) { + str_len = strlen(str_ptr) + 1; + } + int len = ::MultiByteToWideChar(code_page, flags, + str_ptr, (int)str_len, + NULL, 0); + if (len == 0) { + return NULL; + } + wchar_t *wstr_ptr = (wchar_t *)malloc(len*sizeof(wchar_t)); + if (wstr_ptr == NULL) { + return NULL; + } + len = ::MultiByteToWideChar(code_page, flags, + str_ptr, (int)str_len, + wstr_ptr, len); + if (len == 0) { + free(wstr_ptr); + return NULL; + } + if (w_len_ != NULL) { + // \x95ϊ\xB7\x82\xB5\x82\xBD\x95\xB6\x8E\x9A\x97\xF1\x90\x94(wchar_t\x90\x94)\x82\xF0\x95Ԃ\xB7 + *w_len_ = len; + } + return wstr_ptr; +} + +/** + * wchar_t\x95\xB6\x8E\x9A\x97\xF1\x82\xF0\x83}\x83\x8B\x83`\x83o\x83C\x83g\x95\xB6\x8E\x9A\x97\xF1\x82֕ϊ\xB7 + * \x95ϊ\xB7\x82ł\xAB\x82Ȃ\xA2\x95\xB6\x8E\x9A\x82\xCD '?' \x82ŏo\x97͂\xB7\x82\xE9 + * + * @param[in] *wstr_ptr wchar_t\x95\xB6\x8E\x9A\x97\xF1 + * @param[in] wstr_len wchar_t\x95\xB6\x8E\x9A\x97\xF1\x92\xB7(0\x82̂Ƃ\xAB\x8E\xA9\x93\xAE\x81A\x8E\xA9\x93\xAE\x82̂Ƃ\xAB\x82\xCDL'\0'\x82Ń^\x81[\x83~\x83l\x81[\x83g\x82\xB7\x82邱\x82\xC6) + * @param[in] code_page \x95ϊ\xB7\x90\xE6\x83R\x81[\x83h\x83y\x81[\x83W + * @param[out] *mb_len_ \x95ϊ\xB7\x82\xB5\x82\xBD\x95\xB6\x8E\x9A\x97\xF1\x92\xB7,byte\x90\x94,L'\0'\x82\xF0\x95ϊ\xB7\x82\xB5\x82\xBD\x82\xE7'\0'\x82\xE0\x8A܂\xDE + * (NULL\x82̂Ƃ\xAB\x95\xB6\x8E\x9A\x97\xF0\x95Ԃ\xB3\x82Ȃ\xA2) + * @retval mb\x95\xB6\x8E\x9A\x97\xF1\x82ւ̃|\x83C\x83\x93\x83^(NULL\x82̎\x9E\x95ϊ\xB7\x83G\x83\x89\x81[) + * \x8Eg\x97p\x8C\xE3 free() \x82\xB7\x82邱\x82\xC6 + */ +static char *_WideCharToMultiByte(const wchar_t *wstr_ptr, size_t wstr_len, int code_page, size_t *mb_len_) +{ + const DWORD flags = 0; + char *mb_ptr; + if (code_page == CP_ACP) { + code_page = (int)GetACP(); + } + if (mb_len_ != NULL) { + *mb_len_ = 0; + } + if (wstr_len == 0) { + wstr_len = wcslen(wstr_ptr) + 1; + } + size_t len = ::WideCharToMultiByte(code_page, flags, + wstr_ptr, (DWORD)wstr_len, + NULL, 0, + NULL, NULL); + if (len == 0) { + return NULL; + } + mb_ptr = (char *)malloc(len); + if (mb_ptr == NULL) { + return NULL; + } + len = ::WideCharToMultiByte(code_page, flags, + wstr_ptr, (DWORD)wstr_len, + mb_ptr, (int)len, + NULL,NULL); + if (len == 0) { + free(mb_ptr); + return NULL; + } + if (mb_len_ != NULL) { + // \x95ϊ\xB7\x82\xB5\x82\xBD\x95\xB6\x8E\x9A\x97\xF1\x90\x94(byte\x90\x94)\x82\xF0\x95Ԃ\xB7 + *mb_len_ = len; + } + return mb_ptr; +} + +char *ToCharW(const wchar_t *strW) +{ + if (strW == NULL) return NULL; + char *strA = _WideCharToMultiByte(strW, 0, CP_ACP, NULL); + return strA; +} + +wchar_t *ToWcharA(const char *strA) +{ + if (strA == NULL) return NULL; + wchar_t *strW = _MultiByteToWideChar(strA, 0, CP_ACP, NULL); + return strW; +} + +wchar_t *ToWcharU8(const char *strU8) +{ + if (strU8 == NULL) return NULL; + wchar_t *strW = _MultiByteToWideChar(strU8, 0, CP_UTF8, NULL); + return strW; +} + +char *ToU8W(const wchar_t *strW) +{ + if (strW == NULL) return NULL; + char *strU8 = _WideCharToMultiByte(strW, 0, CP_UTF8, NULL); + return strU8; +} + +/** + * GetModuleFileNameW() \x82̓\xAE\x93I\x83o\x83b\x83t\x83@\x94\xC5 + * + * @param buf \x95\xB6\x8E\x9A\x97\xF1\x82\xF0\x8Ai\x94[\x82\xB7\x82\xE9\x83o\x83b\x83t\x83@ + * \x95s\x97v\x82ɂȂ\xC1\x82\xBD\x82\xE7free()\x82\xB7\x82\xE9 + * @return \x83G\x83\x89\x81[\x83R\x81[\x83h,0(=NO_ERROR)\x82̂Ƃ\xAB\x83G\x83\x89\x81[\x82Ȃ\xB5 + */ +DWORD hGetModuleFileNameT(HMODULE hModule, TCHAR **buf) +{ + size_t size = MAX_PATH; + TCHAR *b = (TCHAR *)malloc(sizeof(TCHAR) * size); + DWORD error; + if (b == NULL) { + error = ERROR_NOT_ENOUGH_MEMORY; + goto error_return; + } + + for(;;) { + DWORD r = GetModuleFileName(hModule, b, (DWORD)size); + if (r == 0) { + // \x8A\x94\x82\xAA\x8E\xB8\x94s + error = GetLastError(); + break; + } else if (r < size - 1) { + // \x8E擾\x90\xAC\x8C\xF7 + size = r + 1; + b = (TCHAR *)realloc(b, sizeof(TCHAR) * size); + *buf = b; + return NO_ERROR; + } else { + size *= 2; + TCHAR *p = (TCHAR *)realloc(b, sizeof(TCHAR) * size); + if (p == NULL) { + free(b); + error = ERROR_NOT_ENOUGH_MEMORY; + break; + } + b = p; + } + } + + // error + free(b); +error_return: + *buf = NULL; + return error; +} + +/** + * \x83|\x81[\x83^\x83u\x83\x8B\x94łƂ\xB5\x82ē\xAE\x8D삷\x82邩 + * + * @retval TRUE \x83|\x81[\x83^\x83u\x83\x8B\x94\xC5 + * @retval FALSE \x92ʏ\xED\x83C\x83\x93\x83X\x83g\x81[\x83\x8B\x94\xC5 + */ +BOOL IsPortableMode(void) +{ + return FALSE; +#if 0 + static BOOL called = FALSE; + static BOOL ret_val = FALSE; + if (called == FALSE) { + called = TRUE; + TCHAR *exe; + hGetModuleFileNameT(NULL, &exe); +#if UNICODE + wchar_t *exe_path = wcsdup(exe); +#else + wchar_t *exe_path = ToWcharA(exe); +#endif + wchar_t *bs = wcsrchr(exe_path, L'\\'); + *bs = 0; + const wchar_t *portable_ini_base = L"\\portable.ini"; + size_t len = wcslen(exe_path) + wcslen(portable_ini_base) + 1; + wchar_t *portable_iniW = (wchar_t *)malloc(sizeof(wchar_t) * len); + wcscpy(portable_iniW, exe_path); + wcscat(portable_iniW, portable_ini_base); +#if UNICODE + DWORD r = GetFileAttributesW(portable_iniW); +#else + char *portable_iniA = ToCharW(portable_iniW); + DWORD r = GetFileAttributesA(portable_iniA); + free(portable_iniA); +#endif + free(portable_iniW); + if (r == INVALID_FILE_ATTRIBUTES) { + //\x83t\x83@\x83C\x83\x8B\x82\xAA\x91\xB6\x8D݂\xB5\x82Ȃ\xA2 + ret_val = FALSE; + } + else { + ret_val = TRUE; + } + } + return ret_val; +#endif +} + +// $APPDATA wchar_t +static wchar_t *get_appdata_dir(void) +{ +#if _WIN32_WINNT > 0x0600 + wchar_t *appdata; + SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &appdata); + wchar_t *retval = wcsdup(appdata); + CoTaskMemFree(appdata); + return retval; +#else + LPITEMIDLIST pidl; + HRESULT r = SHGetSpecialFolderLocation(NULL, CSIDL_APPDATA, &pidl); + if (r == NOERROR) { + wchar_t appdata[MAX_PATH]; + SHGetPathFromIDListW(pidl, appdata); + wchar_t *retval = wcsdup(appdata); + CoTaskMemFree(pidl); + return retval; + } + char *env = getenv("APPDATA"); + if (env == NULL) { + // \x82\xE0\x82\xC1\x82ƌÂ\xA2 windows ? + abort(); + } + wchar_t *appdata = ToWcharA(env); + return appdata; +#endif +} + +// L'\\' -> L'/' +static void convert_bsW(wchar_t *path) +{ + wchar_t *p = path; + while(*p != 0) { + if (*p == L'\\') { + *p = L'/'; + } + p++; + } +} + +// cygwin \x8C\xFC\x82\xAF $APPDATA utf-8 \x82\xF0\x95Ԃ\xB7 +char *GetAppDataDirU8() +{ + wchar_t *appdataW = get_appdata_dir(); + convert_bsW(appdataW); + char *appdataU8 = ToU8W(appdataW); + free(appdataW); + return appdataU8; +} + +/** + * full path exe\x83t\x83@\x83C\x83\x8B\x96\xBC\x82\xF0\x95Ԃ\xB7 + */ +char *GetModuleFileNameU8(void) +{ + TCHAR *buf; + hGetModuleFileNameT(NULL, &buf); + convert_bsW(buf); +#if UNICODE + char *exe = ToU8W(buf); +#else + char *exe = strdup(buf); +#endif + return exe; +} Added: trunk/cygwin/cygterm/sub.h =================================================================== --- trunk/cygwin/cygterm/sub.h (rev 0) +++ trunk/cygwin/cygterm/sub.h 2022-02-05 17:46:17 UTC (rev 9725) @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022- TeraTerm Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +BOOL IsPortableMode(void); +char *GetAppDataDirU8(void); +wchar_t *ToWcharU8(const char *strU8); +char *ToU8W(const wchar_t *strW); +char *GetModuleFileNameU8(void); Modified: trunk/doc/ja/html/setup/folder.md =================================================================== --- trunk/doc/ja/html/setup/folder.md 2022-02-05 17:46:06 UTC (rev 9724) +++ trunk/doc/ja/html/setup/folder.md 2022-02-05 17:46:17 UTC (rev 9725) @@ -1,40 +1,54 @@ # Tera Term が使用するフォルダ -## デフォルトで使用するフォルダ +| | インストール版(*1) | ポータブル版 | +|------------------|----------------------------|------------------------------| +| 設定ファイル | `%APPDATA%\teraterm5` | exeファイルのあるフォルダ | +| 動作ログ、ダンプ | `%LOCALAPPDATA%\teraterm5` | `%USERPROFILE%\Documents\teraterm5` (*2) | +- *1 インストール先 (32bitOS+32bitEXEの場合) `%PROGRAMFILES%\teraterm5` +- *2 SHGetKnownFolderPath(FOLDERID_Documents) + "\teraterm5" + +# ポータブル版について + +- 環境(システム設定、個人設定)に左右されず使用することができる + - 次のような用途を想定 + - USBメモリに入れて、そこから起動する + - インストールせずに使用する (コピーするだけで使えるようになる) +- 実行する ttermpro.exe と同じフォルダに portable.ini ファイルを置くことでポータブル版として動作 +- portable.ini の中身はなんでもよい(サイズ0でよい) + +# インストール版の使用するフォルダ,ファイルについて + +*注意* +現在 Tera Term 5 は Windows10,11 のみをターゲットとしている + +## Windows 10, 11 のフォルダ例 + - 設定ファイル - `%APPDATA%\teraterm5` - 動作ログ、ダンプ - `%LOCALAPPDATA%\teraterm5` -- 実行ファイル(32bitOS+32bitEXEの場合) - - `%PROGRAMFILES%\teraterm5` -### Windows Vista以降 の場合のフォルダ例 +## Windows Vista以降 の場合のフォルダ例 - 設定ファイル - `C:\Users\[usernane]\AppData\Roaming\teraterm5` - 動作ログ、ダンプ - `C:\Users\[usernane]\AppData\Local\teraterm5` -- 実行ファイル(32bitOS+32bitEXE) - - `C:\Program Files\teraterm5` -### Windows 2000, XP の場合のフォルダ例 +## Windows 2000, XP の場合のフォルダ例 - 設定ファイル - `C:\Documents and Settings\[usernane]\AppData\Roaming\teraterm5` - 動作ログ、ダンプ(未定義) - `C:\Documents and Settings\[usernane]\Application Data\teraterm5` -- 実行ファイル - - `C:\Program Files\teraterm5` -### Windows 2000より以前の場合のフォルダ例 +## Windows 2000より以前の場合のフォルダ例 - 設定ファイル - `C:\My Documents\teraterm5` -- 動作ログ、ダンプ(未定義) +- 動作ログ、ダンプ - `C:\My Documents\teraterm5` -- 実行ファイル - - `C:\Program Files\teraterm5` 参考 @@ -56,4 +70,3 @@ - 設定ファイルを置くフォルダを作成する - ttermpro.exeのフォルダにある設定ファイルを - 設定ファイルを置くフォルダにコピーする -