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