]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - scp.c
Be prepared to print spaces at the end of a statistics line in case
[PuTTY.git] / scp.c
1 /*
2  *  scp.c  -  Scp (Secure Copy) client for PuTTY.
3  *  Joris van Rantwijk, Simon Tatham
4  *
5  *  This is mainly based on ssh-1.2.26/scp.c by Timo Rinne & Tatu Ylonen.
6  *  They, in turn, used stuff from BSD rcp.
7  *
8  *  Adaptations to enable connecting a GUI by L. Gunnarsson - Sept 2000
9  */
10
11 #include <windows.h>
12 #ifndef AUTO_WINSOCK
13 #ifdef WINSOCK_TWO
14 #include <winsock2.h>
15 #else
16 #include <winsock.h>
17 #endif
18 #endif
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <time.h>
23 #include <assert.h>
24 /* GUI Adaptation - Sept 2000 */
25 #include <winuser.h>
26 #include <winbase.h>
27
28 #define PUTTY_DO_GLOBALS
29 #include "putty.h"
30 #include "winstuff.h"
31 #include "storage.h"
32
33 #define TIME_POSIX_TO_WIN(t, ft) (*(LONGLONG*)&(ft) = \
34         ((LONGLONG) (t) + (LONGLONG) 11644473600) * (LONGLONG) 10000000)
35 #define TIME_WIN_TO_POSIX(ft, t) ((t) = (unsigned long) \
36         ((*(LONGLONG*)&(ft)) / (LONGLONG) 10000000 - (LONGLONG) 11644473600))
37
38 /* GUI Adaptation - Sept 2000 */
39 #define   WM_APP_BASE           0x8000
40 #define   WM_STD_OUT_CHAR       ( WM_APP_BASE+400 )
41 #define   WM_STD_ERR_CHAR       ( WM_APP_BASE+401 )
42 #define   WM_STATS_CHAR         ( WM_APP_BASE+402 )
43 #define   WM_STATS_SIZE         ( WM_APP_BASE+403 )
44 #define   WM_STATS_PERCENT      ( WM_APP_BASE+404 )
45 #define   WM_STATS_ELAPSED      ( WM_APP_BASE+405 )
46 #define   WM_RET_ERR_CNT        ( WM_APP_BASE+406 )
47 #define   WM_LS_RET_ERR_CNT     ( WM_APP_BASE+407 )
48
49 static int list = 0;
50 static int verbose = 0;
51 static int recursive = 0;
52 static int preserve = 0;
53 static int targetshouldbedirectory = 0;
54 static int statistics = 1;
55 static int portnumber = 0;
56 static int prev_stats_len = 0;
57 static char *password = NULL;
58 static int errs = 0;
59 /* GUI Adaptation - Sept 2000 */
60 #define NAME_STR_MAX 2048
61 static char statname[NAME_STR_MAX + 1];
62 static unsigned long statsize = 0;
63 static int statperct = 0;
64 static unsigned long statelapsed = 0;
65 static int gui_mode = 0;
66 static char *gui_hwnd = NULL;
67
68 static void source(char *src);
69 static void rsource(char *src);
70 static void sink(char *targ, char *src);
71 /* GUI Adaptation - Sept 2000 */
72 static void tell_char(FILE * stream, char c);
73 static void tell_str(FILE * stream, char *str);
74 static void tell_user(FILE * stream, char *fmt, ...);
75 static void gui_update_stats(char *name, unsigned long size,
76                              int percentage, unsigned long elapsed);
77
78 void logevent(char *string)
79 {
80 }
81
82 void ldisc_send(char *buf, int len)
83 {
84     /*
85      * This is only here because of the calls to ldisc_send(NULL,
86      * 0) in ssh.c. Nothing in PSCP actually needs to use the ldisc
87      * as an ldisc. So if we get called with any real data, I want
88      * to know about it.
89      */
90     assert(len == 0);
91 }
92
93 void verify_ssh_host_key(char *host, int port, char *keytype,
94                          char *keystr, char *fingerprint)
95 {
96     int ret;
97     HANDLE hin;
98     DWORD savemode, i;
99
100     static const char absentmsg[] =
101         "The server's host key is not cached in the registry. You\n"
102         "have no guarantee that the server is the computer you\n"
103         "think it is.\n"
104         "The server's key fingerprint is:\n"
105         "%s\n"
106         "If you trust this host, enter \"y\" to add the key to\n"
107         "PuTTY's cache and carry on connecting.\n"
108         "If you want to carry on connecting just once, without\n"
109         "adding the key to the cache, enter \"n\".\n"
110         "If you do not trust this host, press Return to abandon the\n"
111         "connection.\n"
112         "Store key in cache? (y/n) ";
113
114     static const char wrongmsg[] =
115         "WARNING - POTENTIAL SECURITY BREACH!\n"
116         "The server's host key does not match the one PuTTY has\n"
117         "cached in the registry. This means that either the\n"
118         "server administrator has changed the host key, or you\n"
119         "have actually connected to another computer pretending\n"
120         "to be the server.\n"
121         "The new key fingerprint is:\n"
122         "%s\n"
123         "If you were expecting this change and trust the new key,\n"
124         "enter \"y\" to update PuTTY's cache and continue connecting.\n"
125         "If you want to carry on connecting but without updating\n"
126         "the cache, enter \"n\".\n"
127         "If you want to abandon the connection completely, press\n"
128         "Return to cancel. Pressing Return is the ONLY guaranteed\n"
129         "safe choice.\n"
130         "Update cached key? (y/n, Return cancels connection) ";
131
132     static const char abandoned[] = "Connection abandoned.\n";
133
134     char line[32];
135
136     /*
137      * Verify the key against the registry.
138      */
139     ret = verify_host_key(host, port, keytype, keystr);
140
141     if (ret == 0)                      /* success - key matched OK */
142         return;
143
144     if (ret == 2) {                    /* key was different */
145         fprintf(stderr, wrongmsg, fingerprint);
146         fflush(stderr);
147     }
148     if (ret == 1) {                    /* key was absent */
149         fprintf(stderr, absentmsg, fingerprint);
150         fflush(stderr);
151     }
152
153     hin = GetStdHandle(STD_INPUT_HANDLE);
154     GetConsoleMode(hin, &savemode);
155     SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
156                          ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
157     ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
158     SetConsoleMode(hin, savemode);
159
160     if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
161         if (line[0] == 'y' || line[0] == 'Y')
162             store_host_key(host, port, keytype, keystr);
163     } else {
164         fprintf(stderr, abandoned);
165         exit(0);
166     }
167 }
168
169 /* GUI Adaptation - Sept 2000 */
170 static void send_msg(HWND h, UINT message, WPARAM wParam)
171 {
172     while (!PostMessage(h, message, wParam, 0))
173         SleepEx(1000, TRUE);
174 }
175
176 static void tell_char(FILE * stream, char c)
177 {
178     if (!gui_mode)
179         fputc(c, stream);
180     else {
181         unsigned int msg_id = WM_STD_OUT_CHAR;
182         if (stream == stderr)
183             msg_id = WM_STD_ERR_CHAR;
184         send_msg((HWND) atoi(gui_hwnd), msg_id, (WPARAM) c);
185     }
186 }
187
188 static void tell_str(FILE * stream, char *str)
189 {
190     unsigned int i;
191
192     for (i = 0; i < strlen(str); ++i)
193         tell_char(stream, str[i]);
194 }
195
196 static void tell_user(FILE * stream, char *fmt, ...)
197 {
198     char str[0x100];                   /* Make the size big enough */
199     va_list ap;
200     va_start(ap, fmt);
201     vsprintf(str, fmt, ap);
202     va_end(ap);
203     strcat(str, "\n");
204     tell_str(stream, str);
205 }
206
207 static void gui_update_stats(char *name, unsigned long size,
208                              int percentage, unsigned long elapsed)
209 {
210     unsigned int i;
211
212     if (strcmp(name, statname) != 0) {
213         for (i = 0; i < strlen(name); ++i)
214             send_msg((HWND) atoi(gui_hwnd), WM_STATS_CHAR,
215                      (WPARAM) name[i]);
216         send_msg((HWND) atoi(gui_hwnd), WM_STATS_CHAR, (WPARAM) '\n');
217         strcpy(statname, name);
218     }
219     if (statsize != size) {
220         send_msg((HWND) atoi(gui_hwnd), WM_STATS_SIZE, (WPARAM) size);
221         statsize = size;
222     }
223     if (statelapsed != elapsed) {
224         send_msg((HWND) atoi(gui_hwnd), WM_STATS_ELAPSED,
225                  (WPARAM) elapsed);
226         statelapsed = elapsed;
227     }
228     if (statperct != percentage) {
229         send_msg((HWND) atoi(gui_hwnd), WM_STATS_PERCENT,
230                  (WPARAM) percentage);
231         statperct = percentage;
232     }
233 }
234
235 /*
236  *  Print an error message and perform a fatal exit.
237  */
238 void fatalbox(char *fmt, ...)
239 {
240     char str[0x100];                   /* Make the size big enough */
241     va_list ap;
242     va_start(ap, fmt);
243     strcpy(str, "Fatal:");
244     vsprintf(str + strlen(str), fmt, ap);
245     va_end(ap);
246     strcat(str, "\n");
247     tell_str(stderr, str);
248     errs++;
249
250     if (gui_mode) {
251         unsigned int msg_id = WM_RET_ERR_CNT;
252         if (list)
253             msg_id = WM_LS_RET_ERR_CNT;
254         while (!PostMessage
255                ((HWND) atoi(gui_hwnd), msg_id, (WPARAM) errs,
256                 0 /*lParam */ ))SleepEx(1000, TRUE);
257     }
258
259     exit(1);
260 }
261 void connection_fatal(char *fmt, ...)
262 {
263     char str[0x100];                   /* Make the size big enough */
264     va_list ap;
265     va_start(ap, fmt);
266     strcpy(str, "Fatal:");
267     vsprintf(str + strlen(str), fmt, ap);
268     va_end(ap);
269     strcat(str, "\n");
270     tell_str(stderr, str);
271     errs++;
272
273     if (gui_mode) {
274         unsigned int msg_id = WM_RET_ERR_CNT;
275         if (list)
276             msg_id = WM_LS_RET_ERR_CNT;
277         while (!PostMessage
278                ((HWND) atoi(gui_hwnd), msg_id, (WPARAM) errs,
279                 0 /*lParam */ ))SleepEx(1000, TRUE);
280     }
281
282     exit(1);
283 }
284
285 /*
286  * Be told what socket we're supposed to be using.
287  */
288 static SOCKET scp_ssh_socket;
289 char *do_select(SOCKET skt, int startup)
290 {
291     if (startup)
292         scp_ssh_socket = skt;
293     else
294         scp_ssh_socket = INVALID_SOCKET;
295     return NULL;
296 }
297 extern int select_result(WPARAM, LPARAM);
298
299 /*
300  * Receive a block of data from the SSH link. Block until all data
301  * is available.
302  *
303  * To do this, we repeatedly call the SSH protocol module, with our
304  * own trap in from_backend() to catch the data that comes back. We
305  * do this until we have enough data.
306  */
307
308 static unsigned char *outptr;          /* where to put the data */
309 static unsigned outlen;                /* how much data required */
310 static unsigned char *pending = NULL;  /* any spare data */
311 static unsigned pendlen = 0, pendsize = 0;      /* length and phys. size of buffer */
312 void from_backend(int is_stderr, char *data, int datalen)
313 {
314     unsigned char *p = (unsigned char *) data;
315     unsigned len = (unsigned) datalen;
316
317     /*
318      * stderr data is just spouted to local stderr and otherwise
319      * ignored.
320      */
321     if (is_stderr) {
322         fwrite(data, 1, len, stderr);
323         return;
324     }
325
326     inbuf_head = 0;
327
328     /*
329      * If this is before the real session begins, just return.
330      */
331     if (!outptr)
332         return;
333
334     if (outlen > 0) {
335         unsigned used = outlen;
336         if (used > len)
337             used = len;
338         memcpy(outptr, p, used);
339         outptr += used;
340         outlen -= used;
341         p += used;
342         len -= used;
343     }
344
345     if (len > 0) {
346         if (pendsize < pendlen + len) {
347             pendsize = pendlen + len + 4096;
348             pending = (pending ? srealloc(pending, pendsize) :
349                        smalloc(pendsize));
350             if (!pending)
351                 fatalbox("Out of memory");
352         }
353         memcpy(pending + pendlen, p, len);
354         pendlen += len;
355     }
356 }
357 static int ssh_scp_recv(unsigned char *buf, int len)
358 {
359     outptr = buf;
360     outlen = len;
361
362     /*
363      * See if the pending-input block contains some of what we
364      * need.
365      */
366     if (pendlen > 0) {
367         unsigned pendused = pendlen;
368         if (pendused > outlen)
369             pendused = outlen;
370         memcpy(outptr, pending, pendused);
371         memmove(pending, pending + pendused, pendlen - pendused);
372         outptr += pendused;
373         outlen -= pendused;
374         pendlen -= pendused;
375         if (pendlen == 0) {
376             pendsize = 0;
377             sfree(pending);
378             pending = NULL;
379         }
380         if (outlen == 0)
381             return len;
382     }
383
384     while (outlen > 0) {
385         fd_set readfds;
386
387         FD_ZERO(&readfds);
388         FD_SET(scp_ssh_socket, &readfds);
389         if (select(1, &readfds, NULL, NULL, NULL) < 0)
390             return 0;                  /* doom */
391         select_result((WPARAM) scp_ssh_socket, (LPARAM) FD_READ);
392     }
393
394     return len;
395 }
396
397 /*
398  * Loop through the ssh connection and authentication process.
399  */
400 static void ssh_scp_init(void)
401 {
402     if (scp_ssh_socket == INVALID_SOCKET)
403         return;
404     while (!back->sendok()) {
405         fd_set readfds;
406         FD_ZERO(&readfds);
407         FD_SET(scp_ssh_socket, &readfds);
408         if (select(1, &readfds, NULL, NULL, NULL) < 0)
409             return;                    /* doom */
410         select_result((WPARAM) scp_ssh_socket, (LPARAM) FD_READ);
411     }
412 }
413
414 /*
415  *  Print an error message and exit after closing the SSH link.
416  */
417 static void bump(char *fmt, ...)
418 {
419     char str[0x100];                   /* Make the size big enough */
420     va_list ap;
421     va_start(ap, fmt);
422     strcpy(str, "Fatal:");
423     vsprintf(str + strlen(str), fmt, ap);
424     va_end(ap);
425     strcat(str, "\n");
426     tell_str(stderr, str);
427     errs++;
428
429     if (back != NULL && back->socket() != NULL) {
430         char ch;
431         back->special(TS_EOF);
432         ssh_scp_recv(&ch, 1);
433     }
434
435     if (gui_mode) {
436         unsigned int msg_id = WM_RET_ERR_CNT;
437         if (list)
438             msg_id = WM_LS_RET_ERR_CNT;
439         while (!PostMessage
440                ((HWND) atoi(gui_hwnd), msg_id, (WPARAM) errs,
441                 0 /*lParam */ ))SleepEx(1000, TRUE);
442     }
443
444     exit(1);
445 }
446
447 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
448 {
449     HANDLE hin, hout;
450     DWORD savemode, newmode, i;
451
452     if (is_pw && password) {
453         static int tried_once = 0;
454
455         if (tried_once) {
456             return 0;
457         } else {
458             strncpy(str, password, maxlen);
459             str[maxlen - 1] = '\0';
460             tried_once = 1;
461             return 1;
462         }
463     }
464
465     /* GUI Adaptation - Sept 2000 */
466     if (gui_mode) {
467         if (maxlen > 0)
468             str[0] = '\0';
469     } else {
470         hin = GetStdHandle(STD_INPUT_HANDLE);
471         hout = GetStdHandle(STD_OUTPUT_HANDLE);
472         if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE)
473             bump("Cannot get standard input/output handles");
474
475         GetConsoleMode(hin, &savemode);
476         newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
477         if (is_pw)
478             newmode &= ~ENABLE_ECHO_INPUT;
479         else
480             newmode |= ENABLE_ECHO_INPUT;
481         SetConsoleMode(hin, newmode);
482
483         WriteFile(hout, prompt, strlen(prompt), &i, NULL);
484         ReadFile(hin, str, maxlen - 1, &i, NULL);
485
486         SetConsoleMode(hin, savemode);
487
488         if ((int) i > maxlen)
489             i = maxlen - 1;
490         else
491             i = i - 2;
492         str[i] = '\0';
493
494         if (is_pw)
495             WriteFile(hout, "\r\n", 2, &i, NULL);
496     }
497
498     return 1;
499 }
500
501 /*
502  *  Open an SSH connection to user@host and execute cmd.
503  */
504 static void do_cmd(char *host, char *user, char *cmd)
505 {
506     char *err, *realhost;
507     DWORD namelen;
508
509     if (host == NULL || host[0] == '\0')
510         bump("Empty host name");
511
512     /* Try to load settings for this host */
513     do_defaults(host, &cfg);
514     if (cfg.host[0] == '\0') {
515         /* No settings for this host; use defaults */
516         do_defaults(NULL, &cfg);
517         strncpy(cfg.host, host, sizeof(cfg.host) - 1);
518         cfg.host[sizeof(cfg.host) - 1] = '\0';
519         cfg.port = 22;
520     }
521
522     /* Set username */
523     if (user != NULL && user[0] != '\0') {
524         strncpy(cfg.username, user, sizeof(cfg.username) - 1);
525         cfg.username[sizeof(cfg.username) - 1] = '\0';
526     } else if (cfg.username[0] == '\0') {
527         namelen = 0;
528         if (GetUserName(user, &namelen) == FALSE)
529             bump("Empty user name");
530         user = smalloc(namelen * sizeof(char));
531         GetUserName(user, &namelen);
532         if (verbose)
533             tell_user(stderr, "Guessing user name: %s", user);
534         strncpy(cfg.username, user, sizeof(cfg.username) - 1);
535         cfg.username[sizeof(cfg.username) - 1] = '\0';
536         free(user);
537     }
538
539     if (cfg.protocol != PROT_SSH)
540         cfg.port = 22;
541
542     if (portnumber)
543         cfg.port = portnumber;
544
545     strncpy(cfg.remote_cmd, cmd, sizeof(cfg.remote_cmd));
546     cfg.remote_cmd[sizeof(cfg.remote_cmd) - 1] = '\0';
547     cfg.nopty = TRUE;
548
549     back = &ssh_backend;
550
551     err = back->init(cfg.host, cfg.port, &realhost);
552     if (err != NULL)
553         bump("ssh_init: %s", err);
554     ssh_scp_init();
555     if (verbose && realhost != NULL)
556         tell_user(stderr, "Connected to %s\n", realhost);
557     sfree(realhost);
558 }
559
560 /*
561  *  Update statistic information about current file.
562  */
563 static void print_stats(char *name, unsigned long size, unsigned long done,
564                         time_t start, time_t now)
565 {
566     float ratebs;
567     unsigned long eta;
568     char etastr[10];
569     int pct;
570     int len;
571
572     /* GUI Adaptation - Sept 2000 */
573     if (gui_mode)
574         gui_update_stats(name, size, (int) (100 * (done * 1.0 / size)),
575                          (unsigned long) difftime(now, start));
576     else {
577         if (now > start)
578             ratebs = (float) done / (now - start);
579         else
580             ratebs = (float) done;
581
582         if (ratebs < 1.0)
583             eta = size - done;
584         else
585             eta = (unsigned long) ((size - done) / ratebs);
586         sprintf(etastr, "%02ld:%02ld:%02ld",
587                 eta / 3600, (eta % 3600) / 60, eta % 60);
588
589         pct = (int) (100.0 * (float) done / size);
590
591         len = printf("\r%-25.25s | %10ld kB | %5.1f kB/s | ETA: %8s | %3d%%",
592                      name, done / 1024, ratebs / 1024.0, etastr, pct);
593         if (len < prev_stats_len)
594             printf("%*s", prev_stats_len - len, "");
595         prev_stats_len = len;
596
597         if (done == size)
598             printf("\n");
599     }
600 }
601
602 /*
603  *  Find a colon in str and return a pointer to the colon.
604  *  This is used to separate hostname from filename.
605  */
606 static char *colon(char *str)
607 {
608     /* We ignore a leading colon, since the hostname cannot be
609        empty. We also ignore a colon as second character because
610        of filenames like f:myfile.txt. */
611     if (str[0] == '\0' || str[0] == ':' || str[1] == ':')
612         return (NULL);
613     while (*str != '\0' && *str != ':' && *str != '/' && *str != '\\')
614         str++;
615     if (*str == ':')
616         return (str);
617     else
618         return (NULL);
619 }
620
621 /*
622  *  Wait for a response from the other side.
623  *  Return 0 if ok, -1 if error.
624  */
625 static int response(void)
626 {
627     char ch, resp, rbuf[2048];
628     int p;
629
630     if (ssh_scp_recv(&resp, 1) <= 0)
631         bump("Lost connection");
632
633     p = 0;
634     switch (resp) {
635       case 0:                          /* ok */
636         return (0);
637       default:
638         rbuf[p++] = resp;
639         /* fallthrough */
640       case 1:                          /* error */
641       case 2:                          /* fatal error */
642         do {
643             if (ssh_scp_recv(&ch, 1) <= 0)
644                 bump("Protocol error: Lost connection");
645             rbuf[p++] = ch;
646         } while (p < sizeof(rbuf) && ch != '\n');
647         rbuf[p - 1] = '\0';
648         if (resp == 1)
649             tell_user(stderr, "%s\n", rbuf);
650         else
651             bump("%s", rbuf);
652         errs++;
653         return (-1);
654     }
655 }
656
657 /*
658  *  Send an error message to the other side and to the screen.
659  *  Increment error counter.
660  */
661 static void run_err(const char *fmt, ...)
662 {
663     char str[2048];
664     va_list ap;
665     va_start(ap, fmt);
666     errs++;
667     strcpy(str, "scp: ");
668     vsprintf(str + strlen(str), fmt, ap);
669     strcat(str, "\n");
670     back->send("\001", 1);             /* scp protocol error prefix */
671     back->send(str, strlen(str));
672     tell_user(stderr, "%s", str);
673     va_end(ap);
674 }
675
676 /*
677  *  Execute the source part of the SCP protocol.
678  */
679 static void source(char *src)
680 {
681     char buf[2048];
682     unsigned long size;
683     char *last;
684     HANDLE f;
685     DWORD attr;
686     unsigned long i;
687     unsigned long stat_bytes;
688     time_t stat_starttime, stat_lasttime;
689
690     attr = GetFileAttributes(src);
691     if (attr == (DWORD) - 1) {
692         run_err("%s: No such file or directory", src);
693         return;
694     }
695
696     if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
697         if (recursive) {
698             /*
699              * Avoid . and .. directories.
700              */
701             char *p;
702             p = strrchr(src, '/');
703             if (!p)
704                 p = strrchr(src, '\\');
705             if (!p)
706                 p = src;
707             else
708                 p++;
709             if (!strcmp(p, ".") || !strcmp(p, ".."))
710                 /* skip . and .. */ ;
711             else
712                 rsource(src);
713         } else {
714             run_err("%s: not a regular file", src);
715         }
716         return;
717     }
718
719     if ((last = strrchr(src, '/')) == NULL)
720         last = src;
721     else
722         last++;
723     if (strrchr(last, '\\') != NULL)
724         last = strrchr(last, '\\') + 1;
725     if (last == src && strchr(src, ':') != NULL)
726         last = strchr(src, ':') + 1;
727
728     f = CreateFile(src, GENERIC_READ, FILE_SHARE_READ, NULL,
729                    OPEN_EXISTING, 0, 0);
730     if (f == INVALID_HANDLE_VALUE) {
731         run_err("%s: Cannot open file", src);
732         return;
733     }
734
735     if (preserve) {
736         FILETIME actime, wrtime;
737         unsigned long mtime, atime;
738         GetFileTime(f, NULL, &actime, &wrtime);
739         TIME_WIN_TO_POSIX(actime, atime);
740         TIME_WIN_TO_POSIX(wrtime, mtime);
741         sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
742         back->send(buf, strlen(buf));
743         if (response())
744             return;
745     }
746
747     size = GetFileSize(f, NULL);
748     sprintf(buf, "C0644 %lu %s\n", size, last);
749     if (verbose)
750         tell_user(stderr, "Sending file modes: %s", buf);
751     back->send(buf, strlen(buf));
752     if (response())
753         return;
754
755     stat_bytes = 0;
756     stat_starttime = time(NULL);
757     stat_lasttime = 0;
758
759     for (i = 0; i < size; i += 4096) {
760         char transbuf[4096];
761         DWORD j, k = 4096;
762         if (i + k > size)
763             k = size - i;
764         if (!ReadFile(f, transbuf, k, &j, NULL) || j != k) {
765             if (statistics)
766                 printf("\n");
767             bump("%s: Read error", src);
768         }
769         back->send(transbuf, k);
770         if (statistics) {
771             stat_bytes += k;
772             if (time(NULL) != stat_lasttime || i + k == size) {
773                 stat_lasttime = time(NULL);
774                 print_stats(last, size, stat_bytes,
775                             stat_starttime, stat_lasttime);
776             }
777         }
778     }
779     CloseHandle(f);
780
781     back->send("", 1);
782     (void) response();
783 }
784
785 /*
786  *  Recursively send the contents of a directory.
787  */
788 static void rsource(char *src)
789 {
790     char buf[2048];
791     char *last;
792     HANDLE dir;
793     WIN32_FIND_DATA fdat;
794     int ok;
795
796     if ((last = strrchr(src, '/')) == NULL)
797         last = src;
798     else
799         last++;
800     if (strrchr(last, '\\') != NULL)
801         last = strrchr(last, '\\') + 1;
802     if (last == src && strchr(src, ':') != NULL)
803         last = strchr(src, ':') + 1;
804
805     /* maybe send filetime */
806
807     sprintf(buf, "D0755 0 %s\n", last);
808     if (verbose)
809         tell_user(stderr, "Entering directory: %s", buf);
810     back->send(buf, strlen(buf));
811     if (response())
812         return;
813
814     sprintf(buf, "%s/*", src);
815     dir = FindFirstFile(buf, &fdat);
816     ok = (dir != INVALID_HANDLE_VALUE);
817     while (ok) {
818         if (strcmp(fdat.cFileName, ".") == 0 ||
819             strcmp(fdat.cFileName, "..") == 0) {
820         } else if (strlen(src) + 1 + strlen(fdat.cFileName) >= sizeof(buf)) {
821             run_err("%s/%s: Name too long", src, fdat.cFileName);
822         } else {
823             sprintf(buf, "%s/%s", src, fdat.cFileName);
824             source(buf);
825         }
826         ok = FindNextFile(dir, &fdat);
827     }
828     FindClose(dir);
829
830     sprintf(buf, "E\n");
831     back->send(buf, strlen(buf));
832     (void) response();
833 }
834
835 /*
836  *  Execute the sink part of the SCP protocol.
837  */
838 static void sink(char *targ, char *src)
839 {
840     char buf[2048];
841     char namebuf[2048];
842     char ch;
843     int targisdir = 0;
844     int settime;
845     int exists;
846     DWORD attr;
847     HANDLE f;
848     unsigned long mtime, atime;
849     unsigned int mode;
850     unsigned long size, i;
851     int wrerror = 0;
852     unsigned long stat_bytes;
853     time_t stat_starttime, stat_lasttime;
854     char *stat_name;
855
856     attr = GetFileAttributes(targ);
857     if (attr != (DWORD) - 1 && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
858         targisdir = 1;
859
860     if (targetshouldbedirectory && !targisdir)
861         bump("%s: Not a directory", targ);
862
863     back->send("", 1);
864     while (1) {
865         settime = 0;
866       gottime:
867         if (ssh_scp_recv(&ch, 1) <= 0)
868             return;
869         if (ch == '\n')
870             bump("Protocol error: Unexpected newline");
871         i = 0;
872         buf[i++] = ch;
873         do {
874             if (ssh_scp_recv(&ch, 1) <= 0)
875                 bump("Lost connection");
876             buf[i++] = ch;
877         } while (i < sizeof(buf) && ch != '\n');
878         buf[i - 1] = '\0';
879         switch (buf[0]) {
880           case '\01':                  /* error */
881             tell_user(stderr, "%s\n", buf + 1);
882             errs++;
883             continue;
884           case '\02':                  /* fatal error */
885             bump("%s", buf + 1);
886           case 'E':
887             back->send("", 1);
888             return;
889           case 'T':
890             if (sscanf(buf, "T%ld %*d %ld %*d", &mtime, &atime) == 2) {
891                 settime = 1;
892                 back->send("", 1);
893                 goto gottime;
894             }
895             bump("Protocol error: Illegal time format");
896           case 'C':
897           case 'D':
898             break;
899           default:
900             bump("Protocol error: Expected control record");
901         }
902
903         if (sscanf(buf + 1, "%u %lu %[^\n]", &mode, &size, namebuf) != 3)
904             bump("Protocol error: Illegal file descriptor format");
905         /* Security fix: ensure the file ends up where we asked for it. */
906         if (targisdir) {
907             char t[2048];
908             char *p;
909             strcpy(t, targ);
910             if (targ[0] != '\0')
911                 strcat(t, "/");
912             p = namebuf + strlen(namebuf);
913             while (p > namebuf && p[-1] != '/' && p[-1] != '\\')
914                 p--;
915             strcat(t, p);
916             strcpy(namebuf, t);
917         } else {
918             strcpy(namebuf, targ);
919         }
920         attr = GetFileAttributes(namebuf);
921         exists = (attr != (DWORD) - 1);
922
923         if (buf[0] == 'D') {
924             if (exists && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
925                 run_err("%s: Not a directory", namebuf);
926                 continue;
927             }
928             if (!exists) {
929                 if (!CreateDirectory(namebuf, NULL)) {
930                     run_err("%s: Cannot create directory", namebuf);
931                     continue;
932                 }
933             }
934             sink(namebuf, NULL);
935             /* can we set the timestamp for directories ? */
936             continue;
937         }
938
939         f = CreateFile(namebuf, GENERIC_WRITE, 0, NULL,
940                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
941         if (f == INVALID_HANDLE_VALUE) {
942             run_err("%s: Cannot create file", namebuf);
943             continue;
944         }
945
946         back->send("", 1);
947
948         stat_bytes = 0;
949         stat_starttime = time(NULL);
950         stat_lasttime = 0;
951         if ((stat_name = strrchr(namebuf, '/')) == NULL)
952             stat_name = namebuf;
953         else
954             stat_name++;
955         if (strrchr(stat_name, '\\') != NULL)
956             stat_name = strrchr(stat_name, '\\') + 1;
957
958         for (i = 0; i < size; i += 4096) {
959             char transbuf[4096];
960             DWORD j, k = 4096;
961             if (i + k > size)
962                 k = size - i;
963             if (ssh_scp_recv(transbuf, k) == 0)
964                 bump("Lost connection");
965             if (wrerror)
966                 continue;
967             if (!WriteFile(f, transbuf, k, &j, NULL) || j != k) {
968                 wrerror = 1;
969                 if (statistics)
970                     printf("\r%-25.25s | %50s\n",
971                            stat_name,
972                            "Write error.. waiting for end of file");
973                 continue;
974             }
975             if (statistics) {
976                 stat_bytes += k;
977                 if (time(NULL) > stat_lasttime || i + k == size) {
978                     stat_lasttime = time(NULL);
979                     print_stats(stat_name, size, stat_bytes,
980                                 stat_starttime, stat_lasttime);
981                 }
982             }
983         }
984         (void) response();
985
986         if (settime) {
987             FILETIME actime, wrtime;
988             TIME_POSIX_TO_WIN(atime, actime);
989             TIME_POSIX_TO_WIN(mtime, wrtime);
990             SetFileTime(f, NULL, &actime, &wrtime);
991         }
992
993         CloseHandle(f);
994         if (wrerror) {
995             run_err("%s: Write error", namebuf);
996             continue;
997         }
998         back->send("", 1);
999     }
1000 }
1001
1002 /*
1003  *  We will copy local files to a remote server.
1004  */
1005 static void toremote(int argc, char *argv[])
1006 {
1007     char *src, *targ, *host, *user;
1008     char *cmd;
1009     int i;
1010
1011     targ = argv[argc - 1];
1012
1013     /* Separate host from filename */
1014     host = targ;
1015     targ = colon(targ);
1016     if (targ == NULL)
1017         bump("targ == NULL in toremote()");
1018     *targ++ = '\0';
1019     if (*targ == '\0')
1020         targ = ".";
1021     /* Substitute "." for emtpy target */
1022
1023     /* Separate host and username */
1024     user = host;
1025     host = strrchr(host, '@');
1026     if (host == NULL) {
1027         host = user;
1028         user = NULL;
1029     } else {
1030         *host++ = '\0';
1031         if (*user == '\0')
1032             user = NULL;
1033     }
1034
1035     if (argc == 2) {
1036         /* Find out if the source filespec covers multiple files
1037            if so, we should set the targetshouldbedirectory flag */
1038         HANDLE fh;
1039         WIN32_FIND_DATA fdat;
1040         if (colon(argv[0]) != NULL)
1041             bump("%s: Remote to remote not supported", argv[0]);
1042         fh = FindFirstFile(argv[0], &fdat);
1043         if (fh == INVALID_HANDLE_VALUE)
1044             bump("%s: No such file or directory\n", argv[0]);
1045         if (FindNextFile(fh, &fdat))
1046             targetshouldbedirectory = 1;
1047         FindClose(fh);
1048     }
1049
1050     cmd = smalloc(strlen(targ) + 100);
1051     sprintf(cmd, "scp%s%s%s%s -t %s",
1052             verbose ? " -v" : "",
1053             recursive ? " -r" : "",
1054             preserve ? " -p" : "",
1055             targetshouldbedirectory ? " -d" : "", targ);
1056     do_cmd(host, user, cmd);
1057     sfree(cmd);
1058
1059     (void) response();
1060
1061     for (i = 0; i < argc - 1; i++) {
1062         HANDLE dir;
1063         WIN32_FIND_DATA fdat;
1064         src = argv[i];
1065         if (colon(src) != NULL) {
1066             tell_user(stderr, "%s: Remote to remote not supported\n", src);
1067             errs++;
1068             continue;
1069         }
1070         dir = FindFirstFile(src, &fdat);
1071         if (dir == INVALID_HANDLE_VALUE) {
1072             run_err("%s: No such file or directory", src);
1073             continue;
1074         }
1075         do {
1076             char *last;
1077             char namebuf[2048];
1078             /*
1079              * Ensure that . and .. are never matched by wildcards,
1080              * but only by deliberate action.
1081              */
1082             if (!strcmp(fdat.cFileName, ".") ||
1083                 !strcmp(fdat.cFileName, "..")) {
1084                 /*
1085                  * Find*File has returned a special dir. We require
1086                  * that _either_ `src' ends in a backslash followed
1087                  * by that string, _or_ `src' is precisely that
1088                  * string.
1089                  */
1090                 int len = strlen(src), dlen = strlen(fdat.cFileName);
1091                 if (len == dlen && !strcmp(src, fdat.cFileName)) {
1092                     /* ok */ ;
1093                 } else if (len > dlen + 1 && src[len - dlen - 1] == '\\' &&
1094                            !strcmp(src + len - dlen, fdat.cFileName)) {
1095                     /* ok */ ;
1096                 } else
1097                     continue;          /* ignore this one */
1098             }
1099             if (strlen(src) + strlen(fdat.cFileName) >= sizeof(namebuf)) {
1100                 tell_user(stderr, "%s: Name too long", src);
1101                 continue;
1102             }
1103             strcpy(namebuf, src);
1104             if ((last = strrchr(namebuf, '/')) == NULL)
1105                 last = namebuf;
1106             else
1107                 last++;
1108             if (strrchr(last, '\\') != NULL)
1109                 last = strrchr(last, '\\') + 1;
1110             if (last == namebuf && strrchr(namebuf, ':') != NULL)
1111                 last = strchr(namebuf, ':') + 1;
1112             strcpy(last, fdat.cFileName);
1113             source(namebuf);
1114         } while (FindNextFile(dir, &fdat));
1115         FindClose(dir);
1116     }
1117 }
1118
1119 /*
1120  *  We will copy files from a remote server to the local machine.
1121  */
1122 static void tolocal(int argc, char *argv[])
1123 {
1124     char *src, *targ, *host, *user;
1125     char *cmd;
1126
1127     if (argc != 2)
1128         bump("More than one remote source not supported");
1129
1130     src = argv[0];
1131     targ = argv[1];
1132
1133     /* Separate host from filename */
1134     host = src;
1135     src = colon(src);
1136     if (src == NULL)
1137         bump("Local to local copy not supported");
1138     *src++ = '\0';
1139     if (*src == '\0')
1140         src = ".";
1141     /* Substitute "." for empty filename */
1142
1143     /* Separate username and hostname */
1144     user = host;
1145     host = strrchr(host, '@');
1146     if (host == NULL) {
1147         host = user;
1148         user = NULL;
1149     } else {
1150         *host++ = '\0';
1151         if (*user == '\0')
1152             user = NULL;
1153     }
1154
1155     cmd = smalloc(strlen(src) + 100);
1156     sprintf(cmd, "scp%s%s%s%s -f %s",
1157             verbose ? " -v" : "",
1158             recursive ? " -r" : "",
1159             preserve ? " -p" : "",
1160             targetshouldbedirectory ? " -d" : "", src);
1161     do_cmd(host, user, cmd);
1162     sfree(cmd);
1163
1164     sink(targ, src);
1165 }
1166
1167 /*
1168  *  We will issue a list command to get a remote directory.
1169  */
1170 static void get_dir_list(int argc, char *argv[])
1171 {
1172     char *src, *host, *user;
1173     char *cmd, *p, *q;
1174     char c;
1175
1176     src = argv[0];
1177
1178     /* Separate host from filename */
1179     host = src;
1180     src = colon(src);
1181     if (src == NULL)
1182         bump("Local to local copy not supported");
1183     *src++ = '\0';
1184     if (*src == '\0')
1185         src = ".";
1186     /* Substitute "." for empty filename */
1187
1188     /* Separate username and hostname */
1189     user = host;
1190     host = strrchr(host, '@');
1191     if (host == NULL) {
1192         host = user;
1193         user = NULL;
1194     } else {
1195         *host++ = '\0';
1196         if (*user == '\0')
1197             user = NULL;
1198     }
1199
1200     cmd = smalloc(4 * strlen(src) + 100);
1201     strcpy(cmd, "ls -la '");
1202     p = cmd + strlen(cmd);
1203     for (q = src; *q; q++) {
1204         if (*q == '\'') {
1205             *p++ = '\'';
1206             *p++ = '\\';
1207             *p++ = '\'';
1208             *p++ = '\'';
1209         } else {
1210             *p++ = *q;
1211         }
1212     }
1213     *p++ = '\'';
1214     *p = '\0';
1215
1216     do_cmd(host, user, cmd);
1217     sfree(cmd);
1218
1219     while (ssh_scp_recv(&c, 1) > 0)
1220         tell_char(stdout, c);
1221 }
1222
1223 /*
1224  *  Initialize the Win$ock driver.
1225  */
1226 static void init_winsock(void)
1227 {
1228     WORD winsock_ver;
1229     WSADATA wsadata;
1230
1231     winsock_ver = MAKEWORD(1, 1);
1232     if (WSAStartup(winsock_ver, &wsadata))
1233         bump("Unable to initialise WinSock");
1234     if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1)
1235         bump("WinSock version is incompatible with 1.1");
1236 }
1237
1238 /*
1239  *  Short description of parameters.
1240  */
1241 static void usage(void)
1242 {
1243     printf("PuTTY Secure Copy client\n");
1244     printf("%s\n", ver);
1245     printf("Usage: pscp [options] [user@]host:source target\n");
1246     printf
1247         ("       pscp [options] source [source...] [user@]host:target\n");
1248     printf("       pscp [options] -ls user@host:filespec\n");
1249     printf("Options:\n");
1250     printf("  -p        preserve file attributes\n");
1251     printf("  -q        quiet, don't show statistics\n");
1252     printf("  -r        copy directories recursively\n");
1253     printf("  -v        show verbose messages\n");
1254     printf("  -P port   connect to specified port\n");
1255     printf("  -pw passw login with specified password\n");
1256 #if 0
1257     /*
1258      * -gui is an internal option, used by GUI front ends to get
1259      * pscp to pass progress reports back to them. It's not an
1260      * ordinary user-accessible option, so it shouldn't be part of
1261      * the command-line help. The only people who need to know
1262      * about it are programmers, and they can read the source.
1263      */
1264     printf
1265         ("  -gui hWnd GUI mode with the windows handle for receiving messages\n");
1266 #endif
1267     exit(1);
1268 }
1269
1270 /*
1271  *  Main program (no, really?)
1272  */
1273 int main(int argc, char *argv[])
1274 {
1275     int i;
1276
1277     default_protocol = PROT_TELNET;
1278
1279     flags = FLAG_STDERR;
1280     ssh_get_line = &get_line;
1281     init_winsock();
1282     sk_init();
1283
1284     for (i = 1; i < argc; i++) {
1285         if (argv[i][0] != '-')
1286             break;
1287         if (strcmp(argv[i], "-v") == 0)
1288             verbose = 1, flags |= FLAG_VERBOSE;
1289         else if (strcmp(argv[i], "-r") == 0)
1290             recursive = 1;
1291         else if (strcmp(argv[i], "-p") == 0)
1292             preserve = 1;
1293         else if (strcmp(argv[i], "-q") == 0)
1294             statistics = 0;
1295         else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-?") == 0)
1296             usage();
1297         else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc)
1298             portnumber = atoi(argv[++i]);
1299         else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc)
1300             password = argv[++i];
1301         else if (strcmp(argv[i], "-gui") == 0 && i + 1 < argc) {
1302             gui_hwnd = argv[++i];
1303             gui_mode = 1;
1304         } else if (strcmp(argv[i], "-ls") == 0)
1305             list = 1;
1306         else if (strcmp(argv[i], "--") == 0) {
1307             i++;
1308             break;
1309         } else
1310             usage();
1311     }
1312     argc -= i;
1313     argv += i;
1314     back = NULL;
1315
1316     if (list) {
1317         if (argc != 1)
1318             usage();
1319         get_dir_list(argc, argv);
1320
1321     } else {
1322
1323         if (argc < 2)
1324             usage();
1325         if (argc > 2)
1326             targetshouldbedirectory = 1;
1327
1328         if (colon(argv[argc - 1]) != NULL)
1329             toremote(argc, argv);
1330         else
1331             tolocal(argc, argv);
1332     }
1333
1334     if (back != NULL && back->socket() != NULL) {
1335         char ch;
1336         back->special(TS_EOF);
1337         ssh_scp_recv(&ch, 1);
1338     }
1339     WSACleanup();
1340     random_save_seed();
1341
1342     /* GUI Adaptation - August 2000 */
1343     if (gui_mode) {
1344         unsigned int msg_id = WM_RET_ERR_CNT;
1345         if (list)
1346             msg_id = WM_LS_RET_ERR_CNT;
1347         while (!PostMessage
1348                ((HWND) atoi(gui_hwnd), msg_id, (WPARAM) errs,
1349                 0 /*lParam */ ))SleepEx(1000, TRUE);
1350     }
1351     return (errs == 0 ? 0 : 1);
1352 }
1353
1354 /* end */