SFTP client now successfully handles cd, ls, get and put.
[u/mdw/putty] / psftp.c
CommitLineData
4c7f0d61 1/*
2 * psftp.c: front end for PSFTP.
3 */
4
5#include <stdio.h>
6#include <stdlib.h>
f9e162aa 7#include <stdarg.h>
4c7f0d61 8#include <assert.h>
4c7f0d61 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
21char *dupstr(char *s) {
22 int len = strlen(s);
23 char *p = smalloc(len+1);
24 strcpy(p, s);
25 return p;
26}
27
f9e162aa 28/* Allocate the concatenation of N strings. Terminate arg list with NULL. */
29char *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
4c7f0d61 61/* ----------------------------------------------------------------------
62 * sftp client state.
63 */
64
65char *pwd, *homedir;
66
67/* ----------------------------------------------------------------------
68 * Higher-level helper functions used in commands.
69 */
70
71/*
f9e162aa 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).
4c7f0d61 75 */
76char *canonify(char *name) {
f9e162aa 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;
4c7f0d61 89}
90
91/* ----------------------------------------------------------------------
92 * Actual sftp commands.
93 */
94struct sftp_command {
95 char **words;
96 int nwords, wordssize;
97 int (*obey)(struct sftp_command *);/* returns <0 to quit */
98};
99
100int sftp_cmd_null(struct sftp_command *cmd) {
101 return 0;
102}
103
104int sftp_cmd_unknown(struct sftp_command *cmd) {
105 printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
106 return 0;
107}
108
109int 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 */
117static 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}
122int 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 */
199int sftp_cmd_cd(struct sftp_command *cmd) {
200 struct fxp_handle *dirh;
201 char *dir;
202
203 if (cmd->nwords < 2)
f9e162aa 204 dir = dupstr(homedir);
4c7f0d61 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 */
232int 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 */
310int 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);
f9e162aa 324 if (!outfname) {
4c7f0d61 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
f9e162aa 355 len = fread(buffer, 1, sizeof(buffer), fp);
4c7f0d61 356 if (len == -1) {
357 printf("error while reading local file\n");
358 break;
359 } else if (len == 0) {
360 break;
361 }
f9e162aa 362 if (!fxp_write(fh, buffer, offset, len)) {
4c7f0d61 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
376static 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 */
396struct 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
507void 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 */
f9e162aa 520 homedir = fxp_realpath(".");
4c7f0d61 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
549int main(void) {
550 io_init();
551 do_sftp();
552 return 0;
553}