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