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