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