Bug fix: line discipline selection is not enabled until after ssh
[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>
12#include <winsock.h>
13#include <stdlib.h>
14#include <stdio.h>
15#include <string.h>
16#include <time.h>
cc87246d 17/* GUI Adaptation - Sept 2000 */
18#include <winuser.h>
19#include <winbase.h>
07d9aa13 20
21#define PUTTY_DO_GLOBALS
22#include "putty.h"
23#include "scp.h"
24
07d9aa13 25#define TIME_POSIX_TO_WIN(t, ft) (*(LONGLONG*)&(ft) = \
c51a56e2 26 ((LONGLONG) (t) + (LONGLONG) 11644473600) * (LONGLONG) 10000000)
07d9aa13 27#define TIME_WIN_TO_POSIX(ft, t) ((t) = (unsigned long) \
c51a56e2 28 ((*(LONGLONG*)&(ft)) / (LONGLONG) 10000000 - (LONGLONG) 11644473600))
07d9aa13 29
cc87246d 30/* GUI Adaptation - Sept 2000 */
31#define WM_APP_BASE 0x8000
32#define WM_STD_OUT_CHAR ( WM_APP_BASE+400 )
33#define WM_STD_ERR_CHAR ( WM_APP_BASE+401 )
34#define WM_STATS_CHAR ( WM_APP_BASE+402 )
35#define WM_STATS_SIZE ( WM_APP_BASE+403 )
36#define WM_STATS_PERCENT ( WM_APP_BASE+404 )
37#define WM_STATS_ELAPSED ( WM_APP_BASE+405 )
38#define WM_RET_ERR_CNT ( WM_APP_BASE+406 )
39#define WM_LS_RET_ERR_CNT ( WM_APP_BASE+407 )
40
fb09bf1c 41static int verbose = 0;
07d9aa13 42static int recursive = 0;
43static int preserve = 0;
44static int targetshouldbedirectory = 0;
45static int statistics = 1;
b8a19193 46static int portnumber = 0;
47static char *password = NULL;
07d9aa13 48static int errs = 0;
49static int connection_open = 0;
cc87246d 50/* GUI Adaptation - Sept 2000 */
51#define NAME_STR_MAX 2048
52static char statname[NAME_STR_MAX+1];
53static unsigned long statsize = 0;
54static int statperct = 0;
55static time_t statelapsed = 0;
56static int gui_mode = 0;
57static char *gui_hwnd = NULL;
07d9aa13 58
59static void source(char *src);
60static void rsource(char *src);
61static void sink(char *targ);
cc87246d 62/* GUI Adaptation - Sept 2000 */
63static void tell_char(FILE *stream, char c);
64static void tell_str(FILE *stream, char *str);
65static void tell_user(FILE *stream, char *fmt, ...);
66static void send_char_msg(unsigned int msg_id, char c);
67static void send_str_msg(unsigned int msg_id, char *str);
68static void gui_update_stats(char *name, unsigned long size, int percentage, time_t elapsed);
07d9aa13 69
07d9aa13 70/*
6f34e365 71 * These functions are needed to link with ssh.c, but never get called.
fb09bf1c 72 */
73void term_out(void)
74{
75 abort();
76}
6f34e365 77void begin_session(void) {
78}
fb09bf1c 79
cc87246d 80/* GUI Adaptation - Sept 2000 */
81void send_msg(HWND h, UINT message, WPARAM wParam)
82{
83 while (!PostMessage( h, message, wParam, 0))
84 SleepEx(1000,TRUE);
85}
86
87void tell_char(FILE *stream, char c)
88{
89 if (!gui_mode)
90 fputc(c, stream);
91 else
92 {
93 unsigned int msg_id = WM_STD_OUT_CHAR;
94 if (stream = stderr) msg_id = WM_STD_ERR_CHAR;
95 send_msg( (HWND)atoi(gui_hwnd), msg_id, (WPARAM)c );
96 }
97}
98
99void tell_str(FILE *stream, char *str)
100{
101 unsigned int i;
102
103 for( i = 0; i < strlen(str); ++i )
104 tell_char(stream, str[i]);
105}
106
107void tell_user(FILE *stream, char *fmt, ...)
108{
109 char str[0x100]; /* Make the size big enough */
110 va_list ap;
111 va_start(ap, fmt);
112 vsprintf(str, fmt, ap);
113 va_end(ap);
114 strcat(str, "\n");
115 tell_str(stream, str);
116}
117
118void gui_update_stats(char *name, unsigned long size, int percentage, time_t elapsed)
119{
120 unsigned int i;
121
122 if (strcmp(name,statname) != 0)
123 {
124 for( i = 0; i < strlen(name); ++i )
125 send_msg( (HWND)atoi(gui_hwnd), WM_STATS_CHAR, (WPARAM)name[i]);
126 send_msg( (HWND)atoi(gui_hwnd), WM_STATS_CHAR, (WPARAM)'\n' );
127 strcpy(statname,name);
128 }
129 if (statsize != size)
130 {
131 send_msg( (HWND)atoi(gui_hwnd), WM_STATS_SIZE, (WPARAM)size );
132 statsize = size;
133 }
134 if (statelapsed != elapsed)
135 {
136 send_msg( (HWND)atoi(gui_hwnd), WM_STATS_ELAPSED, (WPARAM)elapsed );
137 statelapsed = elapsed;
138 }
139 if (statperct != percentage)
140 {
141 send_msg( (HWND)atoi(gui_hwnd), WM_STATS_PERCENT, (WPARAM)percentage );
142 statperct = percentage;
143 }
144}
145
fb09bf1c 146/*
07d9aa13 147 * Print an error message and perform a fatal exit.
148 */
149void fatalbox(char *fmt, ...)
150{
cc87246d 151 char str[0x100]; /* Make the size big enough */
c51a56e2 152 va_list ap;
153 va_start(ap, fmt);
cc87246d 154 strcpy(str, "Fatal:");
155 vsprintf(str+strlen(str), fmt, ap);
c51a56e2 156 va_end(ap);
cc87246d 157 strcat(str, "\n");
158 tell_str(stderr, str);
159
c51a56e2 160 exit(1);
07d9aa13 161}
8d5de777 162void connection_fatal(char *fmt, ...)
163{
164 char str[0x100]; /* Make the size big enough */
165 va_list ap;
166 va_start(ap, fmt);
167 strcpy(str, "Fatal:");
168 vsprintf(str+strlen(str), fmt, ap);
169 va_end(ap);
170 strcat(str, "\n");
171 tell_str(stderr, str);
172
173 exit(1);
174}
07d9aa13 175
07d9aa13 176/*
177 * Print an error message and exit after closing the SSH link.
178 */
179static void bump(char *fmt, ...)
180{
cc87246d 181 char str[0x100]; /* Make the size big enough */
c51a56e2 182 va_list ap;
183 va_start(ap, fmt);
cc87246d 184 strcpy(str, "Fatal:");
185 vsprintf(str+strlen(str), fmt, ap);
c51a56e2 186 va_end(ap);
cc87246d 187 strcat(str, "\n");
188 tell_str(stderr, str);
189
c51a56e2 190 if (connection_open) {
191 char ch;
fb09bf1c 192 ssh_scp_send_eof();
193 ssh_scp_recv(&ch, 1);
c51a56e2 194 }
195 exit(1);
07d9aa13 196}
197
85ee8208 198static int get_password(const char *prompt, char *str, int maxlen)
07d9aa13 199{
c51a56e2 200 HANDLE hin, hout;
b8a19193 201 DWORD savemode, i;
202
203 if (password) {
85ee8208 204 static int tried_once = 0;
205
206 if (tried_once) {
207 return 0;
208 } else {
209 strncpy(str, password, maxlen);
210 str[maxlen-1] = '\0';
211 tried_once = 1;
212 return 1;
213 }
b8a19193 214 }
07d9aa13 215
cc87246d 216 /* GUI Adaptation - Sept 2000 */
217 if (gui_mode) {
218 if (maxlen>0) str[0] = '\0';
219 } else {
220 hin = GetStdHandle(STD_INPUT_HANDLE);
221 hout = GetStdHandle(STD_OUTPUT_HANDLE);
222 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE)
223 bump("Cannot get standard input/output handles");
07d9aa13 224
cc87246d 225 GetConsoleMode(hin, &savemode);
226 SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
227 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
07d9aa13 228
cc87246d 229 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
230 ReadFile(hin, str, maxlen-1, &i, NULL);
07d9aa13 231
cc87246d 232 SetConsoleMode(hin, savemode);
07d9aa13 233
cc87246d 234 if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
235 str[i] = '\0';
07d9aa13 236
cc87246d 237 WriteFile(hout, "\r\n", 2, &i, NULL);
238 }
85ee8208 239
240 return 1;
07d9aa13 241}
242
07d9aa13 243/*
244 * Open an SSH connection to user@host and execute cmd.
245 */
246static void do_cmd(char *host, char *user, char *cmd)
247{
c51a56e2 248 char *err, *realhost;
249
250 if (host == NULL || host[0] == '\0')
251 bump("Empty host name");
252
253 /* Try to load settings for this host */
254 do_defaults(host);
255 if (cfg.host[0] == '\0') {
256 /* No settings for this host; use defaults */
257 strncpy(cfg.host, host, sizeof(cfg.host)-1);
258 cfg.host[sizeof(cfg.host)-1] = '\0';
259 cfg.port = 22;
260 }
261
262 /* Set username */
263 if (user != NULL && user[0] != '\0') {
264 strncpy(cfg.username, user, sizeof(cfg.username)-1);
265 cfg.username[sizeof(cfg.username)-1] = '\0';
c51a56e2 266 } else if (cfg.username[0] == '\0') {
267 bump("Empty user name");
268 }
269
270 if (cfg.protocol != PROT_SSH)
271 cfg.port = 22;
272
ed89e8a5 273 if (portnumber)
274 cfg.port = portnumber;
275
fb09bf1c 276 err = ssh_scp_init(cfg.host, cfg.port, cmd, &realhost);
c51a56e2 277 if (err != NULL)
278 bump("ssh_init: %s", err);
279 if (verbose && realhost != NULL)
cc87246d 280 tell_user(stderr, "Connected to %s\n", realhost);
c51a56e2 281
282 connection_open = 1;
07d9aa13 283}
284
07d9aa13 285/*
286 * Update statistic information about current file.
287 */
288static void print_stats(char *name, unsigned long size, unsigned long done,
c51a56e2 289 time_t start, time_t now)
07d9aa13 290{
c51a56e2 291 float ratebs;
292 unsigned long eta;
293 char etastr[10];
294 int pct;
295
cc87246d 296 /* GUI Adaptation - Sept 2000 */
297 if (gui_mode)
298 gui_update_stats(name, size, ((done *100) / size), now-start);
299 else {
300 if (now > start)
301 ratebs = (float) done / (now - start);
302 else
303 ratebs = (float) done;
c51a56e2 304
cc87246d 305 if (ratebs < 1.0)
306 eta = size - done;
307 else
308 eta = (unsigned long) ((size - done) / ratebs);
309 sprintf(etastr, "%02ld:%02ld:%02ld",
310 eta / 3600, (eta % 3600) / 60, eta % 60);
c51a56e2 311
cc87246d 312 pct = (int) (100.0 * (float) done / size);
c51a56e2 313
cc87246d 314 printf("\r%-25.25s | %10ld kB | %5.1f kB/s | ETA: %8s | %3d%%",
315 name, done / 1024, ratebs / 1024.0,
316 etastr, pct);
c51a56e2 317
cc87246d 318 if (done == size)
319 printf("\n");
320 }
07d9aa13 321}
322
07d9aa13 323/*
324 * Find a colon in str and return a pointer to the colon.
39ddf0ff 325 * This is used to separate hostname from filename.
07d9aa13 326 */
327static char * colon(char *str)
328{
c51a56e2 329 /* We ignore a leading colon, since the hostname cannot be
330 empty. We also ignore a colon as second character because
331 of filenames like f:myfile.txt. */
332 if (str[0] == '\0' ||
333 str[0] == ':' ||
334 str[1] == ':')
335 return (NULL);
336 while (*str != '\0' &&
337 *str != ':' &&
338 *str != '/' &&
339 *str != '\\')
340 str++;
341 if (*str == ':')
342 return (str);
343 else
344 return (NULL);
07d9aa13 345}
346
07d9aa13 347/*
348 * Wait for a response from the other side.
349 * Return 0 if ok, -1 if error.
350 */
351static int response(void)
352{
c51a56e2 353 char ch, resp, rbuf[2048];
354 int p;
355
fb09bf1c 356 if (ssh_scp_recv(&resp, 1) <= 0)
c51a56e2 357 bump("Lost connection");
358
359 p = 0;
360 switch (resp) {
361 case 0: /* ok */
362 return (0);
363 default:
364 rbuf[p++] = resp;
365 /* fallthrough */
366 case 1: /* error */
367 case 2: /* fatal error */
368 do {
fb09bf1c 369 if (ssh_scp_recv(&ch, 1) <= 0)
c51a56e2 370 bump("Protocol error: Lost connection");
371 rbuf[p++] = ch;
372 } while (p < sizeof(rbuf) && ch != '\n');
373 rbuf[p-1] = '\0';
374 if (resp == 1)
cc87246d 375 tell_user(stderr, "%s\n", rbuf);
c51a56e2 376 else
377 bump("%s", rbuf);
378 errs++;
379 return (-1);
380 }
07d9aa13 381}
382
07d9aa13 383/*
384 * Send an error message to the other side and to the screen.
385 * Increment error counter.
386 */
387static void run_err(const char *fmt, ...)
388{
c51a56e2 389 char str[2048];
390 va_list ap;
391 va_start(ap, fmt);
392 errs++;
393 strcpy(str, "\01scp: ");
394 vsprintf(str+strlen(str), fmt, ap);
395 strcat(str, "\n");
fb09bf1c 396 ssh_scp_send(str, strlen(str));
cc87246d 397 tell_user(stderr, "%s",str);
c51a56e2 398 va_end(ap);
07d9aa13 399}
400
07d9aa13 401/*
402 * Execute the source part of the SCP protocol.
403 */
404static void source(char *src)
405{
c51a56e2 406 char buf[2048];
407 unsigned long size;
408 char *last;
409 HANDLE f;
410 DWORD attr;
411 unsigned long i;
412 unsigned long stat_bytes;
413 time_t stat_starttime, stat_lasttime;
414
415 attr = GetFileAttributes(src);
996c8c3b 416 if (attr == (DWORD)-1) {
c51a56e2 417 run_err("%s: No such file or directory", src);
418 return;
419 }
420
421 if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
7f1f80de 422 if (recursive) {
423 /*
424 * Avoid . and .. directories.
425 */
426 char *p;
427 p = strrchr(src, '/');
428 if (!p)
429 p = strrchr(src, '\\');
430 if (!p)
431 p = src;
432 else
433 p++;
434 if (!strcmp(p, ".") || !strcmp(p, ".."))
435 /* skip . and .. */;
436 else
437 rsource(src);
438 } else {
c51a56e2 439 run_err("%s: not a regular file", src);
7f1f80de 440 }
c51a56e2 441 return;
442 }
443
444 if ((last = strrchr(src, '/')) == NULL)
445 last = src;
446 else
447 last++;
448 if (strrchr(last, '\\') != NULL)
449 last = strrchr(last, '\\') + 1;
450 if (last == src && strchr(src, ':') != NULL)
451 last = strchr(src, ':') + 1;
452
453 f = CreateFile(src, GENERIC_READ, FILE_SHARE_READ, NULL,
454 OPEN_EXISTING, 0, 0);
455 if (f == INVALID_HANDLE_VALUE) {
486543a1 456 run_err("%s: Cannot open file", src);
c51a56e2 457 return;
458 }
459
460 if (preserve) {
461 FILETIME actime, wrtime;
462 unsigned long mtime, atime;
463 GetFileTime(f, NULL, &actime, &wrtime);
464 TIME_WIN_TO_POSIX(actime, atime);
465 TIME_WIN_TO_POSIX(wrtime, mtime);
466 sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
fb09bf1c 467 ssh_scp_send(buf, strlen(buf));
07d9aa13 468 if (response())
c51a56e2 469 return;
470 }
471
472 size = GetFileSize(f, NULL);
473 sprintf(buf, "C0644 %lu %s\n", size, last);
474 if (verbose)
cc87246d 475 tell_user(stderr, "Sending file modes: %s", buf);
fb09bf1c 476 ssh_scp_send(buf, strlen(buf));
c51a56e2 477 if (response())
478 return;
479
480 if (statistics) {
481 stat_bytes = 0;
482 stat_starttime = time(NULL);
483 stat_lasttime = 0;
484 }
485
486 for (i = 0; i < size; i += 4096) {
487 char transbuf[4096];
488 DWORD j, k = 4096;
489 if (i + k > size) k = size - i;
490 if (! ReadFile(f, transbuf, k, &j, NULL) || j != k) {
491 if (statistics) printf("\n");
492 bump("%s: Read error", src);
07d9aa13 493 }
fb09bf1c 494 ssh_scp_send(transbuf, k);
c51a56e2 495 if (statistics) {
496 stat_bytes += k;
497 if (time(NULL) != stat_lasttime ||
498 i + k == size) {
499 stat_lasttime = time(NULL);
500 print_stats(last, size, stat_bytes,
501 stat_starttime, stat_lasttime);
502 }
07d9aa13 503 }
c51a56e2 504 }
505 CloseHandle(f);
07d9aa13 506
fb09bf1c 507 ssh_scp_send("", 1);
c51a56e2 508 (void) response();
07d9aa13 509}
510
07d9aa13 511/*
512 * Recursively send the contents of a directory.
513 */
514static void rsource(char *src)
515{
c51a56e2 516 char buf[2048];
517 char *last;
518 HANDLE dir;
519 WIN32_FIND_DATA fdat;
520 int ok;
521
522 if ((last = strrchr(src, '/')) == NULL)
523 last = src;
524 else
525 last++;
526 if (strrchr(last, '\\') != NULL)
527 last = strrchr(last, '\\') + 1;
528 if (last == src && strchr(src, ':') != NULL)
529 last = strchr(src, ':') + 1;
530
531 /* maybe send filetime */
532
533 sprintf(buf, "D0755 0 %s\n", last);
534 if (verbose)
cc87246d 535 tell_user(stderr, "Entering directory: %s", buf);
fb09bf1c 536 ssh_scp_send(buf, strlen(buf));
c51a56e2 537 if (response())
538 return;
539
540 sprintf(buf, "%s/*", src);
541 dir = FindFirstFile(buf, &fdat);
542 ok = (dir != INVALID_HANDLE_VALUE);
543 while (ok) {
544 if (strcmp(fdat.cFileName, ".") == 0 ||
545 strcmp(fdat.cFileName, "..") == 0) {
546 } else if (strlen(src) + 1 + strlen(fdat.cFileName) >=
547 sizeof(buf)) {
548 run_err("%s/%s: Name too long", src, fdat.cFileName);
549 } else {
550 sprintf(buf, "%s/%s", src, fdat.cFileName);
551 source(buf);
07d9aa13 552 }
c51a56e2 553 ok = FindNextFile(dir, &fdat);
554 }
555 FindClose(dir);
07d9aa13 556
c51a56e2 557 sprintf(buf, "E\n");
fb09bf1c 558 ssh_scp_send(buf, strlen(buf));
c51a56e2 559 (void) response();
07d9aa13 560}
561
07d9aa13 562/*
563 * Execute the sink part of the SCP protocol.
564 */
565static void sink(char *targ)
566{
c51a56e2 567 char buf[2048];
568 char namebuf[2048];
569 char ch;
570 int targisdir = 0;
996c8c3b 571 int settime;
c51a56e2 572 int exists;
573 DWORD attr;
574 HANDLE f;
575 unsigned long mtime, atime;
576 unsigned int mode;
577 unsigned long size, i;
578 int wrerror = 0;
579 unsigned long stat_bytes;
580 time_t stat_starttime, stat_lasttime;
581 char *stat_name;
582
583 attr = GetFileAttributes(targ);
996c8c3b 584 if (attr != (DWORD)-1 && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
c51a56e2 585 targisdir = 1;
586
587 if (targetshouldbedirectory && !targisdir)
588 bump("%s: Not a directory", targ);
589
fb09bf1c 590 ssh_scp_send("", 1);
c51a56e2 591 while (1) {
592 settime = 0;
593 gottime:
fb09bf1c 594 if (ssh_scp_recv(&ch, 1) <= 0)
c51a56e2 595 return;
596 if (ch == '\n')
597 bump("Protocol error: Unexpected newline");
598 i = 0;
599 buf[i++] = ch;
600 do {
fb09bf1c 601 if (ssh_scp_recv(&ch, 1) <= 0)
c51a56e2 602 bump("Lost connection");
603 buf[i++] = ch;
604 } while (i < sizeof(buf) && ch != '\n');
605 buf[i-1] = '\0';
606 switch (buf[0]) {
607 case '\01': /* error */
cc87246d 608 tell_user(stderr, "%s\n", buf+1);
c51a56e2 609 errs++;
610 continue;
611 case '\02': /* fatal error */
612 bump("%s", buf+1);
613 case 'E':
fb09bf1c 614 ssh_scp_send("", 1);
c51a56e2 615 return;
616 case 'T':
1d470ad2 617 if (sscanf(buf, "T%ld %*d %ld %*d",
c51a56e2 618 &mtime, &atime) == 2) {
619 settime = 1;
fb09bf1c 620 ssh_scp_send("", 1);
c51a56e2 621 goto gottime;
622 }
623 bump("Protocol error: Illegal time format");
624 case 'C':
625 case 'D':
626 break;
627 default:
628 bump("Protocol error: Expected control record");
629 }
07d9aa13 630
1d470ad2 631 if (sscanf(buf+1, "%u %lu %[^\n]", &mode, &size, namebuf) != 3)
c51a56e2 632 bump("Protocol error: Illegal file descriptor format");
633 if (targisdir) {
634 char t[2048];
635 strcpy(t, targ);
636 if (targ[0] != '\0')
637 strcat(t, "/");
638 strcat(t, namebuf);
639 strcpy(namebuf, t);
640 } else {
641 strcpy(namebuf, targ);
642 }
643 attr = GetFileAttributes(namebuf);
996c8c3b 644 exists = (attr != (DWORD)-1);
c51a56e2 645
646 if (buf[0] == 'D') {
647 if (exists && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
648 run_err("%s: Not a directory", namebuf);
649 continue;
650 }
651 if (!exists) {
652 if (! CreateDirectory(namebuf, NULL)) {
653 run_err("%s: Cannot create directory",
654 namebuf);
655 continue;
656 }
657 }
658 sink(namebuf);
659 /* can we set the timestamp for directories ? */
660 continue;
661 }
07d9aa13 662
c51a56e2 663 f = CreateFile(namebuf, GENERIC_WRITE, 0, NULL,
664 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
665 if (f == INVALID_HANDLE_VALUE) {
666 run_err("%s: Cannot create file", namebuf);
667 continue;
668 }
07d9aa13 669
fb09bf1c 670 ssh_scp_send("", 1);
07d9aa13 671
c51a56e2 672 if (statistics) {
673 stat_bytes = 0;
674 stat_starttime = time(NULL);
675 stat_lasttime = 0;
676 if ((stat_name = strrchr(namebuf, '/')) == NULL)
677 stat_name = namebuf;
678 else
679 stat_name++;
680 if (strrchr(stat_name, '\\') != NULL)
681 stat_name = strrchr(stat_name, '\\') + 1;
682 }
07d9aa13 683
c51a56e2 684 for (i = 0; i < size; i += 4096) {
685 char transbuf[4096];
996c8c3b 686 DWORD j, k = 4096;
c51a56e2 687 if (i + k > size) k = size - i;
fb09bf1c 688 if (ssh_scp_recv(transbuf, k) == 0)
c51a56e2 689 bump("Lost connection");
690 if (wrerror) continue;
691 if (! WriteFile(f, transbuf, k, &j, NULL) || j != k) {
692 wrerror = 1;
693 if (statistics)
694 printf("\r%-25.25s | %50s\n",
695 stat_name,
696 "Write error.. waiting for end of file");
697 continue;
698 }
699 if (statistics) {
700 stat_bytes += k;
701 if (time(NULL) > stat_lasttime ||
702 i + k == size) {
703 stat_lasttime = time(NULL);
704 print_stats(stat_name, size, stat_bytes,
705 stat_starttime, stat_lasttime);
07d9aa13 706 }
c51a56e2 707 }
708 }
709 (void) response();
07d9aa13 710
c51a56e2 711 if (settime) {
712 FILETIME actime, wrtime;
713 TIME_POSIX_TO_WIN(atime, actime);
714 TIME_POSIX_TO_WIN(mtime, wrtime);
715 SetFileTime(f, NULL, &actime, &wrtime);
07d9aa13 716 }
07d9aa13 717
c51a56e2 718 CloseHandle(f);
719 if (wrerror) {
720 run_err("%s: Write error", namebuf);
721 continue;
722 }
fb09bf1c 723 ssh_scp_send("", 1);
c51a56e2 724 }
725}
07d9aa13 726
727/*
728 * We will copy local files to a remote server.
729 */
730static void toremote(int argc, char *argv[])
731{
c51a56e2 732 char *src, *targ, *host, *user;
733 char *cmd;
734 int i;
735
736 targ = argv[argc-1];
737
39ddf0ff 738 /* Separate host from filename */
c51a56e2 739 host = targ;
740 targ = colon(targ);
741 if (targ == NULL)
742 bump("targ == NULL in toremote()");
743 *targ++ = '\0';
744 if (*targ == '\0')
745 targ = ".";
746 /* Substitute "." for emtpy target */
747
39ddf0ff 748 /* Separate host and username */
c51a56e2 749 user = host;
750 host = strrchr(host, '@');
751 if (host == NULL) {
752 host = user;
753 user = NULL;
754 } else {
755 *host++ = '\0';
756 if (*user == '\0')
757 user = NULL;
758 }
759
760 if (argc == 2) {
761 /* Find out if the source filespec covers multiple files
762 if so, we should set the targetshouldbedirectory flag */
763 HANDLE fh;
764 WIN32_FIND_DATA fdat;
765 if (colon(argv[0]) != NULL)
766 bump("%s: Remote to remote not supported", argv[0]);
767 fh = FindFirstFile(argv[0], &fdat);
768 if (fh == INVALID_HANDLE_VALUE)
769 bump("%s: No such file or directory\n", argv[0]);
770 if (FindNextFile(fh, &fdat))
771 targetshouldbedirectory = 1;
772 FindClose(fh);
773 }
774
775 cmd = smalloc(strlen(targ) + 100);
776 sprintf(cmd, "scp%s%s%s%s -t %s",
777 verbose ? " -v" : "",
778 recursive ? " -r" : "",
779 preserve ? " -p" : "",
780 targetshouldbedirectory ? " -d" : "",
781 targ);
782 do_cmd(host, user, cmd);
783 sfree(cmd);
784
785 (void) response();
786
787 for (i = 0; i < argc - 1; i++) {
788 HANDLE dir;
789 WIN32_FIND_DATA fdat;
790 src = argv[i];
791 if (colon(src) != NULL) {
cc87246d 792 tell_user(stderr, "%s: Remote to remote not supported\n", src);
c51a56e2 793 errs++;
794 continue;
07d9aa13 795 }
c51a56e2 796 dir = FindFirstFile(src, &fdat);
797 if (dir == INVALID_HANDLE_VALUE) {
798 run_err("%s: No such file or directory", src);
799 continue;
07d9aa13 800 }
c51a56e2 801 do {
802 char *last;
803 char namebuf[2048];
804 if (strlen(src) + strlen(fdat.cFileName) >=
805 sizeof(namebuf)) {
cc87246d 806 tell_user(stderr, "%s: Name too long", src);
c51a56e2 807 continue;
808 }
809 strcpy(namebuf, src);
810 if ((last = strrchr(namebuf, '/')) == NULL)
811 last = namebuf;
812 else
813 last++;
814 if (strrchr(last, '\\') != NULL)
815 last = strrchr(last, '\\') + 1;
816 if (last == namebuf && strrchr(namebuf, ':') != NULL)
817 last = strchr(namebuf, ':') + 1;
818 strcpy(last, fdat.cFileName);
819 source(namebuf);
820 } while (FindNextFile(dir, &fdat));
821 FindClose(dir);
822 }
07d9aa13 823}
824
07d9aa13 825/*
826 * We will copy files from a remote server to the local machine.
827 */
828static void tolocal(int argc, char *argv[])
829{
c51a56e2 830 char *src, *targ, *host, *user;
831 char *cmd;
832
833 if (argc != 2)
834 bump("More than one remote source not supported");
835
836 src = argv[0];
837 targ = argv[1];
838
39ddf0ff 839 /* Separate host from filename */
c51a56e2 840 host = src;
841 src = colon(src);
842 if (src == NULL)
843 bump("Local to local copy not supported");
844 *src++ = '\0';
845 if (*src == '\0')
846 src = ".";
847 /* Substitute "." for empty filename */
848
39ddf0ff 849 /* Separate username and hostname */
c51a56e2 850 user = host;
851 host = strrchr(host, '@');
852 if (host == NULL) {
853 host = user;
854 user = NULL;
855 } else {
856 *host++ = '\0';
857 if (*user == '\0')
858 user = NULL;
859 }
860
861 cmd = smalloc(strlen(src) + 100);
862 sprintf(cmd, "scp%s%s%s%s -f %s",
863 verbose ? " -v" : "",
864 recursive ? " -r" : "",
865 preserve ? " -p" : "",
866 targetshouldbedirectory ? " -d" : "",
867 src);
868 do_cmd(host, user, cmd);
869 sfree(cmd);
870
871 sink(targ);
07d9aa13 872}
873
07d9aa13 874/*
39ddf0ff 875 * We will issue a list command to get a remote directory.
876 */
877static void get_dir_list(int argc, char *argv[])
878{
879 char *src, *host, *user;
880 char *cmd, *p, *q;
881 char c;
882
883 src = argv[0];
884
885 /* Separate host from filename */
886 host = src;
887 src = colon(src);
888 if (src == NULL)
889 bump("Local to local copy not supported");
890 *src++ = '\0';
891 if (*src == '\0')
892 src = ".";
893 /* Substitute "." for empty filename */
894
895 /* Separate username and hostname */
896 user = host;
897 host = strrchr(host, '@');
898 if (host == NULL) {
899 host = user;
900 user = NULL;
901 } else {
902 *host++ = '\0';
903 if (*user == '\0')
904 user = NULL;
905 }
906
907 cmd = smalloc(4*strlen(src) + 100);
908 strcpy(cmd, "ls -la '");
909 p = cmd + strlen(cmd);
910 for (q = src; *q; q++) {
911 if (*q == '\'') {
912 *p++ = '\''; *p++ = '\\'; *p++ = '\''; *p++ = '\'';
913 } else {
914 *p++ = *q;
915 }
916 }
917 *p++ = '\'';
918 *p = '\0';
cc87246d 919
39ddf0ff 920 do_cmd(host, user, cmd);
921 sfree(cmd);
922
fb09bf1c 923 while (ssh_scp_recv(&c, 1) > 0)
cc87246d 924 tell_char(stdout, c);
39ddf0ff 925}
926
927/*
07d9aa13 928 * Initialize the Win$ock driver.
929 */
996c8c3b 930static void init_winsock(void)
07d9aa13 931{
c51a56e2 932 WORD winsock_ver;
933 WSADATA wsadata;
934
935 winsock_ver = MAKEWORD(1, 1);
936 if (WSAStartup(winsock_ver, &wsadata))
937 bump("Unable to initialise WinSock");
938 if (LOBYTE(wsadata.wVersion) != 1 ||
939 HIBYTE(wsadata.wVersion) != 1)
940 bump("WinSock version is incompatible with 1.1");
07d9aa13 941}
942
07d9aa13 943/*
944 * Short description of parameters.
945 */
996c8c3b 946static void usage(void)
07d9aa13 947{
c51a56e2 948 printf("PuTTY Secure Copy client\n");
949 printf("%s\n", ver);
a3e55ea1 950 printf("Usage: pscp [options] [user@]host:source target\n");
951 printf(" pscp [options] source [source...] [user@]host:target\n");
952 printf(" pscp [options] -ls user@host:filespec\n");
b8a19193 953 printf("Options:\n");
954 printf(" -p preserve file attributes\n");
955 printf(" -q quiet, don't show statistics\n");
956 printf(" -r copy directories recursively\n");
957 printf(" -v show verbose messages\n");
958 printf(" -P port connect to specified port\n");
959 printf(" -pw passw login with specified password\n");
cc87246d 960 /* GUI Adaptation - Sept 2000 */
961 printf(" -gui hWnd GUI mode with the windows handle for receiving messages\n");
c51a56e2 962 exit(1);
07d9aa13 963}
964
07d9aa13 965/*
966 * Main program (no, really?)
967 */
968int main(int argc, char *argv[])
969{
c51a56e2 970 int i;
39ddf0ff 971 int list = 0;
c51a56e2 972
fb09bf1c 973 default_protocol = PROT_TELNET;
974
67779be7 975 flags = FLAG_STDERR;
fb09bf1c 976 ssh_get_password = &get_password;
c51a56e2 977 init_winsock();
978
979 for (i = 1; i < argc; i++) {
980 if (argv[i][0] != '-')
981 break;
982 if (strcmp(argv[i], "-v") == 0)
4017be6d 983 verbose = 1, flags |= FLAG_VERBOSE;
c51a56e2 984 else if (strcmp(argv[i], "-r") == 0)
985 recursive = 1;
986 else if (strcmp(argv[i], "-p") == 0)
987 preserve = 1;
988 else if (strcmp(argv[i], "-q") == 0)
989 statistics = 0;
990 else if (strcmp(argv[i], "-h") == 0 ||
991 strcmp(argv[i], "-?") == 0)
992 usage();
b8a19193 993 else if (strcmp(argv[i], "-P") == 0 && i+1 < argc)
994 portnumber = atoi(argv[++i]);
995 else if (strcmp(argv[i], "-pw") == 0 && i+1 < argc)
996 password = argv[++i];
cc87246d 997 else if (strcmp(argv[i], "-gui") == 0 && i+1 < argc) {
998 gui_hwnd = argv[++i];
999 gui_mode = 1;
1000 } else if (strcmp(argv[i], "-ls") == 0)
1001 list = 1;
c51a56e2 1002 else if (strcmp(argv[i], "--") == 0)
1003 { i++; break; }
07d9aa13 1004 else
c51a56e2 1005 usage();
1006 }
1007 argc -= i;
1008 argv += i;
1009
39ddf0ff 1010 if (list) {
1011 if (argc != 1)
1012 usage();
1013 get_dir_list(argc, argv);
c51a56e2 1014
39ddf0ff 1015 } else {
1016
1017 if (argc < 2)
1018 usage();
1019 if (argc > 2)
1020 targetshouldbedirectory = 1;
1021
1022 if (colon(argv[argc-1]) != NULL)
1023 toremote(argc, argv);
1024 else
1025 tolocal(argc, argv);
1026 }
c51a56e2 1027
1028 if (connection_open) {
1029 char ch;
fb09bf1c 1030 ssh_scp_send_eof();
1031 ssh_scp_recv(&ch, 1);
c51a56e2 1032 }
1033 WSACleanup();
1034 random_save_seed();
07d9aa13 1035
cc87246d 1036 /* GUI Adaptation - August 2000 */
1037 if (gui_mode) {
1038 unsigned int msg_id = WM_RET_ERR_CNT;
1039 if (list) msg_id = WM_LS_RET_ERR_CNT;
1040 while (!PostMessage( (HWND)atoi(gui_hwnd), msg_id, (WPARAM)errs, 0/*lParam*/ ) )
1041 SleepEx(1000,TRUE);
1042 }
c51a56e2 1043 return (errs == 0 ? 0 : 1);
07d9aa13 1044}
1045
1046/* end */