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