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