]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - scp.c
Minor modification: in remote->local non-recursive mode matching a
[PuTTY.git] / scp.c
1 /*
2  *  scp.c  -  Scp (Secure Copy) client for PuTTY.
3  *  Joris van Rantwijk, Simon Tatham
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.
7  *
8  *  Adaptations to enable connecting a GUI by L. Gunnarsson - Sept 2000
9  */
10
11 #include <windows.h>
12 #ifndef AUTO_WINSOCK
13 #ifdef WINSOCK_TWO
14 #include <winsock2.h>
15 #else
16 #include <winsock.h>
17 #endif
18 #endif
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <limits.h>
23 #include <time.h>
24 #include <assert.h>
25 /* GUI Adaptation - Sept 2000 */
26 #include <winuser.h>
27 #include <winbase.h>
28
29 #define PUTTY_DO_GLOBALS
30 #include "putty.h"
31 #include "ssh.h"
32 #include "sftp.h"
33 #include "winstuff.h"
34 #include "storage.h"
35
36 #define TIME_POSIX_TO_WIN(t, ft) (*(LONGLONG*)&(ft) = \
37         ((LONGLONG) (t) + (LONGLONG) 11644473600) * (LONGLONG) 10000000)
38 #define TIME_WIN_TO_POSIX(ft, t) ((t) = (unsigned long) \
39         ((*(LONGLONG*)&(ft)) / (LONGLONG) 10000000 - (LONGLONG) 11644473600))
40
41 /* GUI Adaptation - Sept 2000 */
42 #define   WM_APP_BASE           0x8000
43 #define   WM_STD_OUT_CHAR       ( WM_APP_BASE+400 )
44 #define   WM_STD_ERR_CHAR       ( WM_APP_BASE+401 )
45 #define   WM_STATS_CHAR         ( WM_APP_BASE+402 )
46 #define   WM_STATS_SIZE         ( WM_APP_BASE+403 )
47 #define   WM_STATS_PERCENT      ( WM_APP_BASE+404 )
48 #define   WM_STATS_ELAPSED      ( WM_APP_BASE+405 )
49 #define   WM_RET_ERR_CNT        ( WM_APP_BASE+406 )
50 #define   WM_LS_RET_ERR_CNT     ( WM_APP_BASE+407 )
51
52 static int list = 0;
53 static int verbose = 0;
54 static int recursive = 0;
55 static int preserve = 0;
56 static int targetshouldbedirectory = 0;
57 static int statistics = 1;
58 static int portnumber = 0;
59 static int prev_stats_len = 0;
60 static char *password = NULL;
61 static int errs = 0;
62 /* GUI Adaptation - Sept 2000 */
63 #define NAME_STR_MAX 2048
64 static char statname[NAME_STR_MAX + 1];
65 static unsigned long statsize = 0;
66 static int statperct = 0;
67 static unsigned long statelapsed = 0;
68 static int gui_mode = 0;
69 static char *gui_hwnd = NULL;
70 static int using_sftp = 0;
71
72 static void source(char *src);
73 static void rsource(char *src);
74 static void sink(char *targ, char *src);
75 /* GUI Adaptation - Sept 2000 */
76 static void tell_char(FILE * stream, char c);
77 static void tell_str(FILE * stream, char *str);
78 static void tell_user(FILE * stream, char *fmt, ...);
79 static void gui_update_stats(char *name, unsigned long size,
80                              int percentage, unsigned long elapsed);
81
82 /*
83  * The maximum amount of queued data we accept before we stop and
84  * wait for the server to process some.
85  */
86 #define MAX_SCP_BUFSIZE 16384
87
88 void logevent(char *string)
89 {
90 }
91
92 void ldisc_send(char *buf, int len)
93 {
94     /*
95      * This is only here because of the calls to ldisc_send(NULL,
96      * 0) in ssh.c. Nothing in PSCP actually needs to use the ldisc
97      * as an ldisc. So if we get called with any real data, I want
98      * to know about it.
99      */
100     assert(len == 0);
101 }
102
103 void verify_ssh_host_key(char *host, int port, char *keytype,
104                          char *keystr, char *fingerprint)
105 {
106     int ret;
107     HANDLE hin;
108     DWORD savemode, i;
109
110     static const char absentmsg[] =
111         "The server's host key is not cached in the registry. You\n"
112         "have no guarantee that the server is the computer you\n"
113         "think it is.\n"
114         "The server's key fingerprint is:\n"
115         "%s\n"
116         "If you trust this host, enter \"y\" to add the key to\n"
117         "PuTTY's cache and carry on connecting.\n"
118         "If you want to carry on connecting just once, without\n"
119         "adding the key to the cache, enter \"n\".\n"
120         "If you do not trust this host, press Return to abandon the\n"
121         "connection.\n"
122         "Store key in cache? (y/n) ";
123
124     static const char wrongmsg[] =
125         "WARNING - POTENTIAL SECURITY BREACH!\n"
126         "The server's host key does not match the one PuTTY has\n"
127         "cached in the registry. This means that either the\n"
128         "server administrator has changed the host key, or you\n"
129         "have actually connected to another computer pretending\n"
130         "to be the server.\n"
131         "The new key fingerprint is:\n"
132         "%s\n"
133         "If you were expecting this change and trust the new key,\n"
134         "enter \"y\" to update PuTTY's cache and continue connecting.\n"
135         "If you want to carry on connecting but without updating\n"
136         "the cache, enter \"n\".\n"
137         "If you want to abandon the connection completely, press\n"
138         "Return to cancel. Pressing Return is the ONLY guaranteed\n"
139         "safe choice.\n"
140         "Update cached key? (y/n, Return cancels connection) ";
141
142     static const char abandoned[] = "Connection abandoned.\n";
143
144     char line[32];
145
146     /*
147      * Verify the key against the registry.
148      */
149     ret = verify_host_key(host, port, keytype, keystr);
150
151     if (ret == 0)                      /* success - key matched OK */
152         return;
153
154     if (ret == 2) {                    /* key was different */
155         fprintf(stderr, wrongmsg, fingerprint);
156         fflush(stderr);
157     }
158     if (ret == 1) {                    /* key was absent */
159         fprintf(stderr, absentmsg, fingerprint);
160         fflush(stderr);
161     }
162
163     hin = GetStdHandle(STD_INPUT_HANDLE);
164     GetConsoleMode(hin, &savemode);
165     SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
166                          ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
167     ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
168     SetConsoleMode(hin, savemode);
169
170     if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
171         if (line[0] == 'y' || line[0] == 'Y')
172             store_host_key(host, port, keytype, keystr);
173     } else {
174         fprintf(stderr, abandoned);
175         exit(0);
176     }
177 }
178
179 /*
180  * Ask whether the selected cipher is acceptable (since it was
181  * below the configured 'warn' threshold).
182  * cs: 0 = both ways, 1 = client->server, 2 = server->client
183  */
184 void askcipher(char *ciphername, int cs)
185 {
186     HANDLE hin;
187     DWORD savemode, i;
188
189     static const char msg[] =
190         "The first %scipher supported by the server is\n"
191         "%s, which is below the configured warning threshold.\n"
192         "Continue with connection? (y/n) ";
193     static const char abandoned[] = "Connection abandoned.\n";
194
195     char line[32];
196
197     fprintf(stderr, msg,
198             (cs == 0) ? "" :
199             (cs == 1) ? "client-to-server " :
200                         "server-to-client ",
201             ciphername);
202     fflush(stderr);
203
204     hin = GetStdHandle(STD_INPUT_HANDLE);
205     GetConsoleMode(hin, &savemode);
206     SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
207                          ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
208     ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
209     SetConsoleMode(hin, savemode);
210
211     if (line[0] == 'y' || line[0] == 'Y') {
212         return;
213     } else {
214         fprintf(stderr, abandoned);
215         exit(0);
216     }
217 }
218
219 /* GUI Adaptation - Sept 2000 */
220 static void send_msg(HWND h, UINT message, WPARAM wParam)
221 {
222     while (!PostMessage(h, message, wParam, 0))
223         SleepEx(1000, TRUE);
224 }
225
226 static void tell_char(FILE * stream, char c)
227 {
228     if (!gui_mode)
229         fputc(c, stream);
230     else {
231         unsigned int msg_id = WM_STD_OUT_CHAR;
232         if (stream == stderr)
233             msg_id = WM_STD_ERR_CHAR;
234         send_msg((HWND) atoi(gui_hwnd), msg_id, (WPARAM) c);
235     }
236 }
237
238 static void tell_str(FILE * stream, char *str)
239 {
240     unsigned int i;
241
242     for (i = 0; i < strlen(str); ++i)
243         tell_char(stream, str[i]);
244 }
245
246 static void tell_user(FILE * stream, char *fmt, ...)
247 {
248     char str[0x100];                   /* Make the size big enough */
249     va_list ap;
250     va_start(ap, fmt);
251     vsprintf(str, fmt, ap);
252     va_end(ap);
253     strcat(str, "\n");
254     tell_str(stream, str);
255 }
256
257 static void gui_update_stats(char *name, unsigned long size,
258                              int percentage, unsigned long elapsed)
259 {
260     unsigned int i;
261
262     if (strcmp(name, statname) != 0) {
263         for (i = 0; i < strlen(name); ++i)
264             send_msg((HWND) atoi(gui_hwnd), WM_STATS_CHAR,
265                      (WPARAM) name[i]);
266         send_msg((HWND) atoi(gui_hwnd), WM_STATS_CHAR, (WPARAM) '\n');
267         strcpy(statname, name);
268     }
269     if (statsize != size) {
270         send_msg((HWND) atoi(gui_hwnd), WM_STATS_SIZE, (WPARAM) size);
271         statsize = size;
272     }
273     if (statelapsed != elapsed) {
274         send_msg((HWND) atoi(gui_hwnd), WM_STATS_ELAPSED,
275                  (WPARAM) elapsed);
276         statelapsed = elapsed;
277     }
278     if (statperct != percentage) {
279         send_msg((HWND) atoi(gui_hwnd), WM_STATS_PERCENT,
280                  (WPARAM) percentage);
281         statperct = percentage;
282     }
283 }
284
285 /*
286  *  Print an error message and perform a fatal exit.
287  */
288 void fatalbox(char *fmt, ...)
289 {
290     char str[0x100];                   /* Make the size big enough */
291     va_list ap;
292     va_start(ap, fmt);
293     strcpy(str, "Fatal: ");
294     vsprintf(str + strlen(str), fmt, ap);
295     va_end(ap);
296     strcat(str, "\n");
297     tell_str(stderr, str);
298     errs++;
299
300     if (gui_mode) {
301         unsigned int msg_id = WM_RET_ERR_CNT;
302         if (list)
303             msg_id = WM_LS_RET_ERR_CNT;
304         while (!PostMessage
305                ((HWND) atoi(gui_hwnd), msg_id, (WPARAM) errs,
306                 0 /*lParam */ ))SleepEx(1000, TRUE);
307     }
308
309     exit(1);
310 }
311 void connection_fatal(char *fmt, ...)
312 {
313     char str[0x100];                   /* Make the size big enough */
314     va_list ap;
315     va_start(ap, fmt);
316     strcpy(str, "Fatal: ");
317     vsprintf(str + strlen(str), fmt, ap);
318     va_end(ap);
319     strcat(str, "\n");
320     tell_str(stderr, str);
321     errs++;
322
323     if (gui_mode) {
324         unsigned int msg_id = WM_RET_ERR_CNT;
325         if (list)
326             msg_id = WM_LS_RET_ERR_CNT;
327         while (!PostMessage
328                ((HWND) atoi(gui_hwnd), msg_id, (WPARAM) errs,
329                 0 /*lParam */ ))SleepEx(1000, TRUE);
330     }
331
332     exit(1);
333 }
334
335 /*
336  * Be told what socket we're supposed to be using.
337  */
338 static SOCKET scp_ssh_socket;
339 char *do_select(SOCKET skt, int startup)
340 {
341     if (startup)
342         scp_ssh_socket = skt;
343     else
344         scp_ssh_socket = INVALID_SOCKET;
345     return NULL;
346 }
347 extern int select_result(WPARAM, LPARAM);
348
349 /*
350  * Receive a block of data from the SSH link. Block until all data
351  * is available.
352  *
353  * To do this, we repeatedly call the SSH protocol module, with our
354  * own trap in from_backend() to catch the data that comes back. We
355  * do this until we have enough data.
356  */
357
358 static unsigned char *outptr;          /* where to put the data */
359 static unsigned outlen;                /* how much data required */
360 static unsigned char *pending = NULL;  /* any spare data */
361 static unsigned pendlen = 0, pendsize = 0;      /* length and phys. size of buffer */
362 int from_backend(int is_stderr, char *data, int datalen)
363 {
364     unsigned char *p = (unsigned char *) data;
365     unsigned len = (unsigned) datalen;
366
367     /*
368      * stderr data is just spouted to local stderr and otherwise
369      * ignored.
370      */
371     if (is_stderr) {
372         fwrite(data, 1, len, stderr);
373         return 0;
374     }
375
376     inbuf_head = 0;
377
378     /*
379      * If this is before the real session begins, just return.
380      */
381     if (!outptr)
382         return 0;
383
384     if (outlen > 0) {
385         unsigned used = outlen;
386         if (used > len)
387             used = len;
388         memcpy(outptr, p, used);
389         outptr += used;
390         outlen -= used;
391         p += used;
392         len -= used;
393     }
394
395     if (len > 0) {
396         if (pendsize < pendlen + len) {
397             pendsize = pendlen + len + 4096;
398             pending = (pending ? srealloc(pending, pendsize) :
399                        smalloc(pendsize));
400             if (!pending)
401                 fatalbox("Out of memory");
402         }
403         memcpy(pending + pendlen, p, len);
404         pendlen += len;
405     }
406
407     return 0;
408 }
409 static int scp_process_network_event(void)
410 {
411     fd_set readfds;
412
413     FD_ZERO(&readfds);
414     FD_SET(scp_ssh_socket, &readfds);
415     if (select(1, &readfds, NULL, NULL, NULL) < 0)
416         return 0;                      /* doom */
417     select_result((WPARAM) scp_ssh_socket, (LPARAM) FD_READ);
418     return 1;
419 }
420 static int ssh_scp_recv(unsigned char *buf, int len)
421 {
422     outptr = buf;
423     outlen = len;
424
425     /*
426      * See if the pending-input block contains some of what we
427      * need.
428      */
429     if (pendlen > 0) {
430         unsigned pendused = pendlen;
431         if (pendused > outlen)
432             pendused = outlen;
433         memcpy(outptr, pending, pendused);
434         memmove(pending, pending + pendused, pendlen - pendused);
435         outptr += pendused;
436         outlen -= pendused;
437         pendlen -= pendused;
438         if (pendlen == 0) {
439             pendsize = 0;
440             sfree(pending);
441             pending = NULL;
442         }
443         if (outlen == 0)
444             return len;
445     }
446
447     while (outlen > 0) {
448         if (!scp_process_network_event())
449             return 0;                  /* doom */
450     }
451
452     return len;
453 }
454
455 /*
456  * Loop through the ssh connection and authentication process.
457  */
458 static void ssh_scp_init(void)
459 {
460     if (scp_ssh_socket == INVALID_SOCKET)
461         return;
462     while (!back->sendok()) {
463         fd_set readfds;
464         FD_ZERO(&readfds);
465         FD_SET(scp_ssh_socket, &readfds);
466         if (select(1, &readfds, NULL, NULL, NULL) < 0)
467             return;                    /* doom */
468         select_result((WPARAM) scp_ssh_socket, (LPARAM) FD_READ);
469     }
470     using_sftp = !ssh_fallback_cmd;
471 }
472
473 /*
474  *  Print an error message and exit after closing the SSH link.
475  */
476 static void bump(char *fmt, ...)
477 {
478     char str[0x100];                   /* Make the size big enough */
479     va_list ap;
480     va_start(ap, fmt);
481     strcpy(str, "Fatal: ");
482     vsprintf(str + strlen(str), fmt, ap);
483     va_end(ap);
484     strcat(str, "\n");
485     tell_str(stderr, str);
486     errs++;
487
488     if (back != NULL && back->socket() != NULL) {
489         char ch;
490         back->special(TS_EOF);
491         ssh_scp_recv(&ch, 1);
492     }
493
494     if (gui_mode) {
495         unsigned int msg_id = WM_RET_ERR_CNT;
496         if (list)
497             msg_id = WM_LS_RET_ERR_CNT;
498         while (!PostMessage
499                ((HWND) atoi(gui_hwnd), msg_id, (WPARAM) errs,
500                 0 /*lParam */ ))SleepEx(1000, TRUE);
501     }
502
503     exit(1);
504 }
505
506 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
507 {
508     HANDLE hin, hout;
509     DWORD savemode, newmode, i;
510
511     if (is_pw && password) {
512         static int tried_once = 0;
513
514         if (tried_once) {
515             return 0;
516         } else {
517             strncpy(str, password, maxlen);
518             str[maxlen - 1] = '\0';
519             tried_once = 1;
520             return 1;
521         }
522     }
523
524     /* GUI Adaptation - Sept 2000 */
525     if (gui_mode) {
526         if (maxlen > 0)
527             str[0] = '\0';
528     } else {
529         hin = GetStdHandle(STD_INPUT_HANDLE);
530         hout = GetStdHandle(STD_OUTPUT_HANDLE);
531         if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE)
532             bump("Cannot get standard input/output handles");
533
534         GetConsoleMode(hin, &savemode);
535         newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
536         if (is_pw)
537             newmode &= ~ENABLE_ECHO_INPUT;
538         else
539             newmode |= ENABLE_ECHO_INPUT;
540         SetConsoleMode(hin, newmode);
541
542         WriteFile(hout, prompt, strlen(prompt), &i, NULL);
543         ReadFile(hin, str, maxlen - 1, &i, NULL);
544
545         SetConsoleMode(hin, savemode);
546
547         if ((int) i > maxlen)
548             i = maxlen - 1;
549         else
550             i = i - 2;
551         str[i] = '\0';
552
553         if (is_pw)
554             WriteFile(hout, "\r\n", 2, &i, NULL);
555     }
556
557     return 1;
558 }
559
560 /*
561  *  Open an SSH connection to user@host and execute cmd.
562  */
563 static void do_cmd(char *host, char *user, char *cmd)
564 {
565     char *err, *realhost;
566     DWORD namelen;
567
568     if (host == NULL || host[0] == '\0')
569         bump("Empty host name");
570
571     /* Try to load settings for this host */
572     do_defaults(host, &cfg);
573     if (cfg.host[0] == '\0') {
574         /* No settings for this host; use defaults */
575         do_defaults(NULL, &cfg);
576         strncpy(cfg.host, host, sizeof(cfg.host) - 1);
577         cfg.host[sizeof(cfg.host) - 1] = '\0';
578         cfg.port = 22;
579     }
580
581     /* Set username */
582     if (user != NULL && user[0] != '\0') {
583         strncpy(cfg.username, user, sizeof(cfg.username) - 1);
584         cfg.username[sizeof(cfg.username) - 1] = '\0';
585     } else if (cfg.username[0] == '\0') {
586         namelen = 0;
587         if (GetUserName(user, &namelen) == FALSE)
588             bump("Empty user name");
589         user = smalloc(namelen * sizeof(char));
590         GetUserName(user, &namelen);
591         if (verbose)
592             tell_user(stderr, "Guessing user name: %s", user);
593         strncpy(cfg.username, user, sizeof(cfg.username) - 1);
594         cfg.username[sizeof(cfg.username) - 1] = '\0';
595         free(user);
596     }
597
598     if (cfg.protocol != PROT_SSH)
599         cfg.port = 22;
600
601     if (portnumber)
602         cfg.port = portnumber;
603
604     /*
605      * Attempt to start the SFTP subsystem as a first choice,
606      * falling back to the provided scp command if that fails.
607      */
608     strcpy(cfg.remote_cmd, "sftp");
609     cfg.ssh_subsys = TRUE;
610     cfg.remote_cmd_ptr2 = cmd;
611     cfg.ssh_subsys2 = FALSE;
612     cfg.nopty = TRUE;
613
614     back = &ssh_backend;
615
616     err = back->init(cfg.host, cfg.port, &realhost);
617     if (err != NULL)
618         bump("ssh_init: %s", err);
619     ssh_scp_init();
620     if (verbose && realhost != NULL)
621         tell_user(stderr, "Connected to %s\n", realhost);
622     sfree(realhost);
623 }
624
625 /*
626  *  Update statistic information about current file.
627  */
628 static void print_stats(char *name, unsigned long size, unsigned long done,
629                         time_t start, time_t now)
630 {
631     float ratebs;
632     unsigned long eta;
633     char etastr[10];
634     int pct;
635     int len;
636
637     /* GUI Adaptation - Sept 2000 */
638     if (gui_mode)
639         gui_update_stats(name, size, (int) (100 * (done * 1.0 / size)),
640                          (unsigned long) difftime(now, start));
641     else {
642         if (now > start)
643             ratebs = (float) done / (now - start);
644         else
645             ratebs = (float) done;
646
647         if (ratebs < 1.0)
648             eta = size - done;
649         else
650             eta = (unsigned long) ((size - done) / ratebs);
651         sprintf(etastr, "%02ld:%02ld:%02ld",
652                 eta / 3600, (eta % 3600) / 60, eta % 60);
653
654         pct = (int) (100.0 * (float) done / size);
655
656         len = printf("\r%-25.25s | %10ld kB | %5.1f kB/s | ETA: %8s | %3d%%",
657                      name, done / 1024, ratebs / 1024.0, etastr, pct);
658         if (len < prev_stats_len)
659             printf("%*s", prev_stats_len - len, "");
660         prev_stats_len = len;
661
662         if (done == size)
663             printf("\n");
664     }
665 }
666
667 /*
668  *  Find a colon in str and return a pointer to the colon.
669  *  This is used to separate hostname from filename.
670  */
671 static char *colon(char *str)
672 {
673     /* We ignore a leading colon, since the hostname cannot be
674        empty. We also ignore a colon as second character because
675        of filenames like f:myfile.txt. */
676     if (str[0] == '\0' || str[0] == ':' || str[1] == ':')
677         return (NULL);
678     while (*str != '\0' && *str != ':' && *str != '/' && *str != '\\')
679         str++;
680     if (*str == ':')
681         return (str);
682     else
683         return (NULL);
684 }
685
686 /*
687  * Return a pointer to the portion of str that comes after the last
688  * slash (or backslash, if `local' is TRUE).
689  */
690 static char *stripslashes(char *str, int local)
691 {
692     char *p;
693
694     p = strrchr(str, '/');
695     if (p) str = p+1;
696
697     if (local) {
698         p = strrchr(str, '\\');
699         if (p) str = p+1;
700     }
701
702     return str;
703 }
704
705 /*
706  * Determine whether a string is entirely composed of dots.
707  */
708 static int is_dots(char *str)
709 {
710     return str[strspn(str, ".")] == '\0';
711 }
712
713 /*
714  *  Wait for a response from the other side.
715  *  Return 0 if ok, -1 if error.
716  */
717 static int response(void)
718 {
719     char ch, resp, rbuf[2048];
720     int p;
721
722     if (ssh_scp_recv(&resp, 1) <= 0)
723         bump("Lost connection");
724
725     p = 0;
726     switch (resp) {
727       case 0:                          /* ok */
728         return (0);
729       default:
730         rbuf[p++] = resp;
731         /* fallthrough */
732       case 1:                          /* error */
733       case 2:                          /* fatal error */
734         do {
735             if (ssh_scp_recv(&ch, 1) <= 0)
736                 bump("Protocol error: Lost connection");
737             rbuf[p++] = ch;
738         } while (p < sizeof(rbuf) && ch != '\n');
739         rbuf[p - 1] = '\0';
740         if (resp == 1)
741             tell_user(stderr, "%s\n", rbuf);
742         else
743             bump("%s", rbuf);
744         errs++;
745         return (-1);
746     }
747 }
748
749 int sftp_recvdata(char *buf, int len)
750 {
751     return ssh_scp_recv(buf, len);
752 }
753 int sftp_senddata(char *buf, int len)
754 {
755     back->send((unsigned char *) buf, len);
756     return 1;
757 }
758
759 /* ----------------------------------------------------------------------
760  * sftp-based replacement for the hacky `pscp -ls'.
761  */
762 static int sftp_ls_compare(const void *av, const void *bv)
763 {
764     const struct fxp_name *a = (const struct fxp_name *) av;
765     const struct fxp_name *b = (const struct fxp_name *) bv;
766     return strcmp(a->filename, b->filename);
767 }
768 void scp_sftp_listdir(char *dirname)
769 {
770     struct fxp_handle *dirh;
771     struct fxp_names *names;
772     struct fxp_name *ournames;
773     int nnames, namesize;
774     char *dir;
775     int i;
776
777     printf("Listing directory %s\n", dirname);
778
779     dirh = fxp_opendir(dirname);
780     if (dirh == NULL) {
781         printf("Unable to open %s: %s\n", dir, fxp_error());
782     } else {
783         nnames = namesize = 0;
784         ournames = NULL;
785
786         while (1) {
787
788             names = fxp_readdir(dirh);
789             if (names == NULL) {
790                 if (fxp_error_type() == SSH_FX_EOF)
791                     break;
792                 printf("Reading directory %s: %s\n", dir, fxp_error());
793                 break;
794             }
795             if (names->nnames == 0) {
796                 fxp_free_names(names);
797                 break;
798             }
799
800             if (nnames + names->nnames >= namesize) {
801                 namesize += names->nnames + 128;
802                 ournames =
803                     srealloc(ournames, namesize * sizeof(*ournames));
804             }
805
806             for (i = 0; i < names->nnames; i++)
807                 ournames[nnames++] = names->names[i];
808
809             names->nnames = 0;         /* prevent free_names */
810             fxp_free_names(names);
811         }
812         fxp_close(dirh);
813
814         /*
815          * Now we have our filenames. Sort them by actual file
816          * name, and then output the longname parts.
817          */
818         qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
819
820         /*
821          * And print them.
822          */
823         for (i = 0; i < nnames; i++)
824             printf("%s\n", ournames[i].longname);
825     }
826 }
827
828 /* ----------------------------------------------------------------------
829  * Helper routines that contain the actual SCP protocol elements,
830  * implemented both as SCP1 and SFTP.
831  */
832
833 static struct scp_sftp_dirstack {
834     struct scp_sftp_dirstack *next;
835     struct fxp_name *names;
836     int namepos, namelen;
837     char *dirpath;
838     char *wildcard;
839 } *scp_sftp_dirstack_head;
840 static char *scp_sftp_remotepath, *scp_sftp_currentname;
841 static char *scp_sftp_wildcard;
842 static int scp_sftp_targetisdir, scp_sftp_donethistarget;
843 static int scp_sftp_preserve, scp_sftp_recursive;
844 static unsigned long scp_sftp_mtime, scp_sftp_atime;
845 static int scp_has_times;
846 static struct fxp_handle *scp_sftp_filehandle;
847 static uint64 scp_sftp_fileoffset;
848
849 void scp_source_setup(char *target, int shouldbedir)
850 {
851     if (using_sftp) {
852         /*
853          * Find out whether the target filespec is in fact a
854          * directory.
855          */
856         struct fxp_attrs attrs;
857
858         if (!fxp_stat(target, &attrs) ||
859             !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS))
860             scp_sftp_targetisdir = 0;
861         else
862             scp_sftp_targetisdir = (attrs.permissions & 0040000) != 0;
863
864         if (shouldbedir && !scp_sftp_targetisdir) {
865             bump("pscp: remote filespec %s: not a directory\n", target);
866         }
867
868         scp_sftp_remotepath = dupstr(target);
869
870         scp_has_times = 0;
871     } else {
872         (void) response();
873     }
874 }
875
876 int scp_send_errmsg(char *str)
877 {
878     if (using_sftp) {
879         /* do nothing; we never need to send our errors to the server */
880     } else {
881         back->send("\001", 1);         /* scp protocol error prefix */
882         back->send(str, strlen(str));
883     }
884     return 0;                          /* can't fail */
885 }
886
887 int scp_send_filetimes(unsigned long mtime, unsigned long atime)
888 {
889     if (using_sftp) {
890         scp_sftp_mtime = mtime;
891         scp_sftp_atime = atime;
892         scp_has_times = 1;
893         return 0;
894     } else {
895         char buf[80];
896         sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
897         back->send(buf, strlen(buf));
898         return response();
899     }
900 }
901
902 int scp_send_filename(char *name, unsigned long size, int modes)
903 {
904     if (using_sftp) {
905         char *fullname;
906         if (scp_sftp_targetisdir) {
907             fullname = dupcat(scp_sftp_remotepath, "/", name, NULL);
908         } else {
909             fullname = dupstr(scp_sftp_remotepath);
910         }
911         scp_sftp_filehandle =
912             fxp_open(fullname, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
913         if (!scp_sftp_filehandle) {
914             tell_user(stderr, "pscp: unable to open %s: %s",
915                       fullname, fxp_error());
916             errs++;
917             return 1;
918         }
919         scp_sftp_fileoffset = uint64_make(0, 0);
920         sfree(fullname);
921         return 0;
922     } else {
923         char buf[40];
924         sprintf(buf, "C%04o %lu ", modes, size);
925         back->send(buf, strlen(buf));
926         back->send(name, strlen(name));
927         back->send("\n", 1);
928         return response();
929     }
930 }
931
932 int scp_send_filedata(char *data, int len)
933 {
934     if (using_sftp) {
935         if (!scp_sftp_filehandle) {
936             return 1;
937         }
938         if (!fxp_write(scp_sftp_filehandle, data, scp_sftp_fileoffset, len)) {
939             tell_user(stderr, "error while writing: %s\n", fxp_error());
940             errs++;
941             return 1;
942         }
943         scp_sftp_fileoffset = uint64_add32(scp_sftp_fileoffset, len);
944         return 0;
945     } else {
946         int bufsize = back->send(data, len);
947
948         /*
949          * If the network transfer is backing up - that is, the
950          * remote site is not accepting data as fast as we can
951          * produce it - then we must loop on network events until
952          * we have space in the buffer again.
953          */
954         while (bufsize > MAX_SCP_BUFSIZE) {
955             if (!scp_process_network_event())
956                 return 1;
957             bufsize = back->sendbuffer();
958         }
959
960         return 0;
961     }
962 }
963
964 int scp_send_finish(void)
965 {
966     if (using_sftp) {
967         struct fxp_attrs attrs;
968         if (!scp_sftp_filehandle) {
969             return 1;
970         }
971         if (scp_has_times) {
972             attrs.flags = SSH_FILEXFER_ATTR_ACMODTIME;
973             attrs.atime = scp_sftp_atime;
974             attrs.mtime = scp_sftp_mtime;
975             if (!fxp_fsetstat(scp_sftp_filehandle, attrs)) {
976                 tell_user(stderr, "unable to set file times: %s\n", fxp_error());
977                 errs++;
978             }
979         }
980         fxp_close(scp_sftp_filehandle);
981         scp_has_times = 0;
982         return 0;
983     } else {
984         back->send("", 1);
985         return response();
986     }
987 }
988
989 char *scp_save_remotepath(void)
990 {
991     if (using_sftp)
992         return scp_sftp_remotepath;
993     else
994         return NULL;
995 }
996
997 void scp_restore_remotepath(char *data)
998 {
999     if (using_sftp)
1000         scp_sftp_remotepath = data;
1001 }
1002
1003 int scp_send_dirname(char *name, int modes)
1004 {
1005     if (using_sftp) {
1006         char *fullname;
1007         char const *err;
1008         struct fxp_attrs attrs;
1009         if (scp_sftp_targetisdir) {
1010             fullname = dupcat(scp_sftp_remotepath, "/", name, NULL);
1011         } else {
1012             fullname = dupstr(scp_sftp_remotepath);
1013         }
1014
1015         /*
1016          * We don't worry about whether we managed to create the
1017          * directory, because if it exists already it's OK just to
1018          * use it. Instead, we will stat it afterwards, and if it
1019          * exists and is a directory we will assume we were either
1020          * successful or it didn't matter.
1021          */
1022         if (!fxp_mkdir(fullname))
1023             err = fxp_error();
1024         else
1025             err = "server reported no error";
1026         if (!fxp_stat(fullname, &attrs) ||
1027             !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) ||
1028             !(attrs.permissions & 0040000)) {
1029             tell_user(stderr, "unable to create directory %s: %s",
1030                       fullname, err);
1031             errs++;
1032             return 1;
1033         }
1034
1035         scp_sftp_remotepath = fullname;
1036
1037         return 0;
1038     } else {
1039         char buf[40];
1040         sprintf(buf, "D%04o 0 ", modes);
1041         back->send(buf, strlen(buf));
1042         back->send(name, strlen(name));
1043         back->send("\n", 1);
1044         return response();
1045     }
1046 }
1047
1048 int scp_send_enddir(void)
1049 {
1050     if (using_sftp) {
1051         sfree(scp_sftp_remotepath);
1052         return 0;
1053     } else {
1054         back->send("E\n", 2);
1055         return response();
1056     }
1057 }
1058
1059 /*
1060  * Yes, I know; I have an scp_sink_setup _and_ an scp_sink_init.
1061  * That's bad. The difference is that scp_sink_setup is called once
1062  * right at the start, whereas scp_sink_init is called to
1063  * initialise every level of recursion in the protocol.
1064  */
1065 int scp_sink_setup(char *source, int preserve, int recursive)
1066 {
1067     if (using_sftp) {
1068         char *newsource;
1069         /*
1070          * It's possible that the source string we've been given
1071          * contains a wildcard. If so, we must split the directory
1072          * away from the wildcard itself (throwing an error if any
1073          * wildcardness comes before the final slash) and arrange
1074          * things so that a dirstack entry will be set up.
1075          */
1076         newsource = smalloc(1+strlen(source));
1077         if (!wc_unescape(newsource, source)) {
1078             /* Yes, here we go; it's a wildcard. Bah. */
1079             char *dupsource, *lastpart, *dirpart, *wildcard;
1080             dupsource = dupstr(source);
1081             lastpart = stripslashes(dupsource, 0);
1082             wildcard = dupstr(lastpart);
1083             *lastpart = '\0';
1084             if (*dupsource && dupsource[1]) {
1085                 /*
1086                  * The remains of dupsource are at least two
1087                  * characters long, meaning the pathname wasn't
1088                  * empty or just `/'. Hence, we remove the trailing
1089                  * slash.
1090                  */
1091                 lastpart[-1] = '\0';
1092             }
1093
1094             /*
1095              * Now we have separated our string into dupsource (the
1096              * directory part) and wildcard. Both of these will
1097              * need freeing at some point. Next step is to remove
1098              * wildcard escapes from the directory part, throwing
1099              * an error if it contains a real wildcard.
1100              */
1101             dirpart = smalloc(1+strlen(dupsource));
1102             if (!wc_unescape(dirpart, dupsource)) {
1103                 tell_user(stderr, "%s: multiple-level wildcards unsupported",
1104                           source);
1105                 errs++;
1106                 sfree(dirpart);
1107                 sfree(wildcard);
1108                 sfree(dupsource);
1109                 return 1;
1110             }
1111
1112             /*
1113              * Now we have dirpart (unescaped, ie a valid remote
1114              * path), and wildcard (a wildcard). This will be
1115              * sufficient to arrange a dirstack entry.
1116              */
1117             scp_sftp_remotepath = dirpart;
1118             scp_sftp_wildcard = wildcard;
1119             sfree(dupsource);
1120         } else {
1121             scp_sftp_remotepath = newsource;
1122             scp_sftp_wildcard = NULL;
1123         }
1124         scp_sftp_preserve = preserve;
1125         scp_sftp_recursive = recursive;
1126         scp_sftp_donethistarget = 0;
1127         scp_sftp_dirstack_head = NULL;
1128     }
1129     return 0;
1130 }
1131
1132 int scp_sink_init(void)
1133 {
1134     if (!using_sftp) {
1135         back->send("", 1);
1136     }
1137     return 0;
1138 }
1139
1140 #define SCP_SINK_FILE   1
1141 #define SCP_SINK_DIR    2
1142 #define SCP_SINK_ENDDIR 3
1143 #define SCP_SINK_RETRY  4              /* not an action; just try again */
1144 struct scp_sink_action {
1145     int action;                        /* FILE, DIR, ENDDIR */
1146     char *buf;                         /* will need freeing after use */
1147     char *name;                        /* filename or dirname (not ENDDIR) */
1148     int mode;                          /* access mode (not ENDDIR) */
1149     unsigned long size;                /* file size (not ENDDIR) */
1150     int settime;                       /* 1 if atime and mtime are filled */
1151     unsigned long atime, mtime;        /* access times for the file */
1152 };
1153
1154 int scp_get_sink_action(struct scp_sink_action *act)
1155 {
1156     if (using_sftp) {
1157         char *fname;
1158         int must_free_fname;
1159         struct fxp_attrs attrs;
1160         int ret;
1161
1162         if (!scp_sftp_dirstack_head) {
1163             if (!scp_sftp_donethistarget) {
1164                 /*
1165                  * Simple case: we are only dealing with one file.
1166                  */
1167                 fname = scp_sftp_remotepath;
1168                 must_free_fname = 0;
1169                 scp_sftp_donethistarget = 1;
1170             } else {
1171                 /*
1172                  * Even simpler case: one file _which we've done_.
1173                  * Return 1 (finished).
1174                  */
1175                 return 1;
1176             }
1177         } else {
1178             /*
1179              * We're now in the middle of stepping through a list
1180              * of names returned from fxp_readdir(); so let's carry
1181              * on.
1182              */
1183             struct scp_sftp_dirstack *head = scp_sftp_dirstack_head;
1184             while (head->namepos < head->namelen &&
1185                    (is_dots(head->names[head->namepos].filename) ||
1186                     (head->wildcard &&
1187                      !wc_match(head->wildcard,
1188                                head->names[head->namepos].filename))))
1189                 head->namepos++;       /* skip . and .. */
1190             if (head->namepos < head->namelen) {
1191                 fname = dupcat(head->dirpath, "/",
1192                                head->names[head->namepos++].filename,
1193                                NULL);
1194                 must_free_fname = 1;
1195             } else {
1196                 /*
1197                  * We've come to the end of the list; pop it off
1198                  * the stack and return an ENDDIR action (or RETRY
1199                  * if this was a wildcard match).
1200                  */
1201                 if (head->wildcard) {
1202                     act->action = SCP_SINK_RETRY;
1203                     sfree(head->wildcard);
1204                 } else {
1205                     act->action = SCP_SINK_ENDDIR;
1206                 }
1207
1208                 sfree(head->dirpath);
1209                 sfree(head->names);
1210                 scp_sftp_dirstack_head = head->next;
1211                 sfree(head);
1212
1213                 return 0;
1214             }
1215         }
1216
1217         /*
1218          * Now we have a filename. Stat it, and see if it's a file
1219          * or a directory.
1220          */
1221         ret = fxp_stat(fname, &attrs);
1222         if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
1223             tell_user(stderr, "unable to identify %s: %s", fname,
1224                       ret ? "file type not supplied" : fxp_error());
1225             errs++;
1226             return 1;
1227         }
1228
1229         if (attrs.permissions & 0040000) {
1230             struct scp_sftp_dirstack *newitem;
1231             struct fxp_handle *dirhandle;
1232             int nnames, namesize;
1233             struct fxp_name *ournames;
1234             struct fxp_names *names;
1235
1236             /*
1237              * It's a directory. If we're not in recursive mode,
1238              * this merits a complaint (which is fatal if the name
1239              * was specified directly, but not if it was matched by
1240              * a wildcard).
1241              * 
1242              * We skip this complaint completely if
1243              * scp_sftp_wildcard is set, because that's an
1244              * indication that we're not actually supposed to
1245              * _recursively_ transfer the dir, just scan it for
1246              * things matching the wildcard.
1247              */
1248             if (!scp_sftp_recursive && !scp_sftp_wildcard) {
1249                 tell_user(stderr, "pscp: %s: is a directory", fname);
1250                 errs++;
1251                 if (must_free_fname) sfree(fname);
1252                 if (scp_sftp_dirstack_head) {
1253                     act->action = SCP_SINK_RETRY;
1254                     return 0;
1255                 } else {
1256                     return 1;
1257                 }
1258             }
1259
1260             /*
1261              * Otherwise, the fun begins. We must fxp_opendir() the
1262              * directory, slurp the filenames into memory, return
1263              * SCP_SINK_DIR (unless this is a wildcard match), and
1264              * set targetisdir. The next time we're called, we will
1265              * run through the list of filenames one by one,
1266              * matching them against a wildcard if present.
1267              * 
1268              * If targetisdir is _already_ set (meaning we're
1269              * already in the middle of going through another such
1270              * list), we must push the other (target,namelist) pair
1271              * on a stack.
1272              */
1273             dirhandle = fxp_opendir(fname);
1274             if (!dirhandle) {
1275                 tell_user(stderr, "scp: unable to open directory %s: %s",
1276                           fname, fxp_error());
1277                 if (must_free_fname) sfree(fname);
1278                 errs++;
1279                 return 1;
1280             }
1281             nnames = namesize = 0;
1282             ournames = NULL;
1283             while (1) {
1284                 int i;
1285
1286                 names = fxp_readdir(dirhandle);
1287                 if (names == NULL) {
1288                     if (fxp_error_type() == SSH_FX_EOF)
1289                         break;
1290                     tell_user(stderr, "scp: reading directory %s: %s\n",
1291                               fname, fxp_error());
1292                     if (must_free_fname) sfree(fname);
1293                     sfree(ournames);
1294                     errs++;
1295                     return 1;
1296                 }
1297                 if (names->nnames == 0) {
1298                     fxp_free_names(names);
1299                     break;
1300                 }
1301                 if (nnames + names->nnames >= namesize) {
1302                     namesize += names->nnames + 128;
1303                     ournames =
1304                         srealloc(ournames, namesize * sizeof(*ournames));
1305                 }
1306                 for (i = 0; i < names->nnames; i++)
1307                     ournames[nnames++] = names->names[i];
1308                 names->nnames = 0;             /* prevent free_names */
1309                 fxp_free_names(names);
1310             }
1311             fxp_close(dirhandle);
1312
1313             newitem = smalloc(sizeof(struct scp_sftp_dirstack));
1314             newitem->next = scp_sftp_dirstack_head;
1315             newitem->names = ournames;
1316             newitem->namepos = 0;
1317             newitem->namelen = nnames;
1318             if (must_free_fname)
1319                 newitem->dirpath = fname;
1320             else
1321                 newitem->dirpath = dupstr(fname);
1322             if (scp_sftp_wildcard) {
1323                 newitem->wildcard = scp_sftp_wildcard;
1324                 scp_sftp_wildcard = NULL;
1325             } else {
1326                 newitem->wildcard = NULL;
1327             }
1328             scp_sftp_dirstack_head = newitem;
1329
1330             if (newitem->wildcard) {
1331                 act->action = SCP_SINK_RETRY;
1332             } else {
1333                 act->action = SCP_SINK_DIR;
1334                 act->buf = dupstr(stripslashes(fname, 0));
1335                 act->name = act->buf;
1336                 act->size = 0;         /* duhh, it's a directory */
1337                 act->mode = 07777 & attrs.permissions;
1338                 if (scp_sftp_preserve &&
1339                     (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) {
1340                     act->atime = attrs.atime;
1341                     act->mtime = attrs.mtime;
1342                     act->settime = 1;
1343                 } else
1344                     act->settime = 0;
1345             }
1346             return 0;
1347
1348         } else {
1349             /*
1350              * It's a file. Return SCP_SINK_FILE.
1351              */
1352             act->action = SCP_SINK_FILE;
1353             act->buf = dupstr(stripslashes(fname, 0));
1354             act->name = act->buf;
1355             if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) {
1356                 if (uint64_compare(attrs.size,
1357                                    uint64_make(0, ULONG_MAX)) > 0) {
1358                     act->size = ULONG_MAX;   /* *boggle* */
1359                 } else
1360                     act->size = attrs.size.lo;
1361             } else
1362                 act->size = ULONG_MAX;   /* no idea */
1363             act->mode = 07777 & attrs.permissions;
1364             if (scp_sftp_preserve &&
1365                 (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) {
1366                 act->atime = attrs.atime;
1367                 act->mtime = attrs.mtime;
1368                 act->settime = 1;
1369             } else
1370                 act->settime = 0;
1371             if (must_free_fname)
1372                 scp_sftp_currentname = fname;
1373             else
1374                 scp_sftp_currentname = dupstr(fname);
1375             return 0;
1376         }
1377
1378     } else {
1379         int done = 0;
1380         int i, bufsize;
1381         int action;
1382         char ch;
1383
1384         act->settime = 0;
1385         act->buf = NULL;
1386         bufsize = 0;
1387
1388         while (!done) {
1389             if (ssh_scp_recv(&ch, 1) <= 0)
1390                 return 1;
1391             if (ch == '\n')
1392                 bump("Protocol error: Unexpected newline");
1393             i = 0;
1394             action = ch;
1395             do {
1396                 if (ssh_scp_recv(&ch, 1) <= 0)
1397                     bump("Lost connection");
1398                 if (i >= bufsize) {
1399                     bufsize = i + 128;
1400                     act->buf = srealloc(act->buf, bufsize);
1401                 }
1402                 act->buf[i++] = ch;
1403             } while (ch != '\n');
1404             act->buf[i - 1] = '\0';
1405             switch (action) {
1406               case '\01':                      /* error */
1407                 tell_user(stderr, "%s\n", act->buf);
1408                 errs++;
1409                 continue;                      /* go round again */
1410               case '\02':                      /* fatal error */
1411                 bump("%s", act->buf);
1412               case 'E':
1413                 back->send("", 1);
1414                 act->action = SCP_SINK_ENDDIR;
1415                 return 0;
1416               case 'T':
1417                 if (sscanf(act->buf, "%ld %*d %ld %*d",
1418                            &act->mtime, &act->atime) == 2) {
1419                     act->settime = 1;
1420                     back->send("", 1);
1421                     continue;          /* go round again */
1422                 }
1423                 bump("Protocol error: Illegal time format");
1424               case 'C':
1425               case 'D':
1426                 act->action = (action == 'C' ? SCP_SINK_FILE : SCP_SINK_DIR);
1427                 break;
1428               default:
1429                 bump("Protocol error: Expected control record");
1430             }
1431             /*
1432              * We will go round this loop only once, unless we hit
1433              * `continue' above.
1434              */
1435             done = 1;
1436         }
1437
1438         /*
1439          * If we get here, we must have seen SCP_SINK_FILE or
1440          * SCP_SINK_DIR.
1441          */
1442         if (sscanf(act->buf, "%o %lu %n", &act->mode, &act->size, &i) != 2)
1443             bump("Protocol error: Illegal file descriptor format");
1444         act->name = act->buf + i;
1445         return 0;
1446     }
1447 }
1448
1449 int scp_accept_filexfer(void)
1450 {
1451     if (using_sftp) {
1452         scp_sftp_filehandle =
1453             fxp_open(scp_sftp_currentname, SSH_FXF_READ);
1454         if (!scp_sftp_filehandle) {
1455             tell_user(stderr, "pscp: unable to open %s: %s",
1456                       scp_sftp_currentname, fxp_error());
1457             errs++;
1458             return 1;
1459         }
1460         scp_sftp_fileoffset = uint64_make(0, 0);
1461         sfree(scp_sftp_currentname);
1462         return 0;
1463     } else {
1464         back->send("", 1);
1465         return 0;                      /* can't fail */
1466     }
1467 }
1468
1469 int scp_recv_filedata(char *data, int len)
1470 {
1471     if (using_sftp) {
1472         int actuallen = fxp_read(scp_sftp_filehandle, data,
1473                                  scp_sftp_fileoffset, len);
1474         if (actuallen == -1 && fxp_error_type() != SSH_FX_EOF) {
1475             tell_user(stderr, "pscp: error while reading: %s", fxp_error());
1476             errs++;
1477             return -1;
1478         }
1479         if (actuallen < 0)
1480             actuallen = 0;
1481
1482         scp_sftp_fileoffset = uint64_add32(scp_sftp_fileoffset, actuallen);
1483
1484         return actuallen;
1485     } else {
1486         return ssh_scp_recv(data, len);
1487     }
1488 }
1489
1490 int scp_finish_filerecv(void)
1491 {
1492     if (using_sftp) {
1493         fxp_close(scp_sftp_filehandle);
1494         return 0;
1495     } else {
1496         back->send("", 1);
1497         return response();
1498     }
1499 }
1500
1501 /* ----------------------------------------------------------------------
1502  *  Send an error message to the other side and to the screen.
1503  *  Increment error counter.
1504  */
1505 static void run_err(const char *fmt, ...)
1506 {
1507     char str[2048];
1508     va_list ap;
1509     va_start(ap, fmt);
1510     errs++;
1511     strcpy(str, "scp: ");
1512     vsprintf(str + strlen(str), fmt, ap);
1513     strcat(str, "\n");
1514     scp_send_errmsg(str);
1515     tell_user(stderr, "%s", str);
1516     va_end(ap);
1517 }
1518
1519 /*
1520  *  Execute the source part of the SCP protocol.
1521  */
1522 static void source(char *src)
1523 {
1524     unsigned long size;
1525     char *last;
1526     HANDLE f;
1527     DWORD attr;
1528     unsigned long i;
1529     unsigned long stat_bytes;
1530     time_t stat_starttime, stat_lasttime;
1531
1532     attr = GetFileAttributes(src);
1533     if (attr == (DWORD) - 1) {
1534         run_err("%s: No such file or directory", src);
1535         return;
1536     }
1537
1538     if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
1539         if (recursive) {
1540             /*
1541              * Avoid . and .. directories.
1542              */
1543             char *p;
1544             p = strrchr(src, '/');
1545             if (!p)
1546                 p = strrchr(src, '\\');
1547             if (!p)
1548                 p = src;
1549             else
1550                 p++;
1551             if (!strcmp(p, ".") || !strcmp(p, ".."))
1552                 /* skip . and .. */ ;
1553             else
1554                 rsource(src);
1555         } else {
1556             run_err("%s: not a regular file", src);
1557         }
1558         return;
1559     }
1560
1561     if ((last = strrchr(src, '/')) == NULL)
1562         last = src;
1563     else
1564         last++;
1565     if (strrchr(last, '\\') != NULL)
1566         last = strrchr(last, '\\') + 1;
1567     if (last == src && strchr(src, ':') != NULL)
1568         last = strchr(src, ':') + 1;
1569
1570     f = CreateFile(src, GENERIC_READ, FILE_SHARE_READ, NULL,
1571                    OPEN_EXISTING, 0, 0);
1572     if (f == INVALID_HANDLE_VALUE) {
1573         run_err("%s: Cannot open file", src);
1574         return;
1575     }
1576
1577     if (preserve) {
1578         FILETIME actime, wrtime;
1579         unsigned long mtime, atime;
1580         GetFileTime(f, NULL, &actime, &wrtime);
1581         TIME_WIN_TO_POSIX(actime, atime);
1582         TIME_WIN_TO_POSIX(wrtime, mtime);
1583         if (scp_send_filetimes(mtime, atime))
1584             return;
1585     }
1586
1587     size = GetFileSize(f, NULL);
1588     if (verbose)
1589         tell_user(stderr, "Sending file %s, size=%lu", last, size);
1590     if (scp_send_filename(last, size, 0644))
1591         return;
1592
1593     stat_bytes = 0;
1594     stat_starttime = time(NULL);
1595     stat_lasttime = 0;
1596
1597     for (i = 0; i < size; i += 4096) {
1598         char transbuf[4096];
1599         DWORD j, k = 4096;
1600
1601         if (i + k > size)
1602             k = size - i;
1603         if (!ReadFile(f, transbuf, k, &j, NULL) || j != k) {
1604             if (statistics)
1605                 printf("\n");
1606             bump("%s: Read error", src);
1607         }
1608         if (scp_send_filedata(transbuf, k))
1609             bump("%s: Network error occurred", src);
1610
1611         if (statistics) {
1612             stat_bytes += k;
1613             if (time(NULL) != stat_lasttime || i + k == size) {
1614                 stat_lasttime = time(NULL);
1615                 print_stats(last, size, stat_bytes,
1616                             stat_starttime, stat_lasttime);
1617             }
1618         }
1619
1620     }
1621     CloseHandle(f);
1622
1623     (void) scp_send_finish();
1624 }
1625
1626 /*
1627  *  Recursively send the contents of a directory.
1628  */
1629 static void rsource(char *src)
1630 {
1631     char *last, *findfile;
1632     char *save_target;
1633     HANDLE dir;
1634     WIN32_FIND_DATA fdat;
1635     int ok;
1636
1637     if ((last = strrchr(src, '/')) == NULL)
1638         last = src;
1639     else
1640         last++;
1641     if (strrchr(last, '\\') != NULL)
1642         last = strrchr(last, '\\') + 1;
1643     if (last == src && strchr(src, ':') != NULL)
1644         last = strchr(src, ':') + 1;
1645
1646     /* maybe send filetime */
1647
1648     save_target = scp_save_remotepath();
1649
1650     if (verbose)
1651         tell_user(stderr, "Entering directory: %s", last);
1652     if (scp_send_dirname(last, 0755))
1653         return;
1654
1655     findfile = dupcat(src, "/*", NULL);
1656     dir = FindFirstFile(findfile, &fdat);
1657     ok = (dir != INVALID_HANDLE_VALUE);
1658     while (ok) {
1659         if (strcmp(fdat.cFileName, ".") == 0 ||
1660             strcmp(fdat.cFileName, "..") == 0) {
1661             /* ignore . and .. */
1662         } else {
1663             char *foundfile = dupcat(src, "/", fdat.cFileName, NULL);
1664             source(foundfile);
1665             sfree(foundfile);
1666         }
1667         ok = FindNextFile(dir, &fdat);
1668     }
1669     FindClose(dir);
1670     sfree(findfile);
1671
1672     (void) scp_send_enddir();
1673
1674     scp_restore_remotepath(save_target);
1675 }
1676
1677 /*
1678  * Execute the sink part of the SCP protocol.
1679  */
1680 static void sink(char *targ, char *src)
1681 {
1682     char *destfname;
1683     char ch;
1684     int targisdir = 0;
1685     int settime;
1686     int exists;
1687     DWORD attr;
1688     HANDLE f;
1689     unsigned long received;
1690     int wrerror = 0;
1691     unsigned long stat_bytes;
1692     time_t stat_starttime, stat_lasttime;
1693     char *stat_name;
1694
1695     attr = GetFileAttributes(targ);
1696     if (attr != (DWORD) - 1 && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
1697         targisdir = 1;
1698
1699     if (targetshouldbedirectory && !targisdir)
1700         bump("%s: Not a directory", targ);
1701
1702     scp_sink_init();
1703     while (1) {
1704         struct scp_sink_action act;
1705         if (scp_get_sink_action(&act))
1706             return;
1707
1708         if (act.action == SCP_SINK_ENDDIR)
1709             return;
1710
1711         if (act.action == SCP_SINK_RETRY)
1712             continue;
1713
1714         if (targisdir) {
1715             /*
1716              * Prevent the remote side from maliciously writing to
1717              * files outside the target area by sending a filename
1718              * containing `../'. In fact, it shouldn't be sending
1719              * filenames with any slashes in at all; so we'll find
1720              * the last slash or backslash in the filename and use
1721              * only the part after that. (And warn!)
1722              * 
1723              * In addition, we also ensure here that if we're
1724              * copying a single file and the target is a directory
1725              * (common usage: `pscp host:filename .') the remote
1726              * can't send us a _different_ file name. We can
1727              * distinguish this case because `src' will be non-NULL
1728              * and the last component of that will fail to match
1729              * (the last component of) the name sent.
1730              * 
1731              * (Well, not always; if `src' is a wildcard, we do
1732              * expect to get back filenames that don't correspond
1733              * exactly to it. So we skip this check if `src'
1734              * contains a *, a ? or a []. This is non-ideal - we
1735              * would like to ensure that the returned filename
1736              * actually matches the wildcard pattern - but one of
1737              * SCP's protocol infelicities is that wildcard
1738              * matching is done at the server end _by the server's
1739              * rules_ and so in general this is infeasible. Live
1740              * with it, or upgrade to SFTP.)
1741              */
1742             char *striptarget, *stripsrc;
1743
1744             striptarget = stripslashes(act.name, 1);
1745             if (striptarget != act.name) {
1746                 tell_user(stderr, "warning: remote host sent a compound"
1747                           " pathname - possibly malicious! (ignored)");
1748             }
1749
1750             /*
1751              * Also check to see if the target filename is '.' or
1752              * '..', or indeed '...' and so on because Windows
1753              * appears to interpret those like '..'.
1754              */
1755             if (is_dots(striptarget)) {
1756                 bump("security violation: remote host attempted to write to"
1757                      " a '.' or '..' path!");
1758             }
1759
1760             if (src) {
1761                 stripsrc = stripslashes(src, 1);
1762                 if (!stripsrc[strcspn(stripsrc, "*?[]")] &&
1763                     strcmp(striptarget, stripsrc)) {
1764                     tell_user(stderr, "warning: remote host attempted to"
1765                               " write to a different filename: disallowing");
1766                     /* Override the name the server provided with our own. */
1767                     striptarget = stripsrc;
1768                 }
1769             }
1770
1771             if (targ[0] != '\0')
1772                 destfname = dupcat(targ, "\\", striptarget, NULL);
1773             else
1774                 destfname = dupstr(striptarget);
1775         } else {
1776             /*
1777              * In this branch of the if, the target area is a
1778              * single file with an explicitly specified name in any
1779              * case, so there's no danger.
1780              */
1781             destfname = dupstr(targ);
1782         }
1783         attr = GetFileAttributes(destfname);
1784         exists = (attr != (DWORD) - 1);
1785
1786         if (act.action == SCP_SINK_DIR) {
1787             if (exists && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
1788                 run_err("%s: Not a directory", destfname);
1789                 continue;
1790             }
1791             if (!exists) {
1792                 if (!CreateDirectory(destfname, NULL)) {
1793                     run_err("%s: Cannot create directory", destfname);
1794                     continue;
1795                 }
1796             }
1797             sink(destfname, NULL);
1798             /* can we set the timestamp for directories ? */
1799             continue;
1800         }
1801
1802         f = CreateFile(destfname, GENERIC_WRITE, 0, NULL,
1803                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
1804         if (f == INVALID_HANDLE_VALUE) {
1805             run_err("%s: Cannot create file", destfname);
1806             continue;
1807         }
1808
1809         if (scp_accept_filexfer())
1810             return;
1811
1812         stat_bytes = 0;
1813         stat_starttime = time(NULL);
1814         stat_lasttime = 0;
1815         stat_name = stripslashes(destfname, 1);
1816
1817         received = 0;
1818         while (received < act.size) {
1819             char transbuf[4096];
1820             DWORD blksize, read, written;
1821             blksize = 4096;
1822             if (blksize > act.size - received)
1823                 blksize = act.size - received;
1824             read = scp_recv_filedata(transbuf, blksize);
1825             if (read <= 0)
1826                 bump("Lost connection");
1827             if (wrerror)
1828                 continue;
1829             if (!WriteFile(f, transbuf, read, &written, NULL) ||
1830                 written != read) {
1831                 wrerror = 1;
1832                 /* FIXME: in sftp we can actually abort the transfer */
1833                 if (statistics)
1834                     printf("\r%-25.25s | %50s\n",
1835                            stat_name,
1836                            "Write error.. waiting for end of file");
1837                 continue;
1838             }
1839             if (statistics) {
1840                 stat_bytes += read;
1841                 if (time(NULL) > stat_lasttime ||
1842                     received + read == act.size) {
1843                     stat_lasttime = time(NULL);
1844                     print_stats(stat_name, act.size, stat_bytes,
1845                                 stat_starttime, stat_lasttime);
1846                 }
1847             }
1848             received += read;
1849         }
1850         if (act.settime) {
1851             FILETIME actime, wrtime;
1852             TIME_POSIX_TO_WIN(act.atime, actime);
1853             TIME_POSIX_TO_WIN(act.mtime, wrtime);
1854             SetFileTime(f, NULL, &actime, &wrtime);
1855         }
1856
1857         CloseHandle(f);
1858         if (wrerror) {
1859             run_err("%s: Write error", destfname);
1860             continue;
1861         }
1862         (void) scp_finish_filerecv();
1863         sfree(destfname);
1864         sfree(act.name);
1865     }
1866 }
1867
1868 /*
1869  * We will copy local files to a remote server.
1870  */
1871 static void toremote(int argc, char *argv[])
1872 {
1873     char *src, *targ, *host, *user;
1874     char *cmd;
1875     int i;
1876
1877     targ = argv[argc - 1];
1878
1879     /* Separate host from filename */
1880     host = targ;
1881     targ = colon(targ);
1882     if (targ == NULL)
1883         bump("targ == NULL in toremote()");
1884     *targ++ = '\0';
1885     if (*targ == '\0')
1886         targ = ".";
1887     /* Substitute "." for emtpy target */
1888
1889     /* Separate host and username */
1890     user = host;
1891     host = strrchr(host, '@');
1892     if (host == NULL) {
1893         host = user;
1894         user = NULL;
1895     } else {
1896         *host++ = '\0';
1897         if (*user == '\0')
1898             user = NULL;
1899     }
1900
1901     if (argc == 2) {
1902         /* Find out if the source filespec covers multiple files
1903            if so, we should set the targetshouldbedirectory flag */
1904         HANDLE fh;
1905         WIN32_FIND_DATA fdat;
1906         if (colon(argv[0]) != NULL)
1907             bump("%s: Remote to remote not supported", argv[0]);
1908         fh = FindFirstFile(argv[0], &fdat);
1909         if (fh == INVALID_HANDLE_VALUE)
1910             bump("%s: No such file or directory\n", argv[0]);
1911         if (FindNextFile(fh, &fdat))
1912             targetshouldbedirectory = 1;
1913         FindClose(fh);
1914     }
1915
1916     cmd = smalloc(strlen(targ) + 100);
1917     sprintf(cmd, "scp%s%s%s%s -t %s",
1918             verbose ? " -v" : "",
1919             recursive ? " -r" : "",
1920             preserve ? " -p" : "",
1921             targetshouldbedirectory ? " -d" : "", targ);
1922     do_cmd(host, user, cmd);
1923     sfree(cmd);
1924
1925     scp_source_setup(targ, targetshouldbedirectory);
1926
1927     for (i = 0; i < argc - 1; i++) {
1928         char *srcpath, *last;
1929         HANDLE dir;
1930         WIN32_FIND_DATA fdat;
1931         src = argv[i];
1932         if (colon(src) != NULL) {
1933             tell_user(stderr, "%s: Remote to remote not supported\n", src);
1934             errs++;
1935             continue;
1936         }
1937
1938         /*
1939          * Trim off the last pathname component of `src', to
1940          * provide the base pathname which will be prepended to
1941          * filenames returned from Find{First,Next}File.
1942          */
1943         srcpath = dupstr(src);
1944         last = stripslashes(srcpath, 1);
1945         if (last == srcpath) {
1946             last = strchr(srcpath, ':');
1947             if (last)
1948                 last++;
1949             else
1950                 last = srcpath;
1951         }
1952         *last = '\0';
1953
1954         dir = FindFirstFile(src, &fdat);
1955         if (dir == INVALID_HANDLE_VALUE) {
1956             run_err("%s: No such file or directory", src);
1957             continue;
1958         }
1959         do {
1960             char *last;
1961             char *filename;
1962             /*
1963              * Ensure that . and .. are never matched by wildcards,
1964              * but only by deliberate action.
1965              */
1966             if (!strcmp(fdat.cFileName, ".") ||
1967                 !strcmp(fdat.cFileName, "..")) {
1968                 /*
1969                  * Find*File has returned a special dir. We require
1970                  * that _either_ `src' ends in a backslash followed
1971                  * by that string, _or_ `src' is precisely that
1972                  * string.
1973                  */
1974                 int len = strlen(src), dlen = strlen(fdat.cFileName);
1975                 if (len == dlen && !strcmp(src, fdat.cFileName)) {
1976                     /* ok */ ;
1977                 } else if (len > dlen + 1 && src[len - dlen - 1] == '\\' &&
1978                            !strcmp(src + len - dlen, fdat.cFileName)) {
1979                     /* ok */ ;
1980                 } else
1981                     continue;          /* ignore this one */
1982             }
1983             filename = dupcat(srcpath, fdat.cFileName, NULL);
1984             source(filename);
1985             sfree(filename);
1986         } while (FindNextFile(dir, &fdat));
1987         FindClose(dir);
1988         sfree(srcpath);
1989     }
1990 }
1991
1992 /*
1993  *  We will copy files from a remote server to the local machine.
1994  */
1995 static void tolocal(int argc, char *argv[])
1996 {
1997     char *src, *targ, *host, *user;
1998     char *cmd;
1999
2000     if (argc != 2)
2001         bump("More than one remote source not supported");
2002
2003     src = argv[0];
2004     targ = argv[1];
2005
2006     /* Separate host from filename */
2007     host = src;
2008     src = colon(src);
2009     if (src == NULL)
2010         bump("Local to local copy not supported");
2011     *src++ = '\0';
2012     if (*src == '\0')
2013         src = ".";
2014     /* Substitute "." for empty filename */
2015
2016     /* Separate username and hostname */
2017     user = host;
2018     host = strrchr(host, '@');
2019     if (host == NULL) {
2020         host = user;
2021         user = NULL;
2022     } else {
2023         *host++ = '\0';
2024         if (*user == '\0')
2025             user = NULL;
2026     }
2027
2028     cmd = smalloc(strlen(src) + 100);
2029     sprintf(cmd, "scp%s%s%s%s -f %s",
2030             verbose ? " -v" : "",
2031             recursive ? " -r" : "",
2032             preserve ? " -p" : "",
2033             targetshouldbedirectory ? " -d" : "", src);
2034     do_cmd(host, user, cmd);
2035     sfree(cmd);
2036
2037     if (scp_sink_setup(src, preserve, recursive))
2038         return;
2039
2040     sink(targ, src);
2041 }
2042
2043 /*
2044  *  We will issue a list command to get a remote directory.
2045  */
2046 static void get_dir_list(int argc, char *argv[])
2047 {
2048     char *src, *host, *user;
2049     char *cmd, *p, *q;
2050     char c;
2051
2052     src = argv[0];
2053
2054     /* Separate host from filename */
2055     host = src;
2056     src = colon(src);
2057     if (src == NULL)
2058         bump("Local to local copy not supported");
2059     *src++ = '\0';
2060     if (*src == '\0')
2061         src = ".";
2062     /* Substitute "." for empty filename */
2063
2064     /* Separate username and hostname */
2065     user = host;
2066     host = strrchr(host, '@');
2067     if (host == NULL) {
2068         host = user;
2069         user = NULL;
2070     } else {
2071         *host++ = '\0';
2072         if (*user == '\0')
2073             user = NULL;
2074     }
2075
2076     cmd = smalloc(4 * strlen(src) + 100);
2077     strcpy(cmd, "ls -la '");
2078     p = cmd + strlen(cmd);
2079     for (q = src; *q; q++) {
2080         if (*q == '\'') {
2081             *p++ = '\'';
2082             *p++ = '\\';
2083             *p++ = '\'';
2084             *p++ = '\'';
2085         } else {
2086             *p++ = *q;
2087         }
2088     }
2089     *p++ = '\'';
2090     *p = '\0';
2091
2092     do_cmd(host, user, cmd);
2093     sfree(cmd);
2094
2095     if (using_sftp) {
2096         scp_sftp_listdir(src);
2097     } else {
2098         while (ssh_scp_recv(&c, 1) > 0)
2099             tell_char(stdout, c);
2100     }
2101 }
2102
2103 /*
2104  *  Initialize the Win$ock driver.
2105  */
2106 static void init_winsock(void)
2107 {
2108     WORD winsock_ver;
2109     WSADATA wsadata;
2110
2111     winsock_ver = MAKEWORD(1, 1);
2112     if (WSAStartup(winsock_ver, &wsadata))
2113         bump("Unable to initialise WinSock");
2114     if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1)
2115         bump("WinSock version is incompatible with 1.1");
2116 }
2117
2118 /*
2119  *  Short description of parameters.
2120  */
2121 static void usage(void)
2122 {
2123     printf("PuTTY Secure Copy client\n");
2124     printf("%s\n", ver);
2125     printf("Usage: pscp [options] [user@]host:source target\n");
2126     printf
2127         ("       pscp [options] source [source...] [user@]host:target\n");
2128     printf("       pscp [options] -ls user@host:filespec\n");
2129     printf("Options:\n");
2130     printf("  -p        preserve file attributes\n");
2131     printf("  -q        quiet, don't show statistics\n");
2132     printf("  -r        copy directories recursively\n");
2133     printf("  -v        show verbose messages\n");
2134     printf("  -P port   connect to specified port\n");
2135     printf("  -pw passw login with specified password\n");
2136 #if 0
2137     /*
2138      * -gui is an internal option, used by GUI front ends to get
2139      * pscp to pass progress reports back to them. It's not an
2140      * ordinary user-accessible option, so it shouldn't be part of
2141      * the command-line help. The only people who need to know
2142      * about it are programmers, and they can read the source.
2143      */
2144     printf
2145         ("  -gui hWnd GUI mode with the windows handle for receiving messages\n");
2146 #endif
2147     exit(1);
2148 }
2149
2150 /*
2151  *  Main program (no, really?)
2152  */
2153 int main(int argc, char *argv[])
2154 {
2155     int i;
2156
2157     default_protocol = PROT_TELNET;
2158
2159     flags = FLAG_STDERR;
2160     ssh_get_line = &get_line;
2161     init_winsock();
2162     sk_init();
2163
2164     for (i = 1; i < argc; i++) {
2165         if (argv[i][0] != '-')
2166             break;
2167         if (strcmp(argv[i], "-v") == 0)
2168             verbose = 1, flags |= FLAG_VERBOSE;
2169         else if (strcmp(argv[i], "-r") == 0)
2170             recursive = 1;
2171         else if (strcmp(argv[i], "-p") == 0)
2172             preserve = 1;
2173         else if (strcmp(argv[i], "-q") == 0)
2174             statistics = 0;
2175         else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-?") == 0)
2176             usage();
2177         else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc)
2178             portnumber = atoi(argv[++i]);
2179         else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc)
2180             password = argv[++i];
2181         else if (strcmp(argv[i], "-gui") == 0 && i + 1 < argc) {
2182             gui_hwnd = argv[++i];
2183             gui_mode = 1;
2184         } else if (strcmp(argv[i], "-ls") == 0)
2185             list = 1;
2186         else if (strcmp(argv[i], "--") == 0) {
2187             i++;
2188             break;
2189         } else
2190             usage();
2191     }
2192     argc -= i;
2193     argv += i;
2194     back = NULL;
2195
2196     if (list) {
2197         if (argc != 1)
2198             usage();
2199         get_dir_list(argc, argv);
2200
2201     } else {
2202
2203         if (argc < 2)
2204             usage();
2205         if (argc > 2)
2206             targetshouldbedirectory = 1;
2207
2208         if (colon(argv[argc - 1]) != NULL)
2209             toremote(argc, argv);
2210         else
2211             tolocal(argc, argv);
2212     }
2213
2214     if (back != NULL && back->socket() != NULL) {
2215         char ch;
2216         back->special(TS_EOF);
2217         ssh_scp_recv(&ch, 1);
2218     }
2219     WSACleanup();
2220     random_save_seed();
2221
2222     /* GUI Adaptation - August 2000 */
2223     if (gui_mode) {
2224         unsigned int msg_id = WM_RET_ERR_CNT;
2225         if (list)
2226             msg_id = WM_LS_RET_ERR_CNT;
2227         while (!PostMessage
2228                ((HWND) atoi(gui_hwnd), msg_id, (WPARAM) errs,
2229                 0 /*lParam */ ))SleepEx(1000, TRUE);
2230     }
2231     return (errs == 0 ? 0 : 1);
2232 }
2233
2234 /* end */