Run entire source base through GNU indent to tidy up the varying
[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 }
521
522 /*
523 * Update statistic information about current file.
524 */
525 static void print_stats(char *name, unsigned long size, unsigned long done,
526 time_t start, time_t now)
527 {
528 float ratebs;
529 unsigned long eta;
530 char etastr[10];
531 int pct;
532
533 /* GUI Adaptation - Sept 2000 */
534 if (gui_mode)
535 gui_update_stats(name, size, (int) (100 * (done * 1.0 / size)),
536 (unsigned long) difftime(now, start));
537 else {
538 if (now > start)
539 ratebs = (float) done / (now - start);
540 else
541 ratebs = (float) done;
542
543 if (ratebs < 1.0)
544 eta = size - done;
545 else
546 eta = (unsigned long) ((size - done) / ratebs);
547 sprintf(etastr, "%02ld:%02ld:%02ld",
548 eta / 3600, (eta % 3600) / 60, eta % 60);
549
550 pct = (int) (100.0 * (float) done / size);
551
552 printf("\r%-25.25s | %10ld kB | %5.1f kB/s | ETA: %8s | %3d%%",
553 name, done / 1024, ratebs / 1024.0, etastr, pct);
554
555 if (done == size)
556 printf("\n");
557 }
558 }
559
560 /*
561 * Find a colon in str and return a pointer to the colon.
562 * This is used to separate hostname from filename.
563 */
564 static char *colon(char *str)
565 {
566 /* We ignore a leading colon, since the hostname cannot be
567 empty. We also ignore a colon as second character because
568 of filenames like f:myfile.txt. */
569 if (str[0] == '\0' || str[0] == ':' || str[1] == ':')
570 return (NULL);
571 while (*str != '\0' && *str != ':' && *str != '/' && *str != '\\')
572 str++;
573 if (*str == ':')
574 return (str);
575 else
576 return (NULL);
577 }
578
579 /*
580 * Wait for a response from the other side.
581 * Return 0 if ok, -1 if error.
582 */
583 static int response(void)
584 {
585 char ch, resp, rbuf[2048];
586 int p;
587
588 if (ssh_scp_recv(&resp, 1) <= 0)
589 bump("Lost connection");
590
591 p = 0;
592 switch (resp) {
593 case 0: /* ok */
594 return (0);
595 default:
596 rbuf[p++] = resp;
597 /* fallthrough */
598 case 1: /* error */
599 case 2: /* fatal error */
600 do {
601 if (ssh_scp_recv(&ch, 1) <= 0)
602 bump("Protocol error: Lost connection");
603 rbuf[p++] = ch;
604 } while (p < sizeof(rbuf) && ch != '\n');
605 rbuf[p - 1] = '\0';
606 if (resp == 1)
607 tell_user(stderr, "%s\n", rbuf);
608 else
609 bump("%s", rbuf);
610 errs++;
611 return (-1);
612 }
613 }
614
615 /*
616 * Send an error message to the other side and to the screen.
617 * Increment error counter.
618 */
619 static void run_err(const char *fmt, ...)
620 {
621 char str[2048];
622 va_list ap;
623 va_start(ap, fmt);
624 errs++;
625 strcpy(str, "scp: ");
626 vsprintf(str + strlen(str), fmt, ap);
627 strcat(str, "\n");
628 back->send("\001", 1); /* scp protocol error prefix */
629 back->send(str, strlen(str));
630 tell_user(stderr, "%s", str);
631 va_end(ap);
632 }
633
634 /*
635 * Execute the source part of the SCP protocol.
636 */
637 static void source(char *src)
638 {
639 char buf[2048];
640 unsigned long size;
641 char *last;
642 HANDLE f;
643 DWORD attr;
644 unsigned long i;
645 unsigned long stat_bytes;
646 time_t stat_starttime, stat_lasttime;
647
648 attr = GetFileAttributes(src);
649 if (attr == (DWORD) - 1) {
650 run_err("%s: No such file or directory", src);
651 return;
652 }
653
654 if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
655 if (recursive) {
656 /*
657 * Avoid . and .. directories.
658 */
659 char *p;
660 p = strrchr(src, '/');
661 if (!p)
662 p = strrchr(src, '\\');
663 if (!p)
664 p = src;
665 else
666 p++;
667 if (!strcmp(p, ".") || !strcmp(p, ".."))
668 /* skip . and .. */ ;
669 else
670 rsource(src);
671 } else {
672 run_err("%s: not a regular file", src);
673 }
674 return;
675 }
676
677 if ((last = strrchr(src, '/')) == NULL)
678 last = src;
679 else
680 last++;
681 if (strrchr(last, '\\') != NULL)
682 last = strrchr(last, '\\') + 1;
683 if (last == src && strchr(src, ':') != NULL)
684 last = strchr(src, ':') + 1;
685
686 f = CreateFile(src, GENERIC_READ, FILE_SHARE_READ, NULL,
687 OPEN_EXISTING, 0, 0);
688 if (f == INVALID_HANDLE_VALUE) {
689 run_err("%s: Cannot open file", src);
690 return;
691 }
692
693 if (preserve) {
694 FILETIME actime, wrtime;
695 unsigned long mtime, atime;
696 GetFileTime(f, NULL, &actime, &wrtime);
697 TIME_WIN_TO_POSIX(actime, atime);
698 TIME_WIN_TO_POSIX(wrtime, mtime);
699 sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
700 back->send(buf, strlen(buf));
701 if (response())
702 return;
703 }
704
705 size = GetFileSize(f, NULL);
706 sprintf(buf, "C0644 %lu %s\n", size, last);
707 if (verbose)
708 tell_user(stderr, "Sending file modes: %s", buf);
709 back->send(buf, strlen(buf));
710 if (response())
711 return;
712
713 if (statistics) {
714 stat_bytes = 0;
715 stat_starttime = time(NULL);
716 stat_lasttime = 0;
717 }
718
719 for (i = 0; i < size; i += 4096) {
720 char transbuf[4096];
721 DWORD j, k = 4096;
722 if (i + k > size)
723 k = size - i;
724 if (!ReadFile(f, transbuf, k, &j, NULL) || j != k) {
725 if (statistics)
726 printf("\n");
727 bump("%s: Read error", src);
728 }
729 back->send(transbuf, k);
730 if (statistics) {
731 stat_bytes += k;
732 if (time(NULL) != stat_lasttime || i + k == size) {
733 stat_lasttime = time(NULL);
734 print_stats(last, size, stat_bytes,
735 stat_starttime, stat_lasttime);
736 }
737 }
738 }
739 CloseHandle(f);
740
741 back->send("", 1);
742 (void) response();
743 }
744
745 /*
746 * Recursively send the contents of a directory.
747 */
748 static void rsource(char *src)
749 {
750 char buf[2048];
751 char *last;
752 HANDLE dir;
753 WIN32_FIND_DATA fdat;
754 int ok;
755
756 if ((last = strrchr(src, '/')) == NULL)
757 last = src;
758 else
759 last++;
760 if (strrchr(last, '\\') != NULL)
761 last = strrchr(last, '\\') + 1;
762 if (last == src && strchr(src, ':') != NULL)
763 last = strchr(src, ':') + 1;
764
765 /* maybe send filetime */
766
767 sprintf(buf, "D0755 0 %s\n", last);
768 if (verbose)
769 tell_user(stderr, "Entering directory: %s", buf);
770 back->send(buf, strlen(buf));
771 if (response())
772 return;
773
774 sprintf(buf, "%s/*", src);
775 dir = FindFirstFile(buf, &fdat);
776 ok = (dir != INVALID_HANDLE_VALUE);
777 while (ok) {
778 if (strcmp(fdat.cFileName, ".") == 0 ||
779 strcmp(fdat.cFileName, "..") == 0) {
780 } else if (strlen(src) + 1 + strlen(fdat.cFileName) >= sizeof(buf)) {
781 run_err("%s/%s: Name too long", src, fdat.cFileName);
782 } else {
783 sprintf(buf, "%s/%s", src, fdat.cFileName);
784 source(buf);
785 }
786 ok = FindNextFile(dir, &fdat);
787 }
788 FindClose(dir);
789
790 sprintf(buf, "E\n");
791 back->send(buf, strlen(buf));
792 (void) response();
793 }
794
795 /*
796 * Execute the sink part of the SCP protocol.
797 */
798 static void sink(char *targ, char *src)
799 {
800 char buf[2048];
801 char namebuf[2048];
802 char ch;
803 int targisdir = 0;
804 int settime;
805 int exists;
806 DWORD attr;
807 HANDLE f;
808 unsigned long mtime, atime;
809 unsigned int mode;
810 unsigned long size, i;
811 int wrerror = 0;
812 unsigned long stat_bytes;
813 time_t stat_starttime, stat_lasttime;
814 char *stat_name;
815
816 attr = GetFileAttributes(targ);
817 if (attr != (DWORD) - 1 && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
818 targisdir = 1;
819
820 if (targetshouldbedirectory && !targisdir)
821 bump("%s: Not a directory", targ);
822
823 back->send("", 1);
824 while (1) {
825 settime = 0;
826 gottime:
827 if (ssh_scp_recv(&ch, 1) <= 0)
828 return;
829 if (ch == '\n')
830 bump("Protocol error: Unexpected newline");
831 i = 0;
832 buf[i++] = ch;
833 do {
834 if (ssh_scp_recv(&ch, 1) <= 0)
835 bump("Lost connection");
836 buf[i++] = ch;
837 } while (i < sizeof(buf) && ch != '\n');
838 buf[i - 1] = '\0';
839 switch (buf[0]) {
840 case '\01': /* error */
841 tell_user(stderr, "%s\n", buf + 1);
842 errs++;
843 continue;
844 case '\02': /* fatal error */
845 bump("%s", buf + 1);
846 case 'E':
847 back->send("", 1);
848 return;
849 case 'T':
850 if (sscanf(buf, "T%ld %*d %ld %*d", &mtime, &atime) == 2) {
851 settime = 1;
852 back->send("", 1);
853 goto gottime;
854 }
855 bump("Protocol error: Illegal time format");
856 case 'C':
857 case 'D':
858 break;
859 default:
860 bump("Protocol error: Expected control record");
861 }
862
863 if (sscanf(buf + 1, "%u %lu %[^\n]", &mode, &size, namebuf) != 3)
864 bump("Protocol error: Illegal file descriptor format");
865 /* Security fix: ensure the file ends up where we asked for it. */
866 if (targisdir) {
867 char t[2048];
868 char *p;
869 strcpy(t, targ);
870 if (targ[0] != '\0')
871 strcat(t, "/");
872 p = namebuf + strlen(namebuf);
873 while (p > namebuf && p[-1] != '/' && p[-1] != '\\')
874 p--;
875 strcat(t, p);
876 strcpy(namebuf, t);
877 } else {
878 strcpy(namebuf, targ);
879 }
880 attr = GetFileAttributes(namebuf);
881 exists = (attr != (DWORD) - 1);
882
883 if (buf[0] == 'D') {
884 if (exists && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
885 run_err("%s: Not a directory", namebuf);
886 continue;
887 }
888 if (!exists) {
889 if (!CreateDirectory(namebuf, NULL)) {
890 run_err("%s: Cannot create directory", namebuf);
891 continue;
892 }
893 }
894 sink(namebuf, NULL);
895 /* can we set the timestamp for directories ? */
896 continue;
897 }
898
899 f = CreateFile(namebuf, GENERIC_WRITE, 0, NULL,
900 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
901 if (f == INVALID_HANDLE_VALUE) {
902 run_err("%s: Cannot create file", namebuf);
903 continue;
904 }
905
906 back->send("", 1);
907
908 if (statistics) {
909 stat_bytes = 0;
910 stat_starttime = time(NULL);
911 stat_lasttime = 0;
912 if ((stat_name = strrchr(namebuf, '/')) == NULL)
913 stat_name = namebuf;
914 else
915 stat_name++;
916 if (strrchr(stat_name, '\\') != NULL)
917 stat_name = strrchr(stat_name, '\\') + 1;
918 }
919
920 for (i = 0; i < size; i += 4096) {
921 char transbuf[4096];
922 DWORD j, k = 4096;
923 if (i + k > size)
924 k = size - i;
925 if (ssh_scp_recv(transbuf, k) == 0)
926 bump("Lost connection");
927 if (wrerror)
928 continue;
929 if (!WriteFile(f, transbuf, k, &j, NULL) || j != k) {
930 wrerror = 1;
931 if (statistics)
932 printf("\r%-25.25s | %50s\n",
933 stat_name,
934 "Write error.. waiting for end of file");
935 continue;
936 }
937 if (statistics) {
938 stat_bytes += k;
939 if (time(NULL) > stat_lasttime || i + k == size) {
940 stat_lasttime = time(NULL);
941 print_stats(stat_name, size, stat_bytes,
942 stat_starttime, stat_lasttime);
943 }
944 }
945 }
946 (void) response();
947
948 if (settime) {
949 FILETIME actime, wrtime;
950 TIME_POSIX_TO_WIN(atime, actime);
951 TIME_POSIX_TO_WIN(mtime, wrtime);
952 SetFileTime(f, NULL, &actime, &wrtime);
953 }
954
955 CloseHandle(f);
956 if (wrerror) {
957 run_err("%s: Write error", namebuf);
958 continue;
959 }
960 back->send("", 1);
961 }
962 }
963
964 /*
965 * We will copy local files to a remote server.
966 */
967 static void toremote(int argc, char *argv[])
968 {
969 char *src, *targ, *host, *user;
970 char *cmd;
971 int i;
972
973 targ = argv[argc - 1];
974
975 /* Separate host from filename */
976 host = targ;
977 targ = colon(targ);
978 if (targ == NULL)
979 bump("targ == NULL in toremote()");
980 *targ++ = '\0';
981 if (*targ == '\0')
982 targ = ".";
983 /* Substitute "." for emtpy target */
984
985 /* Separate host and username */
986 user = host;
987 host = strrchr(host, '@');
988 if (host == NULL) {
989 host = user;
990 user = NULL;
991 } else {
992 *host++ = '\0';
993 if (*user == '\0')
994 user = NULL;
995 }
996
997 if (argc == 2) {
998 /* Find out if the source filespec covers multiple files
999 if so, we should set the targetshouldbedirectory flag */
1000 HANDLE fh;
1001 WIN32_FIND_DATA fdat;
1002 if (colon(argv[0]) != NULL)
1003 bump("%s: Remote to remote not supported", argv[0]);
1004 fh = FindFirstFile(argv[0], &fdat);
1005 if (fh == INVALID_HANDLE_VALUE)
1006 bump("%s: No such file or directory\n", argv[0]);
1007 if (FindNextFile(fh, &fdat))
1008 targetshouldbedirectory = 1;
1009 FindClose(fh);
1010 }
1011
1012 cmd = smalloc(strlen(targ) + 100);
1013 sprintf(cmd, "scp%s%s%s%s -t %s",
1014 verbose ? " -v" : "",
1015 recursive ? " -r" : "",
1016 preserve ? " -p" : "",
1017 targetshouldbedirectory ? " -d" : "", targ);
1018 do_cmd(host, user, cmd);
1019 sfree(cmd);
1020
1021 (void) response();
1022
1023 for (i = 0; i < argc - 1; i++) {
1024 HANDLE dir;
1025 WIN32_FIND_DATA fdat;
1026 src = argv[i];
1027 if (colon(src) != NULL) {
1028 tell_user(stderr, "%s: Remote to remote not supported\n", src);
1029 errs++;
1030 continue;
1031 }
1032 dir = FindFirstFile(src, &fdat);
1033 if (dir == INVALID_HANDLE_VALUE) {
1034 run_err("%s: No such file or directory", src);
1035 continue;
1036 }
1037 do {
1038 char *last;
1039 char namebuf[2048];
1040 /*
1041 * Ensure that . and .. are never matched by wildcards,
1042 * but only by deliberate action.
1043 */
1044 if (!strcmp(fdat.cFileName, ".") ||
1045 !strcmp(fdat.cFileName, "..")) {
1046 /*
1047 * Find*File has returned a special dir. We require
1048 * that _either_ `src' ends in a backslash followed
1049 * by that string, _or_ `src' is precisely that
1050 * string.
1051 */
1052 int len = strlen(src), dlen = strlen(fdat.cFileName);
1053 if (len == dlen && !strcmp(src, fdat.cFileName)) {
1054 /* ok */ ;
1055 } else if (len > dlen + 1 && src[len - dlen - 1] == '\\' &&
1056 !strcmp(src + len - dlen, fdat.cFileName)) {
1057 /* ok */ ;
1058 } else
1059 continue; /* ignore this one */
1060 }
1061 if (strlen(src) + strlen(fdat.cFileName) >= sizeof(namebuf)) {
1062 tell_user(stderr, "%s: Name too long", src);
1063 continue;
1064 }
1065 strcpy(namebuf, src);
1066 if ((last = strrchr(namebuf, '/')) == NULL)
1067 last = namebuf;
1068 else
1069 last++;
1070 if (strrchr(last, '\\') != NULL)
1071 last = strrchr(last, '\\') + 1;
1072 if (last == namebuf && strrchr(namebuf, ':') != NULL)
1073 last = strchr(namebuf, ':') + 1;
1074 strcpy(last, fdat.cFileName);
1075 source(namebuf);
1076 } while (FindNextFile(dir, &fdat));
1077 FindClose(dir);
1078 }
1079 }
1080
1081 /*
1082 * We will copy files from a remote server to the local machine.
1083 */
1084 static void tolocal(int argc, char *argv[])
1085 {
1086 char *src, *targ, *host, *user;
1087 char *cmd;
1088
1089 if (argc != 2)
1090 bump("More than one remote source not supported");
1091
1092 src = argv[0];
1093 targ = argv[1];
1094
1095 /* Separate host from filename */
1096 host = src;
1097 src = colon(src);
1098 if (src == NULL)
1099 bump("Local to local copy not supported");
1100 *src++ = '\0';
1101 if (*src == '\0')
1102 src = ".";
1103 /* Substitute "." for empty filename */
1104
1105 /* Separate username and hostname */
1106 user = host;
1107 host = strrchr(host, '@');
1108 if (host == NULL) {
1109 host = user;
1110 user = NULL;
1111 } else {
1112 *host++ = '\0';
1113 if (*user == '\0')
1114 user = NULL;
1115 }
1116
1117 cmd = smalloc(strlen(src) + 100);
1118 sprintf(cmd, "scp%s%s%s%s -f %s",
1119 verbose ? " -v" : "",
1120 recursive ? " -r" : "",
1121 preserve ? " -p" : "",
1122 targetshouldbedirectory ? " -d" : "", src);
1123 do_cmd(host, user, cmd);
1124 sfree(cmd);
1125
1126 sink(targ, src);
1127 }
1128
1129 /*
1130 * We will issue a list command to get a remote directory.
1131 */
1132 static void get_dir_list(int argc, char *argv[])
1133 {
1134 char *src, *host, *user;
1135 char *cmd, *p, *q;
1136 char c;
1137
1138 src = argv[0];
1139
1140 /* Separate host from filename */
1141 host = src;
1142 src = colon(src);
1143 if (src == NULL)
1144 bump("Local to local copy not supported");
1145 *src++ = '\0';
1146 if (*src == '\0')
1147 src = ".";
1148 /* Substitute "." for empty filename */
1149
1150 /* Separate username and hostname */
1151 user = host;
1152 host = strrchr(host, '@');
1153 if (host == NULL) {
1154 host = user;
1155 user = NULL;
1156 } else {
1157 *host++ = '\0';
1158 if (*user == '\0')
1159 user = NULL;
1160 }
1161
1162 cmd = smalloc(4 * strlen(src) + 100);
1163 strcpy(cmd, "ls -la '");
1164 p = cmd + strlen(cmd);
1165 for (q = src; *q; q++) {
1166 if (*q == '\'') {
1167 *p++ = '\'';
1168 *p++ = '\\';
1169 *p++ = '\'';
1170 *p++ = '\'';
1171 } else {
1172 *p++ = *q;
1173 }
1174 }
1175 *p++ = '\'';
1176 *p = '\0';
1177
1178 do_cmd(host, user, cmd);
1179 sfree(cmd);
1180
1181 while (ssh_scp_recv(&c, 1) > 0)
1182 tell_char(stdout, c);
1183 }
1184
1185 /*
1186 * Initialize the Win$ock driver.
1187 */
1188 static void init_winsock(void)
1189 {
1190 WORD winsock_ver;
1191 WSADATA wsadata;
1192
1193 winsock_ver = MAKEWORD(1, 1);
1194 if (WSAStartup(winsock_ver, &wsadata))
1195 bump("Unable to initialise WinSock");
1196 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1)
1197 bump("WinSock version is incompatible with 1.1");
1198 }
1199
1200 /*
1201 * Short description of parameters.
1202 */
1203 static void usage(void)
1204 {
1205 printf("PuTTY Secure Copy client\n");
1206 printf("%s\n", ver);
1207 printf("Usage: pscp [options] [user@]host:source target\n");
1208 printf
1209 (" pscp [options] source [source...] [user@]host:target\n");
1210 printf(" pscp [options] -ls user@host:filespec\n");
1211 printf("Options:\n");
1212 printf(" -p preserve file attributes\n");
1213 printf(" -q quiet, don't show statistics\n");
1214 printf(" -r copy directories recursively\n");
1215 printf(" -v show verbose messages\n");
1216 printf(" -P port connect to specified port\n");
1217 printf(" -pw passw login with specified password\n");
1218 #if 0
1219 /*
1220 * -gui is an internal option, used by GUI front ends to get
1221 * pscp to pass progress reports back to them. It's not an
1222 * ordinary user-accessible option, so it shouldn't be part of
1223 * the command-line help. The only people who need to know
1224 * about it are programmers, and they can read the source.
1225 */
1226 printf
1227 (" -gui hWnd GUI mode with the windows handle for receiving messages\n");
1228 #endif
1229 exit(1);
1230 }
1231
1232 /*
1233 * Main program (no, really?)
1234 */
1235 int main(int argc, char *argv[])
1236 {
1237 int i;
1238 int list = 0;
1239
1240 default_protocol = PROT_TELNET;
1241
1242 flags = FLAG_STDERR;
1243 ssh_get_line = &get_line;
1244 init_winsock();
1245 sk_init();
1246
1247 for (i = 1; i < argc; i++) {
1248 if (argv[i][0] != '-')
1249 break;
1250 if (strcmp(argv[i], "-v") == 0)
1251 verbose = 1, flags |= FLAG_VERBOSE;
1252 else if (strcmp(argv[i], "-r") == 0)
1253 recursive = 1;
1254 else if (strcmp(argv[i], "-p") == 0)
1255 preserve = 1;
1256 else if (strcmp(argv[i], "-q") == 0)
1257 statistics = 0;
1258 else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-?") == 0)
1259 usage();
1260 else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc)
1261 portnumber = atoi(argv[++i]);
1262 else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc)
1263 password = argv[++i];
1264 else if (strcmp(argv[i], "-gui") == 0 && i + 1 < argc) {
1265 gui_hwnd = argv[++i];
1266 gui_mode = 1;
1267 } else if (strcmp(argv[i], "-ls") == 0)
1268 list = 1;
1269 else if (strcmp(argv[i], "--") == 0) {
1270 i++;
1271 break;
1272 } else
1273 usage();
1274 }
1275 argc -= i;
1276 argv += i;
1277 back = NULL;
1278
1279 if (list) {
1280 if (argc != 1)
1281 usage();
1282 get_dir_list(argc, argv);
1283
1284 } else {
1285
1286 if (argc < 2)
1287 usage();
1288 if (argc > 2)
1289 targetshouldbedirectory = 1;
1290
1291 if (colon(argv[argc - 1]) != NULL)
1292 toremote(argc, argv);
1293 else
1294 tolocal(argc, argv);
1295 }
1296
1297 if (back != NULL && back->socket() != NULL) {
1298 char ch;
1299 back->special(TS_EOF);
1300 ssh_scp_recv(&ch, 1);
1301 }
1302 WSACleanup();
1303 random_save_seed();
1304
1305 /* GUI Adaptation - August 2000 */
1306 if (gui_mode) {
1307 unsigned int msg_id = WM_RET_ERR_CNT;
1308 if (list)
1309 msg_id = WM_LS_RET_ERR_CNT;
1310 while (!PostMessage
1311 ((HWND) atoi(gui_hwnd), msg_id, (WPARAM) errs,
1312 0 /*lParam */ ))SleepEx(1000, TRUE);
1313 }
1314 return (errs == 0 ? 0 : 1);
1315 }
1316
1317 /* end */