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