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