Apply improved WinSock error handling to the few situations it still
[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;
cc87246d 56/* GUI Adaptation - Sept 2000 */
57#define NAME_STR_MAX 2048
58static char statname[NAME_STR_MAX+1];
59static unsigned long statsize = 0;
60static int statperct = 0;
90a14a09 61static unsigned long statelapsed = 0;
cc87246d 62static int gui_mode = 0;
63static char *gui_hwnd = NULL;
07d9aa13 64
65static void source(char *src);
66static void rsource(char *src);
ca2d5943 67static void sink(char *targ, char *src);
cc87246d 68/* GUI Adaptation - Sept 2000 */
69static void tell_char(FILE *stream, char c);
70static void tell_str(FILE *stream, char *str);
71static void tell_user(FILE *stream, char *fmt, ...);
72static void send_char_msg(unsigned int msg_id, char c);
73static void send_str_msg(unsigned int msg_id, char *str);
90a14a09 74static void gui_update_stats(char *name, unsigned long size,
75 int percentage, unsigned long 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
90a14a09 186static void gui_update_stats(char *name, unsigned long size, int percentage, unsigned long 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;
dcbde236 302 pending = (pending ? srealloc(pending, pendsize) :
303 smalloc(pendsize));
3bdaf79d 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;
dcbde236 330 sfree(pending);
3bdaf79d 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
eba78553 380 if (back != NULL && back->socket() != NULL) {
c51a56e2 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);
07d9aa13 479}
480
07d9aa13 481/*
482 * Update statistic information about current file.
483 */
484static void print_stats(char *name, unsigned long size, unsigned long done,
c51a56e2 485 time_t start, time_t now)
07d9aa13 486{
c51a56e2 487 float ratebs;
488 unsigned long eta;
489 char etastr[10];
490 int pct;
491
cc87246d 492 /* GUI Adaptation - Sept 2000 */
493 if (gui_mode)
90a14a09 494 gui_update_stats(name, size, (int)(100 * (done*1.0/size)),
495 (unsigned long)difftime(now, start));
cc87246d 496 else {
497 if (now > start)
498 ratebs = (float) done / (now - start);
499 else
500 ratebs = (float) done;
c51a56e2 501
cc87246d 502 if (ratebs < 1.0)
503 eta = size - done;
504 else
505 eta = (unsigned long) ((size - done) / ratebs);
506 sprintf(etastr, "%02ld:%02ld:%02ld",
507 eta / 3600, (eta % 3600) / 60, eta % 60);
c51a56e2 508
cc87246d 509 pct = (int) (100.0 * (float) done / size);
c51a56e2 510
cc87246d 511 printf("\r%-25.25s | %10ld kB | %5.1f kB/s | ETA: %8s | %3d%%",
512 name, done / 1024, ratebs / 1024.0,
513 etastr, pct);
c51a56e2 514
cc87246d 515 if (done == size)
516 printf("\n");
517 }
07d9aa13 518}
519
07d9aa13 520/*
521 * Find a colon in str and return a pointer to the colon.
39ddf0ff 522 * This is used to separate hostname from filename.
07d9aa13 523 */
524static char * colon(char *str)
525{
c51a56e2 526 /* We ignore a leading colon, since the hostname cannot be
527 empty. We also ignore a colon as second character because
528 of filenames like f:myfile.txt. */
529 if (str[0] == '\0' ||
530 str[0] == ':' ||
531 str[1] == ':')
532 return (NULL);
533 while (*str != '\0' &&
534 *str != ':' &&
535 *str != '/' &&
536 *str != '\\')
537 str++;
538 if (*str == ':')
539 return (str);
540 else
541 return (NULL);
07d9aa13 542}
543
07d9aa13 544/*
545 * Wait for a response from the other side.
546 * Return 0 if ok, -1 if error.
547 */
548static int response(void)
549{
c51a56e2 550 char ch, resp, rbuf[2048];
551 int p;
552
fb09bf1c 553 if (ssh_scp_recv(&resp, 1) <= 0)
c51a56e2 554 bump("Lost connection");
555
556 p = 0;
557 switch (resp) {
558 case 0: /* ok */
559 return (0);
560 default:
561 rbuf[p++] = resp;
562 /* fallthrough */
563 case 1: /* error */
564 case 2: /* fatal error */
565 do {
fb09bf1c 566 if (ssh_scp_recv(&ch, 1) <= 0)
c51a56e2 567 bump("Protocol error: Lost connection");
568 rbuf[p++] = ch;
569 } while (p < sizeof(rbuf) && ch != '\n');
570 rbuf[p-1] = '\0';
571 if (resp == 1)
cc87246d 572 tell_user(stderr, "%s\n", rbuf);
c51a56e2 573 else
574 bump("%s", rbuf);
575 errs++;
576 return (-1);
577 }
07d9aa13 578}
579
07d9aa13 580/*
581 * Send an error message to the other side and to the screen.
582 * Increment error counter.
583 */
584static void run_err(const char *fmt, ...)
585{
c51a56e2 586 char str[2048];
587 va_list ap;
588 va_start(ap, fmt);
589 errs++;
9520eba8 590 strcpy(str, "scp: ");
c51a56e2 591 vsprintf(str+strlen(str), fmt, ap);
592 strcat(str, "\n");
3bdaf79d 593 back->send(str, strlen(str));
cc87246d 594 tell_user(stderr, "%s",str);
c51a56e2 595 va_end(ap);
07d9aa13 596}
597
07d9aa13 598/*
599 * Execute the source part of the SCP protocol.
600 */
601static void source(char *src)
602{
c51a56e2 603 char buf[2048];
604 unsigned long size;
605 char *last;
606 HANDLE f;
607 DWORD attr;
608 unsigned long i;
609 unsigned long stat_bytes;
610 time_t stat_starttime, stat_lasttime;
611
612 attr = GetFileAttributes(src);
996c8c3b 613 if (attr == (DWORD)-1) {
c51a56e2 614 run_err("%s: No such file or directory", src);
615 return;
616 }
617
618 if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
7f1f80de 619 if (recursive) {
620 /*
621 * Avoid . and .. directories.
622 */
623 char *p;
624 p = strrchr(src, '/');
625 if (!p)
626 p = strrchr(src, '\\');
627 if (!p)
628 p = src;
629 else
630 p++;
631 if (!strcmp(p, ".") || !strcmp(p, ".."))
632 /* skip . and .. */;
633 else
634 rsource(src);
635 } else {
c51a56e2 636 run_err("%s: not a regular file", src);
7f1f80de 637 }
c51a56e2 638 return;
639 }
640
641 if ((last = strrchr(src, '/')) == NULL)
642 last = src;
643 else
644 last++;
645 if (strrchr(last, '\\') != NULL)
646 last = strrchr(last, '\\') + 1;
647 if (last == src && strchr(src, ':') != NULL)
648 last = strchr(src, ':') + 1;
649
650 f = CreateFile(src, GENERIC_READ, FILE_SHARE_READ, NULL,
651 OPEN_EXISTING, 0, 0);
652 if (f == INVALID_HANDLE_VALUE) {
486543a1 653 run_err("%s: Cannot open file", src);
c51a56e2 654 return;
655 }
656
657 if (preserve) {
658 FILETIME actime, wrtime;
659 unsigned long mtime, atime;
660 GetFileTime(f, NULL, &actime, &wrtime);
661 TIME_WIN_TO_POSIX(actime, atime);
662 TIME_WIN_TO_POSIX(wrtime, mtime);
663 sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
3bdaf79d 664 back->send(buf, strlen(buf));
07d9aa13 665 if (response())
c51a56e2 666 return;
667 }
668
669 size = GetFileSize(f, NULL);
670 sprintf(buf, "C0644 %lu %s\n", size, last);
671 if (verbose)
cc87246d 672 tell_user(stderr, "Sending file modes: %s", buf);
3bdaf79d 673 back->send(buf, strlen(buf));
c51a56e2 674 if (response())
675 return;
676
677 if (statistics) {
678 stat_bytes = 0;
679 stat_starttime = time(NULL);
680 stat_lasttime = 0;
681 }
682
683 for (i = 0; i < size; i += 4096) {
684 char transbuf[4096];
685 DWORD j, k = 4096;
686 if (i + k > size) k = size - i;
687 if (! ReadFile(f, transbuf, k, &j, NULL) || j != k) {
688 if (statistics) printf("\n");
689 bump("%s: Read error", src);
07d9aa13 690 }
3bdaf79d 691 back->send(transbuf, k);
c51a56e2 692 if (statistics) {
693 stat_bytes += k;
694 if (time(NULL) != stat_lasttime ||
695 i + k == size) {
696 stat_lasttime = time(NULL);
697 print_stats(last, size, stat_bytes,
698 stat_starttime, stat_lasttime);
699 }
07d9aa13 700 }
c51a56e2 701 }
702 CloseHandle(f);
07d9aa13 703
3bdaf79d 704 back->send("", 1);
c51a56e2 705 (void) response();
07d9aa13 706}
707
07d9aa13 708/*
709 * Recursively send the contents of a directory.
710 */
711static void rsource(char *src)
712{
c51a56e2 713 char buf[2048];
714 char *last;
715 HANDLE dir;
716 WIN32_FIND_DATA fdat;
717 int ok;
718
719 if ((last = strrchr(src, '/')) == NULL)
720 last = src;
721 else
722 last++;
723 if (strrchr(last, '\\') != NULL)
724 last = strrchr(last, '\\') + 1;
725 if (last == src && strchr(src, ':') != NULL)
726 last = strchr(src, ':') + 1;
727
728 /* maybe send filetime */
729
730 sprintf(buf, "D0755 0 %s\n", last);
731 if (verbose)
cc87246d 732 tell_user(stderr, "Entering directory: %s", buf);
3bdaf79d 733 back->send(buf, strlen(buf));
c51a56e2 734 if (response())
735 return;
736
737 sprintf(buf, "%s/*", src);
738 dir = FindFirstFile(buf, &fdat);
739 ok = (dir != INVALID_HANDLE_VALUE);
740 while (ok) {
741 if (strcmp(fdat.cFileName, ".") == 0 ||
742 strcmp(fdat.cFileName, "..") == 0) {
743 } else if (strlen(src) + 1 + strlen(fdat.cFileName) >=
744 sizeof(buf)) {
745 run_err("%s/%s: Name too long", src, fdat.cFileName);
746 } else {
747 sprintf(buf, "%s/%s", src, fdat.cFileName);
748 source(buf);
07d9aa13 749 }
c51a56e2 750 ok = FindNextFile(dir, &fdat);
751 }
752 FindClose(dir);
07d9aa13 753
c51a56e2 754 sprintf(buf, "E\n");
3bdaf79d 755 back->send(buf, strlen(buf));
c51a56e2 756 (void) response();
07d9aa13 757}
758
07d9aa13 759/*
760 * Execute the sink part of the SCP protocol.
761 */
ca2d5943 762static void sink(char *targ, char *src)
07d9aa13 763{
c51a56e2 764 char buf[2048];
765 char namebuf[2048];
766 char ch;
767 int targisdir = 0;
996c8c3b 768 int settime;
c51a56e2 769 int exists;
770 DWORD attr;
771 HANDLE f;
772 unsigned long mtime, atime;
773 unsigned int mode;
774 unsigned long size, i;
775 int wrerror = 0;
776 unsigned long stat_bytes;
777 time_t stat_starttime, stat_lasttime;
778 char *stat_name;
779
780 attr = GetFileAttributes(targ);
996c8c3b 781 if (attr != (DWORD)-1 && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
c51a56e2 782 targisdir = 1;
783
784 if (targetshouldbedirectory && !targisdir)
785 bump("%s: Not a directory", targ);
786
3bdaf79d 787 back->send("", 1);
c51a56e2 788 while (1) {
789 settime = 0;
790 gottime:
fb09bf1c 791 if (ssh_scp_recv(&ch, 1) <= 0)
c51a56e2 792 return;
793 if (ch == '\n')
794 bump("Protocol error: Unexpected newline");
795 i = 0;
796 buf[i++] = ch;
797 do {
fb09bf1c 798 if (ssh_scp_recv(&ch, 1) <= 0)
c51a56e2 799 bump("Lost connection");
800 buf[i++] = ch;
801 } while (i < sizeof(buf) && ch != '\n');
802 buf[i-1] = '\0';
803 switch (buf[0]) {
804 case '\01': /* error */
cc87246d 805 tell_user(stderr, "%s\n", buf+1);
c51a56e2 806 errs++;
807 continue;
808 case '\02': /* fatal error */
809 bump("%s", buf+1);
810 case 'E':
3bdaf79d 811 back->send("", 1);
c51a56e2 812 return;
813 case 'T':
1d470ad2 814 if (sscanf(buf, "T%ld %*d %ld %*d",
c51a56e2 815 &mtime, &atime) == 2) {
816 settime = 1;
3bdaf79d 817 back->send("", 1);
c51a56e2 818 goto gottime;
819 }
820 bump("Protocol error: Illegal time format");
821 case 'C':
822 case 'D':
823 break;
824 default:
825 bump("Protocol error: Expected control record");
826 }
07d9aa13 827
1d470ad2 828 if (sscanf(buf+1, "%u %lu %[^\n]", &mode, &size, namebuf) != 3)
c51a56e2 829 bump("Protocol error: Illegal file descriptor format");
ca2d5943 830 /* Security fix: ensure the file ends up where we asked for it. */
c51a56e2 831 if (targisdir) {
832 char t[2048];
9520eba8 833 char *p;
c51a56e2 834 strcpy(t, targ);
835 if (targ[0] != '\0')
836 strcat(t, "/");
9520eba8 837 p = namebuf + strlen(namebuf);
838 while (p > namebuf && p[-1] != '/' && p[-1] != '\\')
839 p--;
840 strcat(t, p);
c51a56e2 841 strcpy(namebuf, t);
842 } else {
843 strcpy(namebuf, targ);
844 }
845 attr = GetFileAttributes(namebuf);
996c8c3b 846 exists = (attr != (DWORD)-1);
c51a56e2 847
848 if (buf[0] == 'D') {
849 if (exists && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
850 run_err("%s: Not a directory", namebuf);
851 continue;
852 }
853 if (!exists) {
854 if (! CreateDirectory(namebuf, NULL)) {
855 run_err("%s: Cannot create directory",
856 namebuf);
857 continue;
858 }
859 }
ca2d5943 860 sink(namebuf, NULL);
c51a56e2 861 /* can we set the timestamp for directories ? */
862 continue;
863 }
07d9aa13 864
c51a56e2 865 f = CreateFile(namebuf, GENERIC_WRITE, 0, NULL,
866 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
867 if (f == INVALID_HANDLE_VALUE) {
868 run_err("%s: Cannot create file", namebuf);
869 continue;
870 }
07d9aa13 871
3bdaf79d 872 back->send("", 1);
07d9aa13 873
c51a56e2 874 if (statistics) {
875 stat_bytes = 0;
876 stat_starttime = time(NULL);
877 stat_lasttime = 0;
878 if ((stat_name = strrchr(namebuf, '/')) == NULL)
879 stat_name = namebuf;
880 else
881 stat_name++;
882 if (strrchr(stat_name, '\\') != NULL)
883 stat_name = strrchr(stat_name, '\\') + 1;
884 }
07d9aa13 885
c51a56e2 886 for (i = 0; i < size; i += 4096) {
887 char transbuf[4096];
996c8c3b 888 DWORD j, k = 4096;
c51a56e2 889 if (i + k > size) k = size - i;
fb09bf1c 890 if (ssh_scp_recv(transbuf, k) == 0)
c51a56e2 891 bump("Lost connection");
892 if (wrerror) continue;
893 if (! WriteFile(f, transbuf, k, &j, NULL) || j != k) {
894 wrerror = 1;
895 if (statistics)
896 printf("\r%-25.25s | %50s\n",
897 stat_name,
898 "Write error.. waiting for end of file");
899 continue;
900 }
901 if (statistics) {
902 stat_bytes += k;
903 if (time(NULL) > stat_lasttime ||
904 i + k == size) {
905 stat_lasttime = time(NULL);
906 print_stats(stat_name, size, stat_bytes,
907 stat_starttime, stat_lasttime);
07d9aa13 908 }
c51a56e2 909 }
910 }
911 (void) response();
07d9aa13 912
c51a56e2 913 if (settime) {
914 FILETIME actime, wrtime;
915 TIME_POSIX_TO_WIN(atime, actime);
916 TIME_POSIX_TO_WIN(mtime, wrtime);
917 SetFileTime(f, NULL, &actime, &wrtime);
07d9aa13 918 }
07d9aa13 919
c51a56e2 920 CloseHandle(f);
921 if (wrerror) {
922 run_err("%s: Write error", namebuf);
923 continue;
924 }
3bdaf79d 925 back->send("", 1);
c51a56e2 926 }
927}
07d9aa13 928
929/*
930 * We will copy local files to a remote server.
931 */
932static void toremote(int argc, char *argv[])
933{
c51a56e2 934 char *src, *targ, *host, *user;
935 char *cmd;
936 int i;
937
938 targ = argv[argc-1];
939
39ddf0ff 940 /* Separate host from filename */
c51a56e2 941 host = targ;
942 targ = colon(targ);
943 if (targ == NULL)
944 bump("targ == NULL in toremote()");
945 *targ++ = '\0';
946 if (*targ == '\0')
947 targ = ".";
948 /* Substitute "." for emtpy target */
949
39ddf0ff 950 /* Separate host and username */
c51a56e2 951 user = host;
952 host = strrchr(host, '@');
953 if (host == NULL) {
954 host = user;
955 user = NULL;
956 } else {
957 *host++ = '\0';
958 if (*user == '\0')
959 user = NULL;
960 }
961
962 if (argc == 2) {
963 /* Find out if the source filespec covers multiple files
964 if so, we should set the targetshouldbedirectory flag */
965 HANDLE fh;
966 WIN32_FIND_DATA fdat;
967 if (colon(argv[0]) != NULL)
968 bump("%s: Remote to remote not supported", argv[0]);
969 fh = FindFirstFile(argv[0], &fdat);
970 if (fh == INVALID_HANDLE_VALUE)
971 bump("%s: No such file or directory\n", argv[0]);
972 if (FindNextFile(fh, &fdat))
973 targetshouldbedirectory = 1;
974 FindClose(fh);
975 }
976
977 cmd = smalloc(strlen(targ) + 100);
978 sprintf(cmd, "scp%s%s%s%s -t %s",
979 verbose ? " -v" : "",
980 recursive ? " -r" : "",
981 preserve ? " -p" : "",
982 targetshouldbedirectory ? " -d" : "",
983 targ);
984 do_cmd(host, user, cmd);
985 sfree(cmd);
986
987 (void) response();
988
989 for (i = 0; i < argc - 1; i++) {
990 HANDLE dir;
991 WIN32_FIND_DATA fdat;
992 src = argv[i];
993 if (colon(src) != NULL) {
cc87246d 994 tell_user(stderr, "%s: Remote to remote not supported\n", src);
c51a56e2 995 errs++;
996 continue;
07d9aa13 997 }
c51a56e2 998 dir = FindFirstFile(src, &fdat);
999 if (dir == INVALID_HANDLE_VALUE) {
1000 run_err("%s: No such file or directory", src);
1001 continue;
07d9aa13 1002 }
c51a56e2 1003 do {
1004 char *last;
1005 char namebuf[2048];
7f266ffb 1006 /*
1007 * Ensure that . and .. are never matched by wildcards,
1008 * but only by deliberate action.
1009 */
1010 if (!strcmp(fdat.cFileName, ".") ||
1011 !strcmp(fdat.cFileName, "..")) {
1012 /*
1013 * Find*File has returned a special dir. We require
1014 * that _either_ `src' ends in a backslash followed
1015 * by that string, _or_ `src' is precisely that
1016 * string.
1017 */
1018 int len = strlen(src), dlen = strlen(fdat.cFileName);
1019 if (len == dlen && !strcmp(src, fdat.cFileName)) {
1020 /* ok */;
1021 } else if (len > dlen+1 && src[len-dlen-1] == '\\' &&
1022 !strcmp(src+len-dlen, fdat.cFileName)) {
1023 /* ok */;
1024 } else
1025 continue; /* ignore this one */
1026 }
c51a56e2 1027 if (strlen(src) + strlen(fdat.cFileName) >=
1028 sizeof(namebuf)) {
cc87246d 1029 tell_user(stderr, "%s: Name too long", src);
c51a56e2 1030 continue;
1031 }
1032 strcpy(namebuf, src);
1033 if ((last = strrchr(namebuf, '/')) == NULL)
1034 last = namebuf;
1035 else
1036 last++;
1037 if (strrchr(last, '\\') != NULL)
1038 last = strrchr(last, '\\') + 1;
1039 if (last == namebuf && strrchr(namebuf, ':') != NULL)
1040 last = strchr(namebuf, ':') + 1;
1041 strcpy(last, fdat.cFileName);
1042 source(namebuf);
1043 } while (FindNextFile(dir, &fdat));
1044 FindClose(dir);
1045 }
07d9aa13 1046}
1047
07d9aa13 1048/*
1049 * We will copy files from a remote server to the local machine.
1050 */
1051static void tolocal(int argc, char *argv[])
1052{
c51a56e2 1053 char *src, *targ, *host, *user;
1054 char *cmd;
1055
1056 if (argc != 2)
1057 bump("More than one remote source not supported");
1058
1059 src = argv[0];
1060 targ = argv[1];
1061
39ddf0ff 1062 /* Separate host from filename */
c51a56e2 1063 host = src;
1064 src = colon(src);
1065 if (src == NULL)
1066 bump("Local to local copy not supported");
1067 *src++ = '\0';
1068 if (*src == '\0')
1069 src = ".";
1070 /* Substitute "." for empty filename */
1071
39ddf0ff 1072 /* Separate username and hostname */
c51a56e2 1073 user = host;
1074 host = strrchr(host, '@');
1075 if (host == NULL) {
1076 host = user;
1077 user = NULL;
1078 } else {
1079 *host++ = '\0';
1080 if (*user == '\0')
1081 user = NULL;
1082 }
1083
1084 cmd = smalloc(strlen(src) + 100);
1085 sprintf(cmd, "scp%s%s%s%s -f %s",
1086 verbose ? " -v" : "",
1087 recursive ? " -r" : "",
1088 preserve ? " -p" : "",
1089 targetshouldbedirectory ? " -d" : "",
1090 src);
1091 do_cmd(host, user, cmd);
1092 sfree(cmd);
1093
ca2d5943 1094 sink(targ, src);
07d9aa13 1095}
1096
07d9aa13 1097/*
39ddf0ff 1098 * We will issue a list command to get a remote directory.
1099 */
1100static void get_dir_list(int argc, char *argv[])
1101{
1102 char *src, *host, *user;
1103 char *cmd, *p, *q;
1104 char c;
1105
1106 src = argv[0];
1107
1108 /* Separate host from filename */
1109 host = src;
1110 src = colon(src);
1111 if (src == NULL)
1112 bump("Local to local copy not supported");
1113 *src++ = '\0';
1114 if (*src == '\0')
1115 src = ".";
1116 /* Substitute "." for empty filename */
1117
1118 /* Separate username and hostname */
1119 user = host;
1120 host = strrchr(host, '@');
1121 if (host == NULL) {
1122 host = user;
1123 user = NULL;
1124 } else {
1125 *host++ = '\0';
1126 if (*user == '\0')
1127 user = NULL;
1128 }
1129
1130 cmd = smalloc(4*strlen(src) + 100);
1131 strcpy(cmd, "ls -la '");
1132 p = cmd + strlen(cmd);
1133 for (q = src; *q; q++) {
1134 if (*q == '\'') {
1135 *p++ = '\''; *p++ = '\\'; *p++ = '\''; *p++ = '\'';
1136 } else {
1137 *p++ = *q;
1138 }
1139 }
1140 *p++ = '\'';
1141 *p = '\0';
cc87246d 1142
39ddf0ff 1143 do_cmd(host, user, cmd);
1144 sfree(cmd);
1145
fb09bf1c 1146 while (ssh_scp_recv(&c, 1) > 0)
cc87246d 1147 tell_char(stdout, c);
39ddf0ff 1148}
1149
1150/*
07d9aa13 1151 * Initialize the Win$ock driver.
1152 */
996c8c3b 1153static void init_winsock(void)
07d9aa13 1154{
c51a56e2 1155 WORD winsock_ver;
1156 WSADATA wsadata;
1157
1158 winsock_ver = MAKEWORD(1, 1);
1159 if (WSAStartup(winsock_ver, &wsadata))
1160 bump("Unable to initialise WinSock");
1161 if (LOBYTE(wsadata.wVersion) != 1 ||
1162 HIBYTE(wsadata.wVersion) != 1)
1163 bump("WinSock version is incompatible with 1.1");
07d9aa13 1164}
1165
07d9aa13 1166/*
1167 * Short description of parameters.
1168 */
996c8c3b 1169static void usage(void)
07d9aa13 1170{
c51a56e2 1171 printf("PuTTY Secure Copy client\n");
1172 printf("%s\n", ver);
a3e55ea1 1173 printf("Usage: pscp [options] [user@]host:source target\n");
1174 printf(" pscp [options] source [source...] [user@]host:target\n");
1175 printf(" pscp [options] -ls user@host:filespec\n");
b8a19193 1176 printf("Options:\n");
1177 printf(" -p preserve file attributes\n");
1178 printf(" -q quiet, don't show statistics\n");
1179 printf(" -r copy directories recursively\n");
1180 printf(" -v show verbose messages\n");
1181 printf(" -P port connect to specified port\n");
1182 printf(" -pw passw login with specified password\n");
cc87246d 1183 /* GUI Adaptation - Sept 2000 */
1184 printf(" -gui hWnd GUI mode with the windows handle for receiving messages\n");
c51a56e2 1185 exit(1);
07d9aa13 1186}
1187
07d9aa13 1188/*
1189 * Main program (no, really?)
1190 */
1191int main(int argc, char *argv[])
1192{
c51a56e2 1193 int i;
39ddf0ff 1194 int list = 0;
c51a56e2 1195
fb09bf1c 1196 default_protocol = PROT_TELNET;
1197
67779be7 1198 flags = FLAG_STDERR;
fb09bf1c 1199 ssh_get_password = &get_password;
c51a56e2 1200 init_winsock();
8df7a775 1201 sk_init();
c51a56e2 1202
1203 for (i = 1; i < argc; i++) {
1204 if (argv[i][0] != '-')
1205 break;
1206 if (strcmp(argv[i], "-v") == 0)
4017be6d 1207 verbose = 1, flags |= FLAG_VERBOSE;
c51a56e2 1208 else if (strcmp(argv[i], "-r") == 0)
1209 recursive = 1;
1210 else if (strcmp(argv[i], "-p") == 0)
1211 preserve = 1;
1212 else if (strcmp(argv[i], "-q") == 0)
1213 statistics = 0;
1214 else if (strcmp(argv[i], "-h") == 0 ||
1215 strcmp(argv[i], "-?") == 0)
1216 usage();
b8a19193 1217 else if (strcmp(argv[i], "-P") == 0 && i+1 < argc)
1218 portnumber = atoi(argv[++i]);
1219 else if (strcmp(argv[i], "-pw") == 0 && i+1 < argc)
1220 password = argv[++i];
cc87246d 1221 else if (strcmp(argv[i], "-gui") == 0 && i+1 < argc) {
1222 gui_hwnd = argv[++i];
1223 gui_mode = 1;
1224 } else if (strcmp(argv[i], "-ls") == 0)
1225 list = 1;
c51a56e2 1226 else if (strcmp(argv[i], "--") == 0)
1227 { i++; break; }
07d9aa13 1228 else
c51a56e2 1229 usage();
1230 }
1231 argc -= i;
1232 argv += i;
eba78553 1233 back = NULL;
c51a56e2 1234
39ddf0ff 1235 if (list) {
1236 if (argc != 1)
1237 usage();
1238 get_dir_list(argc, argv);
c51a56e2 1239
39ddf0ff 1240 } else {
1241
1242 if (argc < 2)
1243 usage();
1244 if (argc > 2)
1245 targetshouldbedirectory = 1;
1246
1247 if (colon(argv[argc-1]) != NULL)
1248 toremote(argc, argv);
1249 else
1250 tolocal(argc, argv);
1251 }
c51a56e2 1252
eba78553 1253 if (back != NULL && back->socket() != NULL) {
c51a56e2 1254 char ch;
3bdaf79d 1255 back->special(TS_EOF);
fb09bf1c 1256 ssh_scp_recv(&ch, 1);
c51a56e2 1257 }
1258 WSACleanup();
1259 random_save_seed();
07d9aa13 1260
cc87246d 1261 /* GUI Adaptation - August 2000 */
1262 if (gui_mode) {
1263 unsigned int msg_id = WM_RET_ERR_CNT;
1264 if (list) msg_id = WM_LS_RET_ERR_CNT;
1265 while (!PostMessage( (HWND)atoi(gui_hwnd), msg_id, (WPARAM)errs, 0/*lParam*/ ) )
1266 SleepEx(1000,TRUE);
1267 }
c51a56e2 1268 return (errs == 0 ? 0 : 1);
07d9aa13 1269}
1270
1271/* end */