]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - psftp.c
50be458227d98296b1816ddad2768d5fe1dcf7d5
[PuTTY.git] / psftp.c
1 /*
2  * psftp.c: front end for PSFTP.
3  */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <stdarg.h>
8 #include <assert.h>
9
10 #include "sftp.h"
11 #include "int64.h"
12
13 #define smalloc malloc
14 #define srealloc realloc
15 #define sfree free
16
17 /* ----------------------------------------------------------------------
18  * String handling routines.
19  */
20
21 char *dupstr(char *s) {
22     int len = strlen(s);
23     char *p = smalloc(len+1);
24     strcpy(p, s);
25     return p;
26 }
27
28 /* Allocate the concatenation of N strings. Terminate arg list with NULL. */
29 char *dupcat(char *s1, ...) {
30     int len;
31     char *p, *q, *sn;
32     va_list ap;
33
34     len = strlen(s1);
35     va_start(ap, s1);
36     while (1) {
37         sn = va_arg(ap, char *);
38         if (!sn)
39             break;
40         len += strlen(sn);
41     }
42     va_end(ap);
43
44     p = smalloc(len+1);
45     strcpy(p, s1);
46     q = p + strlen(p);
47
48     va_start(ap, s1);
49     while (1) {
50         sn = va_arg(ap, char *);
51         if (!sn)
52             break;
53         strcpy(q, sn);
54         q += strlen(q);
55     }
56     va_end(ap);
57
58     return p;
59 }
60
61 /* ----------------------------------------------------------------------
62  * sftp client state.
63  */
64
65 char *pwd, *homedir;
66
67 /* ----------------------------------------------------------------------
68  * Higher-level helper functions used in commands.
69  */
70
71 /*
72  * Attempt to canonify a pathname starting from the pwd. If
73  * canonification fails, at least fall back to returning a _valid_
74  * pathname (though it may be ugly, eg /home/simon/../foobar).
75  */
76 char *canonify(char *name) {
77     char *fullname, *canonname;
78     if (name[0] == '/') {
79         fullname = dupstr(name);
80     } else {
81         fullname = dupcat(pwd, "/", name, NULL);
82     }
83     canonname = fxp_realpath(name);
84     if (canonname) {
85         sfree(fullname);
86         return canonname;
87     } else
88         return fullname;
89 }
90
91 /* ----------------------------------------------------------------------
92  * Actual sftp commands.
93  */
94 struct sftp_command {
95     char **words;
96     int nwords, wordssize;
97     int (*obey)(struct sftp_command *);/* returns <0 to quit */
98 };
99
100 int sftp_cmd_null(struct sftp_command *cmd) {
101     return 0;
102 }
103
104 int sftp_cmd_unknown(struct sftp_command *cmd) {
105     printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
106     return 0;
107 }
108
109 int sftp_cmd_quit(struct sftp_command *cmd) {
110     return -1;
111 }
112
113 /*
114  * List a directory. If no arguments are given, list pwd; otherwise
115  * list the directory given in words[1].
116  */
117 static int sftp_ls_compare(const void *av, const void *bv) {
118     const struct fxp_name *a = (const struct fxp_name *)av;
119     const struct fxp_name *b = (const struct fxp_name *)bv;
120     return strcmp(a->filename, b->filename);
121 }
122 int sftp_cmd_ls(struct sftp_command *cmd) {
123     struct fxp_handle *dirh;
124     struct fxp_names *names;
125     struct fxp_name *ournames;
126     int nnames, namesize;
127     char *dir, *cdir;
128     int i;
129
130     if (cmd->nwords < 2)
131         dir = ".";
132     else
133         dir = cmd->words[1];
134
135     cdir = canonify(dir);
136     if (!cdir) {
137         printf("%s: %s\n", dir, fxp_error());
138         return 0;
139     }
140
141     printf("Listing directory %s\n", cdir);
142
143     dirh = fxp_opendir(cdir);
144     if (dirh == NULL) {
145         printf("Unable to open %s: %s\n", dir, fxp_error());
146     } else {
147         nnames = namesize = 0;
148         ournames = NULL;
149
150         while (1) {
151
152             names = fxp_readdir(dirh);
153             if (names == NULL) {
154                 if (fxp_error_type() == SSH_FX_EOF)
155                     break;
156                 printf("Reading directory %s: %s\n", dir, fxp_error());
157                 break;
158             }
159             if (names->nnames == 0) {
160                 fxp_free_names(names);
161                 break;
162             }
163
164             if (nnames + names->nnames >= namesize) {
165                 namesize += names->nnames + 128;
166                 ournames = srealloc(ournames, namesize * sizeof(*ournames));
167             }
168
169             for (i = 0; i < names->nnames; i++)
170                 ournames[nnames++] = names->names[i];
171
172             names->nnames = 0;         /* prevent free_names */
173             fxp_free_names(names);
174         }
175         fxp_close(dirh);
176
177         /*
178          * Now we have our filenames. Sort them by actual file
179          * name, and then output the longname parts.
180          */
181         qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
182
183         /*
184          * And print them.
185          */
186         for (i = 0; i < nnames; i++)
187             printf("%s\n", ournames[i].longname);
188     }
189
190     sfree(cdir);
191
192     return 0;
193 }
194
195 /*
196  * Change directories. We do this by canonifying the new name, then
197  * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
198  */
199 int sftp_cmd_cd(struct sftp_command *cmd) {
200     struct fxp_handle *dirh;
201     char *dir;
202
203     if (cmd->nwords < 2)
204         dir = dupstr(homedir);
205     else
206         dir = canonify(cmd->words[1]);
207
208     if (!dir) {
209         printf("%s: %s\n", dir, fxp_error());
210         return 0;
211     }
212
213     dirh = fxp_opendir(dir);
214     if (!dirh) {
215         printf("Directory %s: %s\n", dir, fxp_error());
216         sfree(dir);
217         return 0;
218     }
219
220     fxp_close(dirh);
221
222     sfree(pwd);
223     pwd = dir;
224     printf("Remote directory is now %s\n", pwd);
225
226     return 0;
227 }
228
229 /*
230  * Get a file and save it at the local end.
231  */
232 int sftp_cmd_get(struct sftp_command *cmd) {
233     struct fxp_handle *fh;
234     char *fname, *outfname;
235     uint64 offset;
236     FILE *fp;
237
238     if (cmd->nwords < 2) {
239         printf("get: expects a filename\n");
240         return 0;
241     }
242
243     fname = canonify(cmd->words[1]);
244     if (!fname) {
245         printf("%s: %s\n", cmd->words[1], fxp_error());
246         return 0;
247     }
248     outfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
249
250     fh = fxp_open(fname, SSH_FXF_READ);
251     if (!fh) {
252         printf("%s: %s\n", fname, fxp_error());
253         sfree(fname);
254         return 0;
255     }
256     fp = fopen(outfname, "wb");
257     if (!fp) {
258         printf("local: unable to open %s\n", outfname);
259         fxp_close(fh);
260         sfree(fname);
261         return 0;
262     }
263
264     printf("remote:%s => local:%s\n", fname, outfname);
265
266     offset = uint64_make(0,0);
267
268     /*
269      * FIXME: we can use FXP_FSTAT here to get the file size, and
270      * thus put up a progress bar.
271      */
272     while (1) {
273         char buffer[4096];
274         int len;
275         int wpos, wlen;
276
277         len = fxp_read(fh, buffer, offset, sizeof(buffer));
278         if ((len == -1 && fxp_error_type() == SSH_FX_EOF) ||
279             len == 0)
280             break;
281         if (len == -1) {
282             printf("error while reading: %s\n", fxp_error());
283             break;
284         }
285         
286         wpos = 0;
287         while (wpos < len) {
288             wlen = fwrite(buffer, 1, len-wpos, fp);
289             if (wlen <= 0) {
290                 printf("error while writing local file\n");
291                 break;
292             }
293             wpos += wlen;
294         }
295         if (wpos < len)                /* we had an error */
296             break;
297         offset = uint64_add32(offset, len);
298     }
299
300     fclose(fp);
301     fxp_close(fh);
302     sfree(fname);
303
304     return 0;
305 }
306
307 /*
308  * Send a file and store it at the remote end.
309  */
310 int sftp_cmd_put(struct sftp_command *cmd) {
311     struct fxp_handle *fh;
312     char *fname, *origoutfname, *outfname;
313     uint64 offset;
314     FILE *fp;
315
316     if (cmd->nwords < 2) {
317         printf("put: expects a filename\n");
318         return 0;
319     }
320
321     fname = cmd->words[1];
322     origoutfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
323     outfname = canonify(origoutfname);
324     if (!outfname) {
325         printf("%s: %s\n", origoutfname, fxp_error());
326         return 0;
327     }
328
329     fp = fopen(fname, "rb");
330     if (!fp) {
331         printf("local: unable to open %s\n", fname);
332         fxp_close(fh);
333         sfree(outfname);
334         return 0;
335     }
336     fh = fxp_open(outfname, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
337     if (!fh) {
338         printf("%s: %s\n", outfname, fxp_error());
339         sfree(outfname);
340         return 0;
341     }
342
343     printf("local:%s => remote:%s\n", fname, outfname);
344
345     offset = uint64_make(0,0);
346
347     /*
348      * FIXME: we can use FXP_FSTAT here to get the file size, and
349      * thus put up a progress bar.
350      */
351     while (1) {
352         char buffer[4096];
353         int len;
354
355         len = fread(buffer, 1, sizeof(buffer), fp);
356         if (len == -1) {
357             printf("error while reading local file\n");
358             break;
359         } else if (len == 0) {
360             break;
361         }
362         if (!fxp_write(fh, buffer, offset, len)) {
363             printf("error while writing: %s\n", fxp_error());
364             break;
365         }
366         offset = uint64_add32(offset, len);
367     }
368
369     fxp_close(fh);
370     fclose(fp);
371     sfree(outfname);
372
373     return 0;
374 }
375
376 static struct sftp_cmd_lookup {
377     char *name;
378     int (*obey)(struct sftp_command *);
379 } sftp_lookup[] = {
380     /*
381      * List of sftp commands. This is binary-searched so it MUST be
382      * in ASCII order.
383      */
384     {"bye", sftp_cmd_quit},
385     {"cd", sftp_cmd_cd},
386     {"exit", sftp_cmd_quit},
387     {"get", sftp_cmd_get},
388     {"ls", sftp_cmd_ls},
389     {"put", sftp_cmd_put},
390     {"quit", sftp_cmd_quit},
391 };
392
393 /* ----------------------------------------------------------------------
394  * Command line reading and parsing.
395  */
396 struct sftp_command *sftp_getcmd(void) {
397     char *line;
398     int linelen, linesize;
399     struct sftp_command *cmd;
400     char *p, *q, *r;
401     int quoting;
402
403     printf("psftp> ");
404     fflush(stdout);
405
406     cmd = smalloc(sizeof(struct sftp_command));
407     cmd->words = NULL;
408     cmd->nwords = 0;
409     cmd->wordssize = 0;
410
411     line = NULL;
412     linesize = linelen = 0;
413     while (1) {
414         int len;
415         char *ret;
416
417         linesize += 512;
418         line = srealloc(line, linesize);
419         ret = fgets(line+linelen, linesize-linelen, stdin);
420
421         if (!ret || (linelen == 0 && line[0] == '\0')) {
422             cmd->obey = sftp_cmd_quit;
423             printf("quit\n");
424             return cmd;                /* eof */
425         }
426         len = linelen + strlen(line+linelen);
427         linelen += len;
428         if (line[linelen-1] == '\n') {
429             linelen--;
430             line[linelen] = '\0';
431             break;
432         }
433     }
434
435     /*
436      * Parse the command line into words. The syntax is:
437      *  - double quotes are removed, but cause spaces within to be
438      *    treated as non-separating.
439      *  - a double-doublequote pair is a literal double quote, inside
440      *    _or_ outside quotes. Like this:
441      * 
442      *      firstword "second word" "this has ""quotes"" in" sodoes""this""
443      * 
444      * becomes
445      * 
446      *      >firstword<
447      *      >second word<
448      *      >this has "quotes" in<
449      *      >sodoes"this"<
450      */
451     p = line;
452     while (*p) {
453         /* skip whitespace */
454         while (*p && (*p == ' ' || *p == '\t')) p++;
455         /* mark start of word */
456         q = r = p;                     /* q sits at start, r writes word */
457         quoting = 0;
458         while (*p) {
459             if (!quoting && (*p == ' ' || *p == '\t'))
460                 break;                 /* reached end of word */
461             else if (*p == '"' && p[1] == '"')
462                 p+=2, *r++ = '"';      /* a literal quote */
463             else if (*p == '"')
464                 p++, quoting = !quoting;
465             else
466                 *r++ = *p++;
467         }
468         if (*p) p++;                           /* skip over the whitespace */
469         *r = '\0';
470         if (cmd->nwords >= cmd->wordssize) {
471             cmd->wordssize = cmd->nwords + 16;
472             cmd->words = srealloc(cmd->words, cmd->wordssize*sizeof(char *));
473         }
474         cmd->words[cmd->nwords++] = q;
475     }
476
477     /*
478      * Now parse the first word and assign a function.
479      */
480
481     if (cmd->nwords == 0)
482         cmd->obey = sftp_cmd_null;
483     else {
484         int i, j, k, cmp;
485
486         cmd->obey = sftp_cmd_unknown;
487
488         i = -1;
489         j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
490         while (j - i > 1) {
491             k = (j + i) / 2;
492             cmp = strcmp(cmd->words[0], sftp_lookup[k].name);
493             if (cmp < 0)
494                 j = k;
495             else if (cmp > 0)
496                 i = k;
497             else {
498                 cmd->obey = sftp_lookup[k].obey;
499                 break;
500             }
501         }
502     }
503
504     return cmd;
505 }
506
507 void do_sftp(void) {
508     /*
509      * Do protocol initialisation. 
510      */
511     if (!fxp_init()) {
512         fprintf(stderr,
513                 "Fatal: unable to initialise SFTP: %s\n",
514                 fxp_error());
515     }
516
517     /*
518      * Find out where our home directory is.
519      */
520     homedir = fxp_realpath(".");
521     if (!homedir) {
522         fprintf(stderr,
523                 "Warning: failed to resolve home directory: %s\n",
524                 fxp_error());
525         homedir = dupstr(".");
526     } else {
527         printf("Remote working directory is %s\n", homedir);
528     }
529     pwd = dupstr(homedir);
530
531     /* ------------------------------------------------------------------
532      * Now we're ready to do Real Stuff.
533      */
534     while (1) {
535         struct sftp_command *cmd;
536         cmd = sftp_getcmd();
537         if (!cmd)
538             break;
539         if (cmd->obey(cmd) < 0)
540             break;
541     }
542
543     /* ------------------------------------------------------------------
544      * We've received an exit command. Tidy up and leave.
545      */
546     io_finish();
547 }
548
549 int main(void) {
550     io_init();
551     do_sftp();
552     return 0;
553 }