OSDN Git Service

Fix bugs of simultaneous connection.
[ffftp/ffftp.git] / contrib / putty / PSFTP.C
1 /*\r
2  * psftp.c: (platform-independent) front end for PSFTP.\r
3  */\r
4 \r
5 #include <stdio.h>\r
6 #include <stdlib.h>\r
7 #include <stdarg.h>\r
8 #include <assert.h>\r
9 #include <limits.h>\r
10 \r
11 #define PUTTY_DO_GLOBALS\r
12 #include "putty.h"\r
13 #include "psftp.h"\r
14 #include "storage.h"\r
15 #include "ssh.h"\r
16 #include "sftp.h"\r
17 #include "int64.h"\r
18 \r
19 const char *const appname = "PSFTP";\r
20 \r
21 /*\r
22  * Since SFTP is a request-response oriented protocol, it requires\r
23  * no buffer management: when we send data, we stop and wait for an\r
24  * acknowledgement _anyway_, and so we can't possibly overfill our\r
25  * send buffer.\r
26  */\r
27 \r
28 static int psftp_connect(char *userhost, char *user, int portnumber);\r
29 static int do_sftp_init(void);\r
30 void do_sftp_cleanup();\r
31 \r
32 /* ----------------------------------------------------------------------\r
33  * sftp client state.\r
34  */\r
35 \r
36 char *pwd, *homedir;\r
37 static Backend *back;\r
38 static void *backhandle;\r
39 static Config cfg;\r
40 \r
41 /* ----------------------------------------------------------------------\r
42  * Higher-level helper functions used in commands.\r
43  */\r
44 \r
45 /*\r
46  * Attempt to canonify a pathname starting from the pwd. If\r
47  * canonification fails, at least fall back to returning a _valid_\r
48  * pathname (though it may be ugly, eg /home/simon/../foobar).\r
49  */\r
50 char *canonify(char *name)\r
51 {\r
52     char *fullname, *canonname;\r
53     struct sftp_packet *pktin;\r
54     struct sftp_request *req, *rreq;\r
55 \r
56     if (name[0] == '/') {\r
57         fullname = dupstr(name);\r
58     } else {\r
59         char *slash;\r
60         if (pwd[strlen(pwd) - 1] == '/')\r
61             slash = "";\r
62         else\r
63             slash = "/";\r
64         fullname = dupcat(pwd, slash, name, NULL);\r
65     }\r
66 \r
67     sftp_register(req = fxp_realpath_send(fullname));\r
68     rreq = sftp_find_request(pktin = sftp_recv());\r
69     assert(rreq == req);\r
70     canonname = fxp_realpath_recv(pktin, rreq);\r
71 \r
72     if (canonname) {\r
73         sfree(fullname);\r
74         return canonname;\r
75     } else {\r
76         /*\r
77          * Attempt number 2. Some FXP_REALPATH implementations\r
78          * (glibc-based ones, in particular) require the _whole_\r
79          * path to point to something that exists, whereas others\r
80          * (BSD-based) only require all but the last component to\r
81          * exist. So if the first call failed, we should strip off\r
82          * everything from the last slash onwards and try again,\r
83          * then put the final component back on.\r
84          * \r
85          * Special cases:\r
86          * \r
87          *  - if the last component is "/." or "/..", then we don't\r
88          *    bother trying this because there's no way it can work.\r
89          * \r
90          *  - if the thing actually ends with a "/", we remove it\r
91          *    before we start. Except if the string is "/" itself\r
92          *    (although I can't see why we'd have got here if so,\r
93          *    because surely "/" would have worked the first\r
94          *    time?), in which case we don't bother.\r
95          * \r
96          *  - if there's no slash in the string at all, give up in\r
97          *    confusion (we expect at least one because of the way\r
98          *    we constructed the string).\r
99          */\r
100 \r
101         int i;\r
102         char *returnname;\r
103 \r
104         i = strlen(fullname);\r
105         if (i > 2 && fullname[i - 1] == '/')\r
106             fullname[--i] = '\0';      /* strip trailing / unless at pos 0 */\r
107         while (i > 0 && fullname[--i] != '/');\r
108 \r
109         /*\r
110          * Give up on special cases.\r
111          */\r
112         if (fullname[i] != '/' ||      /* no slash at all */\r
113             !strcmp(fullname + i, "/.") ||      /* ends in /. */\r
114             !strcmp(fullname + i, "/..") ||     /* ends in /.. */\r
115             !strcmp(fullname, "/")) {\r
116             return fullname;\r
117         }\r
118 \r
119         /*\r
120          * Now i points at the slash. Deal with the final special\r
121          * case i==0 (ie the whole path was "/nonexistentfile").\r
122          */\r
123         fullname[i] = '\0';            /* separate the string */\r
124         if (i == 0) {\r
125             sftp_register(req = fxp_realpath_send("/"));\r
126         } else {\r
127             sftp_register(req = fxp_realpath_send(fullname));\r
128         }\r
129         rreq = sftp_find_request(pktin = sftp_recv());\r
130         assert(rreq == req);\r
131         canonname = fxp_realpath_recv(pktin, rreq);\r
132 \r
133         if (!canonname) {\r
134             /* Even that failed. Restore our best guess at the\r
135              * constructed filename and give up */\r
136             fullname[i] = '/';  /* restore slash and last component */\r
137             return fullname;\r
138         }\r
139 \r
140         /*\r
141          * We have a canonical name for all but the last path\r
142          * component. Concatenate the last component and return.\r
143          */\r
144         returnname = dupcat(canonname,\r
145                             canonname[strlen(canonname) - 1] ==\r
146                             '/' ? "" : "/", fullname + i + 1, NULL);\r
147         sfree(fullname);\r
148         sfree(canonname);\r
149         return returnname;\r
150     }\r
151 }\r
152 \r
153 /*\r
154  * Return a pointer to the portion of str that comes after the last\r
155  * slash (or backslash or colon, if `local' is TRUE).\r
156  */\r
157 static char *stripslashes(char *str, int local)\r
158 {\r
159     char *p;\r
160 \r
161     if (local) {\r
162         p = strchr(str, ':');\r
163         if (p) str = p+1;\r
164     }\r
165 \r
166     p = strrchr(str, '/');\r
167     if (p) str = p+1;\r
168 \r
169     if (local) {\r
170         p = strrchr(str, '\\');\r
171         if (p) str = p+1;\r
172     }\r
173 \r
174     return str;\r
175 }\r
176 \r
177 /*\r
178  * qsort comparison routine for fxp_name structures. Sorts by real\r
179  * file name.\r
180  */\r
181 static int sftp_name_compare(const void *av, const void *bv)\r
182 {\r
183     const struct fxp_name *const *a = (const struct fxp_name *const *) av;\r
184     const struct fxp_name *const *b = (const struct fxp_name *const *) bv;\r
185     return strcmp((*a)->filename, (*b)->filename);\r
186 }\r
187 \r
188 /*\r
189  * Likewise, but for a bare char *.\r
190  */\r
191 static int bare_name_compare(const void *av, const void *bv)\r
192 {\r
193     const char **a = (const char **) av;\r
194     const char **b = (const char **) bv;\r
195     return strcmp(*a, *b);\r
196 }\r
197 \r
198 static void not_connected(void)\r
199 {\r
200     printf("psftp: not connected to a host; use \"open host.name\"\n");\r
201 }\r
202 \r
203 /* ----------------------------------------------------------------------\r
204  * The meat of the `get' and `put' commands.\r
205  */\r
206 int sftp_get_file(char *fname, char *outfname, int recurse, int restart)\r
207 {\r
208     struct fxp_handle *fh;\r
209     struct sftp_packet *pktin;\r
210     struct sftp_request *req, *rreq;\r
211     struct fxp_xfer *xfer;\r
212     uint64 offset;\r
213     WFile *file;\r
214     int ret, shown_err = FALSE;\r
215 \r
216     /*\r
217      * In recursive mode, see if we're dealing with a directory.\r
218      * (If we're not in recursive mode, we need not even check: the\r
219      * subsequent FXP_OPEN will return a usable error message.)\r
220      */\r
221     if (recurse) {\r
222         struct fxp_attrs attrs;\r
223         int result;\r
224 \r
225         sftp_register(req = fxp_stat_send(fname));\r
226         rreq = sftp_find_request(pktin = sftp_recv());\r
227         assert(rreq == req);\r
228         result = fxp_stat_recv(pktin, rreq, &attrs);\r
229 \r
230         if (result &&\r
231             (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&\r
232             (attrs.permissions & 0040000)) {\r
233 \r
234             struct fxp_handle *dirhandle;\r
235             int nnames, namesize;\r
236             struct fxp_name **ournames;\r
237             struct fxp_names *names;\r
238             int i;\r
239 \r
240             /*\r
241              * First, attempt to create the destination directory,\r
242              * unless it already exists.\r
243              */\r
244             if (file_type(outfname) != FILE_TYPE_DIRECTORY &&\r
245                 !create_directory(outfname)) {\r
246                 printf("%s: Cannot create directory\n", outfname);\r
247                 return 0;\r
248             }\r
249 \r
250             /*\r
251              * Now get the list of filenames in the remote\r
252              * directory.\r
253              */\r
254             sftp_register(req = fxp_opendir_send(fname));\r
255             rreq = sftp_find_request(pktin = sftp_recv());\r
256             assert(rreq == req);\r
257             dirhandle = fxp_opendir_recv(pktin, rreq);\r
258 \r
259             if (!dirhandle) {\r
260                 printf("%s: unable to open directory: %s\n",\r
261                        fname, fxp_error());\r
262                 return 0;\r
263             }\r
264             nnames = namesize = 0;\r
265             ournames = NULL;\r
266             while (1) {\r
267                 int i;\r
268 \r
269                 sftp_register(req = fxp_readdir_send(dirhandle));\r
270                 rreq = sftp_find_request(pktin = sftp_recv());\r
271                 assert(rreq == req);\r
272                 names = fxp_readdir_recv(pktin, rreq);\r
273 \r
274                 if (names == NULL) {\r
275                     if (fxp_error_type() == SSH_FX_EOF)\r
276                         break;\r
277                     printf("%s: reading directory: %s\n", fname, fxp_error());\r
278                     sfree(ournames);\r
279                     return 0;\r
280                 }\r
281                 if (names->nnames == 0) {\r
282                     fxp_free_names(names);\r
283                     break;\r
284                 }\r
285                 if (nnames + names->nnames >= namesize) {\r
286                     namesize += names->nnames + 128;\r
287                     ournames = sresize(ournames, namesize, struct fxp_name *);\r
288                 }\r
289                 for (i = 0; i < names->nnames; i++)\r
290                     if (strcmp(names->names[i].filename, ".") &&\r
291                         strcmp(names->names[i].filename, "..")) {\r
292                         if (!vet_filename(names->names[i].filename)) {\r
293                             printf("ignoring potentially dangerous server-"\r
294                                    "supplied filename '%s'\n",\r
295                                    names->names[i].filename);\r
296                         } else {\r
297                             ournames[nnames++] =\r
298                                 fxp_dup_name(&names->names[i]);\r
299                         }\r
300                     }\r
301                 fxp_free_names(names);\r
302             }\r
303             sftp_register(req = fxp_close_send(dirhandle));\r
304             rreq = sftp_find_request(pktin = sftp_recv());\r
305             assert(rreq == req);\r
306             fxp_close_recv(pktin, rreq);\r
307 \r
308             /*\r
309              * Sort the names into a clear order. This ought to\r
310              * make things more predictable when we're doing a\r
311              * reget of the same directory, just in case two\r
312              * readdirs on the same remote directory return a\r
313              * different order.\r
314              */\r
315             qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare);\r
316 \r
317             /*\r
318              * If we're in restart mode, find the last filename on\r
319              * this list that already exists. We may have to do a\r
320              * reget on _that_ file, but shouldn't have to do\r
321              * anything on the previous files.\r
322              * \r
323              * If none of them exists, of course, we start at 0.\r
324              */\r
325             i = 0;\r
326             if (restart) {\r
327                 while (i < nnames) {\r
328                     char *nextoutfname;\r
329                     int ret;\r
330                     if (outfname)\r
331                         nextoutfname = dir_file_cat(outfname,\r
332                                                     ournames[i]->filename);\r
333                     else\r
334                         nextoutfname = dupstr(ournames[i]->filename);\r
335                     ret = (file_type(nextoutfname) == FILE_TYPE_NONEXISTENT);\r
336                     sfree(nextoutfname);\r
337                     if (ret)\r
338                         break;\r
339                     i++;\r
340                 }\r
341                 if (i > 0)\r
342                     i--;\r
343             }\r
344 \r
345             /*\r
346              * Now we're ready to recurse. Starting at ournames[i]\r
347              * and continuing on to the end of the list, we\r
348              * construct a new source and target file name, and\r
349              * call sftp_get_file again.\r
350              */\r
351             for (; i < nnames; i++) {\r
352                 char *nextfname, *nextoutfname;\r
353                 int ret;\r
354                 \r
355                 nextfname = dupcat(fname, "/", ournames[i]->filename, NULL);\r
356                 if (outfname)\r
357                     nextoutfname = dir_file_cat(outfname,\r
358                                                 ournames[i]->filename);\r
359                 else\r
360                     nextoutfname = dupstr(ournames[i]->filename);\r
361                 ret = sftp_get_file(nextfname, nextoutfname, recurse, restart);\r
362                 restart = FALSE;       /* after first partial file, do full */\r
363                 sfree(nextoutfname);\r
364                 sfree(nextfname);\r
365                 if (!ret) {\r
366                     for (i = 0; i < nnames; i++) {\r
367                         fxp_free_name(ournames[i]);\r
368                     }\r
369                     sfree(ournames);\r
370                     return 0;\r
371                 }\r
372             }\r
373 \r
374             /*\r
375              * Done this recursion level. Free everything.\r
376              */\r
377             for (i = 0; i < nnames; i++) {\r
378                 fxp_free_name(ournames[i]);\r
379             }\r
380             sfree(ournames);\r
381 \r
382             return 1;\r
383         }\r
384     }\r
385 \r
386     sftp_register(req = fxp_open_send(fname, SSH_FXF_READ));\r
387     rreq = sftp_find_request(pktin = sftp_recv());\r
388     assert(rreq == req);\r
389     fh = fxp_open_recv(pktin, rreq);\r
390 \r
391     if (!fh) {\r
392         printf("%s: open for read: %s\n", fname, fxp_error());\r
393         return 0;\r
394     }\r
395 \r
396     if (restart) {\r
397         file = open_existing_wfile(outfname, NULL);\r
398     } else {\r
399         file = open_new_file(outfname);\r
400     }\r
401 \r
402     if (!file) {\r
403         printf("local: unable to open %s\n", outfname);\r
404 \r
405         sftp_register(req = fxp_close_send(fh));\r
406         rreq = sftp_find_request(pktin = sftp_recv());\r
407         assert(rreq == req);\r
408         fxp_close_recv(pktin, rreq);\r
409 \r
410         return 0;\r
411     }\r
412 \r
413     if (restart) {\r
414         char decbuf[30];\r
415         if (seek_file(file, uint64_make(0,0) , FROM_END) == -1) {\r
416             close_wfile(file);\r
417             printf("reget: cannot restart %s - file too large\n",\r
418                    outfname);\r
419             sftp_register(req = fxp_close_send(fh));\r
420             rreq = sftp_find_request(pktin = sftp_recv());\r
421             assert(rreq == req);\r
422             fxp_close_recv(pktin, rreq);\r
423                 \r
424             return 0;\r
425         }\r
426             \r
427         offset = get_file_posn(file);\r
428         uint64_decimal(offset, decbuf);\r
429         printf("reget: restarting at file position %s\n", decbuf);\r
430     } else {\r
431         offset = uint64_make(0, 0);\r
432     }\r
433 \r
434     printf("remote:%s => local:%s\n", fname, outfname);\r
435 \r
436     /*\r
437      * FIXME: we can use FXP_FSTAT here to get the file size, and\r
438      * thus put up a progress bar.\r
439      */\r
440     ret = 1;\r
441     xfer = xfer_download_init(fh, offset);\r
442     while (!xfer_done(xfer)) {\r
443         void *vbuf;\r
444         int ret, len;\r
445         int wpos, wlen;\r
446 \r
447         xfer_download_queue(xfer);\r
448         pktin = sftp_recv();\r
449         ret = xfer_download_gotpkt(xfer, pktin);\r
450 \r
451         if (ret < 0) {\r
452             if (!shown_err) {\r
453                 printf("error while reading: %s\n", fxp_error());\r
454                 shown_err = TRUE;\r
455             }\r
456             ret = 0;\r
457         }\r
458 \r
459         while (xfer_download_data(xfer, &vbuf, &len)) {\r
460             unsigned char *buf = (unsigned char *)vbuf;\r
461 \r
462             wpos = 0;\r
463             while (wpos < len) {\r
464                 wlen = write_to_file(file, buf + wpos, len - wpos);\r
465                 if (wlen <= 0) {\r
466                     printf("error while writing local file\n");\r
467                     ret = 0;\r
468                     xfer_set_error(xfer);\r
469                     break;\r
470                 }\r
471                 wpos += wlen;\r
472             }\r
473             if (wpos < len) {          /* we had an error */\r
474                 ret = 0;\r
475                 xfer_set_error(xfer);\r
476             }\r
477 \r
478             sfree(vbuf);\r
479         }\r
480     }\r
481 \r
482     xfer_cleanup(xfer);\r
483 \r
484     close_wfile(file);\r
485 \r
486     sftp_register(req = fxp_close_send(fh));\r
487     rreq = sftp_find_request(pktin = sftp_recv());\r
488     assert(rreq == req);\r
489     fxp_close_recv(pktin, rreq);\r
490 \r
491     return ret;\r
492 }\r
493 \r
494 int sftp_put_file(char *fname, char *outfname, int recurse, int restart)\r
495 {\r
496     struct fxp_handle *fh;\r
497     struct fxp_xfer *xfer;\r
498     struct sftp_packet *pktin;\r
499     struct sftp_request *req, *rreq;\r
500     uint64 offset;\r
501     RFile *file;\r
502     int ret, err, eof;\r
503 \r
504     /*\r
505      * In recursive mode, see if we're dealing with a directory.\r
506      * (If we're not in recursive mode, we need not even check: the\r
507      * subsequent fopen will return an error message.)\r
508      */\r
509     if (recurse && file_type(fname) == FILE_TYPE_DIRECTORY) {\r
510         struct fxp_attrs attrs;\r
511         int result;\r
512         int nnames, namesize;\r
513         char *name, **ournames;\r
514         DirHandle *dh;\r
515         int i;\r
516 \r
517         /*\r
518          * First, attempt to create the destination directory,\r
519          * unless it already exists.\r
520          */\r
521         sftp_register(req = fxp_stat_send(outfname));\r
522         rreq = sftp_find_request(pktin = sftp_recv());\r
523         assert(rreq == req);\r
524         result = fxp_stat_recv(pktin, rreq, &attrs);\r
525         if (!result ||\r
526             !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) ||\r
527             !(attrs.permissions & 0040000)) {\r
528             sftp_register(req = fxp_mkdir_send(outfname));\r
529             rreq = sftp_find_request(pktin = sftp_recv());\r
530             assert(rreq == req);\r
531             result = fxp_mkdir_recv(pktin, rreq);\r
532 \r
533             if (!result) {\r
534                 printf("%s: create directory: %s\n",\r
535                        outfname, fxp_error());\r
536                 return 0;\r
537             }\r
538         }\r
539 \r
540         /*\r
541          * Now get the list of filenames in the local directory.\r
542          */\r
543         nnames = namesize = 0;\r
544         ournames = NULL;\r
545 \r
546         dh = open_directory(fname);\r
547         if (!dh) {\r
548             printf("%s: unable to open directory\n", fname);\r
549             return 0;\r
550         }\r
551         while ((name = read_filename(dh)) != NULL) {\r
552             if (nnames >= namesize) {\r
553                 namesize += 128;\r
554                 ournames = sresize(ournames, namesize, char *);\r
555             }\r
556             ournames[nnames++] = name;\r
557         }\r
558         close_directory(dh);\r
559 \r
560         /*\r
561          * Sort the names into a clear order. This ought to make\r
562          * things more predictable when we're doing a reput of the\r
563          * same directory, just in case two readdirs on the same\r
564          * local directory return a different order.\r
565          */\r
566         qsort(ournames, nnames, sizeof(*ournames), bare_name_compare);\r
567 \r
568         /*\r
569          * If we're in restart mode, find the last filename on this\r
570          * list that already exists. We may have to do a reput on\r
571          * _that_ file, but shouldn't have to do anything on the\r
572          * previous files.\r
573          *\r
574          * If none of them exists, of course, we start at 0.\r
575          */\r
576         i = 0;\r
577         if (restart) {\r
578             while (i < nnames) {\r
579                 char *nextoutfname;\r
580                 nextoutfname = dupcat(outfname, "/", ournames[i], NULL);\r
581                 sftp_register(req = fxp_stat_send(nextoutfname));\r
582                 rreq = sftp_find_request(pktin = sftp_recv());\r
583                 assert(rreq == req);\r
584                 result = fxp_stat_recv(pktin, rreq, &attrs);\r
585                 sfree(nextoutfname);\r
586                 if (!result)\r
587                     break;\r
588                 i++;\r
589             }\r
590             if (i > 0)\r
591                 i--;\r
592         }\r
593 \r
594         /*\r
595          * Now we're ready to recurse. Starting at ournames[i]\r
596          * and continuing on to the end of the list, we\r
597          * construct a new source and target file name, and\r
598          * call sftp_put_file again.\r
599          */\r
600         for (; i < nnames; i++) {\r
601             char *nextfname, *nextoutfname;\r
602             int ret;\r
603 \r
604             if (fname)\r
605                 nextfname = dir_file_cat(fname, ournames[i]);\r
606             else\r
607                 nextfname = dupstr(ournames[i]);\r
608             nextoutfname = dupcat(outfname, "/", ournames[i], NULL);\r
609             ret = sftp_put_file(nextfname, nextoutfname, recurse, restart);\r
610             restart = FALSE;           /* after first partial file, do full */\r
611             sfree(nextoutfname);\r
612             sfree(nextfname);\r
613             if (!ret) {\r
614                 for (i = 0; i < nnames; i++) {\r
615                     sfree(ournames[i]);\r
616                 }\r
617                 sfree(ournames);\r
618                 return 0;\r
619             }\r
620         }\r
621 \r
622         /*\r
623          * Done this recursion level. Free everything.\r
624          */\r
625         for (i = 0; i < nnames; i++) {\r
626             sfree(ournames[i]);\r
627         }\r
628         sfree(ournames);\r
629 \r
630         return 1;\r
631     }\r
632 \r
633     file = open_existing_file(fname, NULL, NULL, NULL);\r
634     if (!file) {\r
635         printf("local: unable to open %s\n", fname);\r
636         return 0;\r
637     }\r
638     if (restart) {\r
639         sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE));\r
640     } else {\r
641         sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE |\r
642                                           SSH_FXF_CREAT | SSH_FXF_TRUNC));\r
643     }\r
644     rreq = sftp_find_request(pktin = sftp_recv());\r
645     assert(rreq == req);\r
646     fh = fxp_open_recv(pktin, rreq);\r
647 \r
648     if (!fh) {\r
649         close_rfile(file);\r
650         printf("%s: open for write: %s\n", outfname, fxp_error());\r
651         return 0;\r
652     }\r
653 \r
654     if (restart) {\r
655         char decbuf[30];\r
656         struct fxp_attrs attrs;\r
657         int ret;\r
658 \r
659         sftp_register(req = fxp_fstat_send(fh));\r
660         rreq = sftp_find_request(pktin = sftp_recv());\r
661         assert(rreq == req);\r
662         ret = fxp_fstat_recv(pktin, rreq, &attrs);\r
663 \r
664         if (!ret) {\r
665             close_rfile(file);\r
666             printf("read size of %s: %s\n", outfname, fxp_error());\r
667             return 0;\r
668         }\r
669         if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {\r
670             close_rfile(file);\r
671             printf("read size of %s: size was not given\n", outfname);\r
672             return 0;\r
673         }\r
674         offset = attrs.size;\r
675         uint64_decimal(offset, decbuf);\r
676         printf("reput: restarting at file position %s\n", decbuf);\r
677 \r
678         if (seek_file((WFile *)file, offset, FROM_START) != 0)\r
679             seek_file((WFile *)file, uint64_make(0,0), FROM_END);    /* *shrug* */\r
680     } else {\r
681         offset = uint64_make(0, 0);\r
682     }\r
683 \r
684     printf("local:%s => remote:%s\n", fname, outfname);\r
685 \r
686     /*\r
687      * FIXME: we can use FXP_FSTAT here to get the file size, and\r
688      * thus put up a progress bar.\r
689      */\r
690     ret = 1;\r
691     xfer = xfer_upload_init(fh, offset);\r
692     err = eof = 0;\r
693     while ((!err && !eof) || !xfer_done(xfer)) {\r
694         char buffer[4096];\r
695         int len, ret;\r
696 \r
697         while (xfer_upload_ready(xfer) && !err && !eof) {\r
698             len = read_from_file(file, buffer, sizeof(buffer));\r
699             if (len == -1) {\r
700                 printf("error while reading local file\n");\r
701                 err = 1;\r
702             } else if (len == 0) {\r
703                 eof = 1;\r
704             } else {\r
705                 xfer_upload_data(xfer, buffer, len);\r
706             }\r
707         }\r
708 \r
709         if (!xfer_done(xfer)) {\r
710             pktin = sftp_recv();\r
711             ret = xfer_upload_gotpkt(xfer, pktin);\r
712             if (ret <= 0 && !err) {\r
713                 printf("error while writing: %s\n", fxp_error());\r
714                 err = 1;\r
715             }\r
716         }\r
717     }\r
718 \r
719     xfer_cleanup(xfer);\r
720 \r
721     sftp_register(req = fxp_close_send(fh));\r
722     rreq = sftp_find_request(pktin = sftp_recv());\r
723     assert(rreq == req);\r
724     fxp_close_recv(pktin, rreq);\r
725 \r
726     close_rfile(file);\r
727 \r
728     return ret;\r
729 }\r
730 \r
731 /* ----------------------------------------------------------------------\r
732  * A remote wildcard matcher, providing a similar interface to the\r
733  * local one in psftp.h.\r
734  */\r
735 \r
736 typedef struct SftpWildcardMatcher {\r
737     struct fxp_handle *dirh;\r
738     struct fxp_names *names;\r
739     int namepos;\r
740     char *wildcard, *prefix;\r
741 } SftpWildcardMatcher;\r
742 \r
743 SftpWildcardMatcher *sftp_begin_wildcard_matching(char *name)\r
744 {\r
745     struct sftp_packet *pktin;\r
746     struct sftp_request *req, *rreq;\r
747     char *wildcard;\r
748     char *unwcdir, *tmpdir, *cdir;\r
749     int len, check;\r
750     SftpWildcardMatcher *swcm;\r
751     struct fxp_handle *dirh;\r
752 \r
753     /*\r
754      * We don't handle multi-level wildcards; so we expect to find\r
755      * a fully specified directory part, followed by a wildcard\r
756      * after that.\r
757      */\r
758     wildcard = stripslashes(name, 0);\r
759 \r
760     unwcdir = dupstr(name);\r
761     len = wildcard - name;\r
762     unwcdir[len] = '\0';\r
763     if (len > 0 && unwcdir[len-1] == '/')\r
764         unwcdir[len-1] = '\0';\r
765     tmpdir = snewn(1 + len, char);\r
766     check = wc_unescape(tmpdir, unwcdir);\r
767     sfree(tmpdir);\r
768 \r
769     if (!check) {\r
770         printf("Multiple-level wildcards are not supported\n");\r
771         sfree(unwcdir);\r
772         return NULL;\r
773     }\r
774 \r
775     cdir = canonify(unwcdir);\r
776 \r
777     sftp_register(req = fxp_opendir_send(cdir));\r
778     rreq = sftp_find_request(pktin = sftp_recv());\r
779     assert(rreq == req);\r
780     dirh = fxp_opendir_recv(pktin, rreq);\r
781 \r
782     if (dirh) {\r
783         swcm = snew(SftpWildcardMatcher);\r
784         swcm->dirh = dirh;\r
785         swcm->names = NULL;\r
786         swcm->wildcard = dupstr(wildcard);\r
787         swcm->prefix = unwcdir;\r
788     } else {\r
789         printf("Unable to open %s: %s\n", cdir, fxp_error());\r
790         swcm = NULL;\r
791         sfree(unwcdir);\r
792     }\r
793 \r
794     sfree(cdir);\r
795 \r
796     return swcm;\r
797 }\r
798 \r
799 char *sftp_wildcard_get_filename(SftpWildcardMatcher *swcm)\r
800 {\r
801     struct fxp_name *name;\r
802     struct sftp_packet *pktin;\r
803     struct sftp_request *req, *rreq;\r
804 \r
805     while (1) {\r
806         if (swcm->names && swcm->namepos >= swcm->names->nnames) {\r
807             fxp_free_names(swcm->names);\r
808             swcm->names = NULL;\r
809         }\r
810 \r
811         if (!swcm->names) {\r
812             sftp_register(req = fxp_readdir_send(swcm->dirh));\r
813             rreq = sftp_find_request(pktin = sftp_recv());\r
814             assert(rreq == req);\r
815             swcm->names = fxp_readdir_recv(pktin, rreq);\r
816 \r
817             if (!swcm->names) {\r
818                 if (fxp_error_type() != SSH_FX_EOF)\r
819                     printf("%s: reading directory: %s\n", swcm->prefix,\r
820                            fxp_error());\r
821                 return NULL;\r
822             }\r
823 \r
824             swcm->namepos = 0;\r
825         }\r
826 \r
827         assert(swcm->names && swcm->namepos < swcm->names->nnames);\r
828 \r
829         name = &swcm->names->names[swcm->namepos++];\r
830 \r
831         if (!strcmp(name->filename, ".") || !strcmp(name->filename, ".."))\r
832             continue;                  /* expected bad filenames */\r
833 \r
834         if (!vet_filename(name->filename)) {\r
835             printf("ignoring potentially dangerous server-"\r
836                    "supplied filename '%s'\n", name->filename);\r
837             continue;                  /* unexpected bad filename */\r
838         }\r
839 \r
840         if (!wc_match(swcm->wildcard, name->filename))\r
841             continue;                  /* doesn't match the wildcard */\r
842 \r
843         /*\r
844          * We have a working filename. Return it.\r
845          */\r
846         return dupprintf("%s%s%s", swcm->prefix,\r
847                          (!swcm->prefix[0] ||\r
848                           swcm->prefix[strlen(swcm->prefix)-1]=='/' ?\r
849                           "" : "/"),\r
850                          name->filename);\r
851     }\r
852 }\r
853 \r
854 void sftp_finish_wildcard_matching(SftpWildcardMatcher *swcm)\r
855 {\r
856     struct sftp_packet *pktin;\r
857     struct sftp_request *req, *rreq;\r
858 \r
859     sftp_register(req = fxp_close_send(swcm->dirh));\r
860     rreq = sftp_find_request(pktin = sftp_recv());\r
861     assert(rreq == req);\r
862     fxp_close_recv(pktin, rreq);\r
863 \r
864     if (swcm->names)\r
865         fxp_free_names(swcm->names);\r
866 \r
867     sfree(swcm->prefix);\r
868     sfree(swcm->wildcard);\r
869 \r
870     sfree(swcm);\r
871 }\r
872 \r
873 /*\r
874  * General function to match a potential wildcard in a filename\r
875  * argument and iterate over every matching file. Used in several\r
876  * PSFTP commands (rmdir, rm, chmod, mv).\r
877  */\r
878 int wildcard_iterate(char *filename, int (*func)(void *, char *), void *ctx)\r
879 {\r
880     char *unwcfname, *newname, *cname;\r
881     int is_wc, ret;\r
882 \r
883     unwcfname = snewn(strlen(filename)+1, char);\r
884     is_wc = !wc_unescape(unwcfname, filename);\r
885 \r
886     if (is_wc) {\r
887         SftpWildcardMatcher *swcm = sftp_begin_wildcard_matching(filename);\r
888         int matched = FALSE;\r
889         sfree(unwcfname);\r
890 \r
891         if (!swcm)\r
892             return 0;\r
893 \r
894         ret = 1;\r
895 \r
896         while ( (newname = sftp_wildcard_get_filename(swcm)) != NULL ) {\r
897             cname = canonify(newname);\r
898             if (!cname) {\r
899                 printf("%s: canonify: %s\n", newname, fxp_error());\r
900                 ret = 0;\r
901             }\r
902             matched = TRUE;\r
903             ret &= func(ctx, cname);\r
904             sfree(cname);\r
905         }\r
906 \r
907         if (!matched) {\r
908             /* Politely warn the user that nothing matched. */\r
909             printf("%s: nothing matched\n", filename);\r
910         }\r
911 \r
912         sftp_finish_wildcard_matching(swcm);\r
913     } else {\r
914         cname = canonify(unwcfname);\r
915         if (!cname) {\r
916             printf("%s: canonify: %s\n", filename, fxp_error());\r
917             ret = 0;\r
918         }\r
919         ret = func(ctx, cname);\r
920         sfree(cname);\r
921         sfree(unwcfname);\r
922     }\r
923 \r
924     return ret;\r
925 }\r
926 \r
927 /*\r
928  * Handy helper function.\r
929  */\r
930 int is_wildcard(char *name)\r
931 {\r
932     char *unwcfname = snewn(strlen(name)+1, char);\r
933     int is_wc = !wc_unescape(unwcfname, name);\r
934     sfree(unwcfname);\r
935     return is_wc;\r
936 }\r
937 \r
938 /* ----------------------------------------------------------------------\r
939  * Actual sftp commands.\r
940  */\r
941 struct sftp_command {\r
942     char **words;\r
943     int nwords, wordssize;\r
944     int (*obey) (struct sftp_command *);        /* returns <0 to quit */\r
945 };\r
946 \r
947 int sftp_cmd_null(struct sftp_command *cmd)\r
948 {\r
949     return 1;                          /* success */\r
950 }\r
951 \r
952 int sftp_cmd_unknown(struct sftp_command *cmd)\r
953 {\r
954     printf("psftp: unknown command \"%s\"\n", cmd->words[0]);\r
955     return 0;                          /* failure */\r
956 }\r
957 \r
958 int sftp_cmd_quit(struct sftp_command *cmd)\r
959 {\r
960     return -1;\r
961 }\r
962 \r
963 int sftp_cmd_close(struct sftp_command *cmd)\r
964 {\r
965     if (back == NULL) {\r
966         not_connected();\r
967         return 0;\r
968     }\r
969 \r
970     if (back != NULL && back->connected(backhandle)) {\r
971         char ch;\r
972         back->special(backhandle, TS_EOF);\r
973         sftp_recvdata(&ch, 1);\r
974     }\r
975     do_sftp_cleanup();\r
976 \r
977     return 0;\r
978 }\r
979 \r
980 /*\r
981  * List a directory. If no arguments are given, list pwd; otherwise\r
982  * list the directory given in words[1].\r
983  */\r
984 int sftp_cmd_ls(struct sftp_command *cmd)\r
985 {\r
986     struct fxp_handle *dirh;\r
987     struct fxp_names *names;\r
988     struct fxp_name **ournames;\r
989     int nnames, namesize;\r
990     char *dir, *cdir, *unwcdir, *wildcard;\r
991     struct sftp_packet *pktin;\r
992     struct sftp_request *req, *rreq;\r
993     int i;\r
994 \r
995     if (back == NULL) {\r
996         not_connected();\r
997         return 0;\r
998     }\r
999 \r
1000     if (cmd->nwords < 2)\r
1001         dir = ".";\r
1002     else\r
1003         dir = cmd->words[1];\r
1004 \r
1005     unwcdir = snewn(1 + strlen(dir), char);\r
1006     if (wc_unescape(unwcdir, dir)) {\r
1007         dir = unwcdir;\r
1008         wildcard = NULL;\r
1009     } else {\r
1010         char *tmpdir;\r
1011         int len, check;\r
1012 \r
1013         wildcard = stripslashes(dir, 0);\r
1014         unwcdir = dupstr(dir);\r
1015         len = wildcard - dir;\r
1016         unwcdir[len] = '\0';\r
1017         if (len > 0 && unwcdir[len-1] == '/')\r
1018             unwcdir[len-1] = '\0';\r
1019         tmpdir = snewn(1 + len, char);\r
1020         check = wc_unescape(tmpdir, unwcdir);\r
1021         sfree(tmpdir);\r
1022         if (!check) {\r
1023             printf("Multiple-level wildcards are not supported\n");\r
1024             sfree(unwcdir);\r
1025             return 0;\r
1026         }\r
1027         dir = unwcdir;\r
1028     }\r
1029 \r
1030     cdir = canonify(dir);\r
1031     if (!cdir) {\r
1032         printf("%s: canonify: %s\n", dir, fxp_error());\r
1033         sfree(unwcdir);\r
1034         return 0;\r
1035     }\r
1036 \r
1037     printf("Listing directory %s\n", cdir);\r
1038 \r
1039     sftp_register(req = fxp_opendir_send(cdir));\r
1040     rreq = sftp_find_request(pktin = sftp_recv());\r
1041     assert(rreq == req);\r
1042     dirh = fxp_opendir_recv(pktin, rreq);\r
1043 \r
1044     if (dirh == NULL) {\r
1045         printf("Unable to open %s: %s\n", dir, fxp_error());\r
1046     } else {\r
1047         nnames = namesize = 0;\r
1048         ournames = NULL;\r
1049 \r
1050         while (1) {\r
1051 \r
1052             sftp_register(req = fxp_readdir_send(dirh));\r
1053             rreq = sftp_find_request(pktin = sftp_recv());\r
1054             assert(rreq == req);\r
1055             names = fxp_readdir_recv(pktin, rreq);\r
1056 \r
1057             if (names == NULL) {\r
1058                 if (fxp_error_type() == SSH_FX_EOF)\r
1059                     break;\r
1060                 printf("Reading directory %s: %s\n", dir, fxp_error());\r
1061                 break;\r
1062             }\r
1063             if (names->nnames == 0) {\r
1064                 fxp_free_names(names);\r
1065                 break;\r
1066             }\r
1067 \r
1068             if (nnames + names->nnames >= namesize) {\r
1069                 namesize += names->nnames + 128;\r
1070                 ournames = sresize(ournames, namesize, struct fxp_name *);\r
1071             }\r
1072 \r
1073             for (i = 0; i < names->nnames; i++)\r
1074                 if (!wildcard || wc_match(wildcard, names->names[i].filename))\r
1075                     ournames[nnames++] = fxp_dup_name(&names->names[i]);\r
1076 \r
1077             fxp_free_names(names);\r
1078         }\r
1079         sftp_register(req = fxp_close_send(dirh));\r
1080         rreq = sftp_find_request(pktin = sftp_recv());\r
1081         assert(rreq == req);\r
1082         fxp_close_recv(pktin, rreq);\r
1083 \r
1084         /*\r
1085          * Now we have our filenames. Sort them by actual file\r
1086          * name, and then output the longname parts.\r
1087          */\r
1088         qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare);\r
1089 \r
1090         /*\r
1091          * And print them.\r
1092          */\r
1093         for (i = 0; i < nnames; i++) {\r
1094             printf("%s\n", ournames[i]->longname);\r
1095             fxp_free_name(ournames[i]);\r
1096         }\r
1097         sfree(ournames);\r
1098     }\r
1099 \r
1100     sfree(cdir);\r
1101     sfree(unwcdir);\r
1102 \r
1103     return 1;\r
1104 }\r
1105 \r
1106 /*\r
1107  * Change directories. We do this by canonifying the new name, then\r
1108  * trying to OPENDIR it. Only if that succeeds do we set the new pwd.\r
1109  */\r
1110 int sftp_cmd_cd(struct sftp_command *cmd)\r
1111 {\r
1112     struct fxp_handle *dirh;\r
1113     struct sftp_packet *pktin;\r
1114     struct sftp_request *req, *rreq;\r
1115     char *dir;\r
1116 \r
1117     if (back == NULL) {\r
1118         not_connected();\r
1119         return 0;\r
1120     }\r
1121 \r
1122     if (cmd->nwords < 2)\r
1123         dir = dupstr(homedir);\r
1124     else\r
1125         dir = canonify(cmd->words[1]);\r
1126 \r
1127     if (!dir) {\r
1128         printf("%s: canonify: %s\n", dir, fxp_error());\r
1129         return 0;\r
1130     }\r
1131 \r
1132     sftp_register(req = fxp_opendir_send(dir));\r
1133     rreq = sftp_find_request(pktin = sftp_recv());\r
1134     assert(rreq == req);\r
1135     dirh = fxp_opendir_recv(pktin, rreq);\r
1136 \r
1137     if (!dirh) {\r
1138         printf("Directory %s: %s\n", dir, fxp_error());\r
1139         sfree(dir);\r
1140         return 0;\r
1141     }\r
1142 \r
1143     sftp_register(req = fxp_close_send(dirh));\r
1144     rreq = sftp_find_request(pktin = sftp_recv());\r
1145     assert(rreq == req);\r
1146     fxp_close_recv(pktin, rreq);\r
1147 \r
1148     sfree(pwd);\r
1149     pwd = dir;\r
1150     printf("Remote directory is now %s\n", pwd);\r
1151 \r
1152     return 1;\r
1153 }\r
1154 \r
1155 /*\r
1156  * Print current directory. Easy as pie.\r
1157  */\r
1158 int sftp_cmd_pwd(struct sftp_command *cmd)\r
1159 {\r
1160     if (back == NULL) {\r
1161         not_connected();\r
1162         return 0;\r
1163     }\r
1164 \r
1165     printf("Remote directory is %s\n", pwd);\r
1166     return 1;\r
1167 }\r
1168 \r
1169 /*\r
1170  * Get a file and save it at the local end. We have three very\r
1171  * similar commands here. The basic one is `get'; `reget' differs\r
1172  * in that it checks for the existence of the destination file and\r
1173  * starts from where a previous aborted transfer left off; `mget'\r
1174  * differs in that it interprets all its arguments as files to\r
1175  * transfer (never as a different local name for a remote file) and\r
1176  * can handle wildcards.\r
1177  */\r
1178 int sftp_general_get(struct sftp_command *cmd, int restart, int multiple)\r
1179 {\r
1180     char *fname, *unwcfname, *origfname, *origwfname, *outfname;\r
1181     int i, ret;\r
1182     int recurse = FALSE;\r
1183 \r
1184     if (back == NULL) {\r
1185         not_connected();\r
1186         return 0;\r
1187     }\r
1188 \r
1189     i = 1;\r
1190     while (i < cmd->nwords && cmd->words[i][0] == '-') {\r
1191         if (!strcmp(cmd->words[i], "--")) {\r
1192             /* finish processing options */\r
1193             i++;\r
1194             break;\r
1195         } else if (!strcmp(cmd->words[i], "-r")) {\r
1196             recurse = TRUE;\r
1197         } else {\r
1198             printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]);\r
1199             return 0;\r
1200         }\r
1201         i++;\r
1202     }\r
1203 \r
1204     if (i >= cmd->nwords) {\r
1205         printf("%s: expects a filename\n", cmd->words[0]);\r
1206         return 0;\r
1207     }\r
1208 \r
1209     ret = 1;\r
1210     do {\r
1211         SftpWildcardMatcher *swcm;\r
1212 \r
1213         origfname = cmd->words[i++];\r
1214         unwcfname = snewn(strlen(origfname)+1, char);\r
1215 \r
1216         if (multiple && !wc_unescape(unwcfname, origfname)) {\r
1217             swcm = sftp_begin_wildcard_matching(origfname);\r
1218             if (!swcm) {\r
1219                 sfree(unwcfname);\r
1220                 continue;\r
1221             }\r
1222             origwfname = sftp_wildcard_get_filename(swcm);\r
1223             if (!origwfname) {\r
1224                 /* Politely warn the user that nothing matched. */\r
1225                 printf("%s: nothing matched\n", origfname);\r
1226                 sftp_finish_wildcard_matching(swcm);\r
1227                 sfree(unwcfname);\r
1228                 continue;\r
1229             }\r
1230         } else {\r
1231             origwfname = origfname;\r
1232             swcm = NULL;\r
1233         }\r
1234 \r
1235         while (origwfname) {\r
1236             fname = canonify(origwfname);\r
1237 \r
1238             if (!fname) {\r
1239                 printf("%s: canonify: %s\n", origwfname, fxp_error());\r
1240                 sfree(unwcfname);\r
1241                 return 0;\r
1242             }\r
1243 \r
1244             if (!multiple && i < cmd->nwords)\r
1245                 outfname = cmd->words[i++];\r
1246             else\r
1247                 outfname = stripslashes(origwfname, 0);\r
1248 \r
1249             ret = sftp_get_file(fname, outfname, recurse, restart);\r
1250 \r
1251             sfree(fname);\r
1252 \r
1253             if (swcm) {\r
1254                 sfree(origwfname);\r
1255                 origwfname = sftp_wildcard_get_filename(swcm);\r
1256             } else {\r
1257                 origwfname = NULL;\r
1258             }\r
1259         }\r
1260         sfree(unwcfname);\r
1261         if (swcm)\r
1262             sftp_finish_wildcard_matching(swcm);\r
1263         if (!ret)\r
1264             return ret;\r
1265 \r
1266     } while (multiple && i < cmd->nwords);\r
1267 \r
1268     return ret;\r
1269 }\r
1270 int sftp_cmd_get(struct sftp_command *cmd)\r
1271 {\r
1272     return sftp_general_get(cmd, 0, 0);\r
1273 }\r
1274 int sftp_cmd_mget(struct sftp_command *cmd)\r
1275 {\r
1276     return sftp_general_get(cmd, 0, 1);\r
1277 }\r
1278 int sftp_cmd_reget(struct sftp_command *cmd)\r
1279 {\r
1280     return sftp_general_get(cmd, 1, 0);\r
1281 }\r
1282 \r
1283 /*\r
1284  * Send a file and store it at the remote end. We have three very\r
1285  * similar commands here. The basic one is `put'; `reput' differs\r
1286  * in that it checks for the existence of the destination file and\r
1287  * starts from where a previous aborted transfer left off; `mput'\r
1288  * differs in that it interprets all its arguments as files to\r
1289  * transfer (never as a different remote name for a local file) and\r
1290  * can handle wildcards.\r
1291  */\r
1292 int sftp_general_put(struct sftp_command *cmd, int restart, int multiple)\r
1293 {\r
1294     char *fname, *wfname, *origoutfname, *outfname;\r
1295     int i, ret;\r
1296     int recurse = FALSE;\r
1297 \r
1298     if (back == NULL) {\r
1299         not_connected();\r
1300         return 0;\r
1301     }\r
1302 \r
1303     i = 1;\r
1304     while (i < cmd->nwords && cmd->words[i][0] == '-') {\r
1305         if (!strcmp(cmd->words[i], "--")) {\r
1306             /* finish processing options */\r
1307             i++;\r
1308             break;\r
1309         } else if (!strcmp(cmd->words[i], "-r")) {\r
1310             recurse = TRUE;\r
1311         } else {\r
1312             printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]);\r
1313             return 0;\r
1314         }\r
1315         i++;\r
1316     }\r
1317 \r
1318     if (i >= cmd->nwords) {\r
1319         printf("%s: expects a filename\n", cmd->words[0]);\r
1320         return 0;\r
1321     }\r
1322 \r
1323     ret = 1;\r
1324     do {\r
1325         WildcardMatcher *wcm;\r
1326         fname = cmd->words[i++];\r
1327 \r
1328         if (multiple && test_wildcard(fname, FALSE) == WCTYPE_WILDCARD) {\r
1329             wcm = begin_wildcard_matching(fname);\r
1330             wfname = wildcard_get_filename(wcm);\r
1331             if (!wfname) {\r
1332                 /* Politely warn the user that nothing matched. */\r
1333                 printf("%s: nothing matched\n", fname);\r
1334                 finish_wildcard_matching(wcm);\r
1335                 continue;\r
1336             }\r
1337         } else {\r
1338             wfname = fname;\r
1339             wcm = NULL;\r
1340         }\r
1341 \r
1342         while (wfname) {\r
1343             if (!multiple && i < cmd->nwords)\r
1344                 origoutfname = cmd->words[i++];\r
1345             else\r
1346                 origoutfname = stripslashes(wfname, 1);\r
1347 \r
1348             outfname = canonify(origoutfname);\r
1349             if (!outfname) {\r
1350                 printf("%s: canonify: %s\n", origoutfname, fxp_error());\r
1351                 if (wcm) {\r
1352                     sfree(wfname);\r
1353                     finish_wildcard_matching(wcm);\r
1354                 }\r
1355                 return 0;\r
1356             }\r
1357             ret = sftp_put_file(wfname, outfname, recurse, restart);\r
1358             sfree(outfname);\r
1359 \r
1360             if (wcm) {\r
1361                 sfree(wfname);\r
1362                 wfname = wildcard_get_filename(wcm);\r
1363             } else {\r
1364                 wfname = NULL;\r
1365             }\r
1366         }\r
1367 \r
1368         if (wcm)\r
1369             finish_wildcard_matching(wcm);\r
1370 \r
1371         if (!ret)\r
1372             return ret;\r
1373 \r
1374     } while (multiple && i < cmd->nwords);\r
1375 \r
1376     return ret;\r
1377 }\r
1378 int sftp_cmd_put(struct sftp_command *cmd)\r
1379 {\r
1380     return sftp_general_put(cmd, 0, 0);\r
1381 }\r
1382 int sftp_cmd_mput(struct sftp_command *cmd)\r
1383 {\r
1384     return sftp_general_put(cmd, 0, 1);\r
1385 }\r
1386 int sftp_cmd_reput(struct sftp_command *cmd)\r
1387 {\r
1388     return sftp_general_put(cmd, 1, 0);\r
1389 }\r
1390 \r
1391 int sftp_cmd_mkdir(struct sftp_command *cmd)\r
1392 {\r
1393     char *dir;\r
1394     struct sftp_packet *pktin;\r
1395     struct sftp_request *req, *rreq;\r
1396     int result;\r
1397     int i, ret;\r
1398 \r
1399     if (back == NULL) {\r
1400         not_connected();\r
1401         return 0;\r
1402     }\r
1403 \r
1404     if (cmd->nwords < 2) {\r
1405         printf("mkdir: expects a directory\n");\r
1406         return 0;\r
1407     }\r
1408 \r
1409     ret = 1;\r
1410     for (i = 1; i < cmd->nwords; i++) {\r
1411         dir = canonify(cmd->words[i]);\r
1412         if (!dir) {\r
1413             printf("%s: canonify: %s\n", dir, fxp_error());\r
1414             return 0;\r
1415         }\r
1416 \r
1417         sftp_register(req = fxp_mkdir_send(dir));\r
1418         rreq = sftp_find_request(pktin = sftp_recv());\r
1419         assert(rreq == req);\r
1420         result = fxp_mkdir_recv(pktin, rreq);\r
1421 \r
1422         if (!result) {\r
1423             printf("mkdir %s: %s\n", dir, fxp_error());\r
1424             ret = 0;\r
1425         } else\r
1426             printf("mkdir %s: OK\n", dir);\r
1427 \r
1428         sfree(dir);\r
1429     }\r
1430 \r
1431     return ret;\r
1432 }\r
1433 \r
1434 static int sftp_action_rmdir(void *vctx, char *dir)\r
1435 {\r
1436     struct sftp_packet *pktin;\r
1437     struct sftp_request *req, *rreq;\r
1438     int result;\r
1439 \r
1440     sftp_register(req = fxp_rmdir_send(dir));\r
1441     rreq = sftp_find_request(pktin = sftp_recv());\r
1442     assert(rreq == req);\r
1443     result = fxp_rmdir_recv(pktin, rreq);\r
1444 \r
1445     if (!result) {\r
1446         printf("rmdir %s: %s\n", dir, fxp_error());\r
1447         return 0;\r
1448     }\r
1449 \r
1450     printf("rmdir %s: OK\n", dir);\r
1451 \r
1452     return 1;\r
1453 }\r
1454 \r
1455 int sftp_cmd_rmdir(struct sftp_command *cmd)\r
1456 {\r
1457     int i, ret;\r
1458 \r
1459     if (back == NULL) {\r
1460         not_connected();\r
1461         return 0;\r
1462     }\r
1463 \r
1464     if (cmd->nwords < 2) {\r
1465         printf("rmdir: expects a directory\n");\r
1466         return 0;\r
1467     }\r
1468 \r
1469     ret = 1;\r
1470     for (i = 1; i < cmd->nwords; i++)\r
1471         ret &= wildcard_iterate(cmd->words[i], sftp_action_rmdir, NULL);\r
1472 \r
1473     return ret;\r
1474 }\r
1475 \r
1476 static int sftp_action_rm(void *vctx, char *fname)\r
1477 {\r
1478     struct sftp_packet *pktin;\r
1479     struct sftp_request *req, *rreq;\r
1480     int result;\r
1481 \r
1482     sftp_register(req = fxp_remove_send(fname));\r
1483     rreq = sftp_find_request(pktin = sftp_recv());\r
1484     assert(rreq == req);\r
1485     result = fxp_remove_recv(pktin, rreq);\r
1486 \r
1487     if (!result) {\r
1488         printf("rm %s: %s\n", fname, fxp_error());\r
1489         return 0;\r
1490     }\r
1491 \r
1492     printf("rm %s: OK\n", fname);\r
1493 \r
1494     return 1;\r
1495 }\r
1496 \r
1497 int sftp_cmd_rm(struct sftp_command *cmd)\r
1498 {\r
1499     int i, ret;\r
1500 \r
1501     if (back == NULL) {\r
1502         not_connected();\r
1503         return 0;\r
1504     }\r
1505 \r
1506     if (cmd->nwords < 2) {\r
1507         printf("rm: expects a filename\n");\r
1508         return 0;\r
1509     }\r
1510 \r
1511     ret = 1;\r
1512     for (i = 1; i < cmd->nwords; i++)\r
1513         ret &= wildcard_iterate(cmd->words[i], sftp_action_rm, NULL);\r
1514 \r
1515     return ret;\r
1516 }\r
1517 \r
1518 static int check_is_dir(char *dstfname)\r
1519 {\r
1520     struct sftp_packet *pktin;\r
1521     struct sftp_request *req, *rreq;\r
1522     struct fxp_attrs attrs;\r
1523     int result;\r
1524 \r
1525     sftp_register(req = fxp_stat_send(dstfname));\r
1526     rreq = sftp_find_request(pktin = sftp_recv());\r
1527     assert(rreq == req);\r
1528     result = fxp_stat_recv(pktin, rreq, &attrs);\r
1529 \r
1530     if (result &&\r
1531         (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&\r
1532         (attrs.permissions & 0040000))\r
1533         return TRUE;\r
1534     else\r
1535         return FALSE;\r
1536 }\r
1537 \r
1538 struct sftp_context_mv {\r
1539     char *dstfname;\r
1540     int dest_is_dir;\r
1541 };\r
1542 \r
1543 static int sftp_action_mv(void *vctx, char *srcfname)\r
1544 {\r
1545     struct sftp_context_mv *ctx = (struct sftp_context_mv *)vctx;\r
1546     struct sftp_packet *pktin;\r
1547     struct sftp_request *req, *rreq;\r
1548     const char *error;\r
1549     char *finalfname, *newcanon = NULL;\r
1550     int ret, result;\r
1551 \r
1552     if (ctx->dest_is_dir) {\r
1553         char *p;\r
1554         char *newname;\r
1555 \r
1556         p = srcfname + strlen(srcfname);\r
1557         while (p > srcfname && p[-1] != '/') p--;\r
1558         newname = dupcat(ctx->dstfname, "/", p, NULL);\r
1559         newcanon = canonify(newname);\r
1560         if (!newcanon) {\r
1561             printf("%s: canonify: %s\n", newname, fxp_error());\r
1562             sfree(newname);\r
1563             return 0;\r
1564         }\r
1565         sfree(newname);\r
1566 \r
1567         finalfname = newcanon;\r
1568     } else {\r
1569         finalfname = ctx->dstfname;\r
1570     }\r
1571 \r
1572     sftp_register(req = fxp_rename_send(srcfname, finalfname));\r
1573     rreq = sftp_find_request(pktin = sftp_recv());\r
1574     assert(rreq == req);\r
1575     result = fxp_rename_recv(pktin, rreq);\r
1576 \r
1577     error = result ? NULL : fxp_error();\r
1578 \r
1579     if (error) {\r
1580         printf("mv %s %s: %s\n", srcfname, finalfname, error);\r
1581         ret = 0;\r
1582     } else {\r
1583         printf("%s -> %s\n", srcfname, finalfname);\r
1584         ret = 1;\r
1585     }\r
1586 \r
1587     sfree(newcanon);\r
1588     return ret;\r
1589 }\r
1590 \r
1591 int sftp_cmd_mv(struct sftp_command *cmd)\r
1592 {\r
1593     struct sftp_context_mv actx, *ctx = &actx;\r
1594     int i, ret;\r
1595 \r
1596     if (back == NULL) {\r
1597         not_connected();\r
1598         return 0;\r
1599     }\r
1600 \r
1601     if (cmd->nwords < 3) {\r
1602         printf("mv: expects two filenames\n");\r
1603         return 0;\r
1604     }\r
1605 \r
1606     ctx->dstfname = canonify(cmd->words[cmd->nwords-1]);\r
1607     if (!ctx->dstfname) {\r
1608         printf("%s: canonify: %s\n", ctx->dstfname, fxp_error());\r
1609         return 0;\r
1610     }\r
1611 \r
1612     /*\r
1613      * If there's more than one source argument, or one source\r
1614      * argument which is a wildcard, we _require_ that the\r
1615      * destination is a directory.\r
1616      */\r
1617     ctx->dest_is_dir = check_is_dir(ctx->dstfname);\r
1618     if ((cmd->nwords > 3 || is_wildcard(cmd->words[1])) && !ctx->dest_is_dir) {\r
1619         printf("mv: multiple or wildcard arguments require the destination"\r
1620                " to be a directory\n");\r
1621         sfree(ctx->dstfname);\r
1622         return 0;\r
1623     }\r
1624 \r
1625     /*\r
1626      * Now iterate over the source arguments.\r
1627      */\r
1628     ret = 1;\r
1629     for (i = 1; i < cmd->nwords-1; i++)\r
1630         ret &= wildcard_iterate(cmd->words[i], sftp_action_mv, ctx);\r
1631 \r
1632     sfree(ctx->dstfname);\r
1633     return ret;\r
1634 }\r
1635 \r
1636 struct sftp_context_chmod {\r
1637     unsigned attrs_clr, attrs_xor;\r
1638 };\r
1639 \r
1640 static int sftp_action_chmod(void *vctx, char *fname)\r
1641 {\r
1642     struct fxp_attrs attrs;\r
1643     struct sftp_packet *pktin;\r
1644     struct sftp_request *req, *rreq;\r
1645     int result;\r
1646     unsigned oldperms, newperms;\r
1647     struct sftp_context_chmod *ctx = (struct sftp_context_chmod *)vctx;\r
1648 \r
1649     sftp_register(req = fxp_stat_send(fname));\r
1650     rreq = sftp_find_request(pktin = sftp_recv());\r
1651     assert(rreq == req);\r
1652     result = fxp_stat_recv(pktin, rreq, &attrs);\r
1653 \r
1654     if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {\r
1655         printf("get attrs for %s: %s\n", fname,\r
1656                result ? "file permissions not provided" : fxp_error());\r
1657         return 0;\r
1658     }\r
1659 \r
1660     attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS;   /* perms _only_ */\r
1661     oldperms = attrs.permissions & 07777;\r
1662     attrs.permissions &= ~ctx->attrs_clr;\r
1663     attrs.permissions ^= ctx->attrs_xor;\r
1664     newperms = attrs.permissions & 07777;\r
1665 \r
1666     if (oldperms == newperms)\r
1667         return 1;                      /* no need to do anything! */\r
1668 \r
1669     sftp_register(req = fxp_setstat_send(fname, attrs));\r
1670     rreq = sftp_find_request(pktin = sftp_recv());\r
1671     assert(rreq == req);\r
1672     result = fxp_setstat_recv(pktin, rreq);\r
1673 \r
1674     if (!result) {\r
1675         printf("set attrs for %s: %s\n", fname, fxp_error());\r
1676         return 0;\r
1677     }\r
1678 \r
1679     printf("%s: %04o -> %04o\n", fname, oldperms, newperms);\r
1680 \r
1681     return 1;\r
1682 }\r
1683 \r
1684 int sftp_cmd_chmod(struct sftp_command *cmd)\r
1685 {\r
1686     char *mode;\r
1687     int i, ret;\r
1688     struct sftp_context_chmod actx, *ctx = &actx;\r
1689 \r
1690     if (back == NULL) {\r
1691         not_connected();\r
1692         return 0;\r
1693     }\r
1694 \r
1695     if (cmd->nwords < 3) {\r
1696         printf("chmod: expects a mode specifier and a filename\n");\r
1697         return 0;\r
1698     }\r
1699 \r
1700     /*\r
1701      * Attempt to parse the mode specifier in cmd->words[1]. We\r
1702      * don't support the full horror of Unix chmod; instead we\r
1703      * support a much simpler syntax in which the user can either\r
1704      * specify an octal number, or a comma-separated sequence of\r
1705      * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may\r
1706      * _only_ be omitted if the only attribute mentioned is t,\r
1707      * since all others require a user/group/other specification.\r
1708      * Additionally, the s attribute may not be specified for any\r
1709      * [ugoa] specifications other than exactly u or exactly g.\r
1710      */\r
1711     ctx->attrs_clr = ctx->attrs_xor = 0;\r
1712     mode = cmd->words[1];\r
1713     if (mode[0] >= '0' && mode[0] <= '9') {\r
1714         if (mode[strspn(mode, "01234567")]) {\r
1715             printf("chmod: numeric file modes should"\r
1716                    " contain digits 0-7 only\n");\r
1717             return 0;\r
1718         }\r
1719         ctx->attrs_clr = 07777;\r
1720         sscanf(mode, "%o", &ctx->attrs_xor);\r
1721         ctx->attrs_xor &= ctx->attrs_clr;\r
1722     } else {\r
1723         while (*mode) {\r
1724             char *modebegin = mode;\r
1725             unsigned subset, perms;\r
1726             int action;\r
1727 \r
1728             subset = 0;\r
1729             while (*mode && *mode != ',' &&\r
1730                    *mode != '+' && *mode != '-' && *mode != '=') {\r
1731                 switch (*mode) {\r
1732                   case 'u': subset |= 04700; break; /* setuid, user perms */\r
1733                   case 'g': subset |= 02070; break; /* setgid, group perms */\r
1734                   case 'o': subset |= 00007; break; /* just other perms */\r
1735                   case 'a': subset |= 06777; break; /* all of the above */\r
1736                   default:\r
1737                     printf("chmod: file mode '%.*s' contains unrecognised"\r
1738                            " user/group/other specifier '%c'\n",\r
1739                            (int)strcspn(modebegin, ","), modebegin, *mode);\r
1740                     return 0;\r
1741                 }\r
1742                 mode++;\r
1743             }\r
1744             if (!*mode || *mode == ',') {\r
1745                 printf("chmod: file mode '%.*s' is incomplete\n",\r
1746                        (int)strcspn(modebegin, ","), modebegin);\r
1747                 return 0;\r
1748             }\r
1749             action = *mode++;\r
1750             if (!*mode || *mode == ',') {\r
1751                 printf("chmod: file mode '%.*s' is incomplete\n",\r
1752                        (int)strcspn(modebegin, ","), modebegin);\r
1753                 return 0;\r
1754             }\r
1755             perms = 0;\r
1756             while (*mode && *mode != ',') {\r
1757                 switch (*mode) {\r
1758                   case 'r': perms |= 00444; break;\r
1759                   case 'w': perms |= 00222; break;\r
1760                   case 'x': perms |= 00111; break;\r
1761                   case 't': perms |= 01000; subset |= 01000; break;\r
1762                   case 's':\r
1763                     if ((subset & 06777) != 04700 &&\r
1764                         (subset & 06777) != 02070) {\r
1765                         printf("chmod: file mode '%.*s': set[ug]id bit should"\r
1766                                " be used with exactly one of u or g only\n",\r
1767                                (int)strcspn(modebegin, ","), modebegin);\r
1768                         return 0;\r
1769                     }\r
1770                     perms |= 06000;\r
1771                     break;\r
1772                   default:\r
1773                     printf("chmod: file mode '%.*s' contains unrecognised"\r
1774                            " permission specifier '%c'\n",\r
1775                            (int)strcspn(modebegin, ","), modebegin, *mode);\r
1776                     return 0;\r
1777                 }\r
1778                 mode++;\r
1779             }\r
1780             if (!(subset & 06777) && (perms &~ subset)) {\r
1781                 printf("chmod: file mode '%.*s' contains no user/group/other"\r
1782                        " specifier and permissions other than 't' \n",\r
1783                        (int)strcspn(modebegin, ","), modebegin);\r
1784                 return 0;\r
1785             }\r
1786             perms &= subset;\r
1787             switch (action) {\r
1788               case '+':\r
1789                 ctx->attrs_clr |= perms;\r
1790                 ctx->attrs_xor |= perms;\r
1791                 break;\r
1792               case '-':\r
1793                 ctx->attrs_clr |= perms;\r
1794                 ctx->attrs_xor &= ~perms;\r
1795                 break;\r
1796               case '=':\r
1797                 ctx->attrs_clr |= subset;\r
1798                 ctx->attrs_xor |= perms;\r
1799                 break;\r
1800             }\r
1801             if (*mode) mode++;         /* eat comma */\r
1802         }\r
1803     }\r
1804 \r
1805     ret = 1;\r
1806     for (i = 2; i < cmd->nwords; i++)\r
1807         ret &= wildcard_iterate(cmd->words[i], sftp_action_chmod, ctx);\r
1808 \r
1809     return ret;\r
1810 }\r
1811 \r
1812 static int sftp_cmd_open(struct sftp_command *cmd)\r
1813 {\r
1814     int portnumber;\r
1815 \r
1816     if (back != NULL) {\r
1817         printf("psftp: already connected\n");\r
1818         return 0;\r
1819     }\r
1820 \r
1821     if (cmd->nwords < 2) {\r
1822         printf("open: expects a host name\n");\r
1823         return 0;\r
1824     }\r
1825 \r
1826     if (cmd->nwords > 2) {\r
1827         portnumber = atoi(cmd->words[2]);\r
1828         if (portnumber == 0) {\r
1829             printf("open: invalid port number\n");\r
1830             return 0;\r
1831         }\r
1832     } else\r
1833         portnumber = 0;\r
1834 \r
1835     if (psftp_connect(cmd->words[1], NULL, portnumber)) {\r
1836         back = NULL;                   /* connection is already closed */\r
1837         return -1;                     /* this is fatal */\r
1838     }\r
1839     do_sftp_init();\r
1840     return 1;\r
1841 }\r
1842 \r
1843 static int sftp_cmd_lcd(struct sftp_command *cmd)\r
1844 {\r
1845     char *currdir, *errmsg;\r
1846 \r
1847     if (cmd->nwords < 2) {\r
1848         printf("lcd: expects a local directory name\n");\r
1849         return 0;\r
1850     }\r
1851 \r
1852     errmsg = psftp_lcd(cmd->words[1]);\r
1853     if (errmsg) {\r
1854         printf("lcd: unable to change directory: %s\n", errmsg);\r
1855         sfree(errmsg);\r
1856         return 0;\r
1857     }\r
1858 \r
1859     currdir = psftp_getcwd();\r
1860     printf("New local directory is %s\n", currdir);\r
1861     sfree(currdir);\r
1862 \r
1863     return 1;\r
1864 }\r
1865 \r
1866 static int sftp_cmd_lpwd(struct sftp_command *cmd)\r
1867 {\r
1868     char *currdir;\r
1869 \r
1870     currdir = psftp_getcwd();\r
1871     printf("Current local directory is %s\n", currdir);\r
1872     sfree(currdir);\r
1873 \r
1874     return 1;\r
1875 }\r
1876 \r
1877 static int sftp_cmd_pling(struct sftp_command *cmd)\r
1878 {\r
1879     int exitcode;\r
1880 \r
1881     exitcode = system(cmd->words[1]);\r
1882     return (exitcode == 0);\r
1883 }\r
1884 \r
1885 static int sftp_cmd_help(struct sftp_command *cmd);\r
1886 \r
1887 static struct sftp_cmd_lookup {\r
1888     char *name;\r
1889     /*\r
1890      * For help purposes, there are two kinds of command:\r
1891      * \r
1892      *  - primary commands, in which `longhelp' is non-NULL. In\r
1893      *    this case `shorthelp' is descriptive text, and `longhelp'\r
1894      *    is longer descriptive text intended to be printed after\r
1895      *    the command name.\r
1896      * \r
1897      *  - alias commands, in which `longhelp' is NULL. In this case\r
1898      *    `shorthelp' is the name of a primary command, which\r
1899      *    contains the help that should double up for this command.\r
1900      */\r
1901     int listed;                        /* do we list this in primary help? */\r
1902     char *shorthelp;\r
1903     char *longhelp;\r
1904     int (*obey) (struct sftp_command *);\r
1905 } sftp_lookup[] = {\r
1906     /*\r
1907      * List of sftp commands. This is binary-searched so it MUST be\r
1908      * in ASCII order.\r
1909      */\r
1910     {\r
1911         "!", TRUE, "run a local command",\r
1912             "<command>\n"\r
1913             /* FIXME: this example is crap for non-Windows. */\r
1914             "  Runs a local command. For example, \"!del myfile\".\n",\r
1915             sftp_cmd_pling\r
1916     },\r
1917     {\r
1918         "bye", TRUE, "finish your SFTP session",\r
1919             "\n"\r
1920             "  Terminates your SFTP session and quits the PSFTP program.\n",\r
1921             sftp_cmd_quit\r
1922     },\r
1923     {\r
1924         "cd", TRUE, "change your remote working directory",\r
1925             " [ <new working directory> ]\n"\r
1926             "  Change the remote working directory for your SFTP session.\n"\r
1927             "  If a new working directory is not supplied, you will be\n"\r
1928             "  returned to your home directory.\n",\r
1929             sftp_cmd_cd\r
1930     },\r
1931     {\r
1932         "chmod", TRUE, "change file permissions and modes",\r
1933             " <modes> <filename-or-wildcard> [ <filename-or-wildcard>... ]\n"\r
1934             "  Change the file permissions on one or more remote files or\n"\r
1935             "  directories.\n"\r
1936             "  <modes> can be any octal Unix permission specifier.\n"\r
1937             "  Alternatively, <modes> can include the following modifiers:\n"\r
1938             "    u+r     make file readable by owning user\n"\r
1939             "    u+w     make file writable by owning user\n"\r
1940             "    u+x     make file executable by owning user\n"\r
1941             "    u-r     make file not readable by owning user\n"\r
1942             "    [also u-w, u-x]\n"\r
1943             "    g+r     make file readable by members of owning group\n"\r
1944             "    [also g+w, g+x, g-r, g-w, g-x]\n"\r
1945             "    o+r     make file readable by all other users\n"\r
1946             "    [also o+w, o+x, o-r, o-w, o-x]\n"\r
1947             "    a+r     make file readable by absolutely everybody\n"\r
1948             "    [also a+w, a+x, a-r, a-w, a-x]\n"\r
1949             "    u+s     enable the Unix set-user-ID bit\n"\r
1950             "    u-s     disable the Unix set-user-ID bit\n"\r
1951             "    g+s     enable the Unix set-group-ID bit\n"\r
1952             "    g-s     disable the Unix set-group-ID bit\n"\r
1953             "    +t      enable the Unix \"sticky bit\"\n"\r
1954             "  You can give more than one modifier for the same user (\"g-rwx\"), and\n"\r
1955             "  more than one user for the same modifier (\"ug+w\"). You can\n"\r
1956             "  use commas to separate different modifiers (\"u+rwx,g+s\").\n",\r
1957             sftp_cmd_chmod\r
1958     },\r
1959     {\r
1960         "close", TRUE, "finish your SFTP session but do not quit PSFTP",\r
1961             "\n"\r
1962             "  Terminates your SFTP session, but does not quit the PSFTP\n"\r
1963             "  program. You can then use \"open\" to start another SFTP\n"\r
1964             "  session, to the same server or to a different one.\n",\r
1965             sftp_cmd_close\r
1966     },\r
1967     {\r
1968         "del", TRUE, "delete files on the remote server",\r
1969             " <filename-or-wildcard> [ <filename-or-wildcard>... ]\n"\r
1970             "  Delete a file or files from the server.\n",\r
1971             sftp_cmd_rm\r
1972     },\r
1973     {\r
1974         "delete", FALSE, "del", NULL, sftp_cmd_rm\r
1975     },\r
1976     {\r
1977         "dir", TRUE, "list remote files",\r
1978             " [ <directory-name> ]/[ <wildcard> ]\n"\r
1979             "  List the contents of a specified directory on the server.\n"\r
1980             "  If <directory-name> is not given, the current working directory\n"\r
1981             "  is assumed.\n"\r
1982             "  If <wildcard> is given, it is treated as a set of files to\n"\r
1983             "  list; otherwise, all files are listed.\n",\r
1984             sftp_cmd_ls\r
1985     },\r
1986     {\r
1987         "exit", TRUE, "bye", NULL, sftp_cmd_quit\r
1988     },\r
1989     {\r
1990         "get", TRUE, "download a file from the server to your local machine",\r
1991             " [ -r ] [ -- ] <filename> [ <local-filename> ]\n"\r
1992             "  Downloads a file on the server and stores it locally under\n"\r
1993             "  the same name, or under a different one if you supply the\n"\r
1994             "  argument <local-filename>.\n"\r
1995             "  If -r specified, recursively fetch a directory.\n",\r
1996             sftp_cmd_get\r
1997     },\r
1998     {\r
1999         "help", TRUE, "give help",\r
2000             " [ <command> [ <command> ... ] ]\n"\r
2001             "  Give general help if no commands are specified.\n"\r
2002             "  If one or more commands are specified, give specific help on\n"\r
2003             "  those particular commands.\n",\r
2004             sftp_cmd_help\r
2005     },\r
2006     {\r
2007         "lcd", TRUE, "change local working directory",\r
2008             " <local-directory-name>\n"\r
2009             "  Change the local working directory of the PSFTP program (the\n"\r
2010             "  default location where the \"get\" command will save files).\n",\r
2011             sftp_cmd_lcd\r
2012     },\r
2013     {\r
2014         "lpwd", TRUE, "print local working directory",\r
2015             "\n"\r
2016             "  Print the local working directory of the PSFTP program (the\n"\r
2017             "  default location where the \"get\" command will save files).\n",\r
2018             sftp_cmd_lpwd\r
2019     },\r
2020     {\r
2021         "ls", TRUE, "dir", NULL,\r
2022             sftp_cmd_ls\r
2023     },\r
2024     {\r
2025         "mget", TRUE, "download multiple files at once",\r
2026             " [ -r ] [ -- ] <filename-or-wildcard> [ <filename-or-wildcard>... ]\n"\r
2027             "  Downloads many files from the server, storing each one under\n"\r
2028             "  the same name it has on the server side. You can use wildcards\n"\r
2029             "  such as \"*.c\" to specify lots of files at once.\n"\r
2030             "  If -r specified, recursively fetch files and directories.\n",\r
2031             sftp_cmd_mget\r
2032     },\r
2033     {\r
2034         "mkdir", TRUE, "create directories on the remote server",\r
2035             " <directory-name> [ <directory-name>... ]\n"\r
2036             "  Creates directories with the given names on the server.\n",\r
2037             sftp_cmd_mkdir\r
2038     },\r
2039     {\r
2040         "mput", TRUE, "upload multiple files at once",\r
2041             " [ -r ] [ -- ] <filename-or-wildcard> [ <filename-or-wildcard>... ]\n"\r
2042             "  Uploads many files to the server, storing each one under the\n"\r
2043             "  same name it has on the client side. You can use wildcards\n"\r
2044             "  such as \"*.c\" to specify lots of files at once.\n"\r
2045             "  If -r specified, recursively store files and directories.\n",\r
2046             sftp_cmd_mput\r
2047     },\r
2048     {\r
2049         "mv", TRUE, "move or rename file(s) on the remote server",\r
2050             " <source> [ <source>... ] <destination>\n"\r
2051             "  Moves or renames <source>(s) on the server to <destination>,\n"\r
2052             "  also on the server.\n"\r
2053             "  If <destination> specifies an existing directory, then <source>\n"\r
2054             "  may be a wildcard, and multiple <source>s may be given; all\n"\r
2055             "  source files are moved into <destination>.\n"\r
2056             "  Otherwise, <source> must specify a single file, which is moved\n"\r
2057             "  or renamed so that it is accessible under the name <destination>.\n",\r
2058             sftp_cmd_mv\r
2059     },\r
2060     {\r
2061         "open", TRUE, "connect to a host",\r
2062             " [<user>@]<hostname> [<port>]\n"\r
2063             "  Establishes an SFTP connection to a given host. Only usable\n"\r
2064             "  when you are not already connected to a server.\n",\r
2065             sftp_cmd_open\r
2066     },\r
2067     {\r
2068         "put", TRUE, "upload a file from your local machine to the server",\r
2069             " [ -r ] [ -- ] <filename> [ <remote-filename> ]\n"\r
2070             "  Uploads a file to the server and stores it there under\n"\r
2071             "  the same name, or under a different one if you supply the\n"\r
2072             "  argument <remote-filename>.\n"\r
2073             "  If -r specified, recursively store a directory.\n",\r
2074             sftp_cmd_put\r
2075     },\r
2076     {\r
2077         "pwd", TRUE, "print your remote working directory",\r
2078             "\n"\r
2079             "  Print the current remote working directory for your SFTP session.\n",\r
2080             sftp_cmd_pwd\r
2081     },\r
2082     {\r
2083         "quit", TRUE, "bye", NULL,\r
2084             sftp_cmd_quit\r
2085     },\r
2086     {\r
2087         "reget", TRUE, "continue downloading files",\r
2088             " [ -r ] [ -- ] <filename> [ <local-filename> ]\n"\r
2089             "  Works exactly like the \"get\" command, but the local file\n"\r
2090             "  must already exist. The download will begin at the end of the\n"\r
2091             "  file. This is for resuming a download that was interrupted.\n"\r
2092             "  If -r specified, resume interrupted \"get -r\".\n",\r
2093             sftp_cmd_reget\r
2094     },\r
2095     {\r
2096         "ren", TRUE, "mv", NULL,\r
2097             sftp_cmd_mv\r
2098     },\r
2099     {\r
2100         "rename", FALSE, "mv", NULL,\r
2101             sftp_cmd_mv\r
2102     },\r
2103     {\r
2104         "reput", TRUE, "continue uploading files",\r
2105             " [ -r ] [ -- ] <filename> [ <remote-filename> ]\n"\r
2106             "  Works exactly like the \"put\" command, but the remote file\n"\r
2107             "  must already exist. The upload will begin at the end of the\n"\r
2108             "  file. This is for resuming an upload that was interrupted.\n"\r
2109             "  If -r specified, resume interrupted \"put -r\".\n",\r
2110             sftp_cmd_reput\r
2111     },\r
2112     {\r
2113         "rm", TRUE, "del", NULL,\r
2114             sftp_cmd_rm\r
2115     },\r
2116     {\r
2117         "rmdir", TRUE, "remove directories on the remote server",\r
2118             " <directory-name> [ <directory-name>... ]\n"\r
2119             "  Removes the directory with the given name on the server.\n"\r
2120             "  The directory will not be removed unless it is empty.\n"\r
2121             "  Wildcards may be used to specify multiple directories.\n",\r
2122             sftp_cmd_rmdir\r
2123     }\r
2124 };\r
2125 \r
2126 const struct sftp_cmd_lookup *lookup_command(char *name)\r
2127 {\r
2128     int i, j, k, cmp;\r
2129 \r
2130     i = -1;\r
2131     j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);\r
2132     while (j - i > 1) {\r
2133         k = (j + i) / 2;\r
2134         cmp = strcmp(name, sftp_lookup[k].name);\r
2135         if (cmp < 0)\r
2136             j = k;\r
2137         else if (cmp > 0)\r
2138             i = k;\r
2139         else {\r
2140             return &sftp_lookup[k];\r
2141         }\r
2142     }\r
2143     return NULL;\r
2144 }\r
2145 \r
2146 static int sftp_cmd_help(struct sftp_command *cmd)\r
2147 {\r
2148     int i;\r
2149     if (cmd->nwords == 1) {\r
2150         /*\r
2151          * Give short help on each command.\r
2152          */\r
2153         int maxlen;\r
2154         maxlen = 0;\r
2155         for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {\r
2156             int len;\r
2157             if (!sftp_lookup[i].listed)\r
2158                 continue;\r
2159             len = strlen(sftp_lookup[i].name);\r
2160             if (maxlen < len)\r
2161                 maxlen = len;\r
2162         }\r
2163         for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {\r
2164             const struct sftp_cmd_lookup *lookup;\r
2165             if (!sftp_lookup[i].listed)\r
2166                 continue;\r
2167             lookup = &sftp_lookup[i];\r
2168             printf("%-*s", maxlen+2, lookup->name);\r
2169             if (lookup->longhelp == NULL)\r
2170                 lookup = lookup_command(lookup->shorthelp);\r
2171             printf("%s\n", lookup->shorthelp);\r
2172         }\r
2173     } else {\r
2174         /*\r
2175          * Give long help on specific commands.\r
2176          */\r
2177         for (i = 1; i < cmd->nwords; i++) {\r
2178             const struct sftp_cmd_lookup *lookup;\r
2179             lookup = lookup_command(cmd->words[i]);\r
2180             if (!lookup) {\r
2181                 printf("help: %s: command not found\n", cmd->words[i]);\r
2182             } else {\r
2183                 printf("%s", lookup->name);\r
2184                 if (lookup->longhelp == NULL)\r
2185                     lookup = lookup_command(lookup->shorthelp);\r
2186                 printf("%s", lookup->longhelp);\r
2187             }\r
2188         }\r
2189     }\r
2190     return 1;\r
2191 }\r
2192 \r
2193 /* ----------------------------------------------------------------------\r
2194  * Command line reading and parsing.\r
2195  */\r
2196 struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)\r
2197 {\r
2198     char *line;\r
2199     struct sftp_command *cmd;\r
2200     char *p, *q, *r;\r
2201     int quoting;\r
2202 \r
2203     cmd = snew(struct sftp_command);\r
2204     cmd->words = NULL;\r
2205     cmd->nwords = 0;\r
2206     cmd->wordssize = 0;\r
2207 \r
2208     line = NULL;\r
2209 \r
2210     if (fp) {\r
2211         if (modeflags & 1)\r
2212             printf("psftp> ");\r
2213         line = fgetline(fp);\r
2214     } else {\r
2215         line = ssh_sftp_get_cmdline("psftp> ", back == NULL);\r
2216     }\r
2217 \r
2218     if (!line || !*line) {\r
2219         cmd->obey = sftp_cmd_quit;\r
2220         if ((mode == 0) || (modeflags & 1))\r
2221             printf("quit\n");\r
2222         return cmd;                    /* eof */\r
2223     }\r
2224 \r
2225     line[strcspn(line, "\r\n")] = '\0';\r
2226 \r
2227     if (modeflags & 1) {\r
2228         printf("%s\n", line);\r
2229     }\r
2230 \r
2231     p = line;\r
2232     while (*p && (*p == ' ' || *p == '\t'))\r
2233         p++;\r
2234 \r
2235     if (*p == '!') {\r
2236         /*\r
2237          * Special case: the ! command. This is always parsed as\r
2238          * exactly two words: one containing the !, and the second\r
2239          * containing everything else on the line.\r
2240          */\r
2241         cmd->nwords = cmd->wordssize = 2;\r
2242         cmd->words = sresize(cmd->words, cmd->wordssize, char *);\r
2243         cmd->words[0] = dupstr("!");\r
2244         cmd->words[1] = dupstr(p+1);\r
2245     } else if (*p == '#') {\r
2246         /*\r
2247          * Special case: comment. Entire line is ignored.\r
2248          */\r
2249         cmd->nwords = cmd->wordssize = 0;\r
2250     } else {\r
2251 \r
2252         /*\r
2253          * Parse the command line into words. The syntax is:\r
2254          *  - double quotes are removed, but cause spaces within to be\r
2255          *    treated as non-separating.\r
2256          *  - a double-doublequote pair is a literal double quote, inside\r
2257          *    _or_ outside quotes. Like this:\r
2258          *\r
2259          *      firstword "second word" "this has ""quotes"" in" and""this""\r
2260          *\r
2261          * becomes\r
2262          *\r
2263          *      >firstword<\r
2264          *      >second word<\r
2265          *      >this has "quotes" in<\r
2266          *      >and"this"<\r
2267          */\r
2268         while (*p) {\r
2269             /* skip whitespace */\r
2270             while (*p && (*p == ' ' || *p == '\t'))\r
2271                 p++;\r
2272             /* mark start of word */\r
2273             q = r = p;                 /* q sits at start, r writes word */\r
2274             quoting = 0;\r
2275             while (*p) {\r
2276                 if (!quoting && (*p == ' ' || *p == '\t'))\r
2277                     break;                     /* reached end of word */\r
2278                 else if (*p == '"' && p[1] == '"')\r
2279                     p += 2, *r++ = '"';    /* a literal quote */\r
2280                 else if (*p == '"')\r
2281                     p++, quoting = !quoting;\r
2282                 else\r
2283                     *r++ = *p++;\r
2284             }\r
2285             if (*p)\r
2286                 p++;                   /* skip over the whitespace */\r
2287             *r = '\0';\r
2288             if (cmd->nwords >= cmd->wordssize) {\r
2289                 cmd->wordssize = cmd->nwords + 16;\r
2290                 cmd->words = sresize(cmd->words, cmd->wordssize, char *);\r
2291             }\r
2292             cmd->words[cmd->nwords++] = dupstr(q);\r
2293         }\r
2294     }\r
2295 \r
2296     sfree(line);\r
2297 \r
2298     /*\r
2299      * Now parse the first word and assign a function.\r
2300      */\r
2301 \r
2302     if (cmd->nwords == 0)\r
2303         cmd->obey = sftp_cmd_null;\r
2304     else {\r
2305         const struct sftp_cmd_lookup *lookup;\r
2306         lookup = lookup_command(cmd->words[0]);\r
2307         if (!lookup)\r
2308             cmd->obey = sftp_cmd_unknown;\r
2309         else\r
2310             cmd->obey = lookup->obey;\r
2311     }\r
2312 \r
2313     return cmd;\r
2314 }\r
2315 \r
2316 static int do_sftp_init(void)\r
2317 {\r
2318     struct sftp_packet *pktin;\r
2319     struct sftp_request *req, *rreq;\r
2320 \r
2321     /*\r
2322      * Do protocol initialisation. \r
2323      */\r
2324     if (!fxp_init()) {\r
2325         fprintf(stderr,\r
2326                 "Fatal: unable to initialise SFTP: %s\n", fxp_error());\r
2327         return 1;                      /* failure */\r
2328     }\r
2329 \r
2330     /*\r
2331      * Find out where our home directory is.\r
2332      */\r
2333     sftp_register(req = fxp_realpath_send("."));\r
2334     rreq = sftp_find_request(pktin = sftp_recv());\r
2335     assert(rreq == req);\r
2336     homedir = fxp_realpath_recv(pktin, rreq);\r
2337 \r
2338     if (!homedir) {\r
2339         fprintf(stderr,\r
2340                 "Warning: failed to resolve home directory: %s\n",\r
2341                 fxp_error());\r
2342         homedir = dupstr(".");\r
2343     } else {\r
2344         printf("Remote working directory is %s\n", homedir);\r
2345     }\r
2346     pwd = dupstr(homedir);\r
2347     return 0;\r
2348 }\r
2349 \r
2350 void do_sftp_cleanup()\r
2351 {\r
2352     char ch;\r
2353     if (back) {\r
2354         back->special(backhandle, TS_EOF);\r
2355         sftp_recvdata(&ch, 1);\r
2356         back->free(backhandle);\r
2357         sftp_cleanup_request();\r
2358         back = NULL;\r
2359         backhandle = NULL;\r
2360     }\r
2361     if (pwd) {\r
2362         sfree(pwd);\r
2363         pwd = NULL;\r
2364     }\r
2365     if (homedir) {\r
2366         sfree(homedir);\r
2367         homedir = NULL;\r
2368     }\r
2369 }\r
2370 \r
2371 void do_sftp(int mode, int modeflags, char *batchfile)\r
2372 {\r
2373     FILE *fp;\r
2374     int ret;\r
2375 \r
2376     /*\r
2377      * Batch mode?\r
2378      */\r
2379     if (mode == 0) {\r
2380 \r
2381         /* ------------------------------------------------------------------\r
2382          * Now we're ready to do Real Stuff.\r
2383          */\r
2384         while (1) {\r
2385             struct sftp_command *cmd;\r
2386             cmd = sftp_getcmd(NULL, 0, 0);\r
2387             if (!cmd)\r
2388                 break;\r
2389             ret = cmd->obey(cmd);\r
2390             if (cmd->words) {\r
2391                 int i;\r
2392                 for(i = 0; i < cmd->nwords; i++)\r
2393                     sfree(cmd->words[i]);\r
2394                 sfree(cmd->words);\r
2395             }\r
2396             sfree(cmd);\r
2397             if (ret < 0)\r
2398                 break;\r
2399         }\r
2400     } else {\r
2401         fp = fopen(batchfile, "r");\r
2402         if (!fp) {\r
2403             printf("Fatal: unable to open %s\n", batchfile);\r
2404             return;\r
2405         }\r
2406         while (1) {\r
2407             struct sftp_command *cmd;\r
2408             cmd = sftp_getcmd(fp, mode, modeflags);\r
2409             if (!cmd)\r
2410                 break;\r
2411             ret = cmd->obey(cmd);\r
2412             if (ret < 0)\r
2413                 break;\r
2414             if (ret == 0) {\r
2415                 if (!(modeflags & 2))\r
2416                     break;\r
2417             }\r
2418         }\r
2419         fclose(fp);\r
2420 \r
2421     }\r
2422 }\r
2423 \r
2424 /* ----------------------------------------------------------------------\r
2425  * Dirty bits: integration with PuTTY.\r
2426  */\r
2427 \r
2428 static int verbose = 0;\r
2429 \r
2430 /*\r
2431  *  Print an error message and perform a fatal exit.\r
2432  */\r
2433 void fatalbox(char *fmt, ...)\r
2434 {\r
2435     char *str, *str2;\r
2436     va_list ap;\r
2437     va_start(ap, fmt);\r
2438     str = dupvprintf(fmt, ap);\r
2439     str2 = dupcat("Fatal: ", str, "\n", NULL);\r
2440     sfree(str);\r
2441     va_end(ap);\r
2442     fputs(str2, stderr);\r
2443     sfree(str2);\r
2444 \r
2445     cleanup_exit(1);\r
2446 }\r
2447 void modalfatalbox(char *fmt, ...)\r
2448 {\r
2449     char *str, *str2;\r
2450     va_list ap;\r
2451     va_start(ap, fmt);\r
2452     str = dupvprintf(fmt, ap);\r
2453     str2 = dupcat("Fatal: ", str, "\n", NULL);\r
2454     sfree(str);\r
2455     va_end(ap);\r
2456     fputs(str2, stderr);\r
2457     sfree(str2);\r
2458 \r
2459     cleanup_exit(1);\r
2460 }\r
2461 void connection_fatal(void *frontend, char *fmt, ...)\r
2462 {\r
2463     char *str, *str2;\r
2464     va_list ap;\r
2465     va_start(ap, fmt);\r
2466     str = dupvprintf(fmt, ap);\r
2467     str2 = dupcat("Fatal: ", str, "\n", NULL);\r
2468     sfree(str);\r
2469     va_end(ap);\r
2470     fputs(str2, stderr);\r
2471     sfree(str2);\r
2472 \r
2473     cleanup_exit(1);\r
2474 }\r
2475 \r
2476 void ldisc_send(void *handle, char *buf, int len, int interactive)\r
2477 {\r
2478     /*\r
2479      * This is only here because of the calls to ldisc_send(NULL,\r
2480      * 0) in ssh.c. Nothing in PSFTP actually needs to use the\r
2481      * ldisc as an ldisc. So if we get called with any real data, I\r
2482      * want to know about it.\r
2483      */\r
2484     assert(len == 0);\r
2485 }\r
2486 \r
2487 /*\r
2488  * In psftp, all agent requests should be synchronous, so this is a\r
2489  * never-called stub.\r
2490  */\r
2491 void agent_schedule_callback(void (*callback)(void *, void *, int),\r
2492                              void *callback_ctx, void *data, int len)\r
2493 {\r
2494     assert(!"We shouldn't be here");\r
2495 }\r
2496 \r
2497 /*\r
2498  * Receive a block of data from the SSH link. Block until all data\r
2499  * is available.\r
2500  *\r
2501  * To do this, we repeatedly call the SSH protocol module, with our\r
2502  * own trap in from_backend() to catch the data that comes back. We\r
2503  * do this until we have enough data.\r
2504  */\r
2505 \r
2506 static unsigned char *outptr;          /* where to put the data */\r
2507 static unsigned outlen;                /* how much data required */\r
2508 static unsigned char *pending = NULL;  /* any spare data */\r
2509 static unsigned pendlen = 0, pendsize = 0;      /* length and phys. size of buffer */\r
2510 int from_backend(void *frontend, int is_stderr, const char *data, int datalen)\r
2511 {\r
2512     unsigned char *p = (unsigned char *) data;\r
2513     unsigned len = (unsigned) datalen;\r
2514 \r
2515     /*\r
2516      * stderr data is just spouted to local stderr and otherwise\r
2517      * ignored.\r
2518      */\r
2519     if (is_stderr) {\r
2520         if (len > 0)\r
2521             if (fwrite(data, 1, len, stderr) < len)\r
2522                 /* oh well */;\r
2523         return 0;\r
2524     }\r
2525 \r
2526     /*\r
2527      * If this is before the real session begins, just return.\r
2528      */\r
2529     if (!outptr)\r
2530         return 0;\r
2531 \r
2532     if ((outlen > 0) && (len > 0)) {\r
2533         unsigned used = outlen;\r
2534         if (used > len)\r
2535             used = len;\r
2536         memcpy(outptr, p, used);\r
2537         outptr += used;\r
2538         outlen -= used;\r
2539         p += used;\r
2540         len -= used;\r
2541     }\r
2542 \r
2543     if (len > 0) {\r
2544         if (pendsize < pendlen + len) {\r
2545             pendsize = pendlen + len + 4096;\r
2546             pending = sresize(pending, pendsize, unsigned char);\r
2547         }\r
2548         memcpy(pending + pendlen, p, len);\r
2549         pendlen += len;\r
2550     }\r
2551 \r
2552     return 0;\r
2553 }\r
2554 int from_backend_untrusted(void *frontend_handle, const char *data, int len)\r
2555 {\r
2556     /*\r
2557      * No "untrusted" output should get here (the way the code is\r
2558      * currently, it's all diverted by FLAG_STDERR).\r
2559      */\r
2560     assert(!"Unexpected call to from_backend_untrusted()");\r
2561     return 0; /* not reached */\r
2562 }\r
2563 int sftp_recvdata(char *buf, int len)\r
2564 {\r
2565     outptr = (unsigned char *) buf;\r
2566     outlen = len;\r
2567 \r
2568     /*\r
2569      * See if the pending-input block contains some of what we\r
2570      * need.\r
2571      */\r
2572     if (pendlen > 0) {\r
2573         unsigned pendused = pendlen;\r
2574         if (pendused > outlen)\r
2575             pendused = outlen;\r
2576         memcpy(outptr, pending, pendused);\r
2577         memmove(pending, pending + pendused, pendlen - pendused);\r
2578         outptr += pendused;\r
2579         outlen -= pendused;\r
2580         pendlen -= pendused;\r
2581         if (pendlen == 0) {\r
2582             pendsize = 0;\r
2583             sfree(pending);\r
2584             pending = NULL;\r
2585         }\r
2586         if (outlen == 0)\r
2587             return 1;\r
2588     }\r
2589 \r
2590     while (outlen > 0) {\r
2591         if (back->exitcode(backhandle) >= 0 || ssh_sftp_loop_iteration() < 0)\r
2592             return 0;                  /* doom */\r
2593     }\r
2594 \r
2595     return 1;\r
2596 }\r
2597 int sftp_senddata(char *buf, int len)\r
2598 {\r
2599     back->send(backhandle, buf, len);\r
2600     return 1;\r
2601 }\r
2602 \r
2603 /*\r
2604  *  Short description of parameters.\r
2605  */\r
2606 static void usage(void)\r
2607 {\r
2608     printf("PuTTY Secure File Transfer (SFTP) client\n");\r
2609     printf("%s\n", ver);\r
2610     printf("Usage: psftp [options] [user@]host\n");\r
2611     printf("Options:\n");\r
2612     printf("  -V        print version information and exit\n");\r
2613     printf("  -pgpfp    print PGP key fingerprints and exit\n");\r
2614     printf("  -b file   use specified batchfile\n");\r
2615     printf("  -bc       output batchfile commands\n");\r
2616     printf("  -be       don't stop batchfile processing if errors\n");\r
2617     printf("  -v        show verbose messages\n");\r
2618     printf("  -load sessname  Load settings from saved session\n");\r
2619     printf("  -l user   connect with specified username\n");\r
2620     printf("  -P port   connect to specified port\n");\r
2621     printf("  -pw passw login with specified password\n");\r
2622     printf("  -1 -2     force use of particular SSH protocol version\n");\r
2623     printf("  -4 -6     force use of IPv4 or IPv6\n");\r
2624     printf("  -C        enable compression\n");\r
2625     printf("  -i key    private key file for authentication\n");\r
2626     printf("  -noagent  disable use of Pageant\n");\r
2627     printf("  -agent    enable use of Pageant\n");\r
2628     printf("  -batch    disable all interactive prompts\n");\r
2629     cleanup_exit(1);\r
2630 }\r
2631 \r
2632 static void version(void)\r
2633 {\r
2634   printf("psftp: %s\n", ver);\r
2635   cleanup_exit(1);\r
2636 }\r
2637 \r
2638 /*\r
2639  * Connect to a host.\r
2640  */\r
2641 static int psftp_connect(char *userhost, char *user, int portnumber)\r
2642 {\r
2643     char *host, *realhost;\r
2644     const char *err;\r
2645     void *logctx;\r
2646 \r
2647     /* Separate host and username */\r
2648     host = userhost;\r
2649     host = strrchr(host, '@');\r
2650     if (host == NULL) {\r
2651         host = userhost;\r
2652     } else {\r
2653         *host++ = '\0';\r
2654         if (user) {\r
2655             printf("psftp: multiple usernames specified; using \"%s\"\n",\r
2656                    user);\r
2657         } else\r
2658             user = userhost;\r
2659     }\r
2660 \r
2661     /*\r
2662      * If we haven't loaded session details already (e.g., from -load),\r
2663      * try looking for a session called "host".\r
2664      */\r
2665     if (!loaded_session) {\r
2666         /* Try to load settings for `host' into a temporary config */\r
2667         Config cfg2;\r
2668         cfg2.host[0] = '\0';\r
2669         do_defaults(host, &cfg2);\r
2670         if (cfg2.host[0] != '\0') {\r
2671             /* Settings present and include hostname */\r
2672             /* Re-load data into the real config. */\r
2673             do_defaults(host, &cfg);\r
2674         } else {\r
2675             /* Session doesn't exist or mention a hostname. */\r
2676             /* Use `host' as a bare hostname. */\r
2677             strncpy(cfg.host, host, sizeof(cfg.host) - 1);\r
2678             cfg.host[sizeof(cfg.host) - 1] = '\0';\r
2679         }\r
2680     } else {\r
2681         /* Patch in hostname `host' to session details. */\r
2682         strncpy(cfg.host, host, sizeof(cfg.host) - 1);\r
2683         cfg.host[sizeof(cfg.host) - 1] = '\0';\r
2684     }\r
2685 \r
2686     /*\r
2687      * Force use of SSH. (If they got the protocol wrong we assume the\r
2688      * port is useless too.)\r
2689      */\r
2690     if (cfg.protocol != PROT_SSH) {\r
2691         cfg.protocol = PROT_SSH;\r
2692         cfg.port = 22;\r
2693     }\r
2694 \r
2695     /*\r
2696      * If saved session / Default Settings says SSH-1 (`1 only' or `1'),\r
2697      * then change it to SSH-2, on the grounds that that's more likely to\r
2698      * work for SFTP. (Can be overridden with `-1' option.)\r
2699      * But if it says `2 only' or `2', respect which.\r
2700      */\r
2701     if (cfg.sshprot != 2 && cfg.sshprot != 3)\r
2702         cfg.sshprot = 2;\r
2703 \r
2704     /*\r
2705      * Enact command-line overrides.\r
2706      */\r
2707     cmdline_run_saved(&cfg);\r
2708 \r
2709     /*\r
2710      * Trim leading whitespace off the hostname if it's there.\r
2711      */\r
2712     {\r
2713         int space = strspn(cfg.host, " \t");\r
2714         memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);\r
2715     }\r
2716 \r
2717     /* See if host is of the form user@host */\r
2718     if (cfg.host[0] != '\0') {\r
2719         char *atsign = strrchr(cfg.host, '@');\r
2720         /* Make sure we're not overflowing the user field */\r
2721         if (atsign) {\r
2722             if (atsign - cfg.host < sizeof cfg.username) {\r
2723                 strncpy(cfg.username, cfg.host, atsign - cfg.host);\r
2724                 cfg.username[atsign - cfg.host] = '\0';\r
2725             }\r
2726             memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));\r
2727         }\r
2728     }\r
2729 \r
2730     /*\r
2731      * Trim a colon suffix off the hostname if it's there.\r
2732      */\r
2733     cfg.host[strcspn(cfg.host, ":")] = '\0';\r
2734 \r
2735     /*\r
2736      * Remove any remaining whitespace from the hostname.\r
2737      */\r
2738     {\r
2739         int p1 = 0, p2 = 0;\r
2740         while (cfg.host[p2] != '\0') {\r
2741             if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {\r
2742                 cfg.host[p1] = cfg.host[p2];\r
2743                 p1++;\r
2744             }\r
2745             p2++;\r
2746         }\r
2747         cfg.host[p1] = '\0';\r
2748     }\r
2749 \r
2750     /* Set username */\r
2751     if (user != NULL && user[0] != '\0') {\r
2752         strncpy(cfg.username, user, sizeof(cfg.username) - 1);\r
2753         cfg.username[sizeof(cfg.username) - 1] = '\0';\r
2754     }\r
2755 \r
2756     if (portnumber)\r
2757         cfg.port = portnumber;\r
2758 \r
2759     /*\r
2760      * Disable scary things which shouldn't be enabled for simple\r
2761      * things like SCP and SFTP: agent forwarding, port forwarding,\r
2762      * X forwarding.\r
2763      */\r
2764     cfg.x11_forward = 0;\r
2765     cfg.agentfwd = 0;\r
2766     cfg.portfwd[0] = cfg.portfwd[1] = '\0';\r
2767     cfg.ssh_simple = TRUE;\r
2768 \r
2769     /* Set up subsystem name. */\r
2770     strcpy(cfg.remote_cmd, "sftp");\r
2771     cfg.ssh_subsys = TRUE;\r
2772     cfg.nopty = TRUE;\r
2773 \r
2774     /*\r
2775      * Set up fallback option, for SSH-1 servers or servers with the\r
2776      * sftp subsystem not enabled but the server binary installed\r
2777      * in the usual place. We only support fallback on Unix\r
2778      * systems, and we use a kludgy piece of shellery which should\r
2779      * try to find sftp-server in various places (the obvious\r
2780      * systemwide spots /usr/lib and /usr/local/lib, and then the\r
2781      * user's PATH) and finally give up.\r
2782      * \r
2783      *   test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\r
2784      *   test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\r
2785      *   exec sftp-server\r
2786      * \r
2787      * the idea being that this will attempt to use either of the\r
2788      * obvious pathnames and then give up, and when it does give up\r
2789      * it will print the preferred pathname in the error messages.\r
2790      */\r
2791     cfg.remote_cmd_ptr2 =\r
2792         "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"\r
2793         "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"\r
2794         "exec sftp-server";\r
2795     cfg.ssh_subsys2 = FALSE;\r
2796 \r
2797     back = &ssh_backend;\r
2798 \r
2799     err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost,\r
2800                      0, cfg.tcp_keepalives);\r
2801     if (err != NULL) {\r
2802         fprintf(stderr, "ssh_init: %s\n", err);\r
2803         return 1;\r
2804     }\r
2805     logctx = log_init(NULL, &cfg);\r
2806     back->provide_logctx(backhandle, logctx);\r
2807     console_provide_logctx(logctx);\r
2808     while (!back->sendok(backhandle)) {\r
2809         if (back->exitcode(backhandle) >= 0)\r
2810             return 1;\r
2811         if (ssh_sftp_loop_iteration() < 0) {\r
2812             fprintf(stderr, "ssh_init: error during SSH connection setup\n");\r
2813             return 1;\r
2814         }\r
2815     }\r
2816     if (verbose && realhost != NULL)\r
2817         printf("Connected to %s\n", realhost);\r
2818     if (realhost != NULL)\r
2819         sfree(realhost);\r
2820     return 0;\r
2821 }\r
2822 \r
2823 void cmdline_error(char *p, ...)\r
2824 {\r
2825     va_list ap;\r
2826     fprintf(stderr, "psftp: ");\r
2827     va_start(ap, p);\r
2828     vfprintf(stderr, p, ap);\r
2829     va_end(ap);\r
2830     fprintf(stderr, "\n       try typing \"psftp -h\" for help\n");\r
2831     exit(1);\r
2832 }\r
2833 \r
2834 /*\r
2835  * Main program. Parse arguments etc.\r
2836  */\r
2837 int psftp_main(int argc, char *argv[])\r
2838 {\r
2839     int i;\r
2840     int portnumber = 0;\r
2841     char *userhost, *user;\r
2842     int mode = 0;\r
2843     int modeflags = 0;\r
2844     char *batchfile = NULL;\r
2845 \r
2846     flags = FLAG_STDERR | FLAG_INTERACTIVE\r
2847 #ifdef FLAG_SYNCAGENT\r
2848         | FLAG_SYNCAGENT\r
2849 #endif\r
2850         ;\r
2851     cmdline_tooltype = TOOLTYPE_FILETRANSFER;\r
2852     sk_init();\r
2853 \r
2854     userhost = user = NULL;\r
2855 \r
2856     /* Load Default Settings before doing anything else. */\r
2857     do_defaults(NULL, &cfg);\r
2858     loaded_session = FALSE;\r
2859 \r
2860     for (i = 1; i < argc; i++) {\r
2861         int ret;\r
2862         if (argv[i][0] != '-') {\r
2863             if (userhost)\r
2864                 usage();\r
2865             else\r
2866                 userhost = dupstr(argv[i]);\r
2867             continue;\r
2868         }\r
2869         ret = cmdline_process_param(argv[i], i+1<argc?argv[i+1]:NULL, 1, &cfg);\r
2870         if (ret == -2) {\r
2871             cmdline_error("option \"%s\" requires an argument", argv[i]);\r
2872         } else if (ret == 2) {\r
2873             i++;               /* skip next argument */\r
2874         } else if (ret == 1) {\r
2875             /* We have our own verbosity in addition to `flags'. */\r
2876             if (flags & FLAG_VERBOSE)\r
2877                 verbose = 1;\r
2878         } else if (strcmp(argv[i], "-h") == 0 ||\r
2879                    strcmp(argv[i], "-?") == 0) {\r
2880             usage();\r
2881         } else if (strcmp(argv[i], "-pgpfp") == 0) {\r
2882             pgp_fingerprints();\r
2883             return 1;\r
2884         } else if (strcmp(argv[i], "-V") == 0) {\r
2885             version();\r
2886         } else if (strcmp(argv[i], "-batch") == 0) {\r
2887             console_batch_mode = 1;\r
2888         } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {\r
2889             mode = 1;\r
2890             batchfile = argv[++i];\r
2891         } else if (strcmp(argv[i], "-bc") == 0) {\r
2892             modeflags = modeflags | 1;\r
2893         } else if (strcmp(argv[i], "-be") == 0) {\r
2894             modeflags = modeflags | 2;\r
2895         } else if (strcmp(argv[i], "--") == 0) {\r
2896             i++;\r
2897             break;\r
2898         } else {\r
2899             cmdline_error("unknown option \"%s\"", argv[i]);\r
2900         }\r
2901     }\r
2902     argc -= i;\r
2903     argv += i;\r
2904     back = NULL;\r
2905 \r
2906     /*\r
2907      * If the loaded session provides a hostname, and a hostname has not\r
2908      * otherwise been specified, pop it in `userhost' so that\r
2909      * `psftp -load sessname' is sufficient to start a session.\r
2910      */\r
2911     if (!userhost && cfg.host[0] != '\0') {\r
2912         userhost = dupstr(cfg.host);\r
2913     }\r
2914 \r
2915     /*\r
2916      * If a user@host string has already been provided, connect to\r
2917      * it now.\r
2918      */\r
2919     if (userhost) {\r
2920         int ret;\r
2921         ret = psftp_connect(userhost, user, portnumber);\r
2922         sfree(userhost);\r
2923         if (ret)\r
2924             return 1;\r
2925         if (do_sftp_init())\r
2926             return 1;\r
2927     } else {\r
2928         printf("psftp: no hostname specified; use \"open host.name\""\r
2929                " to connect\n");\r
2930     }\r
2931 \r
2932     do_sftp(mode, modeflags, batchfile);\r
2933 \r
2934     if (back != NULL && back->connected(backhandle)) {\r
2935         char ch;\r
2936         back->special(backhandle, TS_EOF);\r
2937         sftp_recvdata(&ch, 1);\r
2938     }\r
2939     do_sftp_cleanup();\r
2940     random_save_seed();\r
2941     cmdline_cleanup();\r
2942     console_provide_logctx(NULL);\r
2943     sk_cleanup();\r
2944 \r
2945     return 0;\r
2946 }\r