Fix the SCO function keys, which weren't quite right.
[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);
6e1ebb76 520 sfree(realhost);
07d9aa13 521}
522
07d9aa13 523/*
524 * Update statistic information about current file.
525 */
526static void print_stats(char *name, unsigned long size, unsigned long done,
32874aea 527 time_t start, time_t now)
07d9aa13 528{
c51a56e2 529 float ratebs;
530 unsigned long eta;
531 char etastr[10];
532 int pct;
533
cc87246d 534 /* GUI Adaptation - Sept 2000 */
535 if (gui_mode)
32874aea 536 gui_update_stats(name, size, (int) (100 * (done * 1.0 / size)),
537 (unsigned long) difftime(now, start));
cc87246d 538 else {
539 if (now > start)
540 ratebs = (float) done / (now - start);
541 else
542 ratebs = (float) done;
c51a56e2 543
cc87246d 544 if (ratebs < 1.0)
545 eta = size - done;
546 else
547 eta = (unsigned long) ((size - done) / ratebs);
548 sprintf(etastr, "%02ld:%02ld:%02ld",
549 eta / 3600, (eta % 3600) / 60, eta % 60);
c51a56e2 550
cc87246d 551 pct = (int) (100.0 * (float) done / size);
c51a56e2 552
cc87246d 553 printf("\r%-25.25s | %10ld kB | %5.1f kB/s | ETA: %8s | %3d%%",
32874aea 554 name, done / 1024, ratebs / 1024.0, etastr, pct);
c51a56e2 555
cc87246d 556 if (done == size)
557 printf("\n");
558 }
07d9aa13 559}
560
07d9aa13 561/*
562 * Find a colon in str and return a pointer to the colon.
39ddf0ff 563 * This is used to separate hostname from filename.
07d9aa13 564 */
32874aea 565static char *colon(char *str)
07d9aa13 566{
c51a56e2 567 /* We ignore a leading colon, since the hostname cannot be
32874aea 568 empty. We also ignore a colon as second character because
569 of filenames like f:myfile.txt. */
570 if (str[0] == '\0' || str[0] == ':' || str[1] == ':')
c51a56e2 571 return (NULL);
32874aea 572 while (*str != '\0' && *str != ':' && *str != '/' && *str != '\\')
c51a56e2 573 str++;
574 if (*str == ':')
575 return (str);
576 else
577 return (NULL);
07d9aa13 578}
579
07d9aa13 580/*
581 * Wait for a response from the other side.
582 * Return 0 if ok, -1 if error.
583 */
584static int response(void)
585{
c51a56e2 586 char ch, resp, rbuf[2048];
587 int p;
588
fb09bf1c 589 if (ssh_scp_recv(&resp, 1) <= 0)
c51a56e2 590 bump("Lost connection");
591
592 p = 0;
593 switch (resp) {
32874aea 594 case 0: /* ok */
c51a56e2 595 return (0);
596 default:
597 rbuf[p++] = resp;
598 /* fallthrough */
32874aea 599 case 1: /* error */
600 case 2: /* fatal error */
c51a56e2 601 do {
fb09bf1c 602 if (ssh_scp_recv(&ch, 1) <= 0)
c51a56e2 603 bump("Protocol error: Lost connection");
604 rbuf[p++] = ch;
605 } while (p < sizeof(rbuf) && ch != '\n');
32874aea 606 rbuf[p - 1] = '\0';
c51a56e2 607 if (resp == 1)
cc87246d 608 tell_user(stderr, "%s\n", rbuf);
c51a56e2 609 else
610 bump("%s", rbuf);
611 errs++;
612 return (-1);
613 }
07d9aa13 614}
615
07d9aa13 616/*
617 * Send an error message to the other side and to the screen.
618 * Increment error counter.
619 */
620static void run_err(const char *fmt, ...)
621{
c51a56e2 622 char str[2048];
623 va_list ap;
624 va_start(ap, fmt);
625 errs++;
9520eba8 626 strcpy(str, "scp: ");
32874aea 627 vsprintf(str + strlen(str), fmt, ap);
c51a56e2 628 strcat(str, "\n");
ff89646a 629 back->send("\001", 1); /* scp protocol error prefix */
3bdaf79d 630 back->send(str, strlen(str));
32874aea 631 tell_user(stderr, "%s", str);
c51a56e2 632 va_end(ap);
07d9aa13 633}
634
07d9aa13 635/*
636 * Execute the source part of the SCP protocol.
637 */
638static void source(char *src)
639{
c51a56e2 640 char buf[2048];
641 unsigned long size;
642 char *last;
643 HANDLE f;
644 DWORD attr;
645 unsigned long i;
646 unsigned long stat_bytes;
647 time_t stat_starttime, stat_lasttime;
648
649 attr = GetFileAttributes(src);
32874aea 650 if (attr == (DWORD) - 1) {
c51a56e2 651 run_err("%s: No such file or directory", src);
652 return;
653 }
654
655 if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
7f1f80de 656 if (recursive) {
32874aea 657 /*
658 * Avoid . and .. directories.
659 */
660 char *p;
661 p = strrchr(src, '/');
662 if (!p)
663 p = strrchr(src, '\\');
664 if (!p)
665 p = src;
666 else
667 p++;
668 if (!strcmp(p, ".") || !strcmp(p, ".."))
669 /* skip . and .. */ ;
670 else
671 rsource(src);
672 } else {
c51a56e2 673 run_err("%s: not a regular file", src);
32874aea 674 }
c51a56e2 675 return;
676 }
677
678 if ((last = strrchr(src, '/')) == NULL)
679 last = src;
680 else
681 last++;
682 if (strrchr(last, '\\') != NULL)
683 last = strrchr(last, '\\') + 1;
684 if (last == src && strchr(src, ':') != NULL)
685 last = strchr(src, ':') + 1;
686
687 f = CreateFile(src, GENERIC_READ, FILE_SHARE_READ, NULL,
688 OPEN_EXISTING, 0, 0);
689 if (f == INVALID_HANDLE_VALUE) {
486543a1 690 run_err("%s: Cannot open file", src);
c51a56e2 691 return;
692 }
693
694 if (preserve) {
695 FILETIME actime, wrtime;
696 unsigned long mtime, atime;
697 GetFileTime(f, NULL, &actime, &wrtime);
698 TIME_WIN_TO_POSIX(actime, atime);
699 TIME_WIN_TO_POSIX(wrtime, mtime);
700 sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
3bdaf79d 701 back->send(buf, strlen(buf));
07d9aa13 702 if (response())
c51a56e2 703 return;
704 }
705
706 size = GetFileSize(f, NULL);
707 sprintf(buf, "C0644 %lu %s\n", size, last);
708 if (verbose)
cc87246d 709 tell_user(stderr, "Sending file modes: %s", buf);
3bdaf79d 710 back->send(buf, strlen(buf));
c51a56e2 711 if (response())
712 return;
713
714 if (statistics) {
715 stat_bytes = 0;
716 stat_starttime = time(NULL);
717 stat_lasttime = 0;
718 }
719
720 for (i = 0; i < size; i += 4096) {
721 char transbuf[4096];
722 DWORD j, k = 4096;
32874aea 723 if (i + k > size)
724 k = size - i;
725 if (!ReadFile(f, transbuf, k, &j, NULL) || j != k) {
726 if (statistics)
727 printf("\n");
c51a56e2 728 bump("%s: Read error", src);
07d9aa13 729 }
3bdaf79d 730 back->send(transbuf, k);
c51a56e2 731 if (statistics) {
732 stat_bytes += k;
32874aea 733 if (time(NULL) != stat_lasttime || i + k == size) {
c51a56e2 734 stat_lasttime = time(NULL);
735 print_stats(last, size, stat_bytes,
736 stat_starttime, stat_lasttime);
737 }
07d9aa13 738 }
c51a56e2 739 }
740 CloseHandle(f);
07d9aa13 741
3bdaf79d 742 back->send("", 1);
c51a56e2 743 (void) response();
07d9aa13 744}
745
07d9aa13 746/*
747 * Recursively send the contents of a directory.
748 */
749static void rsource(char *src)
750{
c51a56e2 751 char buf[2048];
752 char *last;
753 HANDLE dir;
754 WIN32_FIND_DATA fdat;
755 int ok;
756
757 if ((last = strrchr(src, '/')) == NULL)
758 last = src;
759 else
760 last++;
761 if (strrchr(last, '\\') != NULL)
762 last = strrchr(last, '\\') + 1;
763 if (last == src && strchr(src, ':') != NULL)
764 last = strchr(src, ':') + 1;
765
766 /* maybe send filetime */
767
768 sprintf(buf, "D0755 0 %s\n", last);
769 if (verbose)
cc87246d 770 tell_user(stderr, "Entering directory: %s", buf);
3bdaf79d 771 back->send(buf, strlen(buf));
c51a56e2 772 if (response())
773 return;
774
775 sprintf(buf, "%s/*", src);
776 dir = FindFirstFile(buf, &fdat);
777 ok = (dir != INVALID_HANDLE_VALUE);
778 while (ok) {
779 if (strcmp(fdat.cFileName, ".") == 0 ||
780 strcmp(fdat.cFileName, "..") == 0) {
32874aea 781 } else if (strlen(src) + 1 + strlen(fdat.cFileName) >= sizeof(buf)) {
c51a56e2 782 run_err("%s/%s: Name too long", src, fdat.cFileName);
783 } else {
784 sprintf(buf, "%s/%s", src, fdat.cFileName);
785 source(buf);
07d9aa13 786 }
c51a56e2 787 ok = FindNextFile(dir, &fdat);
788 }
789 FindClose(dir);
07d9aa13 790
c51a56e2 791 sprintf(buf, "E\n");
3bdaf79d 792 back->send(buf, strlen(buf));
c51a56e2 793 (void) response();
07d9aa13 794}
795
07d9aa13 796/*
797 * Execute the sink part of the SCP protocol.
798 */
ca2d5943 799static void sink(char *targ, char *src)
07d9aa13 800{
c51a56e2 801 char buf[2048];
802 char namebuf[2048];
803 char ch;
804 int targisdir = 0;
996c8c3b 805 int settime;
c51a56e2 806 int exists;
807 DWORD attr;
808 HANDLE f;
809 unsigned long mtime, atime;
810 unsigned int mode;
811 unsigned long size, i;
812 int wrerror = 0;
813 unsigned long stat_bytes;
814 time_t stat_starttime, stat_lasttime;
815 char *stat_name;
816
817 attr = GetFileAttributes(targ);
32874aea 818 if (attr != (DWORD) - 1 && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
c51a56e2 819 targisdir = 1;
820
821 if (targetshouldbedirectory && !targisdir)
822 bump("%s: Not a directory", targ);
823
3bdaf79d 824 back->send("", 1);
c51a56e2 825 while (1) {
826 settime = 0;
32874aea 827 gottime:
fb09bf1c 828 if (ssh_scp_recv(&ch, 1) <= 0)
c51a56e2 829 return;
830 if (ch == '\n')
831 bump("Protocol error: Unexpected newline");
832 i = 0;
833 buf[i++] = ch;
834 do {
fb09bf1c 835 if (ssh_scp_recv(&ch, 1) <= 0)
c51a56e2 836 bump("Lost connection");
837 buf[i++] = ch;
838 } while (i < sizeof(buf) && ch != '\n');
32874aea 839 buf[i - 1] = '\0';
c51a56e2 840 switch (buf[0]) {
32874aea 841 case '\01': /* error */
842 tell_user(stderr, "%s\n", buf + 1);
c51a56e2 843 errs++;
844 continue;
32874aea 845 case '\02': /* fatal error */
846 bump("%s", buf + 1);
c51a56e2 847 case 'E':
3bdaf79d 848 back->send("", 1);
c51a56e2 849 return;
850 case 'T':
32874aea 851 if (sscanf(buf, "T%ld %*d %ld %*d", &mtime, &atime) == 2) {
c51a56e2 852 settime = 1;
3bdaf79d 853 back->send("", 1);
c51a56e2 854 goto gottime;
855 }
856 bump("Protocol error: Illegal time format");
857 case 'C':
858 case 'D':
859 break;
860 default:
861 bump("Protocol error: Expected control record");
862 }
07d9aa13 863
32874aea 864 if (sscanf(buf + 1, "%u %lu %[^\n]", &mode, &size, namebuf) != 3)
c51a56e2 865 bump("Protocol error: Illegal file descriptor format");
ca2d5943 866 /* Security fix: ensure the file ends up where we asked for it. */
c51a56e2 867 if (targisdir) {
868 char t[2048];
9520eba8 869 char *p;
c51a56e2 870 strcpy(t, targ);
871 if (targ[0] != '\0')
872 strcat(t, "/");
9520eba8 873 p = namebuf + strlen(namebuf);
874 while (p > namebuf && p[-1] != '/' && p[-1] != '\\')
875 p--;
876 strcat(t, p);
c51a56e2 877 strcpy(namebuf, t);
878 } else {
879 strcpy(namebuf, targ);
880 }
881 attr = GetFileAttributes(namebuf);
32874aea 882 exists = (attr != (DWORD) - 1);
c51a56e2 883
884 if (buf[0] == 'D') {
885 if (exists && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
886 run_err("%s: Not a directory", namebuf);
887 continue;
888 }
889 if (!exists) {
32874aea 890 if (!CreateDirectory(namebuf, NULL)) {
891 run_err("%s: Cannot create directory", namebuf);
c51a56e2 892 continue;
893 }
894 }
ca2d5943 895 sink(namebuf, NULL);
c51a56e2 896 /* can we set the timestamp for directories ? */
897 continue;
898 }
07d9aa13 899
c51a56e2 900 f = CreateFile(namebuf, GENERIC_WRITE, 0, NULL,
901 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
902 if (f == INVALID_HANDLE_VALUE) {
903 run_err("%s: Cannot create file", namebuf);
904 continue;
905 }
07d9aa13 906
3bdaf79d 907 back->send("", 1);
07d9aa13 908
c51a56e2 909 if (statistics) {
910 stat_bytes = 0;
911 stat_starttime = time(NULL);
912 stat_lasttime = 0;
913 if ((stat_name = strrchr(namebuf, '/')) == NULL)
914 stat_name = namebuf;
915 else
916 stat_name++;
917 if (strrchr(stat_name, '\\') != NULL)
918 stat_name = strrchr(stat_name, '\\') + 1;
919 }
07d9aa13 920
c51a56e2 921 for (i = 0; i < size; i += 4096) {
922 char transbuf[4096];
996c8c3b 923 DWORD j, k = 4096;
32874aea 924 if (i + k > size)
925 k = size - i;
fb09bf1c 926 if (ssh_scp_recv(transbuf, k) == 0)
c51a56e2 927 bump("Lost connection");
32874aea 928 if (wrerror)
929 continue;
930 if (!WriteFile(f, transbuf, k, &j, NULL) || j != k) {
c51a56e2 931 wrerror = 1;
932 if (statistics)
933 printf("\r%-25.25s | %50s\n",
934 stat_name,
935 "Write error.. waiting for end of file");
936 continue;
937 }
938 if (statistics) {
939 stat_bytes += k;
32874aea 940 if (time(NULL) > stat_lasttime || i + k == size) {
c51a56e2 941 stat_lasttime = time(NULL);
942 print_stats(stat_name, size, stat_bytes,
943 stat_starttime, stat_lasttime);
07d9aa13 944 }
c51a56e2 945 }
946 }
947 (void) response();
07d9aa13 948
c51a56e2 949 if (settime) {
950 FILETIME actime, wrtime;
951 TIME_POSIX_TO_WIN(atime, actime);
952 TIME_POSIX_TO_WIN(mtime, wrtime);
953 SetFileTime(f, NULL, &actime, &wrtime);
07d9aa13 954 }
07d9aa13 955
c51a56e2 956 CloseHandle(f);
957 if (wrerror) {
958 run_err("%s: Write error", namebuf);
959 continue;
960 }
3bdaf79d 961 back->send("", 1);
c51a56e2 962 }
963}
07d9aa13 964
965/*
966 * We will copy local files to a remote server.
967 */
968static void toremote(int argc, char *argv[])
969{
c51a56e2 970 char *src, *targ, *host, *user;
971 char *cmd;
972 int i;
973
32874aea 974 targ = argv[argc - 1];
c51a56e2 975
39ddf0ff 976 /* Separate host from filename */
c51a56e2 977 host = targ;
978 targ = colon(targ);
979 if (targ == NULL)
980 bump("targ == NULL in toremote()");
981 *targ++ = '\0';
982 if (*targ == '\0')
983 targ = ".";
984 /* Substitute "." for emtpy target */
985
39ddf0ff 986 /* Separate host and username */
c51a56e2 987 user = host;
988 host = strrchr(host, '@');
989 if (host == NULL) {
990 host = user;
991 user = NULL;
992 } else {
993 *host++ = '\0';
994 if (*user == '\0')
995 user = NULL;
996 }
997
998 if (argc == 2) {
999 /* Find out if the source filespec covers multiple files
32874aea 1000 if so, we should set the targetshouldbedirectory flag */
c51a56e2 1001 HANDLE fh;
1002 WIN32_FIND_DATA fdat;
1003 if (colon(argv[0]) != NULL)
1004 bump("%s: Remote to remote not supported", argv[0]);
1005 fh = FindFirstFile(argv[0], &fdat);
1006 if (fh == INVALID_HANDLE_VALUE)
1007 bump("%s: No such file or directory\n", argv[0]);
1008 if (FindNextFile(fh, &fdat))
1009 targetshouldbedirectory = 1;
1010 FindClose(fh);
1011 }
1012
1013 cmd = smalloc(strlen(targ) + 100);
1014 sprintf(cmd, "scp%s%s%s%s -t %s",
1015 verbose ? " -v" : "",
1016 recursive ? " -r" : "",
1017 preserve ? " -p" : "",
32874aea 1018 targetshouldbedirectory ? " -d" : "", targ);
c51a56e2 1019 do_cmd(host, user, cmd);
1020 sfree(cmd);
1021
1022 (void) response();
1023
1024 for (i = 0; i < argc - 1; i++) {
1025 HANDLE dir;
1026 WIN32_FIND_DATA fdat;
1027 src = argv[i];
1028 if (colon(src) != NULL) {
cc87246d 1029 tell_user(stderr, "%s: Remote to remote not supported\n", src);
c51a56e2 1030 errs++;
1031 continue;
07d9aa13 1032 }
c51a56e2 1033 dir = FindFirstFile(src, &fdat);
1034 if (dir == INVALID_HANDLE_VALUE) {
1035 run_err("%s: No such file or directory", src);
1036 continue;
07d9aa13 1037 }
c51a56e2 1038 do {
1039 char *last;
1040 char namebuf[2048];
7f266ffb 1041 /*
1042 * Ensure that . and .. are never matched by wildcards,
1043 * but only by deliberate action.
1044 */
1045 if (!strcmp(fdat.cFileName, ".") ||
1046 !strcmp(fdat.cFileName, "..")) {
1047 /*
1048 * Find*File has returned a special dir. We require
1049 * that _either_ `src' ends in a backslash followed
1050 * by that string, _or_ `src' is precisely that
1051 * string.
1052 */
1053 int len = strlen(src), dlen = strlen(fdat.cFileName);
1054 if (len == dlen && !strcmp(src, fdat.cFileName)) {
32874aea 1055 /* ok */ ;
1056 } else if (len > dlen + 1 && src[len - dlen - 1] == '\\' &&
1057 !strcmp(src + len - dlen, fdat.cFileName)) {
1058 /* ok */ ;
7f266ffb 1059 } else
1060 continue; /* ignore this one */
1061 }
32874aea 1062 if (strlen(src) + strlen(fdat.cFileName) >= sizeof(namebuf)) {
cc87246d 1063 tell_user(stderr, "%s: Name too long", src);
c51a56e2 1064 continue;
1065 }
1066 strcpy(namebuf, src);
1067 if ((last = strrchr(namebuf, '/')) == NULL)
1068 last = namebuf;
1069 else
1070 last++;
1071 if (strrchr(last, '\\') != NULL)
1072 last = strrchr(last, '\\') + 1;
1073 if (last == namebuf && strrchr(namebuf, ':') != NULL)
1074 last = strchr(namebuf, ':') + 1;
1075 strcpy(last, fdat.cFileName);
1076 source(namebuf);
1077 } while (FindNextFile(dir, &fdat));
1078 FindClose(dir);
1079 }
07d9aa13 1080}
1081
07d9aa13 1082/*
1083 * We will copy files from a remote server to the local machine.
1084 */
1085static void tolocal(int argc, char *argv[])
1086{
c51a56e2 1087 char *src, *targ, *host, *user;
1088 char *cmd;
1089
1090 if (argc != 2)
1091 bump("More than one remote source not supported");
1092
1093 src = argv[0];
1094 targ = argv[1];
1095
39ddf0ff 1096 /* Separate host from filename */
c51a56e2 1097 host = src;
1098 src = colon(src);
1099 if (src == NULL)
1100 bump("Local to local copy not supported");
1101 *src++ = '\0';
1102 if (*src == '\0')
1103 src = ".";
1104 /* Substitute "." for empty filename */
1105
39ddf0ff 1106 /* Separate username and hostname */
c51a56e2 1107 user = host;
1108 host = strrchr(host, '@');
1109 if (host == NULL) {
1110 host = user;
1111 user = NULL;
1112 } else {
1113 *host++ = '\0';
1114 if (*user == '\0')
1115 user = NULL;
1116 }
1117
1118 cmd = smalloc(strlen(src) + 100);
1119 sprintf(cmd, "scp%s%s%s%s -f %s",
1120 verbose ? " -v" : "",
1121 recursive ? " -r" : "",
1122 preserve ? " -p" : "",
32874aea 1123 targetshouldbedirectory ? " -d" : "", src);
c51a56e2 1124 do_cmd(host, user, cmd);
1125 sfree(cmd);
1126
ca2d5943 1127 sink(targ, src);
07d9aa13 1128}
1129
07d9aa13 1130/*
39ddf0ff 1131 * We will issue a list command to get a remote directory.
1132 */
1133static void get_dir_list(int argc, char *argv[])
1134{
1135 char *src, *host, *user;
1136 char *cmd, *p, *q;
1137 char c;
1138
1139 src = argv[0];
1140
1141 /* Separate host from filename */
1142 host = src;
1143 src = colon(src);
1144 if (src == NULL)
1145 bump("Local to local copy not supported");
1146 *src++ = '\0';
1147 if (*src == '\0')
1148 src = ".";
1149 /* Substitute "." for empty filename */
1150
1151 /* Separate username and hostname */
1152 user = host;
1153 host = strrchr(host, '@');
1154 if (host == NULL) {
1155 host = user;
1156 user = NULL;
1157 } else {
1158 *host++ = '\0';
1159 if (*user == '\0')
1160 user = NULL;
1161 }
1162
32874aea 1163 cmd = smalloc(4 * strlen(src) + 100);
39ddf0ff 1164 strcpy(cmd, "ls -la '");
1165 p = cmd + strlen(cmd);
1166 for (q = src; *q; q++) {
1167 if (*q == '\'') {
32874aea 1168 *p++ = '\'';
1169 *p++ = '\\';
1170 *p++ = '\'';
1171 *p++ = '\'';
39ddf0ff 1172 } else {
1173 *p++ = *q;
1174 }
1175 }
1176 *p++ = '\'';
1177 *p = '\0';
cc87246d 1178
39ddf0ff 1179 do_cmd(host, user, cmd);
1180 sfree(cmd);
1181
fb09bf1c 1182 while (ssh_scp_recv(&c, 1) > 0)
cc87246d 1183 tell_char(stdout, c);
39ddf0ff 1184}
1185
1186/*
07d9aa13 1187 * Initialize the Win$ock driver.
1188 */
996c8c3b 1189static void init_winsock(void)
07d9aa13 1190{
c51a56e2 1191 WORD winsock_ver;
1192 WSADATA wsadata;
1193
1194 winsock_ver = MAKEWORD(1, 1);
1195 if (WSAStartup(winsock_ver, &wsadata))
1196 bump("Unable to initialise WinSock");
32874aea 1197 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1)
c51a56e2 1198 bump("WinSock version is incompatible with 1.1");
07d9aa13 1199}
1200
07d9aa13 1201/*
1202 * Short description of parameters.
1203 */
996c8c3b 1204static void usage(void)
07d9aa13 1205{
c51a56e2 1206 printf("PuTTY Secure Copy client\n");
1207 printf("%s\n", ver);
a3e55ea1 1208 printf("Usage: pscp [options] [user@]host:source target\n");
32874aea 1209 printf
1210 (" pscp [options] source [source...] [user@]host:target\n");
a3e55ea1 1211 printf(" pscp [options] -ls user@host:filespec\n");
b8a19193 1212 printf("Options:\n");
1213 printf(" -p preserve file attributes\n");
1214 printf(" -q quiet, don't show statistics\n");
1215 printf(" -r copy directories recursively\n");
1216 printf(" -v show verbose messages\n");
1217 printf(" -P port connect to specified port\n");
1218 printf(" -pw passw login with specified password\n");
ee8b0370 1219#if 0
1220 /*
1221 * -gui is an internal option, used by GUI front ends to get
1222 * pscp to pass progress reports back to them. It's not an
1223 * ordinary user-accessible option, so it shouldn't be part of
1224 * the command-line help. The only people who need to know
1225 * about it are programmers, and they can read the source.
1226 */
32874aea 1227 printf
1228 (" -gui hWnd GUI mode with the windows handle for receiving messages\n");
ee8b0370 1229#endif
c51a56e2 1230 exit(1);
07d9aa13 1231}
1232
07d9aa13 1233/*
1234 * Main program (no, really?)
1235 */
1236int main(int argc, char *argv[])
1237{
c51a56e2 1238 int i;
39ddf0ff 1239 int list = 0;
c51a56e2 1240
fb09bf1c 1241 default_protocol = PROT_TELNET;
1242
67779be7 1243 flags = FLAG_STDERR;
fa17a66e 1244 ssh_get_line = &get_line;
c51a56e2 1245 init_winsock();
8df7a775 1246 sk_init();
c51a56e2 1247
1248 for (i = 1; i < argc; i++) {
1249 if (argv[i][0] != '-')
1250 break;
1251 if (strcmp(argv[i], "-v") == 0)
4017be6d 1252 verbose = 1, flags |= FLAG_VERBOSE;
c51a56e2 1253 else if (strcmp(argv[i], "-r") == 0)
1254 recursive = 1;
1255 else if (strcmp(argv[i], "-p") == 0)
1256 preserve = 1;
1257 else if (strcmp(argv[i], "-q") == 0)
1258 statistics = 0;
32874aea 1259 else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-?") == 0)
c51a56e2 1260 usage();
32874aea 1261 else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc)
b8a19193 1262 portnumber = atoi(argv[++i]);
32874aea 1263 else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc)
b8a19193 1264 password = argv[++i];
32874aea 1265 else if (strcmp(argv[i], "-gui") == 0 && i + 1 < argc) {
cc87246d 1266 gui_hwnd = argv[++i];
1267 gui_mode = 1;
1268 } else if (strcmp(argv[i], "-ls") == 0)
32874aea 1269 list = 1;
1270 else if (strcmp(argv[i], "--") == 0) {
1271 i++;
1272 break;
1273 } else
c51a56e2 1274 usage();
1275 }
1276 argc -= i;
1277 argv += i;
eba78553 1278 back = NULL;
c51a56e2 1279
39ddf0ff 1280 if (list) {
1281 if (argc != 1)
1282 usage();
1283 get_dir_list(argc, argv);
c51a56e2 1284
39ddf0ff 1285 } else {
1286
1287 if (argc < 2)
1288 usage();
1289 if (argc > 2)
1290 targetshouldbedirectory = 1;
1291
32874aea 1292 if (colon(argv[argc - 1]) != NULL)
39ddf0ff 1293 toremote(argc, argv);
1294 else
1295 tolocal(argc, argv);
1296 }
c51a56e2 1297
eba78553 1298 if (back != NULL && back->socket() != NULL) {
c51a56e2 1299 char ch;
3bdaf79d 1300 back->special(TS_EOF);
fb09bf1c 1301 ssh_scp_recv(&ch, 1);
c51a56e2 1302 }
1303 WSACleanup();
1304 random_save_seed();
07d9aa13 1305
cc87246d 1306 /* GUI Adaptation - August 2000 */
1307 if (gui_mode) {
1308 unsigned int msg_id = WM_RET_ERR_CNT;
32874aea 1309 if (list)
1310 msg_id = WM_LS_RET_ERR_CNT;
1311 while (!PostMessage
1312 ((HWND) atoi(gui_hwnd), msg_id, (WPARAM) errs,
1313 0 /*lParam */ ))SleepEx(1000, TRUE);
cc87246d 1314 }
c51a56e2 1315 return (errs == 0 ? 0 : 1);
07d9aa13 1316}
1317
1318/* end */