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