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