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