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