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