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