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