Annotation of src/usr.bin/ssh/sftp-int.c, Revision 1.25
1.1 djm 1: /*
2: * Copyright (c) 2001 Damien Miller. All rights reserved.
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: finish implementation of all commands */
26: /* XXX: do fnmatch() instead of using raw pathname */
1.12 djm 27: /* XXX: globbed ls */
1.1 djm 28: /* XXX: recursive operations */
29:
30: #include "includes.h"
1.25 ! deraadt 31: RCSID("$OpenBSD: sftp-int.c,v 1.24 2001/03/04 17:42:28 millert Exp $");
1.1 djm 32:
33: #include "buffer.h"
34: #include "xmalloc.h"
35: #include "log.h"
36: #include "pathnames.h"
37:
38: #include "sftp.h"
39: #include "sftp-common.h"
40: #include "sftp-client.h"
41: #include "sftp-int.h"
42:
1.25 ! deraadt 43: extern FILE* infile;
! 44:
1.1 djm 45: /* Seperators for interactive commands */
46: #define WHITESPACE " \t\r\n"
47:
48: /* Commands for interactive mode */
49: #define I_CHDIR 1
50: #define I_CHGRP 2
51: #define I_CHMOD 3
52: #define I_CHOWN 4
53: #define I_GET 5
54: #define I_HELP 6
55: #define I_LCHDIR 7
56: #define I_LLS 8
57: #define I_LMKDIR 9
58: #define I_LPWD 10
59: #define I_LS 11
60: #define I_LUMASK 12
61: #define I_MKDIR 13
62: #define I_PUT 14
63: #define I_PWD 15
64: #define I_QUIT 16
65: #define I_RENAME 17
66: #define I_RM 18
67: #define I_RMDIR 19
68: #define I_SHELL 20
69:
70: struct CMD {
1.6 deraadt 71: const char *c;
1.1 djm 72: const int n;
73: };
74:
75: const struct CMD cmds[] = {
1.15 stevesk 76: { "cd", I_CHDIR },
77: { "chdir", I_CHDIR },
78: { "chgrp", I_CHGRP },
79: { "chmod", I_CHMOD },
80: { "chown", I_CHOWN },
81: { "dir", I_LS },
82: { "exit", I_QUIT },
83: { "get", I_GET },
84: { "help", I_HELP },
85: { "lcd", I_LCHDIR },
86: { "lchdir", I_LCHDIR },
87: { "lls", I_LLS },
88: { "lmkdir", I_LMKDIR },
89: { "lpwd", I_LPWD },
90: { "ls", I_LS },
91: { "lumask", I_LUMASK },
92: { "mkdir", I_MKDIR },
93: { "put", I_PUT },
94: { "pwd", I_PWD },
95: { "quit", I_QUIT },
96: { "rename", I_RENAME },
97: { "rm", I_RM },
98: { "rmdir", I_RMDIR },
1.6 deraadt 99: { "!", I_SHELL },
1.7 deraadt 100: { "?", I_HELP },
1.6 deraadt 101: { NULL, -1}
1.1 djm 102: };
103:
104: void
105: help(void)
106: {
107: printf("Available commands:\n");
1.13 stevesk 108: printf("cd path Change remote directory to 'path'\n");
109: printf("lcd path Change local directory to 'path'\n");
110: printf("chgrp grp path Change group of file 'path' to 'grp'\n");
111: printf("chmod mode path Change permissions of file 'path' to 'mode'\n");
112: printf("chown own path Change owner of file 'path' to 'own'\n");
113: printf("help Display this help text\n");
114: printf("get remote-path [local-path] Download file\n");
115: printf("lls [ls-options [path]] Display local directory listing\n");
116: printf("lmkdir path Create local directory\n");
117: printf("lpwd Print local working directory\n");
118: printf("ls [path] Display remote directory listing\n");
119: printf("lumask umask Set local umask to 'umask'\n");
120: printf("mkdir path Create remote directory\n");
121: printf("put local-path [remote-path] Upload file\n");
122: printf("pwd Display remote working directory\n");
123: printf("exit Quit sftp\n");
124: printf("quit Quit sftp\n");
125: printf("rename oldpath newpath Rename remote file\n");
126: printf("rmdir path Remove remote directory\n");
127: printf("rm path Delete remote file\n");
1.1 djm 128: printf("!command Execute 'command' in local shell\n");
129: printf("! Escape to local shell\n");
1.13 stevesk 130: printf("? Synonym for help\n");
1.1 djm 131: }
132:
133: void
134: local_do_shell(const char *args)
135: {
136: int ret, status;
137: char *shell;
138: pid_t pid;
1.3 stevesk 139:
1.1 djm 140: if (!*args)
141: args = NULL;
1.3 stevesk 142:
1.1 djm 143: if ((shell = getenv("SHELL")) == NULL)
144: shell = _PATH_BSHELL;
145:
146: if ((pid = fork()) == -1)
147: fatal("Couldn't fork: %s", strerror(errno));
148:
149: if (pid == 0) {
150: /* XXX: child has pipe fds to ssh subproc open - issue? */
151: if (args) {
152: debug3("Executing %s -c \"%s\"", shell, args);
153: ret = execl(shell, shell, "-c", args, NULL);
154: } else {
155: debug3("Executing %s", shell);
156: ret = execl(shell, shell, NULL);
157: }
1.3 stevesk 158: fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
1.1 djm 159: strerror(errno));
160: _exit(1);
161: }
162: if (waitpid(pid, &status, 0) == -1)
163: fatal("Couldn't wait for child: %s", strerror(errno));
164: if (!WIFEXITED(status))
165: error("Shell exited abormally");
166: else if (WEXITSTATUS(status))
167: error("Shell exited with status %d", WEXITSTATUS(status));
168: }
169:
1.3 stevesk 170: void
1.1 djm 171: local_do_ls(const char *args)
172: {
173: if (!args || !*args)
1.18 stevesk 174: local_do_shell(_PATH_LS);
1.1 djm 175: else {
1.18 stevesk 176: int len = strlen(_PATH_LS " ") + strlen(args) + 1;
1.16 deraadt 177: char *buf = xmalloc(len);
1.1 djm 178:
179: /* XXX: quoting - rip quoting code from ftp? */
1.18 stevesk 180: snprintf(buf, len, _PATH_LS " %s", args);
1.1 djm 181: local_do_shell(buf);
1.16 deraadt 182: xfree(buf);
1.1 djm 183: }
184: }
185:
186: char *
187: make_absolute(char *p, char *pwd)
188: {
189: char buf[2048];
190:
191: /* Derelativise */
192: if (p && p[0] != '/') {
193: snprintf(buf, sizeof(buf), "%s/%s", pwd, p);
194: xfree(p);
195: p = xstrdup(buf);
196: }
197:
198: return(p);
199: }
200:
201: int
202: parse_getput_flags(const char **cpp, int *pflag)
203: {
204: const char *cp = *cpp;
205:
206: /* Check for flags */
207: if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
1.20 djm 208: switch (cp[1]) {
1.22 djm 209: case 'p':
1.1 djm 210: case 'P':
211: *pflag = 1;
212: break;
213: default:
1.22 djm 214: error("Invalid flag -%c", cp[1]);
1.1 djm 215: return(-1);
216: }
217: cp += 2;
218: *cpp = cp + strspn(cp, WHITESPACE);
219: }
220:
221: return(0);
222: }
223:
224: int
225: get_pathname(const char **cpp, char **path)
226: {
1.8 provos 227: const char *cp = *cpp, *end;
228: char quot;
1.1 djm 229: int i;
230:
231: cp += strspn(cp, WHITESPACE);
232: if (!*cp) {
233: *cpp = cp;
234: *path = NULL;
1.8 provos 235: return (0);
1.1 djm 236: }
237:
238: /* Check for quoted filenames */
239: if (*cp == '\"' || *cp == '\'') {
1.8 provos 240: quot = *cp++;
241:
242: end = strchr(cp, quot);
243: if (end == NULL) {
1.1 djm 244: error("Unterminated quote");
1.8 provos 245: goto fail;
1.1 djm 246: }
1.8 provos 247: if (cp == end) {
1.1 djm 248: error("Empty quotes");
1.8 provos 249: goto fail;
1.1 djm 250: }
1.8 provos 251: *cpp = end + 1 + strspn(end + 1, WHITESPACE);
252: } else {
253: /* Read to end of filename */
254: end = strpbrk(cp, WHITESPACE);
255: if (end == NULL)
256: end = strchr(cp, '\0');
257: *cpp = end + strspn(end, WHITESPACE);
1.1 djm 258: }
259:
1.8 provos 260: i = end - cp;
1.1 djm 261:
262: *path = xmalloc(i + 1);
263: memcpy(*path, cp, i);
264: (*path)[i] = '\0';
265: return(0);
1.8 provos 266:
267: fail:
268: *path = NULL;
269: return (-1);
1.1 djm 270: }
271:
272: int
273: infer_path(const char *p, char **ifp)
274: {
275: char *cp;
276:
277: debug("XXX: P = \"%s\"", p);
278:
279: cp = strrchr(p, '/');
280: if (cp == NULL) {
281: *ifp = xstrdup(p);
282: return(0);
283: }
284:
285: if (!cp[1]) {
286: error("Invalid path");
287: return(-1);
288: }
289:
290: *ifp = xstrdup(cp + 1);
291: return(0);
292: }
293:
294: int
295: parse_args(const char **cpp, int *pflag, unsigned long *n_arg,
296: char **path1, char **path2)
297: {
298: const char *cmd, *cp = *cpp;
1.21 stevesk 299: char *cp2;
1.4 markus 300: int base = 0;
1.21 stevesk 301: long l;
1.1 djm 302: int i, cmdnum;
303:
304: /* Skip leading whitespace */
305: cp = cp + strspn(cp, WHITESPACE);
306:
307: /* Ignore blank lines */
308: if (!*cp)
309: return(-1);
310:
311: /* Figure out which command we have */
312: for(i = 0; cmds[i].c; i++) {
313: int cmdlen = strlen(cmds[i].c);
314:
315: /* Check for command followed by whitespace */
316: if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
317: strchr(WHITESPACE, cp[cmdlen])) {
318: cp += cmdlen;
319: cp = cp + strspn(cp, WHITESPACE);
320: break;
321: }
322: }
323: cmdnum = cmds[i].n;
324: cmd = cmds[i].c;
325:
326: /* Special case */
327: if (*cp == '!') {
328: cp++;
329: cmdnum = I_SHELL;
330: } else if (cmdnum == -1) {
331: error("Invalid command.");
332: return(-1);
333: }
334:
335: /* Get arguments and parse flags */
336: *pflag = *n_arg = 0;
337: *path1 = *path2 = NULL;
338: switch (cmdnum) {
339: case I_GET:
340: case I_PUT:
341: if (parse_getput_flags(&cp, pflag))
342: return(-1);
343: /* Get first pathname (mandatory) */
344: if (get_pathname(&cp, path1))
345: return(-1);
346: if (*path1 == NULL) {
347: error("You must specify at least one path after a "
348: "%s command.", cmd);
349: return(-1);
350: }
351: /* Try to get second pathname (optional) */
352: if (get_pathname(&cp, path2))
353: return(-1);
354: /* Otherwise try to guess it from first path */
355: if (*path2 == NULL && infer_path(*path1, path2))
356: return(-1);
357: break;
358: case I_RENAME:
359: /* Get first pathname (mandatory) */
360: if (get_pathname(&cp, path1))
361: return(-1);
362: if (get_pathname(&cp, path2))
363: return(-1);
364: if (!*path1 || !*path2) {
365: error("You must specify two paths after a %s "
366: "command.", cmd);
367: return(-1);
368: }
369: break;
370: case I_RM:
371: case I_MKDIR:
372: case I_RMDIR:
373: case I_CHDIR:
374: case I_LCHDIR:
375: case I_LMKDIR:
376: /* Get pathname (mandatory) */
377: if (get_pathname(&cp, path1))
378: return(-1);
379: if (*path1 == NULL) {
1.3 stevesk 380: error("You must specify a path after a %s command.",
1.1 djm 381: cmd);
382: return(-1);
383: }
384: break;
385: case I_LS:
386: /* Path is optional */
387: if (get_pathname(&cp, path1))
388: return(-1);
389: break;
390: case I_LLS:
391: case I_SHELL:
392: /* Uses the rest of the line */
393: break;
394: case I_LUMASK:
1.21 stevesk 395: base = 8;
1.1 djm 396: case I_CHMOD:
1.4 markus 397: base = 8;
1.1 djm 398: case I_CHOWN:
399: case I_CHGRP:
400: /* Get numeric arg (mandatory) */
1.21 stevesk 401: l = strtol(cp, &cp2, base);
402: if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) &&
403: errno == ERANGE) || l < 0) {
1.1 djm 404: error("You must supply a numeric argument "
405: "to the %s command.", cmd);
406: return(-1);
407: }
1.21 stevesk 408: cp = cp2;
409: *n_arg = l;
410: if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp))
411: break;
412: if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
1.1 djm 413: error("You must supply a numeric argument "
414: "to the %s command.", cmd);
415: return(-1);
416: }
417: cp += strspn(cp, WHITESPACE);
418:
419: /* Get pathname (mandatory) */
420: if (get_pathname(&cp, path1))
421: return(-1);
422: if (*path1 == NULL) {
1.3 stevesk 423: error("You must specify a path after a %s command.",
1.1 djm 424: cmd);
425: return(-1);
426: }
427: break;
428: case I_QUIT:
429: case I_PWD:
430: case I_LPWD:
431: case I_HELP:
432: break;
433: default:
434: fatal("Command not implemented");
435: }
436:
437: *cpp = cp;
438: return(cmdnum);
439: }
440:
441: int
442: parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
443: {
1.8 provos 444: char *path1, *path2, *tmp;
1.1 djm 445: int pflag, cmdnum;
446: unsigned long n_arg;
447: Attrib a, *aa;
1.23 deraadt 448: char path_buf[MAXPATHLEN];
1.25 ! deraadt 449: int err = 0;
1.1 djm 450:
451: path1 = path2 = NULL;
452: cmdnum = parse_args(&cmd, &pflag, &n_arg, &path1, &path2);
453:
454: /* Perform command */
455: switch (cmdnum) {
456: case -1:
457: break;
458: case I_GET:
459: path1 = make_absolute(path1, *pwd);
1.25 ! deraadt 460: err = do_download(in, out, path1, path2, pflag);
1.1 djm 461: break;
462: case I_PUT:
463: path2 = make_absolute(path2, *pwd);
1.25 ! deraadt 464: err = do_upload(in, out, path1, path2, pflag);
1.1 djm 465: break;
466: case I_RENAME:
467: path1 = make_absolute(path1, *pwd);
468: path2 = make_absolute(path2, *pwd);
1.25 ! deraadt 469: err = do_rename(in, out, path1, path2);
1.1 djm 470: break;
471: case I_RM:
472: path1 = make_absolute(path1, *pwd);
1.25 ! deraadt 473: err = do_rm(in, out, path1);
1.1 djm 474: break;
475: case I_MKDIR:
476: path1 = make_absolute(path1, *pwd);
477: attrib_clear(&a);
478: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
479: a.perm = 0777;
1.25 ! deraadt 480: err = do_mkdir(in, out, path1, &a);
1.1 djm 481: break;
482: case I_RMDIR:
483: path1 = make_absolute(path1, *pwd);
1.25 ! deraadt 484: err = do_rmdir(in, out, path1);
1.1 djm 485: break;
486: case I_CHDIR:
487: path1 = make_absolute(path1, *pwd);
1.25 ! deraadt 488: if ((tmp = do_realpath(in, out, path1)) == NULL) {
! 489: err = 1;
1.11 markus 490: break;
1.25 ! deraadt 491: }
1.11 markus 492: if ((aa = do_stat(in, out, tmp)) == NULL) {
493: xfree(tmp);
1.25 ! deraadt 494: err = 1;
1.11 markus 495: break;
496: }
1.9 djm 497: if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
498: error("Can't change directory: Can't check target");
499: xfree(tmp);
1.25 ! deraadt 500: err = 1;
1.9 djm 501: break;
502: }
503: if (!S_ISDIR(aa->perm)) {
504: error("Can't change directory: \"%s\" is not "
505: "a directory", tmp);
506: xfree(tmp);
1.25 ! deraadt 507: err = 1;
1.9 djm 508: break;
509: }
1.11 markus 510: xfree(*pwd);
511: *pwd = tmp;
1.1 djm 512: break;
513: case I_LS:
1.12 djm 514: if (!path1) {
515: do_ls(in, out, *pwd);
516: break;
517: }
1.1 djm 518: path1 = make_absolute(path1, *pwd);
1.12 djm 519: if ((tmp = do_realpath(in, out, path1)) == NULL)
520: break;
521: xfree(path1);
522: path1 = tmp;
523: if ((aa = do_stat(in, out, path1)) == NULL)
524: break;
525: if ((aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
526: !S_ISDIR(aa->perm)) {
527: error("Can't ls: \"%s\" is not a directory", path1);
528: break;
529: }
530: do_ls(in, out, path1);
1.1 djm 531: break;
532: case I_LCHDIR:
1.25 ! deraadt 533: if (chdir(path1) == -1) {
1.1 djm 534: error("Couldn't change local directory to "
535: "\"%s\": %s", path1, strerror(errno));
1.25 ! deraadt 536: err = 1;
! 537: }
1.1 djm 538: break;
539: case I_LMKDIR:
1.25 ! deraadt 540: if (mkdir(path1, 0777) == -1) {
1.17 stevesk 541: error("Couldn't create local directory "
1.1 djm 542: "\"%s\": %s", path1, strerror(errno));
1.25 ! deraadt 543: err = 1;
! 544: }
1.1 djm 545: break;
546: case I_LLS:
547: local_do_ls(cmd);
548: break;
549: case I_SHELL:
550: local_do_shell(cmd);
551: break;
552: case I_LUMASK:
553: umask(n_arg);
1.21 stevesk 554: printf("Local umask: %03lo\n", n_arg);
1.1 djm 555: break;
556: case I_CHMOD:
557: path1 = make_absolute(path1, *pwd);
558: attrib_clear(&a);
559: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
560: a.perm = n_arg;
561: do_setstat(in, out, path1, &a);
1.5 stevesk 562: break;
1.1 djm 563: case I_CHOWN:
564: path1 = make_absolute(path1, *pwd);
1.19 djm 565: if (!(aa = do_stat(in, out, path1)))
566: break;
1.5 stevesk 567: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1.1 djm 568: error("Can't get current ownership of "
569: "remote file \"%s\"", path1);
570: break;
571: }
1.19 djm 572: aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1.1 djm 573: aa->uid = n_arg;
574: do_setstat(in, out, path1, aa);
575: break;
576: case I_CHGRP:
577: path1 = make_absolute(path1, *pwd);
1.19 djm 578: if (!(aa = do_stat(in, out, path1)))
579: break;
1.5 stevesk 580: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1.1 djm 581: error("Can't get current ownership of "
582: "remote file \"%s\"", path1);
583: break;
584: }
1.19 djm 585: aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1.1 djm 586: aa->gid = n_arg;
587: do_setstat(in, out, path1, aa);
588: break;
589: case I_PWD:
590: printf("Remote working directory: %s\n", *pwd);
591: break;
592: case I_LPWD:
593: if (!getcwd(path_buf, sizeof(path_buf)))
1.24 millert 594: error("Couldn't get local cwd: %s",
1.1 djm 595: strerror(errno));
596: else
597: printf("Local working directory: %s\n",
598: path_buf);
599: break;
600: case I_QUIT:
601: return(-1);
602: case I_HELP:
603: help();
604: break;
605: default:
606: fatal("%d is not implemented", cmdnum);
607: }
608:
609: if (path1)
610: xfree(path1);
611: if (path2)
612: xfree(path2);
1.25 ! deraadt 613:
! 614: /* If an error occurs in batch mode we should abort. */
! 615: if (infile != stdin && err > 0)
! 616: return -1;
! 617:
1.1 djm 618: return(0);
619: }
620:
621: void
622: interactive_loop(int fd_in, int fd_out)
623: {
624: char *pwd;
625: char cmd[2048];
626:
627: pwd = do_realpath(fd_in, fd_out, ".");
628: if (pwd == NULL)
629: fatal("Need cwd");
630:
1.14 stevesk 631: setvbuf(stdout, NULL, _IOLBF, 0);
1.25 ! deraadt 632: setvbuf(infile, NULL, _IOLBF, 0);
1.1 djm 633:
634: for(;;) {
635: char *cp;
636:
637: printf("sftp> ");
638:
639: /* XXX: use libedit */
1.25 ! deraadt 640: if (fgets(cmd, sizeof(cmd), infile) == NULL) {
1.1 djm 641: printf("\n");
642: break;
1.25 ! deraadt 643: } else if (infile != stdin) /* Bluff typing */
! 644: printf("%s", cmd);
! 645:
1.1 djm 646: cp = strrchr(cmd, '\n');
647: if (cp)
648: *cp = '\0';
1.25 ! deraadt 649:
1.1 djm 650: if (parse_dispatch_command(fd_in, fd_out, cmd, &pwd))
651: break;
652: }
653: xfree(pwd);
654: }