Annotation of src/usr.bin/ssh/sftp-int.c, Revision 1.48
1.1 djm 1: /*
1.44 djm 2: * Copyright (c) 2001,2002 Damien Miller. All rights reserved.
1.1 djm 3: *
4: * Redistribution and use in source and binary forms, with or without
5: * modification, are permitted provided that the following conditions
6: * are met:
7: * 1. Redistributions of source code must retain the above copyright
8: * notice, this list of conditions and the following disclaimer.
9: * 2. Redistributions in binary form must reproduce the above copyright
10: * notice, this list of conditions and the following disclaimer in the
11: * documentation and/or other materials provided with the distribution.
12: *
13: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16: * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23: */
24:
25: /* XXX: recursive operations */
26:
27: #include "includes.h"
1.48 ! djm 28: RCSID("$OpenBSD: sftp-int.c,v 1.47 2002/06/23 09:30:14 deraadt Exp $");
1.27 djm 29:
30: #include <glob.h>
1.1 djm 31:
32: #include "buffer.h"
33: #include "xmalloc.h"
34: #include "log.h"
35: #include "pathnames.h"
36:
37: #include "sftp.h"
38: #include "sftp-common.h"
1.27 djm 39: #include "sftp-glob.h"
1.1 djm 40: #include "sftp-client.h"
41: #include "sftp-int.h"
42:
1.26 djm 43: /* File to read commands from */
44: extern FILE *infile;
45:
1.42 djm 46: /* Size of buffer used when copying files */
47: extern size_t copy_buffer_len;
48:
1.43 djm 49: /* Number of concurrent outstanding requests */
50: extern int num_requests;
51:
1.1 djm 52: /* Seperators for interactive commands */
53: #define WHITESPACE " \t\r\n"
54:
55: /* Commands for interactive mode */
56: #define I_CHDIR 1
57: #define I_CHGRP 2
58: #define I_CHMOD 3
59: #define I_CHOWN 4
60: #define I_GET 5
61: #define I_HELP 6
62: #define I_LCHDIR 7
63: #define I_LLS 8
64: #define I_LMKDIR 9
65: #define I_LPWD 10
66: #define I_LS 11
67: #define I_LUMASK 12
68: #define I_MKDIR 13
69: #define I_PUT 14
70: #define I_PWD 15
71: #define I_QUIT 16
72: #define I_RENAME 17
73: #define I_RM 18
74: #define I_RMDIR 19
75: #define I_SHELL 20
1.26 djm 76: #define I_SYMLINK 21
1.28 markus 77: #define I_VERSION 22
1.1 djm 78:
79: struct CMD {
1.6 deraadt 80: const char *c;
1.1 djm 81: const int n;
82: };
83:
84: const struct CMD cmds[] = {
1.40 markus 85: { "bye", I_QUIT },
1.15 stevesk 86: { "cd", I_CHDIR },
87: { "chdir", I_CHDIR },
88: { "chgrp", I_CHGRP },
89: { "chmod", I_CHMOD },
90: { "chown", I_CHOWN },
91: { "dir", I_LS },
92: { "exit", I_QUIT },
93: { "get", I_GET },
1.34 djm 94: { "mget", I_GET },
1.15 stevesk 95: { "help", I_HELP },
96: { "lcd", I_LCHDIR },
97: { "lchdir", I_LCHDIR },
98: { "lls", I_LLS },
99: { "lmkdir", I_LMKDIR },
1.26 djm 100: { "ln", I_SYMLINK },
1.15 stevesk 101: { "lpwd", I_LPWD },
102: { "ls", I_LS },
103: { "lumask", I_LUMASK },
104: { "mkdir", I_MKDIR },
105: { "put", I_PUT },
1.34 djm 106: { "mput", I_PUT },
1.15 stevesk 107: { "pwd", I_PWD },
108: { "quit", I_QUIT },
109: { "rename", I_RENAME },
110: { "rm", I_RM },
111: { "rmdir", I_RMDIR },
1.26 djm 112: { "symlink", I_SYMLINK },
1.28 markus 113: { "version", I_VERSION },
1.6 deraadt 114: { "!", I_SHELL },
1.7 deraadt 115: { "?", I_HELP },
1.6 deraadt 116: { NULL, -1}
1.1 djm 117: };
118:
1.37 itojun 119: static void
1.1 djm 120: help(void)
121: {
122: printf("Available commands:\n");
1.13 stevesk 123: printf("cd path Change remote directory to 'path'\n");
124: printf("lcd path Change local directory to 'path'\n");
125: printf("chgrp grp path Change group of file 'path' to 'grp'\n");
126: printf("chmod mode path Change permissions of file 'path' to 'mode'\n");
127: printf("chown own path Change owner of file 'path' to 'own'\n");
128: printf("help Display this help text\n");
129: printf("get remote-path [local-path] Download file\n");
130: printf("lls [ls-options [path]] Display local directory listing\n");
1.26 djm 131: printf("ln oldpath newpath Symlink remote file\n");
1.13 stevesk 132: printf("lmkdir path Create local directory\n");
133: printf("lpwd Print local working directory\n");
134: printf("ls [path] Display remote directory listing\n");
135: printf("lumask umask Set local umask to 'umask'\n");
136: printf("mkdir path Create remote directory\n");
137: printf("put local-path [remote-path] Upload file\n");
138: printf("pwd Display remote working directory\n");
139: printf("exit Quit sftp\n");
140: printf("quit Quit sftp\n");
141: printf("rename oldpath newpath Rename remote file\n");
142: printf("rmdir path Remove remote directory\n");
143: printf("rm path Delete remote file\n");
1.26 djm 144: printf("symlink oldpath newpath Symlink remote file\n");
1.28 markus 145: printf("version Show SFTP version\n");
1.1 djm 146: printf("!command Execute 'command' in local shell\n");
147: printf("! Escape to local shell\n");
1.13 stevesk 148: printf("? Synonym for help\n");
1.1 djm 149: }
150:
1.37 itojun 151: static void
1.1 djm 152: local_do_shell(const char *args)
153: {
1.36 markus 154: int status;
1.1 djm 155: char *shell;
156: pid_t pid;
1.3 stevesk 157:
1.1 djm 158: if (!*args)
159: args = NULL;
1.3 stevesk 160:
1.1 djm 161: if ((shell = getenv("SHELL")) == NULL)
162: shell = _PATH_BSHELL;
163:
164: if ((pid = fork()) == -1)
165: fatal("Couldn't fork: %s", strerror(errno));
166:
167: if (pid == 0) {
168: /* XXX: child has pipe fds to ssh subproc open - issue? */
169: if (args) {
170: debug3("Executing %s -c \"%s\"", shell, args);
1.38 deraadt 171: execl(shell, shell, "-c", args, (char *)NULL);
1.1 djm 172: } else {
173: debug3("Executing %s", shell);
1.38 deraadt 174: execl(shell, shell, (char *)NULL);
1.1 djm 175: }
1.3 stevesk 176: fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
1.1 djm 177: strerror(errno));
178: _exit(1);
179: }
1.46 markus 180: while (waitpid(pid, &status, 0) == -1)
181: if (errno != EINTR)
182: fatal("Couldn't wait for child: %s", strerror(errno));
1.1 djm 183: if (!WIFEXITED(status))
184: error("Shell exited abormally");
185: else if (WEXITSTATUS(status))
186: error("Shell exited with status %d", WEXITSTATUS(status));
187: }
188:
1.37 itojun 189: static void
1.1 djm 190: local_do_ls(const char *args)
191: {
192: if (!args || !*args)
1.18 stevesk 193: local_do_shell(_PATH_LS);
1.1 djm 194: else {
1.18 stevesk 195: int len = strlen(_PATH_LS " ") + strlen(args) + 1;
1.16 deraadt 196: char *buf = xmalloc(len);
1.1 djm 197:
198: /* XXX: quoting - rip quoting code from ftp? */
1.18 stevesk 199: snprintf(buf, len, _PATH_LS " %s", args);
1.1 djm 200: local_do_shell(buf);
1.16 deraadt 201: xfree(buf);
1.1 djm 202: }
203: }
204:
1.48 ! djm 205: /* Strip one path (usually the pwd) from the start of another */
! 206: static char *
! 207: path_strip(char *path, char *strip)
! 208: {
! 209: size_t len;
! 210: char *ret;
! 211:
! 212: if (strip == NULL)
! 213: return (xstrdup(path));
! 214:
! 215: len = strlen(strip);
! 216: if (strip != NULL && strncmp(path, strip, len) == 0) {
! 217: if (strip[len - 1] != '/' && path[len] == '/')
! 218: len++;
! 219: return (xstrdup(path + len));
! 220: }
! 221:
! 222: return (xstrdup(path));
! 223: }
! 224:
1.37 itojun 225: static char *
1.29 djm 226: path_append(char *p1, char *p2)
227: {
228: char *ret;
1.31 markus 229: int len = strlen(p1) + strlen(p2) + 2;
1.29 djm 230:
1.31 markus 231: ret = xmalloc(len);
232: strlcpy(ret, p1, len);
1.48 ! djm 233: if (p1[strlen(p1) - 1] != '/')
1.39 jakob 234: strlcat(ret, "/", len);
1.31 markus 235: strlcat(ret, p2, len);
1.29 djm 236:
237: return(ret);
238: }
239:
1.37 itojun 240: static char *
1.1 djm 241: make_absolute(char *p, char *pwd)
242: {
1.29 djm 243: char *abs;
1.1 djm 244:
245: /* Derelativise */
246: if (p && p[0] != '/') {
1.29 djm 247: abs = path_append(pwd, p);
1.1 djm 248: xfree(p);
1.29 djm 249: return(abs);
250: } else
251: return(p);
252: }
253:
1.37 itojun 254: static int
1.29 djm 255: infer_path(const char *p, char **ifp)
256: {
257: char *cp;
258:
259: cp = strrchr(p, '/');
260: if (cp == NULL) {
261: *ifp = xstrdup(p);
262: return(0);
1.1 djm 263: }
264:
1.29 djm 265: if (!cp[1]) {
266: error("Invalid path");
267: return(-1);
268: }
269:
270: *ifp = xstrdup(cp + 1);
271: return(0);
1.1 djm 272: }
273:
1.37 itojun 274: static int
1.1 djm 275: parse_getput_flags(const char **cpp, int *pflag)
276: {
277: const char *cp = *cpp;
278:
279: /* Check for flags */
280: if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
1.20 djm 281: switch (cp[1]) {
1.22 djm 282: case 'p':
1.1 djm 283: case 'P':
284: *pflag = 1;
285: break;
286: default:
1.22 djm 287: error("Invalid flag -%c", cp[1]);
1.1 djm 288: return(-1);
289: }
290: cp += 2;
291: *cpp = cp + strspn(cp, WHITESPACE);
292: }
293:
294: return(0);
295: }
296:
1.37 itojun 297: static int
1.48 ! djm 298: parse_ls_flags(const char **cpp, int *lflag)
! 299: {
! 300: const char *cp = *cpp;
! 301:
! 302: /* Check for flags */
! 303: if (cp++[0] == '-') {
! 304: for(; strchr(WHITESPACE, *cp) == NULL; cp++) {
! 305: switch (*cp) {
! 306: case 'l':
! 307: *lflag = 1;
! 308: break;
! 309: default:
! 310: error("Invalid flag -%c", *cp);
! 311: return(-1);
! 312: }
! 313: }
! 314: *cpp = cp + strspn(cp, WHITESPACE);
! 315: }
! 316:
! 317: return(0);
! 318: }
! 319:
! 320: static int
1.1 djm 321: get_pathname(const char **cpp, char **path)
322: {
1.8 provos 323: const char *cp = *cpp, *end;
324: char quot;
1.1 djm 325: int i;
326:
327: cp += strspn(cp, WHITESPACE);
328: if (!*cp) {
329: *cpp = cp;
330: *path = NULL;
1.8 provos 331: return (0);
1.1 djm 332: }
333:
334: /* Check for quoted filenames */
335: if (*cp == '\"' || *cp == '\'') {
1.8 provos 336: quot = *cp++;
1.30 markus 337:
1.8 provos 338: end = strchr(cp, quot);
339: if (end == NULL) {
1.1 djm 340: error("Unterminated quote");
1.8 provos 341: goto fail;
1.1 djm 342: }
1.8 provos 343: if (cp == end) {
1.1 djm 344: error("Empty quotes");
1.8 provos 345: goto fail;
1.1 djm 346: }
1.8 provos 347: *cpp = end + 1 + strspn(end + 1, WHITESPACE);
348: } else {
349: /* Read to end of filename */
350: end = strpbrk(cp, WHITESPACE);
351: if (end == NULL)
352: end = strchr(cp, '\0');
353: *cpp = end + strspn(end, WHITESPACE);
1.1 djm 354: }
355:
1.8 provos 356: i = end - cp;
1.1 djm 357:
358: *path = xmalloc(i + 1);
359: memcpy(*path, cp, i);
360: (*path)[i] = '\0';
361: return(0);
1.8 provos 362:
363: fail:
364: *path = NULL;
365: return (-1);
1.1 djm 366: }
367:
1.37 itojun 368: static int
1.29 djm 369: is_dir(char *path)
370: {
371: struct stat sb;
372:
373: /* XXX: report errors? */
374: if (stat(path, &sb) == -1)
375: return(0);
376:
377: return(sb.st_mode & S_IFDIR);
378: }
379:
1.37 itojun 380: static int
1.44 djm 381: remote_is_dir(struct sftp_conn *conn, char *path)
1.1 djm 382: {
1.29 djm 383: Attrib *a;
1.1 djm 384:
1.29 djm 385: /* XXX: report errors? */
1.44 djm 386: if ((a = do_stat(conn, path, 1)) == NULL)
1.29 djm 387: return(0);
388: if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
1.1 djm 389: return(0);
1.29 djm 390: return(a->perm & S_IFDIR);
391: }
392:
1.37 itojun 393: static int
1.44 djm 394: process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
1.29 djm 395: {
396: char *abs_src = NULL;
397: char *abs_dst = NULL;
398: char *tmp;
399: glob_t g;
400: int err = 0;
401: int i;
1.30 markus 402:
1.29 djm 403: abs_src = xstrdup(src);
404: abs_src = make_absolute(abs_src, pwd);
405:
1.30 markus 406: memset(&g, 0, sizeof(g));
1.29 djm 407: debug3("Looking up %s", abs_src);
1.44 djm 408: if (remote_glob(conn, abs_src, 0, NULL, &g)) {
1.29 djm 409: error("File \"%s\" not found.", abs_src);
410: err = -1;
411: goto out;
412: }
413:
414: /* Only one match, dst may be file, directory or unspecified */
415: if (g.gl_pathv[0] && g.gl_matchc == 1) {
416: if (dst) {
417: /* If directory specified, append filename */
418: if (is_dir(dst)) {
419: if (infer_path(g.gl_pathv[0], &tmp)) {
420: err = 1;
421: goto out;
422: }
423: abs_dst = path_append(dst, tmp);
424: xfree(tmp);
425: } else
426: abs_dst = xstrdup(dst);
427: } else if (infer_path(g.gl_pathv[0], &abs_dst)) {
428: err = -1;
429: goto out;
430: }
431: printf("Fetching %s to %s\n", g.gl_pathv[0], abs_dst);
1.44 djm 432: err = do_download(conn, g.gl_pathv[0], abs_dst, pflag);
1.29 djm 433: goto out;
434: }
435:
436: /* Multiple matches, dst may be directory or unspecified */
437: if (dst && !is_dir(dst)) {
1.30 markus 438: error("Multiple files match, but \"%s\" is not a directory",
1.29 djm 439: dst);
440: err = -1;
441: goto out;
442: }
1.30 markus 443:
1.41 deraadt 444: for (i = 0; g.gl_pathv[i]; i++) {
1.29 djm 445: if (infer_path(g.gl_pathv[i], &tmp)) {
446: err = -1;
447: goto out;
448: }
449: if (dst) {
450: abs_dst = path_append(dst, tmp);
451: xfree(tmp);
452: } else
453: abs_dst = tmp;
454:
455: printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
1.44 djm 456: if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
1.29 djm 457: err = -1;
458: xfree(abs_dst);
459: abs_dst = NULL;
1.1 djm 460: }
461:
1.29 djm 462: out:
463: xfree(abs_src);
464: if (abs_dst)
465: xfree(abs_dst);
466: globfree(&g);
467: return(err);
468: }
469:
1.37 itojun 470: static int
1.44 djm 471: process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
1.29 djm 472: {
473: char *tmp_dst = NULL;
474: char *abs_dst = NULL;
475: char *tmp;
476: glob_t g;
477: int err = 0;
478: int i;
479:
480: if (dst) {
481: tmp_dst = xstrdup(dst);
482: tmp_dst = make_absolute(tmp_dst, pwd);
483: }
484:
1.30 markus 485: memset(&g, 0, sizeof(g));
1.29 djm 486: debug3("Looking up %s", src);
487: if (glob(src, 0, NULL, &g)) {
488: error("File \"%s\" not found.", src);
489: err = -1;
490: goto out;
491: }
492:
493: /* Only one match, dst may be file, directory or unspecified */
494: if (g.gl_pathv[0] && g.gl_matchc == 1) {
495: if (tmp_dst) {
496: /* If directory specified, append filename */
1.44 djm 497: if (remote_is_dir(conn, tmp_dst)) {
1.29 djm 498: if (infer_path(g.gl_pathv[0], &tmp)) {
499: err = 1;
500: goto out;
501: }
502: abs_dst = path_append(tmp_dst, tmp);
503: xfree(tmp);
504: } else
505: abs_dst = xstrdup(tmp_dst);
1.32 markus 506: } else {
507: if (infer_path(g.gl_pathv[0], &abs_dst)) {
508: err = -1;
509: goto out;
510: }
511: abs_dst = make_absolute(abs_dst, pwd);
1.29 djm 512: }
513: printf("Uploading %s to %s\n", g.gl_pathv[0], abs_dst);
1.44 djm 514: err = do_upload(conn, g.gl_pathv[0], abs_dst, pflag);
1.29 djm 515: goto out;
516: }
517:
518: /* Multiple matches, dst may be directory or unspecified */
1.44 djm 519: if (tmp_dst && !remote_is_dir(conn, tmp_dst)) {
1.30 markus 520: error("Multiple files match, but \"%s\" is not a directory",
1.29 djm 521: tmp_dst);
522: err = -1;
523: goto out;
524: }
525:
1.41 deraadt 526: for (i = 0; g.gl_pathv[i]; i++) {
1.29 djm 527: if (infer_path(g.gl_pathv[i], &tmp)) {
528: err = -1;
529: goto out;
530: }
531: if (tmp_dst) {
532: abs_dst = path_append(tmp_dst, tmp);
533: xfree(tmp);
534: } else
535: abs_dst = make_absolute(tmp, pwd);
536:
537: printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
1.44 djm 538: if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
1.29 djm 539: err = -1;
1.1 djm 540: }
541:
1.29 djm 542: out:
543: if (abs_dst)
544: xfree(abs_dst);
545: if (tmp_dst)
546: xfree(tmp_dst);
547: return(err);
1.1 djm 548: }
549:
1.37 itojun 550: static int
1.48 ! djm 551: sdirent_comp(const void *aa, const void *bb)
! 552: {
! 553: SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
! 554: SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
! 555:
! 556: return (strcmp(a->filename, b->filename));
! 557: }
! 558:
! 559: /* sftp ls.1 replacement for directories */
! 560: static int
! 561: do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
! 562: {
! 563: int n;
! 564: SFTP_DIRENT **d;
! 565:
! 566: if ((n = do_readdir(conn, path, &d)) != 0)
! 567: return (n);
! 568:
! 569: /* Count entries for sort */
! 570: for (n = 0; d[n] != NULL; n++)
! 571: ;
! 572:
! 573: qsort(d, n, sizeof(*d), sdirent_comp);
! 574:
! 575: for (n = 0; d[n] != NULL; n++) {
! 576: char *tmp, *fname;
! 577:
! 578: tmp = path_append(path, d[n]->filename);
! 579: fname = path_strip(tmp, strip_path);
! 580: xfree(tmp);
! 581:
! 582: if (lflag) {
! 583: char *lname;
! 584: struct stat sb;
! 585:
! 586: memset(&sb, 0, sizeof(sb));
! 587: attrib_to_stat(&d[n]->a, &sb);
! 588: lname = ls_file(fname, &sb, 1);
! 589: printf("%s\n", lname);
! 590: xfree(lname);
! 591: } else {
! 592: /* XXX - multicolumn display would be nice here */
! 593: printf("%s\n", fname);
! 594: }
! 595:
! 596: xfree(fname);
! 597: }
! 598:
! 599: free_sftp_dirents(d);
! 600: return (0);
! 601: }
! 602:
! 603: /* sftp ls.1 replacement which handles path globs */
! 604: static int
! 605: do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
! 606: int lflag)
! 607: {
! 608: glob_t g;
! 609: int i;
! 610: Attrib *a;
! 611: struct stat sb;
! 612:
! 613: memset(&g, 0, sizeof(g));
! 614:
! 615: if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
! 616: NULL, &g)) {
! 617: error("Can't ls: \"%s\" not found", path);
! 618: return (-1);
! 619: }
! 620:
! 621: /*
! 622: * If the glob returns a single match, which is the same as the
! 623: * input glob, and it is a directory, then just list its contents
! 624: */
! 625: if (g.gl_pathc == 1 &&
! 626: strncmp(path, g.gl_pathv[0], strlen(g.gl_pathv[0]) - 1) == 0) {
! 627: if ((a = do_lstat(conn, path, 1)) == NULL) {
! 628: globfree(&g);
! 629: return (-1);
! 630: }
! 631: if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
! 632: S_ISDIR(a->perm)) {
! 633: globfree(&g);
! 634: return (do_ls_dir(conn, path, strip_path, lflag));
! 635: }
! 636: }
! 637:
! 638: for (i = 0; g.gl_pathv[i]; i++) {
! 639: char *fname, *lname;
! 640:
! 641: fname = path_strip(g.gl_pathv[i], strip_path);
! 642:
! 643: if (lflag) {
! 644: /*
! 645: * XXX: this is slow - 1 roundtrip per path
! 646: * A solution to this is to fork glob() and
! 647: * build a sftp specific version which keeps the
! 648: * attribs (which currently get thrown away)
! 649: * that the server returns as well as the filenames.
! 650: */
! 651: memset(&sb, 0, sizeof(sb));
! 652: a = do_lstat(conn, g.gl_pathv[i], 1);
! 653: if (a != NULL)
! 654: attrib_to_stat(a, &sb);
! 655: lname = ls_file(fname, &sb, 1);
! 656: printf("%s\n", lname);
! 657: xfree(lname);
! 658: } else {
! 659: /* XXX - multicolumn display would be nice here */
! 660: printf("%s\n", fname);
! 661: }
! 662: xfree(fname);
! 663: }
! 664:
! 665: if (g.gl_pathc)
! 666: globfree(&g);
! 667:
! 668: return (0);
! 669: }
! 670:
! 671: static int
! 672: parse_args(const char **cpp, int *pflag, int *lflag,
! 673: unsigned long *n_arg, char **path1, char **path2)
1.1 djm 674: {
675: const char *cmd, *cp = *cpp;
1.21 stevesk 676: char *cp2;
1.4 markus 677: int base = 0;
1.21 stevesk 678: long l;
1.1 djm 679: int i, cmdnum;
680:
681: /* Skip leading whitespace */
682: cp = cp + strspn(cp, WHITESPACE);
683:
684: /* Ignore blank lines */
685: if (!*cp)
686: return(-1);
687:
688: /* Figure out which command we have */
1.41 deraadt 689: for (i = 0; cmds[i].c; i++) {
1.1 djm 690: int cmdlen = strlen(cmds[i].c);
691:
692: /* Check for command followed by whitespace */
693: if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
694: strchr(WHITESPACE, cp[cmdlen])) {
695: cp += cmdlen;
696: cp = cp + strspn(cp, WHITESPACE);
697: break;
698: }
699: }
700: cmdnum = cmds[i].n;
701: cmd = cmds[i].c;
702:
703: /* Special case */
704: if (*cp == '!') {
705: cp++;
706: cmdnum = I_SHELL;
707: } else if (cmdnum == -1) {
708: error("Invalid command.");
709: return(-1);
710: }
711:
712: /* Get arguments and parse flags */
1.48 ! djm 713: *lflag = *pflag = *n_arg = 0;
1.1 djm 714: *path1 = *path2 = NULL;
715: switch (cmdnum) {
716: case I_GET:
717: case I_PUT:
718: if (parse_getput_flags(&cp, pflag))
719: return(-1);
720: /* Get first pathname (mandatory) */
721: if (get_pathname(&cp, path1))
722: return(-1);
723: if (*path1 == NULL) {
724: error("You must specify at least one path after a "
725: "%s command.", cmd);
726: return(-1);
727: }
728: /* Try to get second pathname (optional) */
729: if (get_pathname(&cp, path2))
730: return(-1);
731: break;
732: case I_RENAME:
1.26 djm 733: case I_SYMLINK:
1.1 djm 734: if (get_pathname(&cp, path1))
735: return(-1);
736: if (get_pathname(&cp, path2))
737: return(-1);
738: if (!*path1 || !*path2) {
739: error("You must specify two paths after a %s "
740: "command.", cmd);
741: return(-1);
742: }
743: break;
744: case I_RM:
745: case I_MKDIR:
746: case I_RMDIR:
747: case I_CHDIR:
748: case I_LCHDIR:
749: case I_LMKDIR:
750: /* Get pathname (mandatory) */
751: if (get_pathname(&cp, path1))
752: return(-1);
753: if (*path1 == NULL) {
1.3 stevesk 754: error("You must specify a path after a %s command.",
1.1 djm 755: cmd);
756: return(-1);
757: }
758: break;
759: case I_LS:
1.48 ! djm 760: if (parse_ls_flags(&cp, lflag))
! 761: return(-1);
1.1 djm 762: /* Path is optional */
763: if (get_pathname(&cp, path1))
764: return(-1);
765: break;
766: case I_LLS:
767: case I_SHELL:
768: /* Uses the rest of the line */
769: break;
770: case I_LUMASK:
1.21 stevesk 771: base = 8;
1.1 djm 772: case I_CHMOD:
1.4 markus 773: base = 8;
1.1 djm 774: case I_CHOWN:
775: case I_CHGRP:
776: /* Get numeric arg (mandatory) */
1.21 stevesk 777: l = strtol(cp, &cp2, base);
778: if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) &&
779: errno == ERANGE) || l < 0) {
1.1 djm 780: error("You must supply a numeric argument "
781: "to the %s command.", cmd);
782: return(-1);
783: }
1.21 stevesk 784: cp = cp2;
785: *n_arg = l;
786: if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp))
787: break;
788: if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
1.1 djm 789: error("You must supply a numeric argument "
790: "to the %s command.", cmd);
791: return(-1);
792: }
793: cp += strspn(cp, WHITESPACE);
794:
795: /* Get pathname (mandatory) */
796: if (get_pathname(&cp, path1))
797: return(-1);
798: if (*path1 == NULL) {
1.3 stevesk 799: error("You must specify a path after a %s command.",
1.1 djm 800: cmd);
801: return(-1);
802: }
803: break;
804: case I_QUIT:
805: case I_PWD:
806: case I_LPWD:
807: case I_HELP:
1.28 markus 808: case I_VERSION:
1.1 djm 809: break;
810: default:
811: fatal("Command not implemented");
812: }
813:
814: *cpp = cp;
815: return(cmdnum);
816: }
817:
1.37 itojun 818: static int
1.44 djm 819: parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd)
1.1 djm 820: {
1.8 provos 821: char *path1, *path2, *tmp;
1.48 ! djm 822: int pflag, lflag, cmdnum, i;
1.1 djm 823: unsigned long n_arg;
824: Attrib a, *aa;
1.23 deraadt 825: char path_buf[MAXPATHLEN];
1.25 deraadt 826: int err = 0;
1.27 djm 827: glob_t g;
1.1 djm 828:
829: path1 = path2 = NULL;
1.48 ! djm 830: cmdnum = parse_args(&cmd, &pflag, &lflag, &n_arg,
! 831: &path1, &path2);
1.1 djm 832:
1.29 djm 833: memset(&g, 0, sizeof(g));
834:
1.1 djm 835: /* Perform command */
836: switch (cmdnum) {
837: case -1:
838: break;
839: case I_GET:
1.44 djm 840: err = process_get(conn, path1, path2, *pwd, pflag);
1.1 djm 841: break;
842: case I_PUT:
1.44 djm 843: err = process_put(conn, path1, path2, *pwd, pflag);
1.33 markus 844: break;
845: case I_RENAME:
1.1 djm 846: path1 = make_absolute(path1, *pwd);
847: path2 = make_absolute(path2, *pwd);
1.44 djm 848: err = do_rename(conn, path1, path2);
1.1 djm 849: break;
1.26 djm 850: case I_SYMLINK:
1.44 djm 851: path2 = make_absolute(path2, *pwd);
852: err = do_symlink(conn, path1, path2);
1.26 djm 853: break;
1.1 djm 854: case I_RM:
855: path1 = make_absolute(path1, *pwd);
1.44 djm 856: remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1.41 deraadt 857: for (i = 0; g.gl_pathv[i]; i++) {
1.27 djm 858: printf("Removing %s\n", g.gl_pathv[i]);
1.44 djm 859: if (do_rm(conn, g.gl_pathv[i]) == -1)
1.27 djm 860: err = -1;
861: }
1.1 djm 862: break;
863: case I_MKDIR:
864: path1 = make_absolute(path1, *pwd);
865: attrib_clear(&a);
866: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
867: a.perm = 0777;
1.44 djm 868: err = do_mkdir(conn, path1, &a);
1.1 djm 869: break;
870: case I_RMDIR:
871: path1 = make_absolute(path1, *pwd);
1.44 djm 872: err = do_rmdir(conn, path1);
1.1 djm 873: break;
874: case I_CHDIR:
875: path1 = make_absolute(path1, *pwd);
1.44 djm 876: if ((tmp = do_realpath(conn, path1)) == NULL) {
1.25 deraadt 877: err = 1;
1.11 markus 878: break;
1.25 deraadt 879: }
1.44 djm 880: if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1.11 markus 881: xfree(tmp);
1.25 deraadt 882: err = 1;
1.11 markus 883: break;
884: }
1.9 djm 885: if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
886: error("Can't change directory: Can't check target");
887: xfree(tmp);
1.25 deraadt 888: err = 1;
1.9 djm 889: break;
890: }
891: if (!S_ISDIR(aa->perm)) {
892: error("Can't change directory: \"%s\" is not "
893: "a directory", tmp);
894: xfree(tmp);
1.25 deraadt 895: err = 1;
1.9 djm 896: break;
897: }
1.11 markus 898: xfree(*pwd);
899: *pwd = tmp;
1.1 djm 900: break;
901: case I_LS:
1.12 djm 902: if (!path1) {
1.48 ! djm 903: do_globbed_ls(conn, *pwd, *pwd, lflag);
1.12 djm 904: break;
905: }
1.48 ! djm 906:
! 907: /* Strip pwd off beginning of non-absolute paths */
! 908: tmp = NULL;
! 909: if (*path1 != '/')
! 910: tmp = *pwd;
! 911:
1.1 djm 912: path1 = make_absolute(path1, *pwd);
1.48 ! djm 913:
! 914: do_globbed_ls(conn, path1, tmp, lflag);
1.1 djm 915: break;
916: case I_LCHDIR:
1.25 deraadt 917: if (chdir(path1) == -1) {
1.1 djm 918: error("Couldn't change local directory to "
919: "\"%s\": %s", path1, strerror(errno));
1.25 deraadt 920: err = 1;
921: }
1.1 djm 922: break;
923: case I_LMKDIR:
1.25 deraadt 924: if (mkdir(path1, 0777) == -1) {
1.17 stevesk 925: error("Couldn't create local directory "
1.1 djm 926: "\"%s\": %s", path1, strerror(errno));
1.25 deraadt 927: err = 1;
928: }
1.1 djm 929: break;
930: case I_LLS:
931: local_do_ls(cmd);
932: break;
933: case I_SHELL:
934: local_do_shell(cmd);
935: break;
936: case I_LUMASK:
937: umask(n_arg);
1.21 stevesk 938: printf("Local umask: %03lo\n", n_arg);
1.1 djm 939: break;
940: case I_CHMOD:
941: path1 = make_absolute(path1, *pwd);
942: attrib_clear(&a);
943: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
944: a.perm = n_arg;
1.44 djm 945: remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1.41 deraadt 946: for (i = 0; g.gl_pathv[i]; i++) {
1.27 djm 947: printf("Changing mode on %s\n", g.gl_pathv[i]);
1.44 djm 948: do_setstat(conn, g.gl_pathv[i], &a);
1.27 djm 949: }
1.5 stevesk 950: break;
1.1 djm 951: case I_CHOWN:
952: path1 = make_absolute(path1, *pwd);
1.44 djm 953: remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1.41 deraadt 954: for (i = 0; g.gl_pathv[i]; i++) {
1.44 djm 955: if (!(aa = do_stat(conn, g.gl_pathv[i], 0)))
1.27 djm 956: continue;
957: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
958: error("Can't get current ownership of "
959: "remote file \"%s\"", g.gl_pathv[i]);
960: continue;
961: }
962: printf("Changing owner on %s\n", g.gl_pathv[i]);
963: aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
964: aa->uid = n_arg;
1.44 djm 965: do_setstat(conn, g.gl_pathv[i], aa);
1.1 djm 966: }
967: break;
968: case I_CHGRP:
969: path1 = make_absolute(path1, *pwd);
1.44 djm 970: remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1.41 deraadt 971: for (i = 0; g.gl_pathv[i]; i++) {
1.44 djm 972: if (!(aa = do_stat(conn, g.gl_pathv[i], 0)))
1.27 djm 973: continue;
974: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
975: error("Can't get current ownership of "
976: "remote file \"%s\"", g.gl_pathv[i]);
977: continue;
978: }
979: printf("Changing group on %s\n", g.gl_pathv[i]);
980: aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
981: aa->gid = n_arg;
1.44 djm 982: do_setstat(conn, g.gl_pathv[i], aa);
1.1 djm 983: }
984: break;
985: case I_PWD:
986: printf("Remote working directory: %s\n", *pwd);
987: break;
988: case I_LPWD:
989: if (!getcwd(path_buf, sizeof(path_buf)))
1.24 millert 990: error("Couldn't get local cwd: %s",
1.1 djm 991: strerror(errno));
992: else
993: printf("Local working directory: %s\n",
994: path_buf);
995: break;
996: case I_QUIT:
997: return(-1);
998: case I_HELP:
999: help();
1.28 markus 1000: break;
1001: case I_VERSION:
1.47 deraadt 1002: printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1.1 djm 1003: break;
1004: default:
1005: fatal("%d is not implemented", cmdnum);
1006: }
1007:
1.29 djm 1008: if (g.gl_pathc)
1009: globfree(&g);
1.1 djm 1010: if (path1)
1011: xfree(path1);
1012: if (path2)
1013: xfree(path2);
1.25 deraadt 1014:
1015: /* If an error occurs in batch mode we should abort. */
1016: if (infile != stdin && err > 0)
1017: return -1;
1018:
1.1 djm 1019: return(0);
1020: }
1021:
1022: void
1.35 mouring 1023: interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
1.1 djm 1024: {
1025: char *pwd;
1.35 mouring 1026: char *dir = NULL;
1.1 djm 1027: char cmd[2048];
1.44 djm 1028: struct sftp_conn *conn;
1.26 djm 1029:
1.44 djm 1030: conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
1031: if (conn == NULL)
1.26 djm 1032: fatal("Couldn't initialise connection to server");
1.1 djm 1033:
1.44 djm 1034: pwd = do_realpath(conn, ".");
1.1 djm 1035: if (pwd == NULL)
1036: fatal("Need cwd");
1037:
1.35 mouring 1038: if (file1 != NULL) {
1039: dir = xstrdup(file1);
1040: dir = make_absolute(dir, pwd);
1041:
1.44 djm 1042: if (remote_is_dir(conn, dir) && file2 == NULL) {
1.35 mouring 1043: printf("Changing to: %s\n", dir);
1044: snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
1.44 djm 1045: parse_dispatch_command(conn, cmd, &pwd);
1.35 mouring 1046: } else {
1047: if (file2 == NULL)
1048: snprintf(cmd, sizeof cmd, "get %s", dir);
1049: else
1050: snprintf(cmd, sizeof cmd, "get %s %s", dir,
1051: file2);
1052:
1.44 djm 1053: parse_dispatch_command(conn, cmd, &pwd);
1.45 mpech 1054: xfree(dir);
1.35 mouring 1055: return;
1056: }
1.45 mpech 1057: xfree(dir);
1.35 mouring 1058: }
1.14 stevesk 1059: setvbuf(stdout, NULL, _IOLBF, 0);
1.25 deraadt 1060: setvbuf(infile, NULL, _IOLBF, 0);
1.1 djm 1061:
1.41 deraadt 1062: for (;;) {
1.1 djm 1063: char *cp;
1064:
1065: printf("sftp> ");
1066:
1067: /* XXX: use libedit */
1.25 deraadt 1068: if (fgets(cmd, sizeof(cmd), infile) == NULL) {
1.1 djm 1069: printf("\n");
1070: break;
1.25 deraadt 1071: } else if (infile != stdin) /* Bluff typing */
1072: printf("%s", cmd);
1073:
1.1 djm 1074: cp = strrchr(cmd, '\n');
1075: if (cp)
1076: *cp = '\0';
1.25 deraadt 1077:
1.44 djm 1078: if (parse_dispatch_command(conn, cmd, &pwd))
1.1 djm 1079: break;
1080: }
1081: xfree(pwd);
1082: }