]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - scp.c
Ooh. Actually, that vulnerability is further-reaching than I
[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 static int connection_open = 0;
57 /* GUI Adaptation - Sept 2000 */
58 #define NAME_STR_MAX 2048
59 static char statname[NAME_STR_MAX+1];
60 static unsigned long statsize = 0;
61 static int statperct = 0;
62 static time_t statelapsed = 0;
63 static int gui_mode = 0;
64 static char *gui_hwnd = NULL;
65
66 static void source(char *src);
67 static void rsource(char *src);
68 static void sink(char *targ, char *src);
69 /* GUI Adaptation - Sept 2000 */
70 static void tell_char(FILE *stream, char c);
71 static void tell_str(FILE *stream, char *str);
72 static void tell_user(FILE *stream, char *fmt, ...);
73 static void send_char_msg(unsigned int msg_id, char c);
74 static void send_str_msg(unsigned int msg_id, char *str);
75 static void gui_update_stats(char *name, unsigned long size, int percentage, time_t 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, time_t 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  * Receive a block of data from the SSH link. Block until all data
246  * is available.
247  *
248  * To do this, we repeatedly call the SSH protocol module, with our
249  * own trap in from_backend() to catch the data that comes back. We
250  * do this until we have enough data.
251  */
252 static unsigned char *outptr;          /* where to put the data */
253 static unsigned outlen;                /* how much data required */
254 static unsigned char *pending = NULL;  /* any spare data */
255 static unsigned pendlen=0, pendsize=0; /* length and phys. size of buffer */
256 void from_backend(int is_stderr, char *data, int datalen) {
257     unsigned char *p = (unsigned char *)data;
258     unsigned len = (unsigned)datalen;
259
260     /*
261      * stderr data is just spouted to local stderr and otherwise
262      * ignored.
263      */
264     if (is_stderr) {
265         fwrite(data, 1, len, stderr);
266         return;
267     }
268
269     inbuf_head = 0;
270
271     /*
272      * If this is before the real session begins, just return.
273      */
274     if (!outptr)
275         return;
276
277     if (outlen > 0) {
278         unsigned used = outlen;
279         if (used > len) used = len;
280         memcpy(outptr, p, used);
281         outptr += used; outlen -= used;
282         p += used; len -= used;
283     }
284
285     if (len > 0) {
286         if (pendsize < pendlen + len) {
287             pendsize = pendlen + len + 4096;
288             pending = (pending ? realloc(pending, pendsize) :
289                        malloc(pendsize));
290             if (!pending)
291                 fatalbox("Out of memory");
292         }
293         memcpy(pending+pendlen, p, len);
294         pendlen += len;
295     }
296 }
297 static int ssh_scp_recv(unsigned char *buf, int len) {
298     SOCKET s;
299
300     outptr = buf;
301     outlen = len;
302
303     /*
304      * See if the pending-input block contains some of what we
305      * need.
306      */
307     if (pendlen > 0) {
308         unsigned pendused = pendlen;
309         if (pendused > outlen)
310             pendused = outlen;
311         memcpy(outptr, pending, pendused);
312         memmove(pending, pending+pendused, pendlen-pendused);
313         outptr += pendused;
314         outlen -= pendused;
315         pendlen -= pendused;
316         if (pendlen == 0) {
317             pendsize = 0;
318             free(pending);
319             pending = NULL;
320         }
321         if (outlen == 0)
322             return len;
323     }
324
325     while (outlen > 0) {
326         fd_set readfds;
327         s = back->socket();
328         if (s == INVALID_SOCKET) {
329             connection_open = FALSE;
330             return 0;
331         }
332         FD_ZERO(&readfds);
333         FD_SET(s, &readfds);
334         if (select(1, &readfds, NULL, NULL, NULL) < 0)
335             return 0;                  /* doom */
336         back->msg(0, FD_READ);
337     }
338
339     return len;
340 }
341
342 /*
343  * Loop through the ssh connection and authentication process.
344  */
345 static void ssh_scp_init(void) {
346     SOCKET s;
347
348     s = back->socket();
349     if (s == INVALID_SOCKET)
350         return;
351     while (!back->sendok()) {
352         fd_set readfds;
353         FD_ZERO(&readfds);
354         FD_SET(s, &readfds);
355         if (select(1, &readfds, NULL, NULL, NULL) < 0)
356             return;                    /* doom */
357         back->msg(0, FD_READ);
358     }
359 }
360
361 /*
362  *  Print an error message and exit after closing the SSH link.
363  */
364 static void bump(char *fmt, ...)
365 {
366     char str[0x100]; /* Make the size big enough */
367     va_list ap;
368     va_start(ap, fmt);
369     strcpy(str, "Fatal:");
370     vsprintf(str+strlen(str), fmt, ap);
371     va_end(ap);
372     strcat(str, "\n");
373     tell_str(stderr, str);
374
375     if (connection_open) {
376         char ch;
377         back->special(TS_EOF);
378         ssh_scp_recv(&ch, 1);
379     }
380     exit(1);
381 }
382
383 static int get_password(const char *prompt, char *str, int maxlen)
384 {
385     HANDLE hin, hout;
386     DWORD savemode, i;
387
388     if (password) {
389         static int tried_once = 0;
390
391         if (tried_once) {
392             return 0;
393         } else {
394             strncpy(str, password, maxlen);
395             str[maxlen-1] = '\0';
396             tried_once = 1;
397             return 1;
398         }
399     }
400
401     /* GUI Adaptation - Sept 2000 */
402     if (gui_mode) {
403         if (maxlen>0) str[0] = '\0';
404     } else {
405         hin = GetStdHandle(STD_INPUT_HANDLE);
406         hout = GetStdHandle(STD_OUTPUT_HANDLE);
407         if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE)
408             bump("Cannot get standard input/output handles");
409
410         GetConsoleMode(hin, &savemode);
411         SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
412                        ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
413
414         WriteFile(hout, prompt, strlen(prompt), &i, NULL);
415         ReadFile(hin, str, maxlen-1, &i, NULL);
416
417         SetConsoleMode(hin, savemode);
418
419         if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
420         str[i] = '\0';
421
422         WriteFile(hout, "\r\n", 2, &i, NULL);
423     }
424
425     return 1;
426 }
427
428 /*
429  *  Open an SSH connection to user@host and execute cmd.
430  */
431 static void do_cmd(char *host, char *user, char *cmd)
432 {
433     char *err, *realhost;
434
435     if (host == NULL || host[0] == '\0')
436         bump("Empty host name");
437
438     /* Try to load settings for this host */
439     do_defaults(host, &cfg);
440     if (cfg.host[0] == '\0') {
441         /* No settings for this host; use defaults */
442         strncpy(cfg.host, host, sizeof(cfg.host)-1);
443         cfg.host[sizeof(cfg.host)-1] = '\0';
444         cfg.port = 22;
445     }
446
447     /* Set username */
448     if (user != NULL && user[0] != '\0') {
449         strncpy(cfg.username, user, sizeof(cfg.username)-1);
450         cfg.username[sizeof(cfg.username)-1] = '\0';
451     } else if (cfg.username[0] == '\0') {
452         bump("Empty user name");
453     }
454
455     if (cfg.protocol != PROT_SSH)
456         cfg.port = 22;
457
458     if (portnumber)
459         cfg.port = portnumber;
460
461     strncpy(cfg.remote_cmd, cmd, sizeof(cfg.remote_cmd));
462     cfg.remote_cmd[sizeof(cfg.remote_cmd)-1] = '\0';
463     cfg.nopty = TRUE;
464
465     back = &ssh_backend;
466
467     err = back->init(NULL, cfg.host, cfg.port, &realhost);
468     if (err != NULL)
469         bump("ssh_init: %s", err);
470     ssh_scp_init();
471     if (verbose && realhost != NULL)
472         tell_user(stderr, "Connected to %s\n", realhost);
473
474     connection_open = 1;
475 }
476
477 /*
478  *  Update statistic information about current file.
479  */
480 static void print_stats(char *name, unsigned long size, unsigned long done,
481                         time_t start, time_t now)
482 {
483     float ratebs;
484     unsigned long eta;
485     char etastr[10];
486     int pct;
487
488     /* GUI Adaptation - Sept 2000 */
489     if (gui_mode)
490         gui_update_stats(name, size, ((done *100) / size), now-start);
491     else {
492         if (now > start)
493             ratebs = (float) done / (now - start);
494         else
495             ratebs = (float) done;
496
497         if (ratebs < 1.0)
498             eta = size - done;
499         else
500             eta = (unsigned long) ((size - done) / ratebs);
501         sprintf(etastr, "%02ld:%02ld:%02ld",
502                 eta / 3600, (eta % 3600) / 60, eta % 60);
503
504         pct = (int) (100.0 * (float) done / size);
505
506         printf("\r%-25.25s | %10ld kB | %5.1f kB/s | ETA: %8s | %3d%%",
507                name, done / 1024, ratebs / 1024.0,
508                etastr, pct);
509
510         if (done == size)
511             printf("\n");
512     }
513 }
514
515 /*
516  *  Find a colon in str and return a pointer to the colon.
517  *  This is used to separate hostname from filename.
518  */
519 static char * colon(char *str)
520 {
521     /* We ignore a leading colon, since the hostname cannot be
522      empty. We also ignore a colon as second character because
523      of filenames like f:myfile.txt. */
524     if (str[0] == '\0' ||
525         str[0] == ':' ||
526         str[1] == ':')
527         return (NULL);
528     while (*str != '\0' &&
529            *str != ':' &&
530            *str != '/' &&
531            *str != '\\')
532         str++;
533     if (*str == ':')
534         return (str);
535     else
536         return (NULL);
537 }
538
539 /*
540  *  Wait for a response from the other side.
541  *  Return 0 if ok, -1 if error.
542  */
543 static int response(void)
544 {
545     char ch, resp, rbuf[2048];
546     int p;
547
548     if (ssh_scp_recv(&resp, 1) <= 0)
549         bump("Lost connection");
550
551     p = 0;
552     switch (resp) {
553       case 0:           /* ok */
554         return (0);
555       default:
556         rbuf[p++] = resp;
557         /* fallthrough */
558       case 1:           /* error */
559       case 2:           /* fatal error */
560         do {
561             if (ssh_scp_recv(&ch, 1) <= 0)
562                 bump("Protocol error: Lost connection");
563             rbuf[p++] = ch;
564         } while (p < sizeof(rbuf) && ch != '\n');
565         rbuf[p-1] = '\0';
566         if (resp == 1)
567             tell_user(stderr, "%s\n", rbuf);
568         else
569             bump("%s", rbuf);
570         errs++;
571         return (-1);
572     }
573 }
574
575 /*
576  *  Send an error message to the other side and to the screen.
577  *  Increment error counter.
578  */
579 static void run_err(const char *fmt, ...)
580 {
581     char str[2048];
582     va_list ap;
583     va_start(ap, fmt);
584     errs++;
585     strcpy(str, "scp: ");
586     vsprintf(str+strlen(str), fmt, ap);
587     strcat(str, "\n");
588     back->send(str, strlen(str));
589     tell_user(stderr, "%s",str);
590     va_end(ap);
591 }
592
593 /*
594  *  Execute the source part of the SCP protocol.
595  */
596 static void source(char *src)
597 {
598     char buf[2048];
599     unsigned long size;
600     char *last;
601     HANDLE f;
602     DWORD attr;
603     unsigned long i;
604     unsigned long stat_bytes;
605     time_t stat_starttime, stat_lasttime;
606
607     attr = GetFileAttributes(src);
608     if (attr == (DWORD)-1) {
609         run_err("%s: No such file or directory", src);
610         return;
611     }
612
613     if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
614         if (recursive) {
615             /*
616              * Avoid . and .. directories.
617              */
618             char *p;
619             p = strrchr(src, '/');
620             if (!p)
621                 p = strrchr(src, '\\');
622             if (!p)
623                 p = src;
624             else
625                 p++;
626             if (!strcmp(p, ".") || !strcmp(p, ".."))
627                 /* skip . and .. */;
628             else
629                 rsource(src);
630         } else {
631             run_err("%s: not a regular file", src);
632         }
633         return;
634     }
635
636     if ((last = strrchr(src, '/')) == NULL)
637         last = src;
638     else
639         last++;
640     if (strrchr(last, '\\') != NULL)
641         last = strrchr(last, '\\') + 1;
642     if (last == src && strchr(src, ':') != NULL)
643         last = strchr(src, ':') + 1;
644
645     f = CreateFile(src, GENERIC_READ, FILE_SHARE_READ, NULL,
646                    OPEN_EXISTING, 0, 0);
647     if (f == INVALID_HANDLE_VALUE) {
648         run_err("%s: Cannot open file", src);
649         return;
650     }
651
652     if (preserve) {
653         FILETIME actime, wrtime;
654         unsigned long mtime, atime;
655         GetFileTime(f, NULL, &actime, &wrtime);
656         TIME_WIN_TO_POSIX(actime, atime);
657         TIME_WIN_TO_POSIX(wrtime, mtime);
658         sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
659         back->send(buf, strlen(buf));
660         if (response())
661             return;
662     }
663
664     size = GetFileSize(f, NULL);
665     sprintf(buf, "C0644 %lu %s\n", size, last);
666     if (verbose)
667         tell_user(stderr, "Sending file modes: %s", buf);
668     back->send(buf, strlen(buf));
669     if (response())
670         return;
671
672     if (statistics) {
673         stat_bytes = 0;
674         stat_starttime = time(NULL);
675         stat_lasttime = 0;
676     }
677
678     for (i = 0; i < size; i += 4096) {
679         char transbuf[4096];
680         DWORD j, k = 4096;
681         if (i + k > size) k = size - i;
682         if (! ReadFile(f, transbuf, k, &j, NULL) || j != k) {
683             if (statistics) printf("\n");
684             bump("%s: Read error", src);
685         }
686         back->send(transbuf, k);
687         if (statistics) {
688             stat_bytes += k;
689             if (time(NULL) != stat_lasttime ||
690                 i + k == size) {
691                 stat_lasttime = time(NULL);
692                 print_stats(last, size, stat_bytes,
693                             stat_starttime, stat_lasttime);
694             }
695         }
696     }
697     CloseHandle(f);
698
699     back->send("", 1);
700     (void) response();
701 }
702
703 /*
704  *  Recursively send the contents of a directory.
705  */
706 static void rsource(char *src)
707 {
708     char buf[2048];
709     char *last;
710     HANDLE dir;
711     WIN32_FIND_DATA fdat;
712     int ok;
713
714     if ((last = strrchr(src, '/')) == NULL)
715         last = src;
716     else
717         last++;
718     if (strrchr(last, '\\') != NULL)
719         last = strrchr(last, '\\') + 1;
720     if (last == src && strchr(src, ':') != NULL)
721         last = strchr(src, ':') + 1;
722
723     /* maybe send filetime */
724
725     sprintf(buf, "D0755 0 %s\n", last);
726     if (verbose)
727         tell_user(stderr, "Entering directory: %s", buf);
728     back->send(buf, strlen(buf));
729     if (response())
730         return;
731
732     sprintf(buf, "%s/*", src);
733     dir = FindFirstFile(buf, &fdat);
734     ok = (dir != INVALID_HANDLE_VALUE);
735     while (ok) {
736         if (strcmp(fdat.cFileName, ".") == 0 ||
737             strcmp(fdat.cFileName, "..") == 0) {
738         } else if (strlen(src) + 1 + strlen(fdat.cFileName) >=
739                    sizeof(buf)) {
740             run_err("%s/%s: Name too long", src, fdat.cFileName);
741         } else {
742             sprintf(buf, "%s/%s", src, fdat.cFileName);
743             source(buf);
744         }
745         ok = FindNextFile(dir, &fdat);
746     }
747     FindClose(dir);
748
749     sprintf(buf, "E\n");
750     back->send(buf, strlen(buf));
751     (void) response();
752 }
753
754 /*
755  *  Execute the sink part of the SCP protocol.
756  */
757 static void sink(char *targ, char *src)
758 {
759     char buf[2048];
760     char namebuf[2048];
761     char ch;
762     int targisdir = 0;
763     int settime;
764     int exists;
765     DWORD attr;
766     HANDLE f;
767     unsigned long mtime, atime;
768     unsigned int mode;
769     unsigned long size, i;
770     int wrerror = 0;
771     unsigned long stat_bytes;
772     time_t stat_starttime, stat_lasttime;
773     char *stat_name;
774
775     attr = GetFileAttributes(targ);
776     if (attr != (DWORD)-1 && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
777         targisdir = 1;
778
779     if (targetshouldbedirectory && !targisdir)
780         bump("%s: Not a directory", targ);
781
782     back->send("", 1);
783     while (1) {
784         settime = 0;
785         gottime:
786         if (ssh_scp_recv(&ch, 1) <= 0)
787             return;
788         if (ch == '\n')
789             bump("Protocol error: Unexpected newline");
790         i = 0;
791         buf[i++] = ch;
792         do {
793             if (ssh_scp_recv(&ch, 1) <= 0)
794                 bump("Lost connection");
795             buf[i++] = ch;
796         } while (i < sizeof(buf) && ch != '\n');
797         buf[i-1] = '\0';
798         switch (buf[0]) {
799           case '\01':   /* error */
800             tell_user(stderr, "%s\n", buf+1);
801             errs++;
802             continue;
803           case '\02':   /* fatal error */
804             bump("%s", buf+1);
805           case 'E':
806             back->send("", 1);
807             return;
808           case 'T':
809             if (sscanf(buf, "T%ld %*d %ld %*d",
810                        &mtime, &atime) == 2) {
811                 settime = 1;
812                 back->send("", 1);
813                 goto gottime;
814             }
815             bump("Protocol error: Illegal time format");
816           case 'C':
817           case 'D':
818             break;
819           default:
820             bump("Protocol error: Expected control record");
821         }
822
823         if (sscanf(buf+1, "%u %lu %[^\n]", &mode, &size, namebuf) != 3)
824             bump("Protocol error: Illegal file descriptor format");
825         /* Security fix: ensure the file ends up where we asked for it. */
826         if (src) {
827             char *p = src + strlen(src);
828             while (p > src && p[-1] != '/' && p[-1] != '\\')
829                 p--;
830             strcpy(namebuf, p);
831         }
832         if (targisdir) {
833             char t[2048];
834             char *p;
835             strcpy(t, targ);
836             if (targ[0] != '\0')
837                 strcat(t, "/");
838             p = namebuf + strlen(namebuf);
839             while (p > namebuf && p[-1] != '/' && p[-1] != '\\')
840                 p--;
841             strcat(t, p);
842             strcpy(namebuf, t);
843         } else {
844             strcpy(namebuf, targ);
845         }
846         attr = GetFileAttributes(namebuf);
847         exists = (attr != (DWORD)-1);
848
849         if (buf[0] == 'D') {
850             if (exists && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
851                 run_err("%s: Not a directory", namebuf);
852                 continue;
853             }
854             if (!exists) {
855                 if (! CreateDirectory(namebuf, NULL)) {
856                     run_err("%s: Cannot create directory",
857                             namebuf);
858                     continue;
859                 }
860             }
861             sink(namebuf, NULL);
862             /* can we set the timestamp for directories ? */
863             continue;
864         }
865
866         f = CreateFile(namebuf, GENERIC_WRITE, 0, NULL,
867                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
868         if (f == INVALID_HANDLE_VALUE) {
869             run_err("%s: Cannot create file", namebuf);
870             continue;
871         }
872
873         back->send("", 1);
874
875         if (statistics) {
876             stat_bytes = 0;
877             stat_starttime = time(NULL);
878             stat_lasttime = 0;
879             if ((stat_name = strrchr(namebuf, '/')) == NULL)
880                 stat_name = namebuf;
881             else
882                 stat_name++;
883             if (strrchr(stat_name, '\\') != NULL)
884                 stat_name = strrchr(stat_name, '\\') + 1;
885         }
886
887         for (i = 0; i < size; i += 4096) {
888             char transbuf[4096];
889             DWORD j, k = 4096;
890             if (i + k > size) k = size - i;
891             if (ssh_scp_recv(transbuf, k) == 0)
892                 bump("Lost connection");
893             if (wrerror) continue;
894             if (! WriteFile(f, transbuf, k, &j, NULL) || j != k) {
895                 wrerror = 1;
896                 if (statistics)
897                     printf("\r%-25.25s | %50s\n",
898                            stat_name,
899                            "Write error.. waiting for end of file");
900                 continue;
901             }
902             if (statistics) {
903                 stat_bytes += k;
904                 if (time(NULL) > stat_lasttime ||
905                     i + k == size) {
906                     stat_lasttime = time(NULL);
907                     print_stats(stat_name, size, stat_bytes,
908                                 stat_starttime, stat_lasttime);
909                 }
910             }
911         }
912         (void) response();
913
914         if (settime) {
915             FILETIME actime, wrtime;
916             TIME_POSIX_TO_WIN(atime, actime);
917             TIME_POSIX_TO_WIN(mtime, wrtime);
918             SetFileTime(f, NULL, &actime, &wrtime);
919         }
920
921         CloseHandle(f);
922         if (wrerror) {
923             run_err("%s: Write error", namebuf);
924             continue;
925         }
926         back->send("", 1);
927     }
928 }
929
930 /*
931  *  We will copy local files to a remote server.
932  */
933 static void toremote(int argc, char *argv[])
934 {
935     char *src, *targ, *host, *user;
936     char *cmd;
937     int i;
938
939     targ = argv[argc-1];
940
941     /* Separate host from filename */
942     host = targ;
943     targ = colon(targ);
944     if (targ == NULL)
945         bump("targ == NULL in toremote()");
946     *targ++ = '\0';
947     if (*targ == '\0')
948         targ = ".";
949     /* Substitute "." for emtpy target */
950
951     /* Separate host and username */
952     user = host;
953     host = strrchr(host, '@');
954     if (host == NULL) {
955         host = user;
956         user = NULL;
957     } else {
958         *host++ = '\0';
959         if (*user == '\0')
960             user = NULL;
961     }
962
963     if (argc == 2) {
964         /* Find out if the source filespec covers multiple files
965          if so, we should set the targetshouldbedirectory flag */
966         HANDLE fh;
967         WIN32_FIND_DATA fdat;
968         if (colon(argv[0]) != NULL)
969             bump("%s: Remote to remote not supported", argv[0]);
970         fh = FindFirstFile(argv[0], &fdat);
971         if (fh == INVALID_HANDLE_VALUE)
972             bump("%s: No such file or directory\n", argv[0]);
973         if (FindNextFile(fh, &fdat))
974             targetshouldbedirectory = 1;
975         FindClose(fh);
976     }
977
978     cmd = smalloc(strlen(targ) + 100);
979     sprintf(cmd, "scp%s%s%s%s -t %s",
980             verbose ? " -v" : "",
981             recursive ? " -r" : "",
982             preserve ? " -p" : "",
983             targetshouldbedirectory ? " -d" : "",
984             targ);
985     do_cmd(host, user, cmd);
986     sfree(cmd);
987
988     (void) response();
989
990     for (i = 0; i < argc - 1; i++) {
991         HANDLE dir;
992         WIN32_FIND_DATA fdat;
993         src = argv[i];
994         if (colon(src) != NULL) {
995             tell_user(stderr, "%s: Remote to remote not supported\n", src);
996             errs++;
997             continue;
998         }
999         dir = FindFirstFile(src, &fdat);
1000         if (dir == INVALID_HANDLE_VALUE) {
1001             run_err("%s: No such file or directory", src);
1002             continue;
1003         }
1004         do {
1005             char *last;
1006             char namebuf[2048];
1007             if (strlen(src) + strlen(fdat.cFileName) >=
1008                 sizeof(namebuf)) {
1009                 tell_user(stderr, "%s: Name too long", src);
1010                 continue;
1011             }
1012             strcpy(namebuf, src);
1013             if ((last = strrchr(namebuf, '/')) == NULL)
1014                 last = namebuf;
1015             else
1016                 last++;
1017             if (strrchr(last, '\\') != NULL)
1018                 last = strrchr(last, '\\') + 1;
1019             if (last == namebuf && strrchr(namebuf, ':') != NULL)
1020                 last = strchr(namebuf, ':') + 1;
1021             strcpy(last, fdat.cFileName);
1022             source(namebuf);
1023         } while (FindNextFile(dir, &fdat));
1024         FindClose(dir);
1025     }
1026 }
1027
1028 /*
1029  *  We will copy files from a remote server to the local machine.
1030  */
1031 static void tolocal(int argc, char *argv[])
1032 {
1033     char *src, *targ, *host, *user;
1034     char *cmd;
1035
1036     if (argc != 2)
1037         bump("More than one remote source not supported");
1038
1039     src = argv[0];
1040     targ = argv[1];
1041
1042     /* Separate host from filename */
1043     host = src;
1044     src = colon(src);
1045     if (src == NULL)
1046         bump("Local to local copy not supported");
1047     *src++ = '\0';
1048     if (*src == '\0')
1049         src = ".";
1050     /* Substitute "." for empty filename */
1051
1052     /* Separate username and hostname */
1053     user = host;
1054     host = strrchr(host, '@');
1055     if (host == NULL) {
1056         host = user;
1057         user = NULL;
1058     } else {
1059         *host++ = '\0';
1060         if (*user == '\0')
1061             user = NULL;
1062     }
1063
1064     cmd = smalloc(strlen(src) + 100);
1065     sprintf(cmd, "scp%s%s%s%s -f %s",
1066             verbose ? " -v" : "",
1067             recursive ? " -r" : "",
1068             preserve ? " -p" : "",
1069             targetshouldbedirectory ? " -d" : "",
1070             src);
1071     do_cmd(host, user, cmd);
1072     sfree(cmd);
1073
1074     sink(targ, src);
1075 }
1076
1077 /*
1078  *  We will issue a list command to get a remote directory.
1079  */
1080 static void get_dir_list(int argc, char *argv[])
1081 {
1082     char *src, *host, *user;
1083     char *cmd, *p, *q;
1084     char c;
1085
1086     src = argv[0];
1087
1088     /* Separate host from filename */
1089     host = src;
1090     src = colon(src);
1091     if (src == NULL)
1092         bump("Local to local copy not supported");
1093     *src++ = '\0';
1094     if (*src == '\0')
1095         src = ".";
1096     /* Substitute "." for empty filename */
1097
1098     /* Separate username and hostname */
1099     user = host;
1100     host = strrchr(host, '@');
1101     if (host == NULL) {
1102         host = user;
1103         user = NULL;
1104     } else {
1105         *host++ = '\0';
1106         if (*user == '\0')
1107             user = NULL;
1108     }
1109
1110     cmd = smalloc(4*strlen(src) + 100);
1111     strcpy(cmd, "ls -la '");
1112     p = cmd + strlen(cmd);
1113     for (q = src; *q; q++) {
1114         if (*q == '\'') {
1115             *p++ = '\''; *p++ = '\\'; *p++ = '\''; *p++ = '\'';
1116         } else {
1117             *p++ = *q;
1118         }
1119     }
1120     *p++ = '\'';
1121     *p = '\0';
1122
1123     do_cmd(host, user, cmd);
1124     sfree(cmd);
1125
1126     while (ssh_scp_recv(&c, 1) > 0)
1127         tell_char(stdout, c);
1128 }
1129
1130 /*
1131  *  Initialize the Win$ock driver.
1132  */
1133 static void init_winsock(void)
1134 {
1135     WORD winsock_ver;
1136     WSADATA wsadata;
1137
1138     winsock_ver = MAKEWORD(1, 1);
1139     if (WSAStartup(winsock_ver, &wsadata))
1140         bump("Unable to initialise WinSock");
1141     if (LOBYTE(wsadata.wVersion) != 1 ||
1142         HIBYTE(wsadata.wVersion) != 1)
1143         bump("WinSock version is incompatible with 1.1");
1144 }
1145
1146 /*
1147  *  Short description of parameters.
1148  */
1149 static void usage(void)
1150 {
1151     printf("PuTTY Secure Copy client\n");
1152     printf("%s\n", ver);
1153     printf("Usage: pscp [options] [user@]host:source target\n");
1154     printf("       pscp [options] source [source...] [user@]host:target\n");
1155     printf("       pscp [options] -ls user@host:filespec\n");
1156     printf("Options:\n");
1157     printf("  -p        preserve file attributes\n");
1158     printf("  -q        quiet, don't show statistics\n");
1159     printf("  -r        copy directories recursively\n");
1160     printf("  -v        show verbose messages\n");
1161     printf("  -P port   connect to specified port\n");
1162     printf("  -pw passw login with specified password\n");
1163     /* GUI Adaptation - Sept 2000 */
1164     printf("  -gui hWnd GUI mode with the windows handle for receiving messages\n");
1165     exit(1);
1166 }
1167
1168 /*
1169  *  Main program (no, really?)
1170  */
1171 int main(int argc, char *argv[])
1172 {
1173     int i;
1174     int list = 0;
1175
1176     default_protocol = PROT_TELNET;
1177
1178     flags = FLAG_STDERR;
1179     ssh_get_password = &get_password;
1180     init_winsock();
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 (connection_open) {
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 */