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