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