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