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