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