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