Annotation of src/usr.bin/ssh/sftp-int.c, Revision 1.49
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.49 ! djm 28: RCSID("$OpenBSD: sftp-int.c,v 1.48 2002/09/11 22:41:50 djm 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:
211: if (strip == NULL)
212: return (xstrdup(path));
213:
214: len = strlen(strip);
215: if (strip != NULL && strncmp(path, strip, len) == 0) {
216: if (strip[len - 1] != '/' && path[len] == '/')
217: len++;
218: return (xstrdup(path + len));
219: }
220:
221: return (xstrdup(path));
222: }
223:
1.37 itojun 224: static char *
1.29 djm 225: path_append(char *p1, char *p2)
226: {
227: char *ret;
1.31 markus 228: int len = strlen(p1) + strlen(p2) + 2;
1.29 djm 229:
1.31 markus 230: ret = xmalloc(len);
231: strlcpy(ret, p1, len);
1.48 djm 232: if (p1[strlen(p1) - 1] != '/')
1.39 jakob 233: strlcat(ret, "/", len);
1.31 markus 234: strlcat(ret, p2, len);
1.29 djm 235:
236: return(ret);
237: }
238:
1.37 itojun 239: static char *
1.1 djm 240: make_absolute(char *p, char *pwd)
241: {
1.29 djm 242: char *abs;
1.1 djm 243:
244: /* Derelativise */
245: if (p && p[0] != '/') {
1.29 djm 246: abs = path_append(pwd, p);
1.1 djm 247: xfree(p);
1.29 djm 248: return(abs);
249: } else
250: return(p);
251: }
252:
1.37 itojun 253: static int
1.29 djm 254: infer_path(const char *p, char **ifp)
255: {
256: char *cp;
257:
258: cp = strrchr(p, '/');
259: if (cp == NULL) {
260: *ifp = xstrdup(p);
261: return(0);
1.1 djm 262: }
263:
1.29 djm 264: if (!cp[1]) {
265: error("Invalid path");
266: return(-1);
267: }
268:
269: *ifp = xstrdup(cp + 1);
270: return(0);
1.1 djm 271: }
272:
1.37 itojun 273: static int
1.1 djm 274: parse_getput_flags(const char **cpp, int *pflag)
275: {
276: const char *cp = *cpp;
277:
278: /* Check for flags */
279: if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
1.20 djm 280: switch (cp[1]) {
1.22 djm 281: case 'p':
1.1 djm 282: case 'P':
283: *pflag = 1;
284: break;
285: default:
1.22 djm 286: error("Invalid flag -%c", cp[1]);
1.1 djm 287: return(-1);
288: }
289: cp += 2;
290: *cpp = cp + strspn(cp, WHITESPACE);
291: }
292:
293: return(0);
294: }
295:
1.37 itojun 296: static int
1.48 djm 297: parse_ls_flags(const char **cpp, int *lflag)
298: {
299: const char *cp = *cpp;
300:
301: /* Check for flags */
302: if (cp++[0] == '-') {
303: for(; strchr(WHITESPACE, *cp) == NULL; cp++) {
304: switch (*cp) {
305: case 'l':
306: *lflag = 1;
307: break;
308: default:
309: error("Invalid flag -%c", *cp);
310: return(-1);
311: }
312: }
313: *cpp = cp + strspn(cp, WHITESPACE);
314: }
315:
316: return(0);
317: }
318:
319: static int
1.1 djm 320: get_pathname(const char **cpp, char **path)
321: {
1.8 provos 322: const char *cp = *cpp, *end;
323: char quot;
1.1 djm 324: int i;
325:
326: cp += strspn(cp, WHITESPACE);
327: if (!*cp) {
328: *cpp = cp;
329: *path = NULL;
1.8 provos 330: return (0);
1.1 djm 331: }
332:
333: /* Check for quoted filenames */
334: if (*cp == '\"' || *cp == '\'') {
1.8 provos 335: quot = *cp++;
1.30 markus 336:
1.8 provos 337: end = strchr(cp, quot);
338: if (end == NULL) {
1.1 djm 339: error("Unterminated quote");
1.8 provos 340: goto fail;
1.1 djm 341: }
1.8 provos 342: if (cp == end) {
1.1 djm 343: error("Empty quotes");
1.8 provos 344: goto fail;
1.1 djm 345: }
1.8 provos 346: *cpp = end + 1 + strspn(end + 1, WHITESPACE);
347: } else {
348: /* Read to end of filename */
349: end = strpbrk(cp, WHITESPACE);
350: if (end == NULL)
351: end = strchr(cp, '\0');
352: *cpp = end + strspn(end, WHITESPACE);
1.1 djm 353: }
354:
1.8 provos 355: i = end - cp;
1.1 djm 356:
357: *path = xmalloc(i + 1);
358: memcpy(*path, cp, i);
359: (*path)[i] = '\0';
360: return(0);
1.8 provos 361:
362: fail:
363: *path = NULL;
364: return (-1);
1.1 djm 365: }
366:
1.37 itojun 367: static int
1.29 djm 368: is_dir(char *path)
369: {
370: struct stat sb;
371:
372: /* XXX: report errors? */
373: if (stat(path, &sb) == -1)
374: return(0);
375:
376: return(sb.st_mode & S_IFDIR);
377: }
378:
1.37 itojun 379: static int
1.44 djm 380: remote_is_dir(struct sftp_conn *conn, char *path)
1.1 djm 381: {
1.29 djm 382: Attrib *a;
1.1 djm 383:
1.29 djm 384: /* XXX: report errors? */
1.44 djm 385: if ((a = do_stat(conn, path, 1)) == NULL)
1.29 djm 386: return(0);
387: if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
1.1 djm 388: return(0);
1.29 djm 389: return(a->perm & S_IFDIR);
390: }
391:
1.37 itojun 392: static int
1.44 djm 393: process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
1.29 djm 394: {
395: char *abs_src = NULL;
396: char *abs_dst = NULL;
397: char *tmp;
398: glob_t g;
399: int err = 0;
400: int i;
1.30 markus 401:
1.29 djm 402: abs_src = xstrdup(src);
403: abs_src = make_absolute(abs_src, pwd);
404:
1.30 markus 405: memset(&g, 0, sizeof(g));
1.29 djm 406: debug3("Looking up %s", abs_src);
1.44 djm 407: if (remote_glob(conn, abs_src, 0, NULL, &g)) {
1.29 djm 408: error("File \"%s\" not found.", abs_src);
409: err = -1;
410: goto out;
411: }
412:
413: /* Only one match, dst may be file, directory or unspecified */
414: if (g.gl_pathv[0] && g.gl_matchc == 1) {
415: if (dst) {
416: /* If directory specified, append filename */
417: if (is_dir(dst)) {
418: if (infer_path(g.gl_pathv[0], &tmp)) {
419: err = 1;
420: goto out;
421: }
422: abs_dst = path_append(dst, tmp);
423: xfree(tmp);
424: } else
425: abs_dst = xstrdup(dst);
426: } else if (infer_path(g.gl_pathv[0], &abs_dst)) {
427: err = -1;
428: goto out;
429: }
430: printf("Fetching %s to %s\n", g.gl_pathv[0], abs_dst);
1.44 djm 431: err = do_download(conn, g.gl_pathv[0], abs_dst, pflag);
1.29 djm 432: goto out;
433: }
434:
435: /* Multiple matches, dst may be directory or unspecified */
436: if (dst && !is_dir(dst)) {
1.30 markus 437: error("Multiple files match, but \"%s\" is not a directory",
1.29 djm 438: dst);
439: err = -1;
440: goto out;
441: }
1.30 markus 442:
1.41 deraadt 443: for (i = 0; g.gl_pathv[i]; i++) {
1.29 djm 444: if (infer_path(g.gl_pathv[i], &tmp)) {
445: err = -1;
446: goto out;
447: }
448: if (dst) {
449: abs_dst = path_append(dst, tmp);
450: xfree(tmp);
451: } else
452: abs_dst = tmp;
453:
454: printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
1.44 djm 455: if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
1.29 djm 456: err = -1;
457: xfree(abs_dst);
458: abs_dst = NULL;
1.1 djm 459: }
460:
1.29 djm 461: out:
462: xfree(abs_src);
463: if (abs_dst)
464: xfree(abs_dst);
465: globfree(&g);
466: return(err);
467: }
468:
1.37 itojun 469: static int
1.44 djm 470: process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
1.29 djm 471: {
472: char *tmp_dst = NULL;
473: char *abs_dst = NULL;
474: char *tmp;
475: glob_t g;
476: int err = 0;
477: int i;
478:
479: if (dst) {
480: tmp_dst = xstrdup(dst);
481: tmp_dst = make_absolute(tmp_dst, pwd);
482: }
483:
1.30 markus 484: memset(&g, 0, sizeof(g));
1.29 djm 485: debug3("Looking up %s", src);
486: if (glob(src, 0, NULL, &g)) {
487: error("File \"%s\" not found.", src);
488: err = -1;
489: goto out;
490: }
491:
492: /* Only one match, dst may be file, directory or unspecified */
493: if (g.gl_pathv[0] && g.gl_matchc == 1) {
494: if (tmp_dst) {
495: /* If directory specified, append filename */
1.44 djm 496: if (remote_is_dir(conn, tmp_dst)) {
1.29 djm 497: if (infer_path(g.gl_pathv[0], &tmp)) {
498: err = 1;
499: goto out;
500: }
501: abs_dst = path_append(tmp_dst, tmp);
502: xfree(tmp);
503: } else
504: abs_dst = xstrdup(tmp_dst);
1.32 markus 505: } else {
506: if (infer_path(g.gl_pathv[0], &abs_dst)) {
507: err = -1;
508: goto out;
509: }
510: abs_dst = make_absolute(abs_dst, pwd);
1.29 djm 511: }
512: printf("Uploading %s to %s\n", g.gl_pathv[0], abs_dst);
1.44 djm 513: err = do_upload(conn, g.gl_pathv[0], abs_dst, pflag);
1.29 djm 514: goto out;
515: }
516:
517: /* Multiple matches, dst may be directory or unspecified */
1.44 djm 518: if (tmp_dst && !remote_is_dir(conn, tmp_dst)) {
1.30 markus 519: error("Multiple files match, but \"%s\" is not a directory",
1.29 djm 520: tmp_dst);
521: err = -1;
522: goto out;
523: }
524:
1.41 deraadt 525: for (i = 0; g.gl_pathv[i]; i++) {
1.29 djm 526: if (infer_path(g.gl_pathv[i], &tmp)) {
527: err = -1;
528: goto out;
529: }
530: if (tmp_dst) {
531: abs_dst = path_append(tmp_dst, tmp);
532: xfree(tmp);
533: } else
534: abs_dst = make_absolute(tmp, pwd);
535:
536: printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
1.44 djm 537: if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
1.29 djm 538: err = -1;
1.1 djm 539: }
540:
1.29 djm 541: out:
542: if (abs_dst)
543: xfree(abs_dst);
544: if (tmp_dst)
545: xfree(tmp_dst);
546: return(err);
1.1 djm 547: }
548:
1.37 itojun 549: static int
1.48 djm 550: sdirent_comp(const void *aa, const void *bb)
551: {
552: SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
553: SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
554:
555: return (strcmp(a->filename, b->filename));
556: }
557:
558: /* sftp ls.1 replacement for directories */
559: static int
560: do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
561: {
562: int n;
563: SFTP_DIRENT **d;
564:
565: if ((n = do_readdir(conn, path, &d)) != 0)
566: return (n);
567:
568: /* Count entries for sort */
569: for (n = 0; d[n] != NULL; n++)
570: ;
571:
572: qsort(d, n, sizeof(*d), sdirent_comp);
573:
574: for (n = 0; d[n] != NULL; n++) {
575: char *tmp, *fname;
576:
577: tmp = path_append(path, d[n]->filename);
578: fname = path_strip(tmp, strip_path);
579: xfree(tmp);
580:
581: if (lflag) {
582: char *lname;
583: struct stat sb;
584:
585: memset(&sb, 0, sizeof(sb));
586: attrib_to_stat(&d[n]->a, &sb);
587: lname = ls_file(fname, &sb, 1);
588: printf("%s\n", lname);
589: xfree(lname);
590: } else {
591: /* XXX - multicolumn display would be nice here */
592: printf("%s\n", fname);
593: }
594:
595: xfree(fname);
596: }
597:
598: free_sftp_dirents(d);
599: return (0);
600: }
601:
602: /* sftp ls.1 replacement which handles path globs */
603: static int
604: do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
605: int lflag)
606: {
607: glob_t g;
608: int i;
609: Attrib *a;
610: struct stat sb;
611:
612: memset(&g, 0, sizeof(g));
613:
614: if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
615: NULL, &g)) {
616: error("Can't ls: \"%s\" not found", path);
617: return (-1);
618: }
619:
620: /*
621: * If the glob returns a single match, which is the same as the
622: * input glob, and it is a directory, then just list its contents
623: */
624: if (g.gl_pathc == 1 &&
625: strncmp(path, g.gl_pathv[0], strlen(g.gl_pathv[0]) - 1) == 0) {
626: if ((a = do_lstat(conn, path, 1)) == NULL) {
627: globfree(&g);
628: return (-1);
629: }
630: if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
631: S_ISDIR(a->perm)) {
632: globfree(&g);
633: return (do_ls_dir(conn, path, strip_path, lflag));
634: }
635: }
636:
637: for (i = 0; g.gl_pathv[i]; i++) {
638: char *fname, *lname;
639:
640: fname = path_strip(g.gl_pathv[i], strip_path);
641:
642: if (lflag) {
643: /*
644: * XXX: this is slow - 1 roundtrip per path
645: * A solution to this is to fork glob() and
646: * build a sftp specific version which keeps the
647: * attribs (which currently get thrown away)
648: * that the server returns as well as the filenames.
649: */
650: memset(&sb, 0, sizeof(sb));
651: a = do_lstat(conn, g.gl_pathv[i], 1);
652: if (a != NULL)
653: attrib_to_stat(a, &sb);
654: lname = ls_file(fname, &sb, 1);
655: printf("%s\n", lname);
656: xfree(lname);
657: } else {
658: /* XXX - multicolumn display would be nice here */
659: printf("%s\n", fname);
660: }
661: xfree(fname);
662: }
663:
664: if (g.gl_pathc)
665: globfree(&g);
666:
667: return (0);
668: }
669:
670: static int
671: parse_args(const char **cpp, int *pflag, int *lflag,
672: unsigned long *n_arg, char **path1, char **path2)
1.1 djm 673: {
674: const char *cmd, *cp = *cpp;
1.21 stevesk 675: char *cp2;
1.4 markus 676: int base = 0;
1.21 stevesk 677: long l;
1.1 djm 678: int i, cmdnum;
679:
680: /* Skip leading whitespace */
681: cp = cp + strspn(cp, WHITESPACE);
682:
683: /* Ignore blank lines */
684: if (!*cp)
685: return(-1);
686:
687: /* Figure out which command we have */
1.41 deraadt 688: for (i = 0; cmds[i].c; i++) {
1.1 djm 689: int cmdlen = strlen(cmds[i].c);
690:
691: /* Check for command followed by whitespace */
692: if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
693: strchr(WHITESPACE, cp[cmdlen])) {
694: cp += cmdlen;
695: cp = cp + strspn(cp, WHITESPACE);
696: break;
697: }
698: }
699: cmdnum = cmds[i].n;
700: cmd = cmds[i].c;
701:
702: /* Special case */
703: if (*cp == '!') {
704: cp++;
705: cmdnum = I_SHELL;
706: } else if (cmdnum == -1) {
707: error("Invalid command.");
708: return(-1);
709: }
710:
711: /* Get arguments and parse flags */
1.48 djm 712: *lflag = *pflag = *n_arg = 0;
1.1 djm 713: *path1 = *path2 = NULL;
714: switch (cmdnum) {
715: case I_GET:
716: case I_PUT:
717: if (parse_getput_flags(&cp, pflag))
718: return(-1);
719: /* Get first pathname (mandatory) */
720: if (get_pathname(&cp, path1))
721: return(-1);
722: if (*path1 == NULL) {
723: error("You must specify at least one path after a "
724: "%s command.", cmd);
725: return(-1);
726: }
727: /* Try to get second pathname (optional) */
728: if (get_pathname(&cp, path2))
729: return(-1);
730: break;
731: case I_RENAME:
1.26 djm 732: case I_SYMLINK:
1.1 djm 733: if (get_pathname(&cp, path1))
734: return(-1);
735: if (get_pathname(&cp, path2))
736: return(-1);
737: if (!*path1 || !*path2) {
738: error("You must specify two paths after a %s "
739: "command.", cmd);
740: return(-1);
741: }
742: break;
743: case I_RM:
744: case I_MKDIR:
745: case I_RMDIR:
746: case I_CHDIR:
747: case I_LCHDIR:
748: case I_LMKDIR:
749: /* Get pathname (mandatory) */
750: if (get_pathname(&cp, path1))
751: return(-1);
752: if (*path1 == NULL) {
1.3 stevesk 753: error("You must specify a path after a %s command.",
1.1 djm 754: cmd);
755: return(-1);
756: }
757: break;
758: case I_LS:
1.48 djm 759: if (parse_ls_flags(&cp, lflag))
760: return(-1);
1.1 djm 761: /* Path is optional */
762: if (get_pathname(&cp, path1))
763: return(-1);
764: break;
765: case I_LLS:
766: case I_SHELL:
767: /* Uses the rest of the line */
768: break;
769: case I_LUMASK:
1.21 stevesk 770: base = 8;
1.1 djm 771: case I_CHMOD:
1.4 markus 772: base = 8;
1.1 djm 773: case I_CHOWN:
774: case I_CHGRP:
775: /* Get numeric arg (mandatory) */
1.21 stevesk 776: l = strtol(cp, &cp2, base);
777: if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) &&
778: errno == ERANGE) || l < 0) {
1.1 djm 779: error("You must supply a numeric argument "
780: "to the %s command.", cmd);
781: return(-1);
782: }
1.21 stevesk 783: cp = cp2;
784: *n_arg = l;
785: if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp))
786: break;
787: if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
1.1 djm 788: error("You must supply a numeric argument "
789: "to the %s command.", cmd);
790: return(-1);
791: }
792: cp += strspn(cp, WHITESPACE);
793:
794: /* Get pathname (mandatory) */
795: if (get_pathname(&cp, path1))
796: return(-1);
797: if (*path1 == NULL) {
1.3 stevesk 798: error("You must specify a path after a %s command.",
1.1 djm 799: cmd);
800: return(-1);
801: }
802: break;
803: case I_QUIT:
804: case I_PWD:
805: case I_LPWD:
806: case I_HELP:
1.28 markus 807: case I_VERSION:
1.1 djm 808: break;
809: default:
810: fatal("Command not implemented");
811: }
812:
813: *cpp = cp;
814: return(cmdnum);
815: }
816:
1.37 itojun 817: static int
1.44 djm 818: parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd)
1.1 djm 819: {
1.8 provos 820: char *path1, *path2, *tmp;
1.48 djm 821: int pflag, lflag, cmdnum, i;
1.1 djm 822: unsigned long n_arg;
823: Attrib a, *aa;
1.23 deraadt 824: char path_buf[MAXPATHLEN];
1.25 deraadt 825: int err = 0;
1.27 djm 826: glob_t g;
1.1 djm 827:
828: path1 = path2 = NULL;
1.48 djm 829: cmdnum = parse_args(&cmd, &pflag, &lflag, &n_arg,
830: &path1, &path2);
1.1 djm 831:
1.29 djm 832: memset(&g, 0, sizeof(g));
833:
1.1 djm 834: /* Perform command */
835: switch (cmdnum) {
836: case -1:
837: break;
838: case I_GET:
1.44 djm 839: err = process_get(conn, path1, path2, *pwd, pflag);
1.1 djm 840: break;
841: case I_PUT:
1.44 djm 842: err = process_put(conn, path1, path2, *pwd, pflag);
1.33 markus 843: break;
844: case I_RENAME:
1.1 djm 845: path1 = make_absolute(path1, *pwd);
846: path2 = make_absolute(path2, *pwd);
1.44 djm 847: err = do_rename(conn, path1, path2);
1.1 djm 848: break;
1.26 djm 849: case I_SYMLINK:
1.44 djm 850: path2 = make_absolute(path2, *pwd);
851: err = do_symlink(conn, path1, path2);
1.26 djm 852: break;
1.1 djm 853: case I_RM:
854: path1 = make_absolute(path1, *pwd);
1.44 djm 855: remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1.41 deraadt 856: for (i = 0; g.gl_pathv[i]; i++) {
1.27 djm 857: printf("Removing %s\n", g.gl_pathv[i]);
1.44 djm 858: if (do_rm(conn, g.gl_pathv[i]) == -1)
1.27 djm 859: err = -1;
860: }
1.1 djm 861: break;
862: case I_MKDIR:
863: path1 = make_absolute(path1, *pwd);
864: attrib_clear(&a);
865: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
866: a.perm = 0777;
1.44 djm 867: err = do_mkdir(conn, path1, &a);
1.1 djm 868: break;
869: case I_RMDIR:
870: path1 = make_absolute(path1, *pwd);
1.44 djm 871: err = do_rmdir(conn, path1);
1.1 djm 872: break;
873: case I_CHDIR:
874: path1 = make_absolute(path1, *pwd);
1.44 djm 875: if ((tmp = do_realpath(conn, path1)) == NULL) {
1.25 deraadt 876: err = 1;
1.11 markus 877: break;
1.25 deraadt 878: }
1.44 djm 879: if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1.11 markus 880: xfree(tmp);
1.25 deraadt 881: err = 1;
1.11 markus 882: break;
883: }
1.9 djm 884: if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
885: error("Can't change directory: Can't check target");
886: xfree(tmp);
1.25 deraadt 887: err = 1;
1.9 djm 888: break;
889: }
890: if (!S_ISDIR(aa->perm)) {
891: error("Can't change directory: \"%s\" is not "
892: "a directory", tmp);
893: xfree(tmp);
1.25 deraadt 894: err = 1;
1.9 djm 895: break;
896: }
1.11 markus 897: xfree(*pwd);
898: *pwd = tmp;
1.1 djm 899: break;
900: case I_LS:
1.12 djm 901: if (!path1) {
1.48 djm 902: do_globbed_ls(conn, *pwd, *pwd, lflag);
1.12 djm 903: break;
904: }
1.48 djm 905:
906: /* Strip pwd off beginning of non-absolute paths */
907: tmp = NULL;
908: if (*path1 != '/')
909: tmp = *pwd;
910:
1.1 djm 911: path1 = make_absolute(path1, *pwd);
1.48 djm 912:
913: do_globbed_ls(conn, path1, tmp, lflag);
1.1 djm 914: break;
915: case I_LCHDIR:
1.25 deraadt 916: if (chdir(path1) == -1) {
1.1 djm 917: error("Couldn't change local directory to "
918: "\"%s\": %s", path1, strerror(errno));
1.25 deraadt 919: err = 1;
920: }
1.1 djm 921: break;
922: case I_LMKDIR:
1.25 deraadt 923: if (mkdir(path1, 0777) == -1) {
1.17 stevesk 924: error("Couldn't create local directory "
1.1 djm 925: "\"%s\": %s", path1, strerror(errno));
1.25 deraadt 926: err = 1;
927: }
1.1 djm 928: break;
929: case I_LLS:
930: local_do_ls(cmd);
931: break;
932: case I_SHELL:
933: local_do_shell(cmd);
934: break;
935: case I_LUMASK:
936: umask(n_arg);
1.21 stevesk 937: printf("Local umask: %03lo\n", n_arg);
1.1 djm 938: break;
939: case I_CHMOD:
940: path1 = make_absolute(path1, *pwd);
941: attrib_clear(&a);
942: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
943: a.perm = n_arg;
1.44 djm 944: remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1.41 deraadt 945: for (i = 0; g.gl_pathv[i]; i++) {
1.27 djm 946: printf("Changing mode on %s\n", g.gl_pathv[i]);
1.44 djm 947: do_setstat(conn, g.gl_pathv[i], &a);
1.27 djm 948: }
1.5 stevesk 949: break;
1.1 djm 950: case I_CHOWN:
951: path1 = make_absolute(path1, *pwd);
1.44 djm 952: remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1.41 deraadt 953: for (i = 0; g.gl_pathv[i]; i++) {
1.44 djm 954: if (!(aa = do_stat(conn, g.gl_pathv[i], 0)))
1.27 djm 955: continue;
956: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
957: error("Can't get current ownership of "
958: "remote file \"%s\"", g.gl_pathv[i]);
959: continue;
960: }
961: printf("Changing owner on %s\n", g.gl_pathv[i]);
962: aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
963: aa->uid = n_arg;
1.44 djm 964: do_setstat(conn, g.gl_pathv[i], aa);
1.1 djm 965: }
966: break;
967: case I_CHGRP:
968: path1 = make_absolute(path1, *pwd);
1.44 djm 969: remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1.41 deraadt 970: for (i = 0; g.gl_pathv[i]; i++) {
1.44 djm 971: if (!(aa = do_stat(conn, g.gl_pathv[i], 0)))
1.27 djm 972: continue;
973: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
974: error("Can't get current ownership of "
975: "remote file \"%s\"", g.gl_pathv[i]);
976: continue;
977: }
978: printf("Changing group on %s\n", g.gl_pathv[i]);
979: aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
980: aa->gid = n_arg;
1.44 djm 981: do_setstat(conn, g.gl_pathv[i], aa);
1.1 djm 982: }
983: break;
984: case I_PWD:
985: printf("Remote working directory: %s\n", *pwd);
986: break;
987: case I_LPWD:
988: if (!getcwd(path_buf, sizeof(path_buf)))
1.24 millert 989: error("Couldn't get local cwd: %s",
1.1 djm 990: strerror(errno));
991: else
992: printf("Local working directory: %s\n",
993: path_buf);
994: break;
995: case I_QUIT:
996: return(-1);
997: case I_HELP:
998: help();
1.28 markus 999: break;
1000: case I_VERSION:
1.47 deraadt 1001: printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1.1 djm 1002: break;
1003: default:
1004: fatal("%d is not implemented", cmdnum);
1005: }
1006:
1.29 djm 1007: if (g.gl_pathc)
1008: globfree(&g);
1.1 djm 1009: if (path1)
1010: xfree(path1);
1011: if (path2)
1012: xfree(path2);
1.25 deraadt 1013:
1014: /* If an error occurs in batch mode we should abort. */
1015: if (infile != stdin && err > 0)
1016: return -1;
1017:
1.1 djm 1018: return(0);
1019: }
1020:
1021: void
1.35 mouring 1022: interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
1.1 djm 1023: {
1024: char *pwd;
1.35 mouring 1025: char *dir = NULL;
1.1 djm 1026: char cmd[2048];
1.44 djm 1027: struct sftp_conn *conn;
1.26 djm 1028:
1.44 djm 1029: conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
1030: if (conn == NULL)
1.26 djm 1031: fatal("Couldn't initialise connection to server");
1.1 djm 1032:
1.44 djm 1033: pwd = do_realpath(conn, ".");
1.1 djm 1034: if (pwd == NULL)
1035: fatal("Need cwd");
1036:
1.35 mouring 1037: if (file1 != NULL) {
1038: dir = xstrdup(file1);
1039: dir = make_absolute(dir, pwd);
1040:
1.44 djm 1041: if (remote_is_dir(conn, dir) && file2 == NULL) {
1.35 mouring 1042: printf("Changing to: %s\n", dir);
1043: snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
1.44 djm 1044: parse_dispatch_command(conn, cmd, &pwd);
1.35 mouring 1045: } else {
1046: if (file2 == NULL)
1047: snprintf(cmd, sizeof cmd, "get %s", dir);
1048: else
1049: snprintf(cmd, sizeof cmd, "get %s %s", dir,
1050: file2);
1051:
1.44 djm 1052: parse_dispatch_command(conn, cmd, &pwd);
1.45 mpech 1053: xfree(dir);
1.35 mouring 1054: return;
1055: }
1.45 mpech 1056: xfree(dir);
1.35 mouring 1057: }
1.14 stevesk 1058: setvbuf(stdout, NULL, _IOLBF, 0);
1.25 deraadt 1059: setvbuf(infile, NULL, _IOLBF, 0);
1.1 djm 1060:
1.41 deraadt 1061: for (;;) {
1.1 djm 1062: char *cp;
1063:
1064: printf("sftp> ");
1065:
1066: /* XXX: use libedit */
1.25 deraadt 1067: if (fgets(cmd, sizeof(cmd), infile) == NULL) {
1.1 djm 1068: printf("\n");
1069: break;
1.25 deraadt 1070: } else if (infile != stdin) /* Bluff typing */
1071: printf("%s", cmd);
1072:
1.1 djm 1073: cp = strrchr(cmd, '\n');
1074: if (cp)
1075: *cp = '\0';
1.25 deraadt 1076:
1.44 djm 1077: if (parse_dispatch_command(conn, cmd, &pwd))
1.1 djm 1078: break;
1079: }
1080: xfree(pwd);
1081: }