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