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