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