diff --git a/version.h b/version.h index 2a98cd34..eddc19ce 100644 --- a/version.h +++ b/version.h @@ -8,6 +8,6 @@ * default stuff used for local development runs of 'make'. */ -#define TEXTVER "发布版 0.78-cn1" -#define SSHVER "-Release-0-78-CN1" -#define BINARY_VERSION 0,78,0,1 +#define TEXTVER "发布版 0.78-cn1 (便携版 0.20)" +#define SSHVER "-Release-0-78-patch20-CN1" +#define BINARY_VERSION 0,78,20,1 diff --git a/windows/pageant.c b/windows/pageant.c index 5b6c4bf7..f5a7aa62 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -2,6 +2,15 @@ * Pageant: the PuTTY Authentication Agent. */ + /* + * JK: disk config 0.20 from 11. 12. 2022 + * + * rewritten for storing information primary to disk + * reasonable error handling and reporting except for + * memory allocation errors (not enough memory) + * + * http://jakub.kotrla.net/putty/ + */ #include #include #include @@ -74,6 +83,54 @@ static filereq *keypath = NULL; #define PUTTY_DEFAULT "Default%20Settings" static int initial_menuitems_count; + +/* JK: buffers for path strings */ +static char oldpath[2 * MAX_PATH] = "\0"; +static char puttypath[2 * MAX_PATH] = "\0"; + +/* JK: my generic function for simplyfing error reporting */ +DWORD errorShow(const char* pcErrText, const char* pcErrParam) { + HWND hwRParent; + DWORD errorCode; + char pcBuf[16]; + char* pcMessage; + + if (pcErrParam != NULL) { + pcMessage = snewn(strlen(pcErrParam) + strlen(pcErrText) + 31, char); + } + else { + pcMessage = snewn(strlen(pcErrText) + 31, char); + } + + errorCode = GetLastError(); + ltoa(errorCode, pcBuf, 10); + + strcpy(pcMessage, "Error: "); + strcat(pcMessage, pcErrText); + strcat(pcMessage, "\n"); + + if (pcErrParam) { + strcat(pcMessage, pcErrParam); + strcat(pcMessage, "\n"); + } + strcat(pcMessage, "Error code: "); + strcat(pcMessage, pcBuf); + + /* JK: get parent-window and show */ + hwRParent = GetActiveWindow(); + if (hwRParent != NULL) { hwRParent = GetLastActivePopup(hwRParent); } + + if (MessageBox(hwRParent, pcMessage, "Error", MB_OK | MB_APPLMODAL | MB_ICONEXCLAMATION) == 0) { + /* JK: this is really bad -> just ignore */ + return 0; + } + + sfree(pcMessage); + return errorCode; +}; + + + /* * Print a modal (Really Bad) message box and perform a fatal exit. */ @@ -893,43 +950,260 @@ static BOOL AddTrayIcon(HWND hwnd) return res; } -/* Update the saved-sessions menu. */ +/* + * JK: rewritten to be able to load configuration from disk + * as rewritten putty saves it there - http://jakub.kotrla.net/putty/ + */ static void update_sessions(void) { int num_entries; HKEY hkey; - TCHAR buf[MAX_PATH + 1]; + TCHAR buf[MAX_PATH + 128]; MENUITEMINFO mii; - strbuf *sb; + HANDLE hFile; + char* fileCont = NULL; + DWORD fileSize; + DWORD bytesRead; + char* p = NULL; + char* p2 = NULL; + char* pcBuf = NULL; + strbuf* sb; + + char sesspath[2 * MAX_PATH] = "\0"; + char curdir[2 * MAX_PATH] = "\0"; + char sessionsuffix[16] = "\0"; + WIN32_FIND_DATA FindFileData; int index_key, index_menu; - if (!putty_path) - return; + if (!putty_path) return; - if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey)) - return; - - for(num_entries = GetMenuItemCount(session_menu); + /* clear old menu */ + for (num_entries = GetMenuItemCount(session_menu); num_entries > initial_menuitems_count; num_entries--) RemoveMenu(session_menu, 0, MF_BYPOSITION); + /* init for new menu */ index_key = 0; index_menu = 0; + + /* JK: save path/curdir */ + GetCurrentDirectory((MAX_PATH * 2), oldpath); + + + /* JK: try curdir for putty.conf first */ + hFile = CreateFile("putty.conf", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (hFile != INVALID_HANDLE_VALUE) + { + /* JK: there is a putty.conf in curdir - use it and use curdir as puttypath */ + GetCurrentDirectory((MAX_PATH * 2), puttypath); + GetCurrentDirectory((MAX_PATH * 2), curdir); + CloseHandle(hFile); + } + else { + /* JK: get where putty.exe is */ + if (GetModuleFileName(NULL, puttypath, (MAX_PATH * 2)) != 0) + { + p = strrchr(puttypath, '\\'); + if (p) + { + *p = '\0'; + } + SetCurrentDirectory(puttypath); + } + else GetCurrentDirectory((MAX_PATH * 2), puttypath); + } + + /* JK: set default values - if there is a config file, it will be overwitten */ + strcpy(sesspath, puttypath); + strcat(sesspath, "\\sessions"); + + hFile = CreateFile("putty.conf", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + /* JK: now we can pre-clean-up */ + SetCurrentDirectory(oldpath); + + /* JK: read from putty.conf if is there */ + if (hFile != INVALID_HANDLE_VALUE) { + fileSize = GetFileSize(hFile, NULL); + fileCont = snewn(fileSize + 16, char); + + if (!ReadFile(hFile, fileCont, fileSize, &bytesRead, NULL)) { + errorShow("无法读取配置文件,回滚到默认设置", NULL); + + /* JK: default values are already there - just clean-up */ + } + else + { + /* JK: parse conf file to path variables */ + *(fileCont + fileSize + 1) = '\0'; + *(fileCont + fileSize) = '\n'; + p = fileCont; + while (p) { + if (*p == ';') { /* JK: comment -> skip line */ + p = strchr(p, '\n'); + ++p; + continue; + } + p2 = strchr(p, '='); + if (!p2) break; + *p2 = '\0'; + ++p2; + + if (!strcmp(p, "sessions")) { + p = strchr(p2, '\n'); + *p = '\0'; + + /* This is actually call to joinPath, done inline and optimized just for pageant */ + pcBuf = snewn(MAX_PATH + 1, char); + + /* at first ExpandEnvironmentStrings */ + if (0 == ExpandEnvironmentStrings(p2, pcBuf, MAX_PATH)) { + /* JK: failure -> revert back - but it ussualy won't work, so report error to user! */ + errorShow("Unable to ExpandEnvironmentStrings for session path", p2); + strncpy(pcBuf, p2, strlen(p2)); + } + + if ((*pcBuf == '/') || (*pcBuf == '\\')) { + /* JK: everything ok */ + strcpy(sesspath, curdir); + strcat(sesspath, pcBuf); + } + else { + if (*(pcBuf + 1) == ':') { + /* JK: absolute path */ + strcpy(sesspath, pcBuf); + } + else { + /* JK: some weird relative path - add '\' */ + strcpy(sesspath, curdir); + strcat(sesspath, "\\"); + strcat(sesspath, pcBuf); + } + } + sfree(pcBuf); + + p2 = sesspath + strlen(sesspath) - 1; + while ((*p2 == ' ') || (*p2 == '\n') || (*p2 == '\r') || (*p2 == '\t')) --p2; + *(p2 + 1) = '\0'; + } + else if (!strcmp(p, "sessionsuffix")) { + p = strchr(p2, '\n'); + *p = '\0'; + strcpy(sessionsuffix, p2); + p2 = sessionsuffix + strlen(sessionsuffix) - 1; + while ((*p2 == ' ') || (*p2 == '\n') || (*p2 == '\r') || (*p2 == '\t')) --p2; + *(p2 + 1) = '\0'; + } + else { + p = strchr(p2, '\n'); + } + /* JK: else if - pageant don't care about other stuff */ + ++p; + } + } + + CloseHandle(hFile); + sfree(fileCont); + } + /* else - INVALID_HANDLE { + * JK: unable to read conf file - probably doesn't exists + * we won't create one, user wants putty light, just fall back to defaults + * and defaults are already there + }*/ + + /* JK: we have path to sessions */ + + if (SetCurrentDirectory(sesspath)) { + + /* JK: we're able to load from files */ + hFile = FindFirstFile("*", &FindFileData); + + /* JK: skip directories ("." and ".." too) */ + while ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) { + if (!FindNextFile(hFile, &FindFileData)) { + break; + } + } + + sb = strbuf_new(); + + /* JK: add first file */ + if (hFile != INVALID_HANDLE_VALUE) { + unescape_registry_key(FindFileData.cFileName, sb); + + /* JK: cut off session.suffix */ + p = sb->s + strlen(sb->s) - strlen(sessionsuffix); + if (strncmp(p, sessionsuffix, strlen(sessionsuffix)) == 0) { + *p = '\0'; + + if (strcmp(sb->s, PUTTY_DEFAULT) != 0) { + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID; + mii.fType = MFT_STRING; + mii.fState = MFS_ENABLED; + mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE; + mii.dwTypeData = sb->s; + InsertMenuItem(session_menu, index_menu, TRUE, &mii); + index_menu++; + } + } + } + + /* JK: enum files */ + while (FindNextFile(hFile, &FindFileData)) { + /* JK: add files as menu item one-by-one */ + + sb->len = 0; + unescape_registry_key(FindFileData.cFileName, sb); + + /* JK: cut off sessionsuffix */ + p = sb->s + strlen(sb->s) - strlen(sessionsuffix); + if (strncmp(p, sessionsuffix, strlen(sessionsuffix)) == 0) { + *p = '\0'; + + if (strcmp(sb->s, PUTTY_DEFAULT) != 0) { + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID; + mii.fType = MFT_STRING; + mii.fState = MFS_ENABLED; + mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE; + mii.dwTypeData = sb->s; + InsertMenuItem(session_menu, index_menu, TRUE, &mii); + index_menu++; + } + } + } + + FindClose(hFile); + strbuf_free(sb); + SetCurrentDirectory(curdir); + } + + /* JK: load keys from registry */ + if (ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey)) { + return; + } + sb = strbuf_new(); - while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) { - if(strcmp(buf, PUTTY_DEFAULT) != 0) { - strbuf_clear(sb); - unescape_registry_key(buf, sb); + while (ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) { + strbuf_clear(sb); + unescape_registry_key(buf, sb); + if (strcmp(buf, PUTTY_DEFAULT) != 0) { memset(&mii, 0, sizeof(mii)); mii.cbSize = sizeof(mii); mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID; mii.fType = MFT_STRING; mii.fState = MFS_ENABLED; mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE; + /* JK: add [registry] mark */ + put_fmt(sb, " [注册表]]"); mii.dwTypeData = sb->s; InsertMenuItem(session_menu, index_menu, true, &mii); index_menu++; @@ -940,7 +1214,7 @@ static void update_sessions(void) RegCloseKey(hkey); - if(index_menu == 0) { + if (index_menu == 0) { mii.cbSize = sizeof(mii); mii.fMask = MIIM_TYPE | MIIM_STATE; mii.fType = MFT_STRING; @@ -1309,8 +1583,9 @@ static LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT message, if (restrict_putty_acl) strcat(cmdline, "&R"); + /* JK: execute putty.exe with working directory same as is for pageant.exe */ if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, cmdline, - _T(""), SW_SHOW) <= 32) { + putty_path, SW_SHOW) <= 32) { MessageBox(NULL, "无法执行 PuTTY!", "错误", MB_OK | MB_ICONERROR); } @@ -1384,8 +1659,9 @@ static LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT message, strcat(param, "&R"); strcat(param, "@"); strcat(param, mii.dwTypeData); + /* JK: execute putty.exe with working directory same as is for pageant.exe */ if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, param, - _T(""), SW_SHOW) <= 32) { + puttypath, SW_SHOW) <= 32) { MessageBox(NULL, "无法执行 PuTTY!", "错误", MB_OK | MB_ICONERROR); } diff --git a/windows/storage.c b/windows/storage.c index 13209609..93834628 100644 --- a/windows/storage.c +++ b/windows/storage.c @@ -3,10 +3,19 @@ * defined in storage.h. */ + /* + * JK: disk config 0.20 from 11. 12. 2022 + * + * rewritten for storing information primary to disk + * reasonable error handling and reporting except for + * memory allocation errors (not enough memory) + * + * http://jakub.kotrla.net/putty/ + */ + #include #include #include -#include #include "putty.h" #include "storage.h" @@ -28,88 +37,773 @@ static HMODULE shell32_module = NULL; DECL_WINDOWS_FUNCTION(static, HRESULT, SHGetFolderPathA, (HWND, int, HANDLE, DWORD, LPSTR)); +/* JK: path of settings saved in files */ +static char seedpath[2 * MAX_PATH + 10] = "\0"; +static char sesspath[2 * MAX_PATH] = "\0"; +static char sshkpath[2 * MAX_PATH] = "\0"; +static char oldpath[2 * MAX_PATH] = "\0"; +static char sessionsuffix[16] = "\0"; +static char keysuffix[16] = "\0"; +static char jumplistpath[2 * MAX_PATH] = "\0"; + +/* JK: structures for handling settings in memory as linked list */ +struct setItem { + char* key; + char* value; + struct setItem* next; +}; +struct setPack { + unsigned int fromFile; + void* handle; + char* fileBuf; +}; + +/* JK: my generic function for simplyfing error reporting */ +DWORD errorShow(const char* pcErrText, const char* pcErrParam) { + HWND hwRParent; + DWORD errorCode; + char pcBuf[16]; + char* pcMessage; + + if (pcErrParam != NULL) { + pcMessage = snewn(strlen(pcErrParam) + strlen(pcErrText) + 31, char); + } else { + pcMessage = snewn(strlen(pcErrText) + 31, char); + } + + errorCode = GetLastError(); + ltoa(errorCode, pcBuf, 10); + + strcpy(pcMessage, "错误: "); + strcat(pcMessage, pcErrText); + strcat(pcMessage, "\n"); + + if (pcErrParam) { + strcat(pcMessage, pcErrParam); + strcat(pcMessage, "\n"); + } + strcat(pcMessage, "错误代码: "); + strcat(pcMessage, pcBuf); + + /* JK: get parent-window and show */ + hwRParent = GetActiveWindow(); + if (hwRParent != NULL) { hwRParent = GetLastActivePopup(hwRParent);} + + if (MessageBox(hwRParent, pcMessage, "错误", MB_OK|MB_APPLMODAL|MB_ICONEXCLAMATION) == 0) { + /* JK: this is really bad -> just ignore */ + return 0; + } + + sfree(pcMessage); + return errorCode; +}; + + +/* JK: pack string for use as filename - pack < > : " / \ | */ +static void packstr(const char *in, char *out) { + const char hex[16] = "0123456789ABCDEF"; + + while (*in) { + if (*in == '<' || *in == '>' || *in == ':' || *in == '"' || + *in == '/' || *in == '|') { + *out++ = '%'; + *out++ = hex[((unsigned char) *in) >> 4]; + *out++ = hex[((unsigned char) *in) & 15]; + } else + *out++ = *in; + in++; + } + *out = '\0'; + return; +} + +/* + * JK: create directory if specified as dir1\dir2\dir3 and dir1|2 doesn't exists + * handle if part of path already exists +*/ +int createPath(char* dir) { + char *p; + + p = strrchr(dir, '\\'); + + if (p == NULL) { + /* what if it already exists */ + if (!SetCurrentDirectory(dir)) { + CreateDirectory(dir, NULL); + return SetCurrentDirectory(dir); + } + return 1; + } + + *p = '\0'; + createPath(dir); + *p = '\\'; + ++p; + + /* what if it already exists */ + if (!SetCurrentDirectory(dir)) { + CreateDirectory(p, NULL); + return SetCurrentDirectory(p); + } + return 1; +} + +/* + * JK: join path pcMain.pcSuf solving extra cases to pcDest + * expecting - pcMain as path from WinAPI ::GetCurrentDirectory()/GetModuleFileName() + * - pcSuf as user input path from config (at least MAX_PATH long) +*/ +char* joinPath(char* pcDest, char* pcMain, char* pcSuf) { + + char* pcBuf = snewn(MAX_PATH+1, char); + + /* at first ExpandEnvironmentStrings */ + if (0 == ExpandEnvironmentStrings(pcSuf, pcBuf, MAX_PATH)) { + /* JK: failure -> revert back - but it usually won't work, so report error to user! */ + errorShow("Unable to ExpandEnvironmentStrings for session path", pcSuf); + strncpy(pcBuf, pcSuf, strlen(pcSuf)); + } + /* now ExpandEnvironmentStringsForUser - only on win2000Pro and above */ + /* It's much more tricky than I've expected, so it's ToDo */ + /* + static HMODULE userenv_module = NULL; + typedef BOOL (WINAPI *p_ExpandESforUser_t) (HANDLE, LPCTSTR, LPTSTR, DWORD); + static p_ExpandESforUser_t p_ExpandESforUser = NULL; + + HMODULE userenv_module = LoadLibrary("USERENV.DLL"); + + if (userenv_module) { + p_ExpandESforUser = (p_ExpandESforUser_t) GetProcAddress(shell32_module, "ExpandEnvironmentStringsForUserA"); + + if (p_ExpandESforUser) { + + TOKEN_IMPERSONATE + + if (0 == (p_ExpandESforUser(NULL, pcSuf, pcBuf, MAX_PATH))) { + /* JK: failure -> revert back - but it ussualy won't work, so report error to user! *//* + errorShow("Unable to ExpandEnvironmentStringsForUser for session path", pcBuf); + strncpy(pcSuf, pcBuf, strlen(pcSuf)); + } + } + }*/ + + /* expand done, result in pcBuf */ + + if ((*pcBuf == '/') || (*pcBuf == '\\')) { + /* everything ok */ + strcpy(pcDest, pcMain); + strcat(pcDest, pcBuf); + } + else { + if (*(pcBuf+1) == ':') { + /* absolute path */ + strcpy(pcDest, pcBuf); + } + else { + /* some weird relative path - add '\' */ + strcpy(pcDest, pcMain); + strcat(pcDest, "\\"); + strcat(pcDest, pcBuf); + } + } + sfree(pcBuf); + return pcDest; +} + +/* + * JK: init path variables from config or otherwise + * as of 1.5 GetModuleFileName solves our currentDirectory problem +*/ +int loadPath() { + + char *fileCont = NULL; + DWORD fileSize; + DWORD bytesRead; + char *p = NULL; + char *p2 = NULL; + HANDLE hFile; + int jumplistdefined = 0; + + char* puttypath = snewn( (MAX_PATH*2), char); + + /* JK: save path/curdir */ + GetCurrentDirectory( (MAX_PATH*2), oldpath); + + + /* JK: try curdir for putty.conf first */ + hFile = CreateFile("putty.conf",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); + + if (hFile != INVALID_HANDLE_VALUE) + { + /* JK: there is a putty.conf in curdir - use it and use curdir as puttypath */ + GetCurrentDirectory( (MAX_PATH*2), puttypath); + CloseHandle(hFile); + } else { + /* JK: get where putty.exe is */ + if (GetModuleFileName(NULL, puttypath, (MAX_PATH*2)) != 0) + { + p = strrchr(puttypath, '\\'); + if (p) + { + *p = '\0'; + } + SetCurrentDirectory(puttypath); + } + else GetCurrentDirectory( (MAX_PATH*2), puttypath); + } + + /* JK: set default values - if there is a config file, it will be overwitten */ + strcpy(sesspath, puttypath); + strcat(sesspath, "\\sessions"); + strcpy(sshkpath, puttypath); + strcat(sshkpath, "\\sshhostkeys"); + strcpy(seedpath, puttypath); + strcat(seedpath, "\\putty.rnd"); + strcpy(jumplistpath, puttypath); + strcat(jumplistpath, "\\jumplist.txt"); + + hFile = CreateFile("putty.conf",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); + + /* JK: now we can pre-clean-up */ + SetCurrentDirectory(oldpath); + + if (hFile != INVALID_HANDLE_VALUE) { + fileSize = GetFileSize(hFile, NULL); + fileCont = snewn(fileSize+16, char); + + if (!ReadFile(hFile, fileCont, fileSize, &bytesRead, NULL)) + { + errorShow("无法读取配置文件,回滚到默认设置", NULL); + /* JK: default values are already there and clean-up at end */ + } + else { + /* JK: parse conf file to path variables */ + *(fileCont+fileSize+1) = '\0'; + *(fileCont+fileSize) = '\n'; + p = fileCont; + while (p) { + if (*p == ';') { /* JK: comment -> skip line */ + p = strchr(p, '\n'); + ++p; + continue; + } + p2 = strchr(p, '='); + if (!p2) break; + *p2 = '\0'; + ++p2; + + if (!strcmp(p, "sessions")) { + p = strchr(p2, '\n'); + *p = '\0'; + joinPath(sesspath, puttypath, p2); + p2 = sesspath+strlen(sesspath)-1; + while ((*p2 == ' ')||(*p2 == '\n')||(*p2 == '\r')||(*p2 == '\t')) --p2; + *(p2+1) = '\0'; + } + else if (!strcmp(p, "sshhostkeys")) { + p = strchr(p2, '\n'); + *p = '\0'; + joinPath(sshkpath, puttypath, p2); + p2 = sshkpath+strlen(sshkpath)-1; + while ((*p2 == ' ')||(*p2 == '\n')||(*p2 == '\r')||(*p2 == '\t')) --p2; + *(p2+1) = '\0'; + } + else if (!strcmp(p, "seedfile")) { + p = strchr(p2, '\n'); + *p = '\0'; + joinPath(seedpath, puttypath, p2); + p2 = seedpath+strlen(seedpath)-1; + while ((*p2 == ' ')||(*p2 == '\n')||(*p2 == '\r')||(*p2 == '\t')) --p2; + *(p2+1) = '\0'; + } + else if (!strcmp(p, "sessionsuffix")) { + p = strchr(p2, '\n'); + *p = '\0'; + strcpy(sessionsuffix, p2); + p2 = sessionsuffix+strlen(sessionsuffix)-1; + while ((*p2 == ' ')||(*p2 == '\n')||(*p2 == '\r')||(*p2 == '\t')) --p2; + *(p2+1) = '\0'; + } + else if (!strcmp(p, "keysuffix")) { + p = strchr(p2, '\n'); + *p = '\0'; + strcpy(keysuffix, p2); + p2 = keysuffix+strlen(keysuffix)-1; + while ((*p2 == ' ')||(*p2 == '\n')||(*p2 == '\r')||(*p2 == '\t')) --p2; + *(p2+1) = '\0'; + } + else if (!strcmp(p, "jumplist")) { + p = strchr(p2, '\n'); + *p = '\0'; + joinPath(jumplistpath, puttypath, p2); + p2 = jumplistpath+strlen(jumplistpath)-1; + while ((*p2 == ' ')||(*p2 == '\n')||(*p2 == '\r')||(*p2 == '\t')) --p2; + *(p2+1) = '\0'; + jumplistdefined = 1; + } + ++p; + } + + if (jumplistdefined == 0) { strcpy(jumplistpath, ":"); } + } + CloseHandle(hFile); + sfree(fileCont); + } + else { /* INVALID_HANDLE + * JK: unable to read conf file - probably doesn't exists + * we won't create one, user wants putty light, just fall back to defaults + * and defaults are already there */ + strcpy(jumplistpath, ":"); + } + + sfree(puttypath); + return 1; +} + struct settings_w { - HKEY sesskey; + struct setPack* sp; }; settings_w *open_settings_w(const char *sessionname, char **errmsg) { + char *p; + strbuf* sb; + struct setPack* sp; *errmsg = NULL; - if (!sessionname || !*sessionname) - sessionname = "默认设置"; + if (!sessionname || !*sessionname) { + sessionname = "默认设置"; + } - strbuf *sb = strbuf_new(); - escape_registry_key(sessionname, sb); + /* JK: if sessionname contains [registry] -> cut it off */ + if ( *(sessionname+strlen(sessionname)-1) == ']') { + p = strrchr(sessionname, '['); + *(p-1) = '\0'; + } + + sb = strbuf_new(); + escape_registry_key(sessionname, sb); - HKEY sesskey = open_regkey(true, HKEY_CURRENT_USER, puttystr, sb->s); - if (!sesskey) { - *errmsg = dupprintf("Unable to create registry key\n" - "HKEY_CURRENT_USER\\%s\\%s", puttystr, sb->s); + sp = snew( struct setPack ); + sp->fromFile = 0; + sp->handle = NULL; + + /* JK: secure pack for filename */ + sp->fileBuf = snewn(3 * strlen(sb->s) + 1 + 16, char); + packstr(sb->s, sp->fileBuf); + strcat(sp->fileBuf, sessionsuffix); strbuf_free(sb); - return NULL; - } - strbuf_free(sb); - settings_w *toret = snew(settings_w); - toret->sesskey = sesskey; + settings_w *toret = snew(settings_w); + toret->sp = sp; return toret; } void write_setting_s(settings_w *handle, const char *key, const char *value) { - if (handle) - put_reg_sz(handle->sesskey, key, value); + struct setItem *st; + struct setPack* sp; + + if (handle) { + sp = handle->sp; + + /* JK: counting max lenght of keys/values */ + sp->fromFile = max(sp->fromFile, strlen(key)+1); + sp->fromFile = max(sp->fromFile, strlen(value)+1); + + st = sp->handle; + while (st) { + if ( strcmp(st->key, key) == 0) { + /* this key already set -> reset */ + sfree(st->value); + st->value = snewn( strlen(value)+1, char); + strcpy(st->value, value); + return; + } + st = st->next; + } + /* JK: key not found -> add to begin */ + st = snew( struct setItem ); + st->key = snewn( strlen(key)+1, char); + strcpy(st->key, key); + st->value = snewn( strlen(value)+1, char); + strcpy(st->value, value); + st->next = sp->handle; + sp->handle = st; + } } void write_setting_i(settings_w *handle, const char *key, int value) { - if (handle) - put_reg_dword(handle->sesskey, key, value); + struct setItem *st; + struct setPack* sp; + + if (handle) { + sp = handle->sp; + + /* JK: counting max lenght of keys/values */ + sp->fromFile = max(sp->fromFile, strlen(key) + 1); + + st = sp->handle; + while (st) { + if ( strcmp(st->key, key) == 0) { + /* this key already set -> reset */ + sfree(st->value); + st->value = snewn(16, char); + itoa(value, st->value, 10); + return; + } + st = st->next; + } + /* JK: key not found -> add to begin */ + st = snew( struct setItem ); + st->key = snewn( strlen(key)+1, char); + strcpy(st->key, key); + st->value = snewn(16, char); + itoa(value, st->value, 10); + st->next = sp->handle; + sp->handle = st; + } } void close_settings_w(settings_w *handle) { - close_regkey(handle->sesskey); - sfree(handle); + HANDLE hFile; + DWORD written; + WIN32_FIND_DATA FindFile; + char *p; + struct setItem *st1,*st2; + int writeok; + + if (!handle) return; + + /* JK: we will write to disk now - open file, filename stored in handle already packed */ + if ((hFile = FindFirstFile(sesspath, &FindFile)) == INVALID_HANDLE_VALUE) { + if (!createPath(sesspath)) { + errorShow("无法创建存储会话的目录", sesspath); + return; + } + } + FindClose(hFile); + GetCurrentDirectory( (MAX_PATH*2), oldpath); + SetCurrentDirectory(sesspath); + + hFile = CreateFile(handle->sp->fileBuf, GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); + if (hFile == INVALID_HANDLE_VALUE) { + errorShow("无法打开用来写入的文件", handle->sp->fileBuf ); + return; + } + + /* JK: allocate enough memory for all keys/values */ + /* p = snewn( max( 3* handle->sp->fromFile ,16), char); no longer need because of strbuf. */ + + /* JK: process linked list */ + st1 = handle->sp->handle; + writeok = 1; + + while (st1) { + strbuf* sb = strbuf_new(); + escape_registry_key(st1->key, sb); + p = strbuf_to_str(sb); + writeok = writeok && WriteFile( hFile, p, strlen(p), &written, NULL); + writeok = writeok && WriteFile( hFile, "\\", 1, &written, NULL); + sfree(p); + + sb = strbuf_new(); + escape_registry_key(st1->value, sb); + p = strbuf_to_str(sb); + writeok = writeok && WriteFile( hFile, p, strlen(p), &written, NULL); + writeok = writeok && WriteFile( hFile, "\\\n", 2, &written, NULL); + sfree(p); + + if (!writeok) { + errorShow("无法保存设置", st1->key); + return; + /* JK: memory should be freed here - fixme */ + } + + st2 = st1->next; + sfree(st1->key); + sfree(st1->value); + sfree(st1); + st1 = st2; + } + + sfree(handle->sp->fileBuf); + CloseHandle( (HANDLE)hFile ); + SetCurrentDirectory(oldpath); } struct settings_r { - HKEY sesskey; + struct setPack* sp; }; +/* JK: Ahead declaration for logical order of functions open_settings_r_inner, open_settings_r */ +settings_r *open_settings_r_inner(const char *sessionname); + settings_r *open_settings_r(const char *sessionname) { - if (!sessionname || !*sessionname) - sessionname = "默认设置"; + void *p = open_settings_r_inner(sessionname); + char *ses; + if (p == NULL) /* JK: try to find session with [registry] suffix*/ + { + ses = snewn(strlen(sessionname)+16, char); + strcpy(ses, sessionname); + strcat(ses, " [注册表]"); + p = open_settings_r_inner(ses); + } + if (p == NULL) + return NULL; - strbuf *sb = strbuf_new(); - escape_registry_key(sessionname, sb); - HKEY sesskey = open_regkey(false, HKEY_CURRENT_USER, puttystr, sb->s); - strbuf_free(sb); + settings_r* toret = snew(settings_r); + toret->sp = p; + return toret; +} - if (!sesskey) - return NULL; +settings_r *open_settings_r_inner(const char *sessionname) +{ + char *p; + strbuf* sb; + char *ses; + char *fileCont; + DWORD fileSize; + DWORD bytesRead; + HANDLE hFile; + struct setPack* sp; + struct setItem *st1, *st2; + + sp = snew( struct setPack ); + + if (!sessionname || !*sessionname) { + sessionname = "默认设置"; + } - settings_r *toret = snew(settings_r); - toret->sesskey = sesskey; - return toret; + /* JK: in the first call of this function we initialize path variables */ + if (*sesspath == '\0') { + loadPath(); + } + + /* JK: if sessionname contains [registry] -> cut it off in another buffer */ + if ( *(sessionname+strlen(sessionname)-1) == ']') { + ses = snewn(strlen(sessionname)+1, char); + strcpy(ses, sessionname); + + p = strrchr(ses, '['); + *(p-1) = '\0'; + + sb = strbuf_new(); + escape_registry_key(ses, sb); /* do not free sb to be used at the end of function */ + sfree(ses); + + sp->fromFile = 0; + } + else if (INVALID_HANDLE_VALUE != CreateFile(sessionname, GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL)) { + /* JK: 6.3.2009 - 0.3.5 - for running putty for session files */ + p = snewn(2 * strlen(sessionname) + 1, char); + strcpy(p, sessionname); + sp->fromFile = 1; + } + else { + sb = strbuf_new(); + escape_registry_key(sessionname, sb); + + /* JK: secure pack for filename */ + p = snewn(3 * strlen(sb->s) + 1 + 16, char); + packstr(sb->s, p); + strcat(p, sessionsuffix); + strbuf_free(sb); + + sp->fromFile = 1; + } + + /* JK: default settings must be read from registry */ + /* 8.1.2007 - 0.1.6 try to load them from file if exists - nasty code duplication */ + if (!strcmp(sessionname, "默认设置")) { + GetCurrentDirectory( (MAX_PATH*2), oldpath); + if (SetCurrentDirectory(sesspath)) { + hFile = CreateFile(p, GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); + } + else { + hFile = INVALID_HANDLE_VALUE; + } + SetCurrentDirectory(oldpath); + + if (hFile == INVALID_HANDLE_VALUE) { + sb = strbuf_new(); + escape_registry_key(sessionname, sb); + sp->fromFile = 0; + } + else { + sp->fromFile = 1; + CloseHandle(hFile); + } + } + + if (sp->fromFile) { + /* JK: session is in file -> open dir/file */ + GetCurrentDirectory( (MAX_PATH*2), oldpath); + if (SetCurrentDirectory(sesspath)) { + hFile = CreateFile(p, GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); + } + else { + hFile = INVALID_HANDLE_VALUE; + } + SetCurrentDirectory(oldpath); + + if (hFile == INVALID_HANDLE_VALUE) { + /* JK: some error occured -> just report and fail */ + + /* JK: PSCP/PSFTP/PLINK always try to load settings for sessionname=hostname (to what PSCP/PLINK is just connecting) + These settings usually doesn't exist. + So for PSCP/PSFTP/PLINK, do not report error - so report error only for PuTTY + assume only PuTTY project has PUTTY_WIN_RES_H defined + */ +#ifdef PUTTY_WIN_RES_H + errorShow("无法载入用来读取的文件", p); +#endif + sfree(p); + return NULL; + } + + /* JK: succes -> load structure setPack from file */ + fileSize = GetFileSize(hFile, NULL); + fileCont = snewn(fileSize+16, char); + + if (!ReadFile(hFile, fileCont, fileSize, &bytesRead, NULL)) { + errorShow("无法从文件读取会话", p); + sfree(p); + return NULL; + } + sfree(p); + + st1 = snew( struct setItem ); + sp->fromFile = 1; + sp->handle = st1; + + p = fileCont; + sp->fileBuf = fileCont; /* JK: remeber for memory freeing */ + + /* JK: parse file in format: + * key1\value1\ + * ... + */ + while (p < (fileCont+fileSize)) { + st1->key = p; + p = strchr(p, '\\'); + if (!p) break; + *p = '\0'; + ++p; + st1->value = p; + p = strchr(p, '\\'); + if (!p) break; + *p = '\0'; + ++p; + ++p; /* for "\\\n" - human readable files */ + + st2 = snew( struct setItem ); + st2->next = NULL; + st2->key = NULL; + st2->value = NULL; + + st1->next = st2; + st1 = st2; + } + CloseHandle(hFile); + } + else { + /* JK: session is in registry */ + HKEY sesskey = open_regkey(false, HKEY_CURRENT_USER, puttystr, sb->s); + + sp->fromFile = 0; + sp->handle = sesskey; + strbuf_free(sb); + } + + return sp; } + + + + char *read_setting_s(settings_r *handle, const char *key) { - if (!handle) + DWORD type; + struct setItem *st; + char *p; + char *p2; + char *buffer; + DWORD size = -1; + + if (!handle) return NULL; + + if (handle->sp->fromFile) { + + strbuf* sb = strbuf_new(); + escape_registry_key(key, sb); + p = strbuf_to_str(sb); + + st = handle->sp->handle; + while (st->key) { + if ( strcmp(st->key, p) == 0) { + size = 2 * strlen(st->value) + 1; + buffer = snewn(size, char); + + strbuf* sb = strbuf_new(); + unescape_registry_key(st->value, sb); + p2 = strbuf_to_str(sb); + + /* JK: at first ExpandEnvironmentStrings */ + if (0 == ExpandEnvironmentStrings(p2, buffer, size)) { + /* JK: failure -> revert back - but it usually won't work, so report error to user! */ + errorShow("Unable to ExpandEnvironmentStrings for session path", p2); + strncpy(p2, buffer, strlen(p2)); + } + sfree(p); + sfree(p2); + return buffer; + } + st = st->next; + } + } + else { + HKEY hKey = (HKEY) handle->sp->handle; + + return get_reg_sz(hKey, key); + } + /* JK: should not end here -> value not found in file */ return NULL; - return get_reg_sz(handle->sesskey, key); } + int read_setting_i(settings_r *handle, const char *key, int defvalue) { DWORD val; - if (!handle || !get_reg_dword(handle->sesskey, key, &val)) + struct setItem *st; + + if (!handle) return 0; /* JK: new in 0.1.3 */ + + if (handle->sp->fromFile) { + st = handle->sp->handle; + while (st->key) { + if ( strcmp(st->key, key) == 0) { + return atoi(st->value); + } + st = st->next; + } + } + else { + HKEY hKey = (HKEY) handle->sp->handle; + + if (!hKey || !get_reg_dword(hKey, key, &val)) { + return defvalue; + } + else { + return val; + } + } + /* JK: should not end here -> value not found in file */ return defvalue; - else - return val; } FontSpec *read_setting_fontspec(settings_r *handle, const char *name) @@ -152,8 +846,7 @@ FontSpec *read_setting_fontspec(settings_r *handle, const char *name) return ret; } -void write_setting_fontspec(settings_w *handle, - const char *name, FontSpec *font) +void write_setting_fontspec(settings_w *handle, const char *name, FontSpec *font) { char *settingname; @@ -174,105 +867,306 @@ Filename *read_setting_filename(settings_r *handle, const char *name) char *tmp = read_setting_s(handle, name); if (tmp) { Filename *ret = filename_from_str(tmp); - sfree(tmp); - return ret; - } else - return NULL; + sfree(tmp); + return ret; + } else { + return NULL; + } } -void write_setting_filename(settings_w *handle, - const char *name, Filename *result) + +void write_setting_filename(settings_w *handle, const char *name, Filename *result) { write_setting_s(handle, name, result->path); } void close_settings_r(settings_r *handle) { - if (handle) { - close_regkey(handle->sesskey); - sfree(handle); - } + if (!handle) return; /* JK: new in 0.1.3 */ + + if (handle->sp->fromFile) { + struct setItem *st1, *st2; + + st1 = handle->sp->handle; + while (st1) { + st2 = st1->next; + sfree(st1); + st1 = st2; + } + sfree(handle->sp->fileBuf ); + sfree(handle); + } + else { + HKEY hKey = (HKEY) handle->sp->handle; + close_regkey(hKey); + sfree(handle); + } } void del_settings(const char *sessionname) { - HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, puttystr); - if (!rkey) - return; - - strbuf *sb = strbuf_new(); - escape_registry_key(sessionname, sb); - del_regkey(rkey, sb->s); - strbuf_free(sb); - - close_regkey(rkey); + char *p; + strbuf* sb; + char *pss; + char *p2; + char *p2ss; + + /* JK: if sessionname contains [registry] -> cut it off and delete from registry */ + if ( *(sessionname+strlen(sessionname)-1) == ']') { + + p = strrchr(sessionname, '['); + *(p-1) = '\0'; + + HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, puttystr); + if (!rkey) + return; + + sb = strbuf_new(); + escape_registry_key(sessionname, sb); + del_regkey(rkey, sb->s); + strbuf_free(sb); + close_regkey(rkey); + } + else { + /* JK: delete from file - file itself */ + p = snewn(3 * strlen(sessionname) + 1, char); + pss = snewn(3 * (strlen(sessionname) + strlen(sessionsuffix)) + 1, char); + strcpy(p, sessionname); + strcpy(pss, sessionname); + strcat(pss, sessionsuffix); + p2 = snewn(3 * strlen(p) + 1, char); + p2ss = snewn(3 * strlen(pss) + 1, char); + + sb = strbuf_new(); + escape_registry_key(p, sb); + strcpy(p, sb->s); + strbuf_free(sb); + packstr(p, p2); + + sb = strbuf_new(); + escape_registry_key(pss, sb); + strcpy(pss, sb->s); + strbuf_free(sb); + packstr(pss, p2ss); + + GetCurrentDirectory( (MAX_PATH*2), oldpath); + if (SetCurrentDirectory(sesspath)) { + if (!DeleteFile(p2ss)) + { + if (!DeleteFile(p2)) + { + errorShow("无法删除设置。", NULL); + } + } + SetCurrentDirectory(oldpath); + } + sfree(p); + sfree(p2); + } - remove_session_from_jumplist(sessionname); + remove_session_from_jumplist(sessionname); } struct settings_e { HKEY key; int i; + int fromFile; + HANDLE hFile; }; + settings_e *enum_settings_start(void) { - HKEY key = open_regkey(false, HKEY_CURRENT_USER, puttystr); - if (!key) - return NULL; + struct settings_e *ret; - settings_e *ret = snew(settings_e); - if (ret) { + /* JK: in the first call of this function we can initialize path variables */ + if (*sesspath == '\0') { + loadPath(); + } + /* JK: we have path variables */ + + /* JK: let's do what this function should normally do */ + ret = snew(struct settings_e); + + HKEY key = open_regkey(false, HKEY_CURRENT_USER, puttystr); + if (!key) { + /* + * JK: nothing in registry -> pretend we found it, first call to enum_settings_next + * will solve this by starting scanning dir sesspath + */ + } ret->key = key; + ret->fromFile = 0; + ret->hFile = NULL; ret->i = 0; - } return ret; } -bool enum_settings_next(settings_e *e, strbuf *sb) +bool enum_settings_next(settings_e *handle, strbuf *sb) { - char *name = enum_regkey(e->key, e->i); - if (!name) - return false; + WIN32_FIND_DATA FindFileData; + HANDLE hFile; + strbuf* othersb; + char *otherbuf; + char *pss; + + if (!handle) return NULL; /* JK: new in 0.1.3 */ + + if (! handle->fromFile ) { + char *name = enum_regkey(handle->key, handle->i); + if (name) { + unescape_registry_key(name, sb); + sfree(name); + handle->i++; + put_fmt(sb, " [注册表]"); + return true; + } + + /* JK: registry scanning done, starting scanning directory "sessions" */ + handle->fromFile = 1; + GetCurrentDirectory( (MAX_PATH*2), oldpath); + if (!SetCurrentDirectory(sesspath)) { + return NULL; + } + hFile = FindFirstFile("*", &FindFileData); - unescape_registry_key(name, sb); - sfree(name); - e->i++; - return true; + if (hFile == INVALID_HANDLE_VALUE) { + return NULL; + } + handle->hFile = hFile; + /* JK: we assume here that first found file will be "." which we ignore so we can call FindNextFile immediately */ + while (FindNextFile(hFile,&FindFileData)) { + if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) { + /* JK: skip directories ("." and ".." too) */ + continue; + } + + othersb = strbuf_new(); + unescape_registry_key(FindFileData.cFileName, othersb); + otherbuf = strbuf_to_str(othersb); + + /* JK: verify and possibly cut off sessionsuffix */ + pss = otherbuf + strlen(otherbuf) - strlen(sessionsuffix); + if (strncmp(pss, sessionsuffix, strlen(sessionsuffix)) == 0) { + *pss = '\0'; + put_fmt(sb, otherbuf); + sfree(otherbuf); + return true; + } + } + /* JK: not a single file found -> give up */ + return false; + } + else if ( handle->fromFile ) { + while (FindNextFile(handle->hFile,&FindFileData)) { + + othersb = strbuf_new(); + unescape_registry_key(FindFileData.cFileName, othersb); + otherbuf = strbuf_to_str(othersb); + + /* JK: verify and possibly cut off sessionsuffix */ + pss = otherbuf + strlen(otherbuf) - strlen(sessionsuffix); + if (strncmp(pss, sessionsuffix, strlen(sessionsuffix)) == 0) { + *pss = '\0'; + put_fmt(sb, otherbuf); + sfree(otherbuf); + return true; + } + } + return false; + } + /* JK: should not end here */ + return false; } -void enum_settings_finish(settings_e *e) +void enum_settings_finish(settings_e *handle) { - close_regkey(e->key); - sfree(e); + if (!handle) return; /* JK: new in 0.1.3 */ + + close_regkey(handle->key); + if (handle->hFile) { + FindClose(handle->hFile); + } + SetCurrentDirectory(oldpath); + sfree(handle); } -static void hostkey_regname(strbuf *sb, const char *hostname, +static void hostkey_regname(strbuf* sb, const char *hostname, int port, const char *keytype) { - put_fmt(sb, "%s@%d:", keytype, port); - escape_registry_key(hostname, sb); + put_fmt(sb, "%s@%d:", keytype, port); + escape_registry_key(hostname, sb); } int check_stored_host_key(const char *hostname, int port, - const char *keytype, const char *key) + const char *keytype, const char *key) { - /* - * Read a saved key in from the registry and see what it says. - */ + char* otherstr; + int ret, compare, userMB; + DWORD fileSize; + DWORD bytesRW; + char *p; + HANDLE hFile; + WIN32_FIND_DATA FindFile; + + + /* Read a saved key in from the registry and see what it says. */ strbuf *regname = strbuf_new(); + hostkey_regname(regname, hostname, port, keytype); - HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, + /* JK: settings on disk - every hostkey as file in dir */ + GetCurrentDirectory( (MAX_PATH*2), oldpath); + if (SetCurrentDirectory(sshkpath)) { + p = snewn(3 * strlen(regname->s) + 1 + 16, char); + packstr(regname->s, p); + strcat(p, keysuffix); + + hFile = CreateFile(p, GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); + SetCurrentDirectory(oldpath); + + if (hFile != INVALID_HANDLE_VALUE) { + /* JK: ok we got it -> read it to otherstr */ + fileSize = GetFileSize(hFile, NULL); + otherstr = snewn(fileSize+1, char); + ReadFile(hFile, otherstr, fileSize, &bytesRW, NULL); + *(otherstr+fileSize) = '\0'; + + compare = strcmp(otherstr, key); + + CloseHandle(hFile); + sfree(otherstr); + strbuf_free(regname); + sfree(p); + + if (compare) { /* key is here, but different */ + return 2; + } + else { /* key is here and match */ + return 0; + } + } + else { + /* not found as file -> try registry */ + sfree(p); + } + } + else { + /* JK: there are no hostkeys as files -> try registry -> nothing to do here now */ + } + + /* JK: directory/file not found -> try registry */ + HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys"); - if (!rkey) { - strbuf_free(regname); - return 1; /* key does not exist in registry */ - } + if (!rkey) { + strbuf_free(regname); + return 1; /* key does not exist in registry */ + } + + *otherstr = get_reg_sz(rkey, regname->s); + if (!otherstr && !strcmp(keytype, "rsa")) { - char *otherstr = get_reg_sz(rkey, regname->s); - if (!otherstr && !strcmp(keytype, "rsa")) { /* * Key didn't exist. If the key type is RSA, we'll try * another trick, which is to look up the _old_ key format @@ -324,31 +1218,83 @@ int check_stored_host_key(const char *hostname, int port, if (!strcmp(new->s, key)) { put_reg_sz(rkey, regname->s, new->s); otherstr = strbuf_to_str(new); + /* JK: session is not saved to file - fixme */ } else { strbuf_free(new); } } - sfree(oldstyle); } - close_regkey(rkey); + compare = otherstr ? strcmp(otherstr, key) : -1; + + strbuf_free(regname); - int compare = otherstr ? strcmp(otherstr, key) : -1; + if (!otherstr) { + strbuf_free(regname); + RegCloseKey(rkey); + return 1; /* key does not exist in registry */ + } + else if (compare) { + strbuf_free(regname); + RegCloseKey(rkey); + return 2; /* key is different in registry */ + } + else { /* key matched OK in registry */ + /* JK: matching key found in registry -> warn user, ask what to do */ + p = snewn(256, char); + userMB = MessageBox(NULL, "主机密钥已缓存在注册表。" + "确认要移动到文件配置中保存么?\n\n" + "是 \t-> 移动 (从注册表中删除)\n" + "否 \t-> 复制 (继续保存在注册表中)\n" + "取消 \t-> 不做任何操作\n", "安全警告", MB_YESNOCANCEL|MB_ICONWARNING); + + if ((userMB == IDYES) || (userMB == IDNO)) { + /* JK: save key to file */ + if ((hFile = FindFirstFile(sshkpath, &FindFile)) == INVALID_HANDLE_VALUE) { + createPath(sshkpath); + } + FindClose(hFile); + GetCurrentDirectory( (MAX_PATH*2), oldpath); + SetCurrentDirectory(sshkpath); + + p = snewn(3*strlen(regname->s) + 1 + 16, char); + packstr(regname->s, p); + strcat(p, keysuffix); + + hFile = CreateFile(p, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if (hFile == INVALID_HANDLE_VALUE) { + errorShow("无法创建文件 (不会从注册表删除密钥)", p); + userMB = IDNO; + } + else { + if (!WriteFile(hFile, key, strlen(key), &bytesRW, NULL)) { + errorShow("无法保存密钥到文件 (不会从注册表删除密钥)", NULL); + userMB = IDNO; + } + CloseHandle(hFile); + } + sfree(p); + SetCurrentDirectory(oldpath); + } + if (userMB == IDYES) { + /* delete from registry */ + if (RegDeleteValue(rkey, regname) != ERROR_SUCCESS) { + errorShow("无法删除注册表值", regname); + } + } + /* JK: else (Cancel) -> nothing to be done right now */ - sfree(otherstr); - strbuf_free(regname); + RegCloseKey(rkey); - if (!otherstr) - return 1; /* key does not exist in registry */ - else if (compare) - return 2; /* key is different in registry */ - else - return 0; /* key matched OK in registry */ + strbuf_free(regname); + return 0; + } } bool have_ssh_host_key(const char *hostname, int port, - const char *keytype) + const char *keytype) { /* * If we have a host key, check_stored_host_key will return 0 or 2. @@ -360,19 +1306,45 @@ bool have_ssh_host_key(const char *hostname, int port, void store_host_key(const char *hostname, int port, const char *keytype, const char *key) { - strbuf *regname = strbuf_new(); + strbuf *regname; + WIN32_FIND_DATA FindFile; + HANDLE hFile = NULL; + char* p = NULL; + DWORD bytesWritten; + + regname = strbuf_new(); hostkey_regname(regname, hostname, port, keytype); - HKEY rkey = open_regkey(true, HKEY_CURRENT_USER, - PUTTY_REG_POS "\\SshHostKeys"); - if (rkey) { - put_reg_sz(rkey, regname->s, key); - close_regkey(rkey); - } /* else key does not exist in registry */ + /* JK: save hostkey to file in dir */ + if ((hFile = FindFirstFile(sshkpath, &FindFile)) == INVALID_HANDLE_VALUE) { + createPath(sshkpath); + } + FindClose(hFile); + GetCurrentDirectory( (MAX_PATH*2), oldpath); + SetCurrentDirectory(sshkpath); - strbuf_free(regname); + p = snewn(3*strlen(regname->s) + 1, char); + packstr(regname->s, p); + strcat(p, keysuffix); + hFile = CreateFile(p, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if (hFile == INVALID_HANDLE_VALUE) { + errorShow("无法创建文件", p); + } + else { + if (!WriteFile(hFile, key, strlen(key), &bytesWritten, NULL)) { + errorShow("无法保存值到文件", NULL); + } + CloseHandle(hFile); + } + SetCurrentDirectory(oldpath); + + sfree(p); + strbuf_free(regname); } + +/* JK: new stuff that should be probably saved in files instead of registry */ struct host_ca_enum { HKEY key; int i; @@ -506,6 +1478,8 @@ char *host_ca_delete(const char *name) return NULL; } +/* end of new stuff for CA */ + /* * Open (or delete) the random seed file. @@ -519,7 +1493,7 @@ static bool try_random_seed(char const *path, int action, HANDLE *ret) win_strerror(GetLastError())); } *ret = INVALID_HANDLE_VALUE; - return false; /* so we'll do the next ones too */ + return false; /* so we'll do the next ones too */ } *ret = CreateFile(path, @@ -534,116 +1508,130 @@ static bool try_random_seed(char const *path, int action, HANDLE *ret) return (*ret != INVALID_HANDLE_VALUE); } -static bool try_random_seed_and_free(char *path, int action, HANDLE *hout) +static bool try_random_seed_and_free(char* path, int action, HANDLE* hout) { - bool retd = try_random_seed(path, action, hout); - sfree(path); - return retd; + bool retd = try_random_seed(path, action, hout); + sfree(path); + return retd; } static HANDLE access_random_seed(int action) { - HANDLE rethandle; - - /* - * Iterate over a selection of possible random seed paths until - * we find one that works. - * - * We do this iteration separately for reading and writing, - * meaning that we will automatically migrate random seed files - * if a better location becomes available (by reading from the - * best location in which we actually find one, and then - * writing to the best location in which we can _create_ one). - */ + HKEY rkey; + HANDLE rethandle; + + /* JK: settings in conf file are the most prior */ + if (seedpath != "\0") { + /* JK: In PuTTY 0.58 this won't ever happen - this function was called only if (!seedpath[0]) + * This changed in PuTTY 0.59 - read the long comment below + */ + // return; + if (try_random_seed(seedpath, action, &rethandle)) { + return rethandle; + } + } + /* JK: ok, try registry and etc. as in original PuTTY */ - /* - * First, try the location specified by the user in the - * Registry, if any. - */ - { - HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, PUTTY_REG_POS); - if (rkey) { - char *regpath = get_reg_sz(rkey, "RandSeedFile"); - close_regkey(rkey); - if (regpath) { - bool success = try_random_seed(regpath, action, &rethandle); - sfree(regpath); - if (success) - return rethandle; - } + /* + * Iterate over a selection of possible random seed paths until + * we find one that works. + * + * We do this iteration separately for reading and writing, + * meaning that we will automatically migrate random seed files + * if a better location becomes available (by reading from the + * best location in which we actually find one, and then + * writing to the best location in which we can _create_ one). + */ + + /* + * First, try the location specified by the user in the + * Registry, if any. + */ + { + HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, PUTTY_REG_POS); + + if (rkey) { + char *regpath = get_reg_sz(rkey, "RandSeedFile"); + close_regkey(rkey); + if (regpath) { + bool success = try_random_seed(regpath, action, &rethandle); + sfree(regpath); + if (success) + return rethandle; + } + } } - } - /* - * Next, try the user's local Application Data directory, - * followed by their non-local one. This is found using the - * SHGetFolderPath function, which won't be present on all - * versions of Windows. - */ - if (!tried_shgetfolderpath) { - /* This is likely only to bear fruit on systems with IE5+ - * installed, or WinMe/2K+. There is some faffing with - * SHFOLDER.DLL we could do to try to find an equivalent - * on older versions of Windows if we cared enough. - * However, the invocation below requires IE5+ anyway, - * so stuff that. */ - shell32_module = load_system32_dll("shell32.dll"); - GET_WINDOWS_FUNCTION(shell32_module, SHGetFolderPathA); - tried_shgetfolderpath = true; - } - if (p_SHGetFolderPathA) { - char profile[MAX_PATH + 1]; - if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, - NULL, SHGFP_TYPE_CURRENT, profile)) && - try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND"), - action, &rethandle)) - return rethandle; - - if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_APPDATA, - NULL, SHGFP_TYPE_CURRENT, profile)) && - try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND"), - action, &rethandle)) - return rethandle; - } + /* + * Next, try the user's local Application Data directory, + * followed by their non-local one. This is found using the + * SHGetFolderPath function, which won't be present on all + * versions of Windows. + */ + if (!tried_shgetfolderpath) { + /* This is likely only to bear fruit on systems with IE5+ + * installed, or WinMe/2K+. There is some faffing with + * SHFOLDER.DLL we could do to try to find an equivalent + * on older versions of Windows if we cared enough. + * However, the invocation below requires IE5+ anyway, + * so stuff that. */ + shell32_module = load_system32_dll("shell32.dll"); + GET_WINDOWS_FUNCTION(shell32_module, SHGetFolderPathA); + tried_shgetfolderpath = true; + } + if (p_SHGetFolderPathA) { + char profile[MAX_PATH + 1]; + if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, + NULL, SHGFP_TYPE_CURRENT, profile)) && + try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND"), + action, &rethandle)) + return rethandle; + + if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_APPDATA, + NULL, SHGFP_TYPE_CURRENT, profile)) && + try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND"), + action, &rethandle)) + return rethandle; + } - /* - * Failing that, try %HOMEDRIVE%%HOMEPATH% as a guess at the - * user's home directory. - */ - { - char drv[MAX_PATH], path[MAX_PATH]; - - DWORD drvlen = GetEnvironmentVariable("HOMEDRIVE", drv, sizeof(drv)); - DWORD pathlen = GetEnvironmentVariable("HOMEPATH", path, sizeof(path)); - - /* We permit %HOMEDRIVE% to expand to an empty string, but if - * %HOMEPATH% does that, we abort the attempt. Same if either - * variable overflows its buffer. */ - if (drvlen == 0) - drv[0] = '\0'; - - if (drvlen < lenof(drv) && pathlen < lenof(path) && pathlen > 0 && - try_random_seed_and_free( - dupcat(drv, path, "\\PUTTY.RND"), action, &rethandle)) - return rethandle; - } + /* + * Failing that, try %HOMEDRIVE%%HOMEPATH% as a guess at the + * user's home directory. + */ + { + char drv[MAX_PATH], path[MAX_PATH]; + + DWORD drvlen = GetEnvironmentVariable("HOMEDRIVE", drv, sizeof(drv)); + DWORD pathlen = GetEnvironmentVariable("HOMEPATH", path, sizeof(path)); + + /* We permit %HOMEDRIVE% to expand to an empty string, but if + * %HOMEPATH% does that, we abort the attempt. Same if either + * variable overflows its buffer. */ + if (drvlen == 0) + drv[0] = '\0'; + + if (drvlen < lenof(drv) && pathlen < lenof(path) && pathlen > 0 && + try_random_seed_and_free( + dupcat(drv, path, "\\PUTTY.RND"), action, &rethandle)) + return rethandle; + } - /* - * And finally, fall back to C:\WINDOWS. - */ - { - char windir[MAX_PATH]; - DWORD len = GetWindowsDirectory(windir, sizeof(windir)); - if (len < lenof(windir) && - try_random_seed_and_free( - dupcat(windir, "\\PUTTY.RND"), action, &rethandle)) - return rethandle; - } + /* + * And finally, fall back to C:\WINDOWS. + */ + { + char windir[MAX_PATH]; + DWORD len = GetWindowsDirectory(windir, sizeof(windir)); + if (len < lenof(windir) && + try_random_seed_and_free( + dupcat(windir, "\\PUTTY.RND"), action, &rethandle)) + return rethandle; + } - /* - * If even that failed, give up. - */ - return INVALID_HANDLE_VALUE; + /* + * If even that failed, give up. + */ + return INVALID_HANDLE_VALUE; } void read_random_seed(noise_consumer_t consumer) @@ -651,15 +1639,15 @@ void read_random_seed(noise_consumer_t consumer) HANDLE seedf = access_random_seed(OPEN_R); if (seedf != INVALID_HANDLE_VALUE) { - while (1) { - char buf[1024]; - DWORD len; - - if (ReadFile(seedf, buf, sizeof(buf), &len, NULL) && len) - consumer(buf, len); - else - break; - } + while (1) { + char buf[1024]; + DWORD len; + + if (ReadFile(seedf, buf, sizeof(buf), &len, NULL) && len) + consumer(buf, len); + else + break; + } CloseHandle(seedf); } } @@ -676,6 +1664,7 @@ void write_random_seed(void *data, int len) } } + /* * Internal function supporting the jump list registry code. All the * functions to add, remove and read the list have substantially @@ -685,69 +1674,127 @@ void write_random_seed(void *data, int len) * returning the resulting concatenated list of strings in 'out' (if * non-null). */ -static int transform_jumplist_registry( - const char *add, const char *rem, char **out) + +/* + * JK: rewritten to store jumplist to one file + * configurable via putty.conf +*/ +static int transform_jumplist_registry + (const char *add, const char *rem, char **out) { - HKEY rkey = open_regkey(true, HKEY_CURRENT_USER, reg_jumplist_key); - if (!rkey) - return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE; - - /* Get current list of saved sessions in the registry. */ - strbuf *oldlist = get_reg_multi_sz(rkey, reg_jumplist_value); - if (!oldlist) { - /* Start again with the empty list. */ - oldlist = strbuf_new(); - put_data(oldlist, "\0\0", 2); - } + int ret; + char *fileCont = NULL; + DWORD fileSize; + HANDLE hFile; + DWORD bytesRW; + DWORD value_length = 0; + char *old_value, *new_value; + char *piterator_old, *piterator_new; + settings_r *psettings_tmp; + int new_value_size = 0; + + + if (*jumplistpath == '\0') { + loadPath(); + } - /* - * Modify the list, if we're modifying. - */ - bool write_failure = false; - if (add || rem) { - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(oldlist)); - strbuf *newlist = strbuf_new(); - - /* First add the new item to the beginning of the list. */ - if (add) - put_asciz(newlist, add); - - /* Now add the existing list, taking care to leave out the removed - * item, if it was already in the existing list. */ - while (true) { - const char *olditem = get_asciz(src); - if (get_err(src)) - break; - - if (!rem || strcmp(olditem, rem) != 0) { - /* Check if this is a valid session, otherwise don't add. */ - settings_r *psettings_tmp = open_settings_r(olditem); - if (psettings_tmp != NULL) { - close_settings_r(psettings_tmp); - put_asciz(newlist, olditem); + hFile = CreateFile(jumplistpath,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + /* JK: there is a no jumplist, so check if user requested it */ + if (jumplistpath[0] != ':'){ + hFile = CreateFile(jumplistpath,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); + if (hFile == INVALID_HANDLE_VALUE) { + errorShow("无法创建 jumplist 文件", jumplistpath); + return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE; + } + } else { + return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE; } - } } - /* Save the new list to the registry. */ - write_failure = !put_reg_multi_sz(rkey, reg_jumplist_value, newlist); + fileSize = GetFileSize(hFile, NULL); + fileCont = snewn(fileSize+16, char); + + if (!ReadFile(hFile, fileCont, fileSize, &bytesRW, NULL)) + { + errorShow("无法载入 jumplist 文件", jumplistpath); + sfree(fileCont); + return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE; + } + CloseHandle(hFile); - strbuf_free(oldlist); - oldlist = newlist; + /* JK: parse jump list file, items separated by \0 */ + /* if data does not end in \0\0 -> corruptted or empty data -> start with empty data */ + if (*(fileCont+fileSize-1) != '\0' || *(fileCont+fileSize-2) != '\0'){ + *(fileCont) = '\0'; + *(fileCont+1) = '\0'; + } + + old_value = fileCont; + new_value_size = fileSize + (add ? strlen(add) + 1 : 0) + 2; + new_value = snewn(new_value_size, char); + + /* Walk through the existing list and construct the new list of saved sessions. */ + piterator_new = new_value; + piterator_old = old_value; + + /* First add the new item to the beginning of the list. */ + if (add) { + strcpy(piterator_new, add); + piterator_new += strlen(piterator_new) + 1; + value_length += strlen(add) + 1; } + /* Now add the existing list, taking care to leave out the removed item, if it was already in the existing list. */ + while (*piterator_old != '\0') { + + if (!rem || strcmp(piterator_old, rem) != 0) { + /* Check if this is a valid session, otherwise don't add. */ + psettings_tmp = open_settings_r(piterator_old); + if (psettings_tmp != NULL) { + close_settings_r(psettings_tmp); + strcpy(piterator_new, piterator_old); + piterator_new += strlen(piterator_old) + 1; + value_length += strlen(piterator_old) + 1; + } + } + piterator_old += strlen(piterator_old) + 1; + } + *piterator_new = '\0'; + value_length++; + ++piterator_new; - close_regkey(rkey); - if (out && !write_failure) - *out = strbuf_to_str(oldlist); - else - strbuf_free(oldlist); - if (write_failure) + hFile = CreateFile(jumplistpath , GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); + if (hFile == INVALID_HANDLE_VALUE) { + errorShow("无法打开用来写入的文件", jumplistpath ); + return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE; + } + if (! WriteFile( (HANDLE) hFile, new_value, value_length, &bytesRW, NULL)) { + errorShow("无法保存 jumplist", jumplistpath); + return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE; + /* JK: memory should be freed here - fixme */ + } + CloseHandle( (HANDLE)hFile ); + + sfree(fileCont); + + /* + * Either return or free the result. + */ + ret = ERROR_SUCCESS; // JK: ret is used only in original registry mode + if (out && ret == ERROR_SUCCESS) + *out = new_value; + else + sfree(new_value); + + + if (ret != ERROR_SUCCESS) { return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE; - else + } else { return JUMPLISTREG_OK; + } } /* Adds a new entry to the jumplist entries in the registry. */ @@ -795,6 +1842,7 @@ static void registry_recursive_remove(HKEY key) } } +/* JK: ToDo we could rmeove all the files same way as registry keys are removed */ void cleanup_all(void) { /* ------------------------------------------------------------