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