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