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