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