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