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