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