/* * winsftp.c: the Windows-specific parts of PSFTP and PSCP. */ #include #include "putty.h" #include "psftp.h" /* ---------------------------------------------------------------------- * Interface to GUI driver program. */ /* This is just a base value from which the main message numbers are * derived. */ #define WM_APP_BASE 0x8000 /* These two pass a single character value in wParam. They represent * the visible output from PSCP. */ #define WM_STD_OUT_CHAR ( WM_APP_BASE+400 ) #define WM_STD_ERR_CHAR ( WM_APP_BASE+401 ) /* These pass a transfer status update. WM_STATS_CHAR passes a single * character in wParam, and is called repeatedly to pass the name of * the file, terminated with "\n". WM_STATS_SIZE passes the size of * the file being transferred in wParam. WM_STATS_ELAPSED is called * to pass the elapsed time (in seconds) in wParam, and * WM_STATS_PERCENT passes the percentage of the transfer which is * complete, also in wParam. */ #define WM_STATS_CHAR ( WM_APP_BASE+402 ) #define WM_STATS_SIZE ( WM_APP_BASE+403 ) #define WM_STATS_PERCENT ( WM_APP_BASE+404 ) #define WM_STATS_ELAPSED ( WM_APP_BASE+405 ) /* These are used at the end of a run to pass an error code in * wParam: zero means success, nonzero means failure. WM_RET_ERR_CNT * is used after a copy, and WM_LS_RET_ERR_CNT is used after a file * list operation. */ #define WM_RET_ERR_CNT ( WM_APP_BASE+406 ) #define WM_LS_RET_ERR_CNT ( WM_APP_BASE+407 ) /* More transfer status update messages. WM_STATS_DONE passes the * number of bytes sent so far in wParam. WM_STATS_ETA passes the * estimated time to completion (in seconds). WM_STATS_RATEBS passes * the average transfer rate (in bytes per second). */ #define WM_STATS_DONE ( WM_APP_BASE+408 ) #define WM_STATS_ETA ( WM_APP_BASE+409 ) #define WM_STATS_RATEBS ( WM_APP_BASE+410 ) #define NAME_STR_MAX 2048 static char statname[NAME_STR_MAX + 1]; static unsigned long statsize = 0; static unsigned long statdone = 0; static unsigned long stateta = 0; static unsigned long statratebs = 0; static int statperct = 0; static unsigned long statelapsed = 0; static HWND gui_hwnd = NULL; static void send_msg(HWND h, UINT message, WPARAM wParam) { while (!PostMessage(h, message, wParam, 0)) SleepEx(1000, TRUE); } void gui_send_char(int is_stderr, int c) { unsigned int msg_id = WM_STD_OUT_CHAR; if (is_stderr) msg_id = WM_STD_ERR_CHAR; send_msg(gui_hwnd, msg_id, (WPARAM) c); } void gui_send_errcount(int list, int errs) { unsigned int msg_id = WM_RET_ERR_CNT; if (list) msg_id = WM_LS_RET_ERR_CNT; while (!PostMessage(gui_hwnd, msg_id, (WPARAM) errs, 0)) SleepEx(1000, TRUE); } void gui_update_stats(char *name, unsigned long size, int percentage, unsigned long elapsed, unsigned long done, unsigned long eta, unsigned long ratebs) { unsigned int i; if (strcmp(name, statname) != 0) { for (i = 0; i < strlen(name); ++i) send_msg(gui_hwnd, WM_STATS_CHAR, (WPARAM) name[i]); send_msg(gui_hwnd, WM_STATS_CHAR, (WPARAM) '\n'); strcpy(statname, name); } if (statsize != size) { send_msg(gui_hwnd, WM_STATS_SIZE, (WPARAM) size); statsize = size; } if (statdone != done) { send_msg(gui_hwnd, WM_STATS_DONE, (WPARAM) done); statdone = done; } if (stateta != eta) { send_msg(gui_hwnd, WM_STATS_ETA, (WPARAM) eta); stateta = eta; } if (statratebs != ratebs) { send_msg(gui_hwnd, WM_STATS_RATEBS, (WPARAM) ratebs); statratebs = ratebs; } if (statelapsed != elapsed) { send_msg(gui_hwnd, WM_STATS_ELAPSED, (WPARAM) elapsed); statelapsed = elapsed; } if (statperct != percentage) { send_msg(gui_hwnd, WM_STATS_PERCENT, (WPARAM) percentage); statperct = percentage; } } void gui_enable(char *arg) { gui_hwnd = (HWND) atoi(arg); } char *get_ttymode(void *frontend, const char *mode) { return NULL; } int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) { int ret; ret = cmdline_get_passwd_input(p, in, inlen); if (ret == -1) ret = console_get_userpass_input(p, in, inlen); return ret; } /* ---------------------------------------------------------------------- * File access abstraction. */ /* * Set local current directory. Returns NULL on success, or else an * error message which must be freed after printing. */ char *psftp_lcd(char *dir) { char *ret = NULL; if (!SetCurrentDirectory(dir)) { LPVOID message; int i; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&message, 0, NULL); i = strcspn((char *)message, "\n"); ret = dupprintf("%.*s", i, (LPCTSTR)message); LocalFree(message); } return ret; } /* * Get local current directory. Returns a string which must be * freed. */ char *psftp_getcwd(void) { char *ret = snewn(256, char); int len = GetCurrentDirectory(256, ret); if (len > 256) ret = sresize(ret, len, char); GetCurrentDirectory(len, ret); return ret; } #define TIME_POSIX_TO_WIN(t, ft) (*(LONGLONG*)&(ft) = \ ((LONGLONG) (t) + (LONGLONG) 11644473600) * (LONGLONG) 10000000) #define TIME_WIN_TO_POSIX(ft, t) ((t) = (unsigned long) \ ((*(LONGLONG*)&(ft)) / (LONGLONG) 10000000 - (LONGLONG) 11644473600)) struct RFile { HANDLE h; }; RFile *open_existing_file(char *name, unsigned long *size, unsigned long *mtime, unsigned long *atime) { HANDLE h; RFile *ret; h = CreateFile(name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); if (h == INVALID_HANDLE_VALUE) return NULL; ret = snew(RFile); ret->h = h; if (size) *size = GetFileSize(h, NULL); if (mtime || atime) { FILETIME actime, wrtime; GetFileTime(h, NULL, &actime, &wrtime); if (atime) TIME_WIN_TO_POSIX(actime, *atime); if (mtime) TIME_WIN_TO_POSIX(wrtime, *mtime); } return ret; } int read_from_file(RFile *f, void *buffer, int length) { int ret, read; ret = ReadFile(f->h, buffer, length, &read, NULL); if (!ret) return -1; /* error */ else return read; } void close_rfile(RFile *f) { CloseHandle(f->h); sfree(f); } struct WFile { HANDLE h; }; WFile *open_new_file(char *name) { HANDLE h; WFile *ret; h = CreateFile(name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (h == INVALID_HANDLE_VALUE) return NULL; ret = snew(WFile); ret->h = h; return ret; } int write_to_file(WFile *f, void *buffer, int length) { int ret, written; ret = WriteFile(f->h, buffer, length, &written, NULL); if (!ret) return -1; /* error */ else return written; } void set_file_times(WFile *f, unsigned long mtime, unsigned long atime) { FILETIME actime, wrtime; TIME_POSIX_TO_WIN(atime, actime); TIME_POSIX_TO_WIN(mtime, wrtime); SetFileTime(f->h, NULL, &actime, &wrtime); } void close_wfile(WFile *f) { CloseHandle(f->h); sfree(f); } int file_type(char *name) { DWORD attr; attr = GetFileAttributes(name); /* We know of no `weird' files under Windows. */ if (attr == (DWORD)-1) return FILE_TYPE_NONEXISTENT; else if (attr & FILE_ATTRIBUTE_DIRECTORY) return FILE_TYPE_DIRECTORY; else return FILE_TYPE_FILE; } struct DirHandle { HANDLE h; char *name; }; DirHandle *open_directory(char *name) { HANDLE h; WIN32_FIND_DATA fdat; char *findfile; DirHandle *ret; /* Enumerate files in dir `foo'. */ findfile = dupcat(name, "/*", NULL); h = FindFirstFile(findfile, &fdat); if (h == INVALID_HANDLE_VALUE) return NULL; sfree(findfile); ret = snew(DirHandle); ret->h = h; ret->name = dupstr(fdat.cFileName); return ret; } char *read_filename(DirHandle *dir) { do { if (!dir->name) { WIN32_FIND_DATA fdat; int ok = FindNextFile(dir->h, &fdat); if (!ok) return NULL; else dir->name = dupstr(fdat.cFileName); } assert(dir->name); if (dir->name[0] == '.' && (dir->name[1] == '\0' || (dir->name[1] == '.' && dir->name[2] == '\0'))) { sfree(dir->name); dir->name = NULL; } } while (!dir->name); if (dir->name) { char *ret = dir->name; dir->name = NULL; return ret; } else return NULL; } void close_directory(DirHandle *dir) { FindClose(dir->h); if (dir->name) sfree(dir->name); sfree(dir); } int test_wildcard(char *name, int cmdline) { HANDLE fh; WIN32_FIND_DATA fdat; /* First see if the exact name exists. */ if (GetFileAttributes(name) != (DWORD)-1) return WCTYPE_FILENAME; /* Otherwise see if a wildcard match finds anything. */ fh = FindFirstFile(name, &fdat); if (fh == INVALID_HANDLE_VALUE) return WCTYPE_NONEXISTENT; FindClose(fh); return WCTYPE_WILDCARD; } struct WildcardMatcher { HANDLE h; char *name; char *srcpath; }; /* * Return a pointer to the portion of str that comes after the last * slash (or backslash or colon, if `local' is TRUE). */ static char *stripslashes(char *str, int local) { char *p; if (local) { p = strchr(str, ':'); if (p) str = p+1; } p = strrchr(str, '/'); if (p) str = p+1; if (local) { p = strrchr(str, '\\'); if (p) str = p+1; } return str; } WildcardMatcher *begin_wildcard_matching(char *name) { HANDLE h; WIN32_FIND_DATA fdat; WildcardMatcher *ret; char *last; h = FindFirstFile(name, &fdat); if (h == INVALID_HANDLE_VALUE) return NULL; ret = snew(WildcardMatcher); ret->h = h; ret->srcpath = dupstr(name); last = stripslashes(ret->srcpath, 1); *last = '\0'; if (fdat.cFileName[0] == '.' && (fdat.cFileName[1] == '\0' || (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0'))) ret->name = NULL; else ret->name = dupcat(ret->srcpath, fdat.cFileName, NULL); return ret; } char *wildcard_get_filename(WildcardMatcher *dir) { while (!dir->name) { WIN32_FIND_DATA fdat; int ok = FindNextFile(dir->h, &fdat); if (!ok) return NULL; if (fdat.cFileName[0] == '.' && (fdat.cFileName[1] == '\0' || (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0'))) dir->name = NULL; else dir->name = dupcat(dir->srcpath, fdat.cFileName, NULL); } if (dir->name) { char *ret = dir->name; dir->name = NULL; return ret; } else return NULL; } void finish_wildcard_matching(WildcardMatcher *dir) { FindClose(dir->h); if (dir->name) sfree(dir->name); sfree(dir->srcpath); sfree(dir); } int vet_filename(char *name) { if (strchr(name, '/') || strchr(name, '\\') || strchr(name, ':')) return FALSE; if (!name[strspn(name, ".")]) /* entirely composed of dots */ return FALSE; return TRUE; } int create_directory(char *name) { return CreateDirectory(name, NULL) != 0; } char *dir_file_cat(char *dir, char *file) { return dupcat(dir, "\\", file, NULL); } /* ---------------------------------------------------------------------- * Platform-specific network handling. */ /* * Be told what socket we're supposed to be using. */ static SOCKET sftp_ssh_socket = INVALID_SOCKET; static HANDLE netevent = NULL; char *do_select(SOCKET skt, int startup) { int events; if (startup) sftp_ssh_socket = skt; else sftp_ssh_socket = INVALID_SOCKET; if (p_WSAEventSelect) { if (startup) { events = (FD_CONNECT | FD_READ | FD_WRITE | FD_OOB | FD_CLOSE | FD_ACCEPT); netevent = CreateEvent(NULL, FALSE, FALSE, NULL); } else { events = 0; } if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) { switch (p_WSAGetLastError()) { case WSAENETDOWN: return "Network is down"; default: return "WSAEventSelect(): unknown error"; } } } return NULL; } extern int select_result(WPARAM, LPARAM); int do_eventsel_loop(HANDLE other_event) { int n; long next, ticks; HANDLE handles[2]; SOCKET *sklist; int skcount; long now = GETTICKCOUNT(); if (!netevent) { return -1; /* doom */ } handles[0] = netevent; handles[1] = other_event; if (run_timers(now, &next)) { ticks = next - GETTICKCOUNT(); if (ticks < 0) ticks = 0; /* just in case */ } else { ticks = INFINITE; } n = MsgWaitForMultipleObjects(other_event ? 2 : 1, handles, FALSE, ticks, QS_POSTMESSAGE); if (n == WAIT_OBJECT_0 + 0) { WSANETWORKEVENTS things; SOCKET socket; extern SOCKET first_socket(int *), next_socket(int *); extern int select_result(WPARAM, LPARAM); int i, socketstate; /* * We must not call select_result() for any socket * until we have finished enumerating within the * tree. This is because select_result() may close * the socket and modify the tree. */ /* Count the active sockets. */ i = 0; for (socket = first_socket(&socketstate); socket != INVALID_SOCKET; socket = next_socket(&socketstate)) i++; /* Expand the buffer if necessary. */ sklist = snewn(i, SOCKET); /* Retrieve the sockets into sklist. */ skcount = 0; for (socket = first_socket(&socketstate); socket != INVALID_SOCKET; socket = next_socket(&socketstate)) { sklist[skcount++] = socket; } /* Now we're done enumerating; go through the list. */ for (i = 0; i < skcount; i++) { WPARAM wp; socket = sklist[i]; wp = (WPARAM) socket; if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) { static const struct { int bit, mask; } eventtypes[] = { {FD_CONNECT_BIT, FD_CONNECT}, {FD_READ_BIT, FD_READ}, {FD_CLOSE_BIT, FD_CLOSE}, {FD_OOB_BIT, FD_OOB}, {FD_WRITE_BIT, FD_WRITE}, {FD_ACCEPT_BIT, FD_ACCEPT}, }; int e; noise_ultralight(socket); noise_ultralight(things.lNetworkEvents); for (e = 0; e < lenof(eventtypes); e++) if (things.lNetworkEvents & eventtypes[e].mask) { LPARAM lp; int err = things.iErrorCode[eventtypes[e].bit]; lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err); select_result(wp, lp); } } } sfree(sklist); } if (n == WAIT_TIMEOUT) { now = next; } else { now = GETTICKCOUNT(); } if (other_event && n == WAIT_OBJECT_0 + 1) return 1; return 0; } /* * Wait for some network data and process it. * * We have two variants of this function. One uses select() so that * it's compatible with WinSock 1. The other uses WSAEventSelect * and MsgWaitForMultipleObjects, so that we can consistently use * WSAEventSelect throughout; this enables us to also implement * ssh_sftp_get_cmdline() using a parallel mechanism. */ int ssh_sftp_loop_iteration(void) { if (sftp_ssh_socket == INVALID_SOCKET) return -1; /* doom */ if (p_WSAEventSelect == NULL) { fd_set readfds; int ret; long now = GETTICKCOUNT(); if (socket_writable(sftp_ssh_socket)) select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_WRITE); do { long next, ticks; struct timeval tv, *ptv; if (run_timers(now, &next)) { ticks = next - GETTICKCOUNT(); if (ticks <= 0) ticks = 1; /* just in case */ tv.tv_sec = ticks / 1000; tv.tv_usec = ticks % 1000 * 1000; ptv = &tv; } else { ptv = NULL; } FD_ZERO(&readfds); FD_SET(sftp_ssh_socket, &readfds); ret = p_select(1, &readfds, NULL, NULL, ptv); if (ret < 0) return -1; /* doom */ else if (ret == 0) now = next; else now = GETTICKCOUNT(); } while (ret == 0); select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ); return 0; } else { return do_eventsel_loop(NULL); } } /* * Read a command line from standard input. * * In the presence of WinSock 2, we can use WSAEventSelect to * mediate between the socket and stdin, meaning we can send * keepalives and respond to server events even while waiting at * the PSFTP command prompt. Without WS2, we fall back to a simple * fgets. */ struct command_read_ctx { HANDLE event; char *line; }; static DWORD WINAPI command_read_thread(void *param) { struct command_read_ctx *ctx = (struct command_read_ctx *) param; ctx->line = fgetline(stdin); SetEvent(ctx->event); return 0; } char *ssh_sftp_get_cmdline(char *prompt, int no_fds_ok) { int ret; struct command_read_ctx actx, *ctx = &actx; DWORD threadid; fputs(prompt, stdout); fflush(stdout); if ((sftp_ssh_socket == INVALID_SOCKET && no_fds_ok) || p_WSAEventSelect == NULL) { return fgetline(stdin); /* very simple */ } /* * Create a second thread to read from stdin. Process network * and timing events until it terminates. */ ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL); ctx->line = NULL; if (!CreateThread(NULL, 0, command_read_thread, ctx, 0, &threadid)) { fprintf(stderr, "Unable to create command input thread\n"); cleanup_exit(1); } do { ret = do_eventsel_loop(ctx->event); /* Error return can only occur if netevent==NULL, and it ain't. */ assert(ret >= 0); } while (ret == 0); return ctx->line; } /* ---------------------------------------------------------------------- * Main program. Parse arguments etc. */ int main(int argc, char *argv[]) { int ret; ret = psftp_main(argc, argv); return ret; }