]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - scp.c
b831fbbfb7d0db0bbe4d0dd4a7e1798b0acce501
[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         do_defaults(NULL, &cfg);
448         strncpy(cfg.host, host, sizeof(cfg.host)-1);
449         cfg.host[sizeof(cfg.host)-1] = '\0';
450         cfg.port = 22;
451     }
452
453     /* Set username */
454     if (user != NULL && user[0] != '\0') {
455         strncpy(cfg.username, user, sizeof(cfg.username)-1);
456         cfg.username[sizeof(cfg.username)-1] = '\0';
457     } else if (cfg.username[0] == '\0') {
458         bump("Empty user name");
459     }
460
461     if (cfg.protocol != PROT_SSH)
462         cfg.port = 22;
463
464     if (portnumber)
465         cfg.port = portnumber;
466
467     strncpy(cfg.remote_cmd, cmd, sizeof(cfg.remote_cmd));
468     cfg.remote_cmd[sizeof(cfg.remote_cmd)-1] = '\0';
469     cfg.nopty = TRUE;
470
471     back = &ssh_backend;
472
473     err = back->init(cfg.host, cfg.port, &realhost);
474     if (err != NULL)
475         bump("ssh_init: %s", err);
476     ssh_scp_init();
477     if (verbose && realhost != NULL)
478         tell_user(stderr, "Connected to %s\n", realhost);
479
480     connection_open = 1;
481 }
482
483 /*
484  *  Update statistic information about current file.
485  */
486 static void print_stats(char *name, unsigned long size, unsigned long done,
487                         time_t start, time_t now)
488 {
489     float ratebs;
490     unsigned long eta;
491     char etastr[10];
492     int pct;
493
494     /* GUI Adaptation - Sept 2000 */
495     if (gui_mode)
496         gui_update_stats(name, size, ((done *100) / size), now-start);
497     else {
498         if (now > start)
499             ratebs = (float) done / (now - start);
500         else
501             ratebs = (float) done;
502
503         if (ratebs < 1.0)
504             eta = size - done;
505         else
506             eta = (unsigned long) ((size - done) / ratebs);
507         sprintf(etastr, "%02ld:%02ld:%02ld",
508                 eta / 3600, (eta % 3600) / 60, eta % 60);
509
510         pct = (int) (100.0 * (float) done / size);
511
512         printf("\r%-25.25s | %10ld kB | %5.1f kB/s | ETA: %8s | %3d%%",
513                name, done / 1024, ratebs / 1024.0,
514                etastr, pct);
515
516         if (done == size)
517             printf("\n");
518     }
519 }
520
521 /*
522  *  Find a colon in str and return a pointer to the colon.
523  *  This is used to separate hostname from filename.
524  */
525 static char * colon(char *str)
526 {
527     /* We ignore a leading colon, since the hostname cannot be
528      empty. We also ignore a colon as second character because
529      of filenames like f:myfile.txt. */
530     if (str[0] == '\0' ||
531         str[0] == ':' ||
532         str[1] == ':')
533         return (NULL);
534     while (*str != '\0' &&
535            *str != ':' &&
536            *str != '/' &&
537            *str != '\\')
538         str++;
539     if (*str == ':')
540         return (str);
541     else
542         return (NULL);
543 }
544
545 /*
546  *  Wait for a response from the other side.
547  *  Return 0 if ok, -1 if error.
548  */
549 static int response(void)
550 {
551     char ch, resp, rbuf[2048];
552     int p;
553
554     if (ssh_scp_recv(&resp, 1) <= 0)
555         bump("Lost connection");
556
557     p = 0;
558     switch (resp) {
559       case 0:           /* ok */
560         return (0);
561       default:
562         rbuf[p++] = resp;
563         /* fallthrough */
564       case 1:           /* error */
565       case 2:           /* fatal error */
566         do {
567             if (ssh_scp_recv(&ch, 1) <= 0)
568                 bump("Protocol error: Lost connection");
569             rbuf[p++] = ch;
570         } while (p < sizeof(rbuf) && ch != '\n');
571         rbuf[p-1] = '\0';
572         if (resp == 1)
573             tell_user(stderr, "%s\n", rbuf);
574         else
575             bump("%s", rbuf);
576         errs++;
577         return (-1);
578     }
579 }
580
581 /*
582  *  Send an error message to the other side and to the screen.
583  *  Increment error counter.
584  */
585 static void run_err(const char *fmt, ...)
586 {
587     char str[2048];
588     va_list ap;
589     va_start(ap, fmt);
590     errs++;
591     strcpy(str, "scp: ");
592     vsprintf(str+strlen(str), fmt, ap);
593     strcat(str, "\n");
594     back->send(str, strlen(str));
595     tell_user(stderr, "%s",str);
596     va_end(ap);
597 }
598
599 /*
600  *  Execute the source part of the SCP protocol.
601  */
602 static void source(char *src)
603 {
604     char buf[2048];
605     unsigned long size;
606     char *last;
607     HANDLE f;
608     DWORD attr;
609     unsigned long i;
610     unsigned long stat_bytes;
611     time_t stat_starttime, stat_lasttime;
612
613     attr = GetFileAttributes(src);
614     if (attr == (DWORD)-1) {
615         run_err("%s: No such file or directory", src);
616         return;
617     }
618
619     if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
620         if (recursive) {
621             /*
622              * Avoid . and .. directories.
623              */
624             char *p;
625             p = strrchr(src, '/');
626             if (!p)
627                 p = strrchr(src, '\\');
628             if (!p)
629                 p = src;
630             else
631                 p++;
632             if (!strcmp(p, ".") || !strcmp(p, ".."))
633                 /* skip . and .. */;
634             else
635                 rsource(src);
636         } else {
637             run_err("%s: not a regular file", src);
638         }
639         return;
640     }
641
642     if ((last = strrchr(src, '/')) == NULL)
643         last = src;
644     else
645         last++;
646     if (strrchr(last, '\\') != NULL)
647         last = strrchr(last, '\\') + 1;
648     if (last == src && strchr(src, ':') != NULL)
649         last = strchr(src, ':') + 1;
650
651     f = CreateFile(src, GENERIC_READ, FILE_SHARE_READ, NULL,
652                    OPEN_EXISTING, 0, 0);
653     if (f == INVALID_HANDLE_VALUE) {
654         run_err("%s: Cannot open file", src);
655         return;
656     }
657
658     if (preserve) {
659         FILETIME actime, wrtime;
660         unsigned long mtime, atime;
661         GetFileTime(f, NULL, &actime, &wrtime);
662         TIME_WIN_TO_POSIX(actime, atime);
663         TIME_WIN_TO_POSIX(wrtime, mtime);
664         sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
665         back->send(buf, strlen(buf));
666         if (response())
667             return;
668     }
669
670     size = GetFileSize(f, NULL);
671     sprintf(buf, "C0644 %lu %s\n", size, last);
672     if (verbose)
673         tell_user(stderr, "Sending file modes: %s", buf);
674     back->send(buf, strlen(buf));
675     if (response())
676         return;
677
678     if (statistics) {
679         stat_bytes = 0;
680         stat_starttime = time(NULL);
681         stat_lasttime = 0;
682     }
683
684     for (i = 0; i < size; i += 4096) {
685         char transbuf[4096];
686         DWORD j, k = 4096;
687         if (i + k > size) k = size - i;
688         if (! ReadFile(f, transbuf, k, &j, NULL) || j != k) {
689             if (statistics) printf("\n");
690             bump("%s: Read error", src);
691         }
692         back->send(transbuf, k);
693         if (statistics) {
694             stat_bytes += k;
695             if (time(NULL) != stat_lasttime ||
696                 i + k == size) {
697                 stat_lasttime = time(NULL);
698                 print_stats(last, size, stat_bytes,
699                             stat_starttime, stat_lasttime);
700             }
701         }
702     }
703     CloseHandle(f);
704
705     back->send("", 1);
706     (void) response();
707 }
708
709 /*
710  *  Recursively send the contents of a directory.
711  */
712 static void rsource(char *src)
713 {
714     char buf[2048];
715     char *last;
716     HANDLE dir;
717     WIN32_FIND_DATA fdat;
718     int ok;
719
720     if ((last = strrchr(src, '/')) == NULL)
721         last = src;
722     else
723         last++;
724     if (strrchr(last, '\\') != NULL)
725         last = strrchr(last, '\\') + 1;
726     if (last == src && strchr(src, ':') != NULL)
727         last = strchr(src, ':') + 1;
728
729     /* maybe send filetime */
730
731     sprintf(buf, "D0755 0 %s\n", last);
732     if (verbose)
733         tell_user(stderr, "Entering directory: %s", buf);
734     back->send(buf, strlen(buf));
735     if (response())
736         return;
737
738     sprintf(buf, "%s/*", src);
739     dir = FindFirstFile(buf, &fdat);
740     ok = (dir != INVALID_HANDLE_VALUE);
741     while (ok) {
742         if (strcmp(fdat.cFileName, ".") == 0 ||
743             strcmp(fdat.cFileName, "..") == 0) {
744         } else if (strlen(src) + 1 + strlen(fdat.cFileName) >=
745                    sizeof(buf)) {
746             run_err("%s/%s: Name too long", src, fdat.cFileName);
747         } else {
748             sprintf(buf, "%s/%s", src, fdat.cFileName);
749             source(buf);
750         }
751         ok = FindNextFile(dir, &fdat);
752     }
753     FindClose(dir);
754
755     sprintf(buf, "E\n");
756     back->send(buf, strlen(buf));
757     (void) response();
758 }
759
760 /*
761  *  Execute the sink part of the SCP protocol.
762  */
763 static void sink(char *targ, char *src)
764 {
765     char buf[2048];
766     char namebuf[2048];
767     char ch;
768     int targisdir = 0;
769     int settime;
770     int exists;
771     DWORD attr;
772     HANDLE f;
773     unsigned long mtime, atime;
774     unsigned int mode;
775     unsigned long size, i;
776     int wrerror = 0;
777     unsigned long stat_bytes;
778     time_t stat_starttime, stat_lasttime;
779     char *stat_name;
780
781     attr = GetFileAttributes(targ);
782     if (attr != (DWORD)-1 && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
783         targisdir = 1;
784
785     if (targetshouldbedirectory && !targisdir)
786         bump("%s: Not a directory", targ);
787
788     back->send("", 1);
789     while (1) {
790         settime = 0;
791         gottime:
792         if (ssh_scp_recv(&ch, 1) <= 0)
793             return;
794         if (ch == '\n')
795             bump("Protocol error: Unexpected newline");
796         i = 0;
797         buf[i++] = ch;
798         do {
799             if (ssh_scp_recv(&ch, 1) <= 0)
800                 bump("Lost connection");
801             buf[i++] = ch;
802         } while (i < sizeof(buf) && ch != '\n');
803         buf[i-1] = '\0';
804         switch (buf[0]) {
805           case '\01':   /* error */
806             tell_user(stderr, "%s\n", buf+1);
807             errs++;
808             continue;
809           case '\02':   /* fatal error */
810             bump("%s", buf+1);
811           case 'E':
812             back->send("", 1);
813             return;
814           case 'T':
815             if (sscanf(buf, "T%ld %*d %ld %*d",
816                        &mtime, &atime) == 2) {
817                 settime = 1;
818                 back->send("", 1);
819                 goto gottime;
820             }
821             bump("Protocol error: Illegal time format");
822           case 'C':
823           case 'D':
824             break;
825           default:
826             bump("Protocol error: Expected control record");
827         }
828
829         if (sscanf(buf+1, "%u %lu %[^\n]", &mode, &size, namebuf) != 3)
830             bump("Protocol error: Illegal file descriptor format");
831         /* Security fix: ensure the file ends up where we asked for it. */
832         if (src) {
833             char *p = src + strlen(src);
834             while (p > src && p[-1] != '/' && p[-1] != '\\')
835                 p--;
836             strcpy(namebuf, p);
837         }
838         if (targisdir) {
839             char t[2048];
840             char *p;
841             strcpy(t, targ);
842             if (targ[0] != '\0')
843                 strcat(t, "/");
844             p = namebuf + strlen(namebuf);
845             while (p > namebuf && p[-1] != '/' && p[-1] != '\\')
846                 p--;
847             strcat(t, p);
848             strcpy(namebuf, t);
849         } else {
850             strcpy(namebuf, targ);
851         }
852         attr = GetFileAttributes(namebuf);
853         exists = (attr != (DWORD)-1);
854
855         if (buf[0] == 'D') {
856             if (exists && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
857                 run_err("%s: Not a directory", namebuf);
858                 continue;
859             }
860             if (!exists) {
861                 if (! CreateDirectory(namebuf, NULL)) {
862                     run_err("%s: Cannot create directory",
863                             namebuf);
864                     continue;
865                 }
866             }
867             sink(namebuf, NULL);
868             /* can we set the timestamp for directories ? */
869             continue;
870         }
871
872         f = CreateFile(namebuf, GENERIC_WRITE, 0, NULL,
873                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
874         if (f == INVALID_HANDLE_VALUE) {
875             run_err("%s: Cannot create file", namebuf);
876             continue;
877         }
878
879         back->send("", 1);
880
881         if (statistics) {
882             stat_bytes = 0;
883             stat_starttime = time(NULL);
884             stat_lasttime = 0;
885             if ((stat_name = strrchr(namebuf, '/')) == NULL)
886                 stat_name = namebuf;
887             else
888                 stat_name++;
889             if (strrchr(stat_name, '\\') != NULL)
890                 stat_name = strrchr(stat_name, '\\') + 1;
891         }
892
893         for (i = 0; i < size; i += 4096) {
894             char transbuf[4096];
895             DWORD j, k = 4096;
896             if (i + k > size) k = size - i;
897             if (ssh_scp_recv(transbuf, k) == 0)
898                 bump("Lost connection");
899             if (wrerror) continue;
900             if (! WriteFile(f, transbuf, k, &j, NULL) || j != k) {
901                 wrerror = 1;
902                 if (statistics)
903                     printf("\r%-25.25s | %50s\n",
904                            stat_name,
905                            "Write error.. waiting for end of file");
906                 continue;
907             }
908             if (statistics) {
909                 stat_bytes += k;
910                 if (time(NULL) > stat_lasttime ||
911                     i + k == size) {
912                     stat_lasttime = time(NULL);
913                     print_stats(stat_name, size, stat_bytes,
914                                 stat_starttime, stat_lasttime);
915                 }
916             }
917         }
918         (void) response();
919
920         if (settime) {
921             FILETIME actime, wrtime;
922             TIME_POSIX_TO_WIN(atime, actime);
923             TIME_POSIX_TO_WIN(mtime, wrtime);
924             SetFileTime(f, NULL, &actime, &wrtime);
925         }
926
927         CloseHandle(f);
928         if (wrerror) {
929             run_err("%s: Write error", namebuf);
930             continue;
931         }
932         back->send("", 1);
933     }
934 }
935
936 /*
937  *  We will copy local files to a remote server.
938  */
939 static void toremote(int argc, char *argv[])
940 {
941     char *src, *targ, *host, *user;
942     char *cmd;
943     int i;
944
945     targ = argv[argc-1];
946
947     /* Separate host from filename */
948     host = targ;
949     targ = colon(targ);
950     if (targ == NULL)
951         bump("targ == NULL in toremote()");
952     *targ++ = '\0';
953     if (*targ == '\0')
954         targ = ".";
955     /* Substitute "." for emtpy target */
956
957     /* Separate host and username */
958     user = host;
959     host = strrchr(host, '@');
960     if (host == NULL) {
961         host = user;
962         user = NULL;
963     } else {
964         *host++ = '\0';
965         if (*user == '\0')
966             user = NULL;
967     }
968
969     if (argc == 2) {
970         /* Find out if the source filespec covers multiple files
971          if so, we should set the targetshouldbedirectory flag */
972         HANDLE fh;
973         WIN32_FIND_DATA fdat;
974         if (colon(argv[0]) != NULL)
975             bump("%s: Remote to remote not supported", argv[0]);
976         fh = FindFirstFile(argv[0], &fdat);
977         if (fh == INVALID_HANDLE_VALUE)
978             bump("%s: No such file or directory\n", argv[0]);
979         if (FindNextFile(fh, &fdat))
980             targetshouldbedirectory = 1;
981         FindClose(fh);
982     }
983
984     cmd = smalloc(strlen(targ) + 100);
985     sprintf(cmd, "scp%s%s%s%s -t %s",
986             verbose ? " -v" : "",
987             recursive ? " -r" : "",
988             preserve ? " -p" : "",
989             targetshouldbedirectory ? " -d" : "",
990             targ);
991     do_cmd(host, user, cmd);
992     sfree(cmd);
993
994     (void) response();
995
996     for (i = 0; i < argc - 1; i++) {
997         HANDLE dir;
998         WIN32_FIND_DATA fdat;
999         src = argv[i];
1000         if (colon(src) != NULL) {
1001             tell_user(stderr, "%s: Remote to remote not supported\n", src);
1002             errs++;
1003             continue;
1004         }
1005         dir = FindFirstFile(src, &fdat);
1006         if (dir == INVALID_HANDLE_VALUE) {
1007             run_err("%s: No such file or directory", src);
1008             continue;
1009         }
1010         do {
1011             char *last;
1012             char namebuf[2048];
1013             if (strlen(src) + strlen(fdat.cFileName) >=
1014                 sizeof(namebuf)) {
1015                 tell_user(stderr, "%s: Name too long", src);
1016                 continue;
1017             }
1018             strcpy(namebuf, src);
1019             if ((last = strrchr(namebuf, '/')) == NULL)
1020                 last = namebuf;
1021             else
1022                 last++;
1023             if (strrchr(last, '\\') != NULL)
1024                 last = strrchr(last, '\\') + 1;
1025             if (last == namebuf && strrchr(namebuf, ':') != NULL)
1026                 last = strchr(namebuf, ':') + 1;
1027             strcpy(last, fdat.cFileName);
1028             source(namebuf);
1029         } while (FindNextFile(dir, &fdat));
1030         FindClose(dir);
1031     }
1032 }
1033
1034 /*
1035  *  We will copy files from a remote server to the local machine.
1036  */
1037 static void tolocal(int argc, char *argv[])
1038 {
1039     char *src, *targ, *host, *user;
1040     char *cmd;
1041
1042     if (argc != 2)
1043         bump("More than one remote source not supported");
1044
1045     src = argv[0];
1046     targ = argv[1];
1047
1048     /* Separate host from filename */
1049     host = src;
1050     src = colon(src);
1051     if (src == NULL)
1052         bump("Local to local copy not supported");
1053     *src++ = '\0';
1054     if (*src == '\0')
1055         src = ".";
1056     /* Substitute "." for empty filename */
1057
1058     /* Separate username and hostname */
1059     user = host;
1060     host = strrchr(host, '@');
1061     if (host == NULL) {
1062         host = user;
1063         user = NULL;
1064     } else {
1065         *host++ = '\0';
1066         if (*user == '\0')
1067             user = NULL;
1068     }
1069
1070     cmd = smalloc(strlen(src) + 100);
1071     sprintf(cmd, "scp%s%s%s%s -f %s",
1072             verbose ? " -v" : "",
1073             recursive ? " -r" : "",
1074             preserve ? " -p" : "",
1075             targetshouldbedirectory ? " -d" : "",
1076             src);
1077     do_cmd(host, user, cmd);
1078     sfree(cmd);
1079
1080     sink(targ, src);
1081 }
1082
1083 /*
1084  *  We will issue a list command to get a remote directory.
1085  */
1086 static void get_dir_list(int argc, char *argv[])
1087 {
1088     char *src, *host, *user;
1089     char *cmd, *p, *q;
1090     char c;
1091
1092     src = argv[0];
1093
1094     /* Separate host from filename */
1095     host = src;
1096     src = colon(src);
1097     if (src == NULL)
1098         bump("Local to local copy not supported");
1099     *src++ = '\0';
1100     if (*src == '\0')
1101         src = ".";
1102     /* Substitute "." for empty filename */
1103
1104     /* Separate username and hostname */
1105     user = host;
1106     host = strrchr(host, '@');
1107     if (host == NULL) {
1108         host = user;
1109         user = NULL;
1110     } else {
1111         *host++ = '\0';
1112         if (*user == '\0')
1113             user = NULL;
1114     }
1115
1116     cmd = smalloc(4*strlen(src) + 100);
1117     strcpy(cmd, "ls -la '");
1118     p = cmd + strlen(cmd);
1119     for (q = src; *q; q++) {
1120         if (*q == '\'') {
1121             *p++ = '\''; *p++ = '\\'; *p++ = '\''; *p++ = '\'';
1122         } else {
1123             *p++ = *q;
1124         }
1125     }
1126     *p++ = '\'';
1127     *p = '\0';
1128
1129     do_cmd(host, user, cmd);
1130     sfree(cmd);
1131
1132     while (ssh_scp_recv(&c, 1) > 0)
1133         tell_char(stdout, c);
1134 }
1135
1136 /*
1137  *  Initialize the Win$ock driver.
1138  */
1139 static void init_winsock(void)
1140 {
1141     WORD winsock_ver;
1142     WSADATA wsadata;
1143
1144     winsock_ver = MAKEWORD(1, 1);
1145     if (WSAStartup(winsock_ver, &wsadata))
1146         bump("Unable to initialise WinSock");
1147     if (LOBYTE(wsadata.wVersion) != 1 ||
1148         HIBYTE(wsadata.wVersion) != 1)
1149         bump("WinSock version is incompatible with 1.1");
1150 }
1151
1152 /*
1153  *  Short description of parameters.
1154  */
1155 static void usage(void)
1156 {
1157     printf("PuTTY Secure Copy client\n");
1158     printf("%s\n", ver);
1159     printf("Usage: pscp [options] [user@]host:source target\n");
1160     printf("       pscp [options] source [source...] [user@]host:target\n");
1161     printf("       pscp [options] -ls user@host:filespec\n");
1162     printf("Options:\n");
1163     printf("  -p        preserve file attributes\n");
1164     printf("  -q        quiet, don't show statistics\n");
1165     printf("  -r        copy directories recursively\n");
1166     printf("  -v        show verbose messages\n");
1167     printf("  -P port   connect to specified port\n");
1168     printf("  -pw passw login with specified password\n");
1169     /* GUI Adaptation - Sept 2000 */
1170     printf("  -gui hWnd GUI mode with the windows handle for receiving messages\n");
1171     exit(1);
1172 }
1173
1174 /*
1175  *  Main program (no, really?)
1176  */
1177 int main(int argc, char *argv[])
1178 {
1179     int i;
1180     int list = 0;
1181
1182     default_protocol = PROT_TELNET;
1183
1184     flags = FLAG_STDERR;
1185     ssh_get_password = &get_password;
1186     init_winsock();
1187     sk_init();
1188
1189     for (i = 1; i < argc; i++) {
1190         if (argv[i][0] != '-')
1191             break;
1192         if (strcmp(argv[i], "-v") == 0)
1193             verbose = 1, flags |= FLAG_VERBOSE;
1194         else if (strcmp(argv[i], "-r") == 0)
1195             recursive = 1;
1196         else if (strcmp(argv[i], "-p") == 0)
1197             preserve = 1;
1198         else if (strcmp(argv[i], "-q") == 0)
1199             statistics = 0;
1200         else if (strcmp(argv[i], "-h") == 0 ||
1201                  strcmp(argv[i], "-?") == 0)
1202             usage();
1203         else if (strcmp(argv[i], "-P") == 0 && i+1 < argc)
1204             portnumber = atoi(argv[++i]);
1205         else if (strcmp(argv[i], "-pw") == 0 && i+1 < argc)
1206             password = argv[++i];
1207         else if (strcmp(argv[i], "-gui") == 0 && i+1 < argc) {
1208             gui_hwnd = argv[++i];
1209             gui_mode = 1;
1210         } else if (strcmp(argv[i], "-ls") == 0)
1211                 list = 1;
1212         else if (strcmp(argv[i], "--") == 0)
1213         { i++; break; }
1214         else
1215             usage();
1216     }
1217     argc -= i;
1218     argv += i;
1219
1220     if (list) {
1221         if (argc != 1)
1222             usage();
1223         get_dir_list(argc, argv);
1224
1225     } else {
1226
1227         if (argc < 2)
1228             usage();
1229         if (argc > 2)
1230             targetshouldbedirectory = 1;
1231
1232         if (colon(argv[argc-1]) != NULL)
1233             toremote(argc, argv);
1234         else
1235             tolocal(argc, argv);
1236     }
1237
1238     if (connection_open) {
1239         char ch;
1240         back->special(TS_EOF);
1241         ssh_scp_recv(&ch, 1);
1242     }
1243     WSACleanup();
1244     random_save_seed();
1245
1246     /* GUI Adaptation - August 2000 */
1247     if (gui_mode) {
1248         unsigned int msg_id = WM_RET_ERR_CNT;
1249         if (list) msg_id = WM_LS_RET_ERR_CNT;
1250         while (!PostMessage( (HWND)atoi(gui_hwnd), msg_id, (WPARAM)errs, 0/*lParam*/ ) )
1251             SleepEx(1000,TRUE);
1252     }
1253     return (errs == 0 ? 0 : 1);
1254 }
1255
1256 /* end */