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