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