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