]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - windows/winsftp.c
Configurable font quality on Windows. (Together with a little bit of
[PuTTY.git] / windows / winsftp.c
1 /*
2  * winsftp.c: the Windows-specific parts of PSFTP and PSCP.
3  */
4
5 #include <assert.h>
6
7 #include "putty.h"
8 #include "psftp.h"
9
10 /* ----------------------------------------------------------------------
11  * Interface to GUI driver program.
12  */
13
14 /* This is just a base value from which the main message numbers are
15  * derived. */
16 #define   WM_APP_BASE           0x8000
17
18 /* These two pass a single character value in wParam. They represent
19  * the visible output from PSCP. */
20 #define   WM_STD_OUT_CHAR       ( WM_APP_BASE+400 )
21 #define   WM_STD_ERR_CHAR       ( WM_APP_BASE+401 )
22
23 /* These pass a transfer status update. WM_STATS_CHAR passes a single
24  * character in wParam, and is called repeatedly to pass the name of
25  * the file, terminated with "\n". WM_STATS_SIZE passes the size of
26  * the file being transferred in wParam. WM_STATS_ELAPSED is called
27  * to pass the elapsed time (in seconds) in wParam, and
28  * WM_STATS_PERCENT passes the percentage of the transfer which is
29  * complete, also in wParam. */
30 #define   WM_STATS_CHAR         ( WM_APP_BASE+402 )
31 #define   WM_STATS_SIZE         ( WM_APP_BASE+403 )
32 #define   WM_STATS_PERCENT      ( WM_APP_BASE+404 )
33 #define   WM_STATS_ELAPSED      ( WM_APP_BASE+405 )
34
35 /* These are used at the end of a run to pass an error code in
36  * wParam: zero means success, nonzero means failure. WM_RET_ERR_CNT
37  * is used after a copy, and WM_LS_RET_ERR_CNT is used after a file
38  * list operation. */
39 #define   WM_RET_ERR_CNT        ( WM_APP_BASE+406 )
40 #define   WM_LS_RET_ERR_CNT     ( WM_APP_BASE+407 )
41
42 /* More transfer status update messages. WM_STATS_DONE passes the
43  * number of bytes sent so far in wParam. WM_STATS_ETA passes the
44  * estimated time to completion (in seconds). WM_STATS_RATEBS passes
45  * the average transfer rate (in bytes per second). */
46 #define   WM_STATS_DONE         ( WM_APP_BASE+408 )
47 #define   WM_STATS_ETA          ( WM_APP_BASE+409 )
48 #define   WM_STATS_RATEBS       ( WM_APP_BASE+410 )
49
50 #define NAME_STR_MAX 2048
51 static char statname[NAME_STR_MAX + 1];
52 static unsigned long statsize = 0;
53 static unsigned long statdone = 0;
54 static unsigned long stateta = 0;
55 static unsigned long statratebs = 0;
56 static int statperct = 0;
57 static unsigned long statelapsed = 0;
58
59 static HWND gui_hwnd = NULL;
60
61 static void send_msg(HWND h, UINT message, WPARAM wParam)
62 {
63     while (!PostMessage(h, message, wParam, 0))
64         SleepEx(1000, TRUE);
65 }
66
67 void gui_send_char(int is_stderr, int c)
68 {
69     unsigned int msg_id = WM_STD_OUT_CHAR;
70     if (is_stderr)
71         msg_id = WM_STD_ERR_CHAR;
72     send_msg(gui_hwnd, msg_id, (WPARAM) c);
73 }
74
75 void gui_send_errcount(int list, int errs)
76 {
77     unsigned int msg_id = WM_RET_ERR_CNT;
78     if (list)
79         msg_id = WM_LS_RET_ERR_CNT;
80     while (!PostMessage(gui_hwnd, msg_id, (WPARAM) errs, 0))
81         SleepEx(1000, TRUE);
82 }
83
84 void gui_update_stats(char *name, unsigned long size,
85                       int percentage, unsigned long elapsed,
86                       unsigned long done, unsigned long eta,
87                       unsigned long ratebs)
88 {
89     unsigned int i;
90
91     if (strcmp(name, statname) != 0) {
92         for (i = 0; i < strlen(name); ++i)
93             send_msg(gui_hwnd, WM_STATS_CHAR, (WPARAM) name[i]);
94         send_msg(gui_hwnd, WM_STATS_CHAR, (WPARAM) '\n');
95         strcpy(statname, name);
96     }
97     if (statsize != size) {
98         send_msg(gui_hwnd, WM_STATS_SIZE, (WPARAM) size);
99         statsize = size;
100     }
101     if (statdone != done) {
102         send_msg(gui_hwnd, WM_STATS_DONE, (WPARAM) done);
103         statdone = done;
104     }
105     if (stateta != eta) {
106         send_msg(gui_hwnd, WM_STATS_ETA, (WPARAM) eta);
107         stateta = eta;
108     }
109     if (statratebs != ratebs) {
110         send_msg(gui_hwnd, WM_STATS_RATEBS, (WPARAM) ratebs);
111         statratebs = ratebs;
112     }
113     if (statelapsed != elapsed) {
114         send_msg(gui_hwnd, WM_STATS_ELAPSED, (WPARAM) elapsed);
115         statelapsed = elapsed;
116     }
117     if (statperct != percentage) {
118         send_msg(gui_hwnd, WM_STATS_PERCENT, (WPARAM) percentage);
119         statperct = percentage;
120     }
121 }
122
123 void gui_enable(char *arg)
124 {
125     gui_hwnd = (HWND) atoi(arg);
126 }
127
128 char *get_ttymode(void *frontend, const char *mode) { return NULL; }
129
130 int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
131 {
132     int ret;
133     ret = cmdline_get_passwd_input(p, in, inlen);
134     if (ret == -1)
135         ret = console_get_userpass_input(p, in, inlen);
136     return ret;
137 }
138
139 /* ----------------------------------------------------------------------
140  * File access abstraction.
141  */
142
143 /*
144  * Set local current directory. Returns NULL on success, or else an
145  * error message which must be freed after printing.
146  */
147 char *psftp_lcd(char *dir)
148 {
149     char *ret = NULL;
150
151     if (!SetCurrentDirectory(dir)) {
152         LPVOID message;
153         int i;
154         FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
155                       FORMAT_MESSAGE_FROM_SYSTEM |
156                       FORMAT_MESSAGE_IGNORE_INSERTS,
157                       NULL, GetLastError(),
158                       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
159                       (LPTSTR)&message, 0, NULL);
160         i = strcspn((char *)message, "\n");
161         ret = dupprintf("%.*s", i, (LPCTSTR)message);
162         LocalFree(message);
163     }
164
165     return ret;
166 }
167
168 /*
169  * Get local current directory. Returns a string which must be
170  * freed.
171  */
172 char *psftp_getcwd(void)
173 {
174     char *ret = snewn(256, char);
175     int len = GetCurrentDirectory(256, ret);
176     if (len > 256)
177         ret = sresize(ret, len, char);
178     GetCurrentDirectory(len, ret);
179     return ret;
180 }
181
182 #define TIME_POSIX_TO_WIN(t, ft) (*(LONGLONG*)&(ft) = \
183         ((LONGLONG) (t) + (LONGLONG) 11644473600) * (LONGLONG) 10000000)
184 #define TIME_WIN_TO_POSIX(ft, t) ((t) = (unsigned long) \
185         ((*(LONGLONG*)&(ft)) / (LONGLONG) 10000000 - (LONGLONG) 11644473600))
186
187 struct RFile {
188     HANDLE h;
189 };
190
191 RFile *open_existing_file(char *name, unsigned long *size,
192                           unsigned long *mtime, unsigned long *atime)
193 {
194     HANDLE h;
195     RFile *ret;
196
197     h = CreateFile(name, GENERIC_READ, FILE_SHARE_READ, NULL,
198                    OPEN_EXISTING, 0, 0);
199     if (h == INVALID_HANDLE_VALUE)
200         return NULL;
201
202     ret = snew(RFile);
203     ret->h = h;
204
205     if (size)
206         *size = GetFileSize(h, NULL);
207
208     if (mtime || atime) {
209         FILETIME actime, wrtime;
210         GetFileTime(h, NULL, &actime, &wrtime);
211         if (atime)
212             TIME_WIN_TO_POSIX(actime, *atime);
213         if (mtime)
214             TIME_WIN_TO_POSIX(wrtime, *mtime);
215     }
216
217     return ret;
218 }
219
220 int read_from_file(RFile *f, void *buffer, int length)
221 {
222     int ret, read;
223     ret = ReadFile(f->h, buffer, length, &read, NULL);
224     if (!ret)
225         return -1;                     /* error */
226     else
227         return read;
228 }
229
230 void close_rfile(RFile *f)
231 {
232     CloseHandle(f->h);
233     sfree(f);
234 }
235
236 struct WFile {
237     HANDLE h;
238 };
239
240 WFile *open_new_file(char *name)
241 {
242     HANDLE h;
243     WFile *ret;
244
245     h = CreateFile(name, GENERIC_WRITE, 0, NULL,
246                    CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
247     if (h == INVALID_HANDLE_VALUE)
248         return NULL;
249
250     ret = snew(WFile);
251     ret->h = h;
252
253     return ret;
254 }
255
256 int write_to_file(WFile *f, void *buffer, int length)
257 {
258     int ret, written;
259     ret = WriteFile(f->h, buffer, length, &written, NULL);
260     if (!ret)
261         return -1;                     /* error */
262     else
263         return written;
264 }
265
266 void set_file_times(WFile *f, unsigned long mtime, unsigned long atime)
267 {
268     FILETIME actime, wrtime;
269     TIME_POSIX_TO_WIN(atime, actime);
270     TIME_POSIX_TO_WIN(mtime, wrtime);
271     SetFileTime(f->h, NULL, &actime, &wrtime);
272 }
273
274 void close_wfile(WFile *f)
275 {
276     CloseHandle(f->h);
277     sfree(f);
278 }
279
280 int file_type(char *name)
281 {
282     DWORD attr;
283     attr = GetFileAttributes(name);
284     /* We know of no `weird' files under Windows. */
285     if (attr == (DWORD)-1)
286         return FILE_TYPE_NONEXISTENT;
287     else if (attr & FILE_ATTRIBUTE_DIRECTORY)
288         return FILE_TYPE_DIRECTORY;
289     else
290         return FILE_TYPE_FILE;
291 }
292
293 struct DirHandle {
294     HANDLE h;
295     char *name;
296 };
297
298 DirHandle *open_directory(char *name)
299 {
300     HANDLE h;
301     WIN32_FIND_DATA fdat;
302     char *findfile;
303     DirHandle *ret;
304
305     /* Enumerate files in dir `foo'. */
306     findfile = dupcat(name, "/*", NULL);
307     h = FindFirstFile(findfile, &fdat);
308     if (h == INVALID_HANDLE_VALUE)
309         return NULL;
310     sfree(findfile);
311
312     ret = snew(DirHandle);
313     ret->h = h;
314     ret->name = dupstr(fdat.cFileName);
315     return ret;
316 }
317
318 char *read_filename(DirHandle *dir)
319 {
320     do {
321
322         if (!dir->name) {
323             WIN32_FIND_DATA fdat;
324             int ok = FindNextFile(dir->h, &fdat);
325             if (!ok)
326                 return NULL;
327             else
328                 dir->name = dupstr(fdat.cFileName);
329         }
330
331         assert(dir->name);
332         if (dir->name[0] == '.' &&
333             (dir->name[1] == '\0' ||
334              (dir->name[1] == '.' && dir->name[2] == '\0'))) {
335             sfree(dir->name);
336             dir->name = NULL;
337         }
338
339     } while (!dir->name);
340
341     if (dir->name) {
342         char *ret = dir->name;
343         dir->name = NULL;
344         return ret;
345     } else
346         return NULL;
347 }
348
349 void close_directory(DirHandle *dir)
350 {
351     FindClose(dir->h);
352     if (dir->name)
353         sfree(dir->name);
354     sfree(dir);
355 }
356
357 int test_wildcard(char *name, int cmdline)
358 {
359     HANDLE fh;
360     WIN32_FIND_DATA fdat;
361
362     /* First see if the exact name exists. */
363     if (GetFileAttributes(name) != (DWORD)-1)
364         return WCTYPE_FILENAME;
365
366     /* Otherwise see if a wildcard match finds anything. */
367     fh = FindFirstFile(name, &fdat);
368     if (fh == INVALID_HANDLE_VALUE)
369         return WCTYPE_NONEXISTENT;
370
371     FindClose(fh);
372     return WCTYPE_WILDCARD;
373 }
374
375 struct WildcardMatcher {
376     HANDLE h;
377     char *name;
378     char *srcpath;
379 };
380
381 /*
382  * Return a pointer to the portion of str that comes after the last
383  * slash (or backslash or colon, if `local' is TRUE).
384  */
385 static char *stripslashes(char *str, int local)
386 {
387     char *p;
388
389     if (local) {
390         p = strchr(str, ':');
391         if (p) str = p+1;
392     }
393
394     p = strrchr(str, '/');
395     if (p) str = p+1;
396
397     if (local) {
398         p = strrchr(str, '\\');
399         if (p) str = p+1;
400     }
401
402     return str;
403 }
404
405 WildcardMatcher *begin_wildcard_matching(char *name)
406 {
407     HANDLE h;
408     WIN32_FIND_DATA fdat;
409     WildcardMatcher *ret;
410     char *last;
411
412     h = FindFirstFile(name, &fdat);
413     if (h == INVALID_HANDLE_VALUE)
414         return NULL;
415
416     ret = snew(WildcardMatcher);
417     ret->h = h;
418     ret->srcpath = dupstr(name);
419     last = stripslashes(ret->srcpath, 1);
420     *last = '\0';
421     if (fdat.cFileName[0] == '.' &&
422         (fdat.cFileName[1] == '\0' ||
423          (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0')))
424         ret->name = NULL;
425     else
426         ret->name = dupcat(ret->srcpath, fdat.cFileName, NULL);
427
428     return ret;
429 }
430
431 char *wildcard_get_filename(WildcardMatcher *dir)
432 {
433     while (!dir->name) {
434         WIN32_FIND_DATA fdat;
435         int ok = FindNextFile(dir->h, &fdat);
436
437         if (!ok)
438             return NULL;
439
440         if (fdat.cFileName[0] == '.' &&
441             (fdat.cFileName[1] == '\0' ||
442              (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0')))
443             dir->name = NULL;
444         else
445             dir->name = dupcat(dir->srcpath, fdat.cFileName, NULL);
446     }
447
448     if (dir->name) {
449         char *ret = dir->name;
450         dir->name = NULL;
451         return ret;
452     } else
453         return NULL;
454 }
455
456 void finish_wildcard_matching(WildcardMatcher *dir)
457 {
458     FindClose(dir->h);
459     if (dir->name)
460         sfree(dir->name);
461     sfree(dir->srcpath);
462     sfree(dir);
463 }
464
465 int vet_filename(char *name)
466 {
467     if (strchr(name, '/') || strchr(name, '\\') || strchr(name, ':'))
468         return FALSE;
469
470     if (!name[strspn(name, ".")])      /* entirely composed of dots */
471         return FALSE;
472
473     return TRUE;
474 }
475
476 int create_directory(char *name)
477 {
478     return CreateDirectory(name, NULL) != 0;
479 }
480
481 char *dir_file_cat(char *dir, char *file)
482 {
483     return dupcat(dir, "\\", file, NULL);
484 }
485
486 /* ----------------------------------------------------------------------
487  * Platform-specific network handling.
488  */
489
490 /*
491  * Be told what socket we're supposed to be using.
492  */
493 static SOCKET sftp_ssh_socket = INVALID_SOCKET;
494 static HANDLE netevent = NULL;
495 char *do_select(SOCKET skt, int startup)
496 {
497     int events;
498     if (startup)
499         sftp_ssh_socket = skt;
500     else
501         sftp_ssh_socket = INVALID_SOCKET;
502
503     if (p_WSAEventSelect) {
504         if (startup) {
505             events = (FD_CONNECT | FD_READ | FD_WRITE |
506                       FD_OOB | FD_CLOSE | FD_ACCEPT);
507             netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
508         } else {
509             events = 0;
510         }
511         if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
512             switch (p_WSAGetLastError()) {
513               case WSAENETDOWN:
514                 return "Network is down";
515               default:
516                 return "WSAEventSelect(): unknown error";
517             }
518         }
519     }
520     return NULL;
521 }
522 extern int select_result(WPARAM, LPARAM);
523
524 int do_eventsel_loop(HANDLE other_event)
525 {
526     int n;
527     long next, ticks;
528     HANDLE handles[2];
529     SOCKET *sklist;
530     int skcount;
531     long now = GETTICKCOUNT();
532
533     if (!netevent) {
534         return -1;                     /* doom */
535     }
536
537     handles[0] = netevent;
538     handles[1] = other_event;
539
540     if (run_timers(now, &next)) {
541         ticks = next - GETTICKCOUNT();
542         if (ticks < 0) ticks = 0;  /* just in case */
543     } else {
544         ticks = INFINITE;
545     }
546
547     n = MsgWaitForMultipleObjects(other_event ? 2 : 1, handles, FALSE, ticks,
548                                   QS_POSTMESSAGE);
549
550     if (n == WAIT_OBJECT_0 + 0) {
551         WSANETWORKEVENTS things;
552         SOCKET socket;
553         extern SOCKET first_socket(int *), next_socket(int *);
554         extern int select_result(WPARAM, LPARAM);
555         int i, socketstate;
556
557         /*
558          * We must not call select_result() for any socket
559          * until we have finished enumerating within the
560          * tree. This is because select_result() may close
561          * the socket and modify the tree.
562          */
563         /* Count the active sockets. */
564         i = 0;
565         for (socket = first_socket(&socketstate);
566              socket != INVALID_SOCKET;
567              socket = next_socket(&socketstate)) i++;
568
569         /* Expand the buffer if necessary. */
570         sklist = snewn(i, SOCKET);
571
572         /* Retrieve the sockets into sklist. */
573         skcount = 0;
574         for (socket = first_socket(&socketstate);
575              socket != INVALID_SOCKET;
576              socket = next_socket(&socketstate)) {
577             sklist[skcount++] = socket;
578         }
579
580         /* Now we're done enumerating; go through the list. */
581         for (i = 0; i < skcount; i++) {
582             WPARAM wp;
583             socket = sklist[i];
584             wp = (WPARAM) socket;
585             if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) {
586                 static const struct { int bit, mask; } eventtypes[] = {
587                     {FD_CONNECT_BIT, FD_CONNECT},
588                     {FD_READ_BIT, FD_READ},
589                     {FD_CLOSE_BIT, FD_CLOSE},
590                     {FD_OOB_BIT, FD_OOB},
591                     {FD_WRITE_BIT, FD_WRITE},
592                     {FD_ACCEPT_BIT, FD_ACCEPT},
593                 };
594                 int e;
595
596                 noise_ultralight(socket);
597                 noise_ultralight(things.lNetworkEvents);
598
599                 for (e = 0; e < lenof(eventtypes); e++)
600                     if (things.lNetworkEvents & eventtypes[e].mask) {
601                         LPARAM lp;
602                         int err = things.iErrorCode[eventtypes[e].bit];
603                         lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err);
604                         select_result(wp, lp);
605                     }
606             }
607         }
608
609         sfree(sklist);
610     }
611
612     if (n == WAIT_TIMEOUT) {
613         now = next;
614     } else {
615         now = GETTICKCOUNT();
616     }
617
618     if (other_event && n == WAIT_OBJECT_0 + 1)
619         return 1;
620
621     return 0;
622 }
623
624 /*
625  * Wait for some network data and process it.
626  *
627  * We have two variants of this function. One uses select() so that
628  * it's compatible with WinSock 1. The other uses WSAEventSelect
629  * and MsgWaitForMultipleObjects, so that we can consistently use
630  * WSAEventSelect throughout; this enables us to also implement
631  * ssh_sftp_get_cmdline() using a parallel mechanism.
632  */
633 int ssh_sftp_loop_iteration(void)
634 {
635     if (sftp_ssh_socket == INVALID_SOCKET)
636         return -1;                     /* doom */
637
638     if (p_WSAEventSelect == NULL) {
639         fd_set readfds;
640         int ret;
641         long now = GETTICKCOUNT();
642
643         if (socket_writable(sftp_ssh_socket))
644             select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_WRITE);
645
646         do {
647             long next, ticks;
648             struct timeval tv, *ptv;
649
650             if (run_timers(now, &next)) {
651                 ticks = next - GETTICKCOUNT();
652                 if (ticks <= 0)
653                     ticks = 1;         /* just in case */
654                 tv.tv_sec = ticks / 1000;
655                 tv.tv_usec = ticks % 1000 * 1000;
656                 ptv = &tv;
657             } else {
658                 ptv = NULL;
659             }
660
661             FD_ZERO(&readfds);
662             FD_SET(sftp_ssh_socket, &readfds);
663             ret = p_select(1, &readfds, NULL, NULL, ptv);
664
665             if (ret < 0)
666                 return -1;                     /* doom */
667             else if (ret == 0)
668                 now = next;
669             else
670                 now = GETTICKCOUNT();
671
672         } while (ret == 0);
673
674         select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
675
676         return 0;
677     } else {
678         return do_eventsel_loop(NULL);
679     }
680 }
681
682 /*
683  * Read a command line from standard input.
684  * 
685  * In the presence of WinSock 2, we can use WSAEventSelect to
686  * mediate between the socket and stdin, meaning we can send
687  * keepalives and respond to server events even while waiting at
688  * the PSFTP command prompt. Without WS2, we fall back to a simple
689  * fgets.
690  */
691 struct command_read_ctx {
692     HANDLE event;
693     char *line;
694 };
695
696 static DWORD WINAPI command_read_thread(void *param)
697 {
698     struct command_read_ctx *ctx = (struct command_read_ctx *) param;
699
700     ctx->line = fgetline(stdin);
701
702     SetEvent(ctx->event);
703
704     return 0;
705 }
706
707 char *ssh_sftp_get_cmdline(char *prompt, int no_fds_ok)
708 {
709     int ret;
710     struct command_read_ctx actx, *ctx = &actx;
711     DWORD threadid;
712
713     fputs(prompt, stdout);
714     fflush(stdout);
715
716     if ((sftp_ssh_socket == INVALID_SOCKET && no_fds_ok) ||
717         p_WSAEventSelect == NULL) {
718         return fgetline(stdin);        /* very simple */
719     }
720
721     /*
722      * Create a second thread to read from stdin. Process network
723      * and timing events until it terminates.
724      */
725     ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL);
726     ctx->line = NULL;
727
728     if (!CreateThread(NULL, 0, command_read_thread,
729                       ctx, 0, &threadid)) {
730         fprintf(stderr, "Unable to create command input thread\n");
731         cleanup_exit(1);
732     }
733
734     do {
735         ret = do_eventsel_loop(ctx->event);
736
737         /* Error return can only occur if netevent==NULL, and it ain't. */
738         assert(ret >= 0);
739     } while (ret == 0);
740
741     return ctx->line;
742 }
743
744 /* ----------------------------------------------------------------------
745  * Main program. Parse arguments etc.
746  */
747 int main(int argc, char *argv[])
748 {
749     int ret;
750
751     ret = psftp_main(argc, argv);
752
753     return ret;
754 }