Annotation of src/usr.bin/ssh/sftp-int.c, Revision 1.21
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.21 ! stevesk 31: RCSID("$OpenBSD: sftp-int.c,v 1.20 2001/02/10 00:45:26 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.1 djm 207: case 'P':
208: *pflag = 1;
209: break;
210: default:
211: error("Invalid flag -%c", *cp);
212: return(-1);
213: }
214: cp += 2;
215: *cpp = cp + strspn(cp, WHITESPACE);
216: }
217:
218: return(0);
219: }
220:
221: int
222: get_pathname(const char **cpp, char **path)
223: {
1.8 provos 224: const char *cp = *cpp, *end;
225: char quot;
1.1 djm 226: int i;
227:
228: cp += strspn(cp, WHITESPACE);
229: if (!*cp) {
230: *cpp = cp;
231: *path = NULL;
1.8 provos 232: return (0);
1.1 djm 233: }
234:
235: /* Check for quoted filenames */
236: if (*cp == '\"' || *cp == '\'') {
1.8 provos 237: quot = *cp++;
238:
239: end = strchr(cp, quot);
240: if (end == NULL) {
1.1 djm 241: error("Unterminated quote");
1.8 provos 242: goto fail;
1.1 djm 243: }
1.8 provos 244: if (cp == end) {
1.1 djm 245: error("Empty quotes");
1.8 provos 246: goto fail;
1.1 djm 247: }
1.8 provos 248: *cpp = end + 1 + strspn(end + 1, WHITESPACE);
249: } else {
250: /* Read to end of filename */
251: end = strpbrk(cp, WHITESPACE);
252: if (end == NULL)
253: end = strchr(cp, '\0');
254: *cpp = end + strspn(end, WHITESPACE);
1.1 djm 255: }
256:
1.8 provos 257: i = end - cp;
1.1 djm 258:
259: *path = xmalloc(i + 1);
260: memcpy(*path, cp, i);
261: (*path)[i] = '\0';
262: return(0);
1.8 provos 263:
264: fail:
265: *path = NULL;
266: return (-1);
1.1 djm 267: }
268:
269: int
270: infer_path(const char *p, char **ifp)
271: {
272: char *cp;
273:
274: debug("XXX: P = \"%s\"", p);
275:
276: cp = strrchr(p, '/');
277: if (cp == NULL) {
278: *ifp = xstrdup(p);
279: return(0);
280: }
281:
282: if (!cp[1]) {
283: error("Invalid path");
284: return(-1);
285: }
286:
287: *ifp = xstrdup(cp + 1);
288: return(0);
289: }
290:
291: int
292: parse_args(const char **cpp, int *pflag, unsigned long *n_arg,
293: char **path1, char **path2)
294: {
295: const char *cmd, *cp = *cpp;
1.21 ! stevesk 296: char *cp2;
1.4 markus 297: int base = 0;
1.21 ! stevesk 298: long l;
1.1 djm 299: int i, cmdnum;
300:
301: /* Skip leading whitespace */
302: cp = cp + strspn(cp, WHITESPACE);
303:
304: /* Ignore blank lines */
305: if (!*cp)
306: return(-1);
307:
308: /* Figure out which command we have */
309: for(i = 0; cmds[i].c; i++) {
310: int cmdlen = strlen(cmds[i].c);
311:
312: /* Check for command followed by whitespace */
313: if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
314: strchr(WHITESPACE, cp[cmdlen])) {
315: cp += cmdlen;
316: cp = cp + strspn(cp, WHITESPACE);
317: break;
318: }
319: }
320: cmdnum = cmds[i].n;
321: cmd = cmds[i].c;
322:
323: /* Special case */
324: if (*cp == '!') {
325: cp++;
326: cmdnum = I_SHELL;
327: } else if (cmdnum == -1) {
328: error("Invalid command.");
329: return(-1);
330: }
331:
332: /* Get arguments and parse flags */
333: *pflag = *n_arg = 0;
334: *path1 = *path2 = NULL;
335: switch (cmdnum) {
336: case I_GET:
337: case I_PUT:
338: if (parse_getput_flags(&cp, pflag))
339: return(-1);
340: /* Get first pathname (mandatory) */
341: if (get_pathname(&cp, path1))
342: return(-1);
343: if (*path1 == NULL) {
344: error("You must specify at least one path after a "
345: "%s command.", cmd);
346: return(-1);
347: }
348: /* Try to get second pathname (optional) */
349: if (get_pathname(&cp, path2))
350: return(-1);
351: /* Otherwise try to guess it from first path */
352: if (*path2 == NULL && infer_path(*path1, path2))
353: return(-1);
354: break;
355: case I_RENAME:
356: /* Get first pathname (mandatory) */
357: if (get_pathname(&cp, path1))
358: return(-1);
359: if (get_pathname(&cp, path2))
360: return(-1);
361: if (!*path1 || !*path2) {
362: error("You must specify two paths after a %s "
363: "command.", cmd);
364: return(-1);
365: }
366: break;
367: case I_RM:
368: case I_MKDIR:
369: case I_RMDIR:
370: case I_CHDIR:
371: case I_LCHDIR:
372: case I_LMKDIR:
373: /* Get pathname (mandatory) */
374: if (get_pathname(&cp, path1))
375: return(-1);
376: if (*path1 == NULL) {
1.3 stevesk 377: error("You must specify a path after a %s command.",
1.1 djm 378: cmd);
379: return(-1);
380: }
381: break;
382: case I_LS:
383: /* Path is optional */
384: if (get_pathname(&cp, path1))
385: return(-1);
386: break;
387: case I_LLS:
388: case I_SHELL:
389: /* Uses the rest of the line */
390: break;
391: case I_LUMASK:
1.21 ! stevesk 392: base = 8;
1.1 djm 393: case I_CHMOD:
1.4 markus 394: base = 8;
1.1 djm 395: case I_CHOWN:
396: case I_CHGRP:
397: /* Get numeric arg (mandatory) */
1.21 ! stevesk 398: l = strtol(cp, &cp2, base);
! 399: if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) &&
! 400: errno == ERANGE) || l < 0) {
1.1 djm 401: error("You must supply a numeric argument "
402: "to the %s command.", cmd);
403: return(-1);
404: }
1.21 ! stevesk 405: cp = cp2;
! 406: *n_arg = l;
! 407: if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp))
! 408: break;
! 409: if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
1.1 djm 410: error("You must supply a numeric argument "
411: "to the %s command.", cmd);
412: return(-1);
413: }
414: cp += strspn(cp, WHITESPACE);
415:
416: /* Get pathname (mandatory) */
417: if (get_pathname(&cp, path1))
418: return(-1);
419: if (*path1 == NULL) {
1.3 stevesk 420: error("You must specify a path after a %s command.",
1.1 djm 421: cmd);
422: return(-1);
423: }
424: break;
425: case I_QUIT:
426: case I_PWD:
427: case I_LPWD:
428: case I_HELP:
429: break;
430: default:
431: fatal("Command not implemented");
432: }
433:
434: *cpp = cp;
435: return(cmdnum);
436: }
437:
438: int
439: parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
440: {
1.8 provos 441: char *path1, *path2, *tmp;
1.1 djm 442: int pflag, cmdnum;
443: unsigned long n_arg;
444: Attrib a, *aa;
445: char path_buf[PATH_MAX];
446:
447: path1 = path2 = NULL;
448: cmdnum = parse_args(&cmd, &pflag, &n_arg, &path1, &path2);
449:
450: /* Perform command */
451: switch (cmdnum) {
452: case -1:
453: break;
454: case I_GET:
455: path1 = make_absolute(path1, *pwd);
456: do_download(in, out, path1, path2, pflag);
457: break;
458: case I_PUT:
459: path2 = make_absolute(path2, *pwd);
460: do_upload(in, out, path1, path2, pflag);
461: break;
462: case I_RENAME:
463: path1 = make_absolute(path1, *pwd);
464: path2 = make_absolute(path2, *pwd);
465: do_rename(in, out, path1, path2);
466: break;
467: case I_RM:
468: path1 = make_absolute(path1, *pwd);
469: do_rm(in, out, path1);
470: break;
471: case I_MKDIR:
472: path1 = make_absolute(path1, *pwd);
473: attrib_clear(&a);
474: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
475: a.perm = 0777;
476: do_mkdir(in, out, path1, &a);
477: break;
478: case I_RMDIR:
479: path1 = make_absolute(path1, *pwd);
480: do_rmdir(in, out, path1);
481: break;
482: case I_CHDIR:
483: path1 = make_absolute(path1, *pwd);
1.11 markus 484: if ((tmp = do_realpath(in, out, path1)) == NULL)
485: break;
486: if ((aa = do_stat(in, out, tmp)) == NULL) {
487: xfree(tmp);
488: break;
489: }
1.9 djm 490: if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
491: error("Can't change directory: Can't check target");
492: xfree(tmp);
493: break;
494: }
495: if (!S_ISDIR(aa->perm)) {
496: error("Can't change directory: \"%s\" is not "
497: "a directory", tmp);
498: xfree(tmp);
499: break;
500: }
1.11 markus 501: xfree(*pwd);
502: *pwd = tmp;
1.1 djm 503: break;
504: case I_LS:
1.12 djm 505: if (!path1) {
506: do_ls(in, out, *pwd);
507: break;
508: }
1.1 djm 509: path1 = make_absolute(path1, *pwd);
1.12 djm 510: if ((tmp = do_realpath(in, out, path1)) == NULL)
511: break;
512: xfree(path1);
513: path1 = tmp;
514: if ((aa = do_stat(in, out, path1)) == NULL)
515: break;
516: if ((aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
517: !S_ISDIR(aa->perm)) {
518: error("Can't ls: \"%s\" is not a directory", path1);
519: break;
520: }
521: do_ls(in, out, path1);
1.1 djm 522: break;
523: case I_LCHDIR:
524: if (chdir(path1) == -1)
525: error("Couldn't change local directory to "
526: "\"%s\": %s", path1, strerror(errno));
527: break;
528: case I_LMKDIR:
529: if (mkdir(path1, 0777) == -1)
1.17 stevesk 530: error("Couldn't create local directory "
1.1 djm 531: "\"%s\": %s", path1, strerror(errno));
532: break;
533: case I_LLS:
534: local_do_ls(cmd);
535: break;
536: case I_SHELL:
537: local_do_shell(cmd);
538: break;
539: case I_LUMASK:
540: umask(n_arg);
1.21 ! stevesk 541: printf("Local umask: %03lo\n", n_arg);
1.1 djm 542: break;
543: case I_CHMOD:
544: path1 = make_absolute(path1, *pwd);
545: attrib_clear(&a);
546: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
547: a.perm = n_arg;
548: do_setstat(in, out, path1, &a);
1.5 stevesk 549: break;
1.1 djm 550: case I_CHOWN:
551: path1 = make_absolute(path1, *pwd);
1.19 djm 552: if (!(aa = do_stat(in, out, path1)))
553: break;
1.5 stevesk 554: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1.1 djm 555: error("Can't get current ownership of "
556: "remote file \"%s\"", path1);
557: break;
558: }
1.19 djm 559: aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1.1 djm 560: aa->uid = n_arg;
561: do_setstat(in, out, path1, aa);
562: break;
563: case I_CHGRP:
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->gid = n_arg;
574: do_setstat(in, out, path1, aa);
575: break;
576: case I_PWD:
577: printf("Remote working directory: %s\n", *pwd);
578: break;
579: case I_LPWD:
580: if (!getcwd(path_buf, sizeof(path_buf)))
581: error("Couldn't get local cwd: %s\n",
582: strerror(errno));
583: else
584: printf("Local working directory: %s\n",
585: path_buf);
586: break;
587: case I_QUIT:
588: return(-1);
589: case I_HELP:
590: help();
591: break;
592: default:
593: fatal("%d is not implemented", cmdnum);
594: }
595:
596: if (path1)
597: xfree(path1);
598: if (path2)
599: xfree(path2);
600: return(0);
601: }
602:
603: void
604: interactive_loop(int fd_in, int fd_out)
605: {
606: char *pwd;
607: char cmd[2048];
608:
609: pwd = do_realpath(fd_in, fd_out, ".");
610: if (pwd == NULL)
611: fatal("Need cwd");
612:
1.14 stevesk 613: setvbuf(stdout, NULL, _IOLBF, 0);
614: setvbuf(stdin, NULL, _IOLBF, 0);
1.1 djm 615:
616: for(;;) {
617: char *cp;
618:
619: printf("sftp> ");
620:
621: /* XXX: use libedit */
622: if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
623: printf("\n");
624: break;
625: }
626: cp = strrchr(cmd, '\n');
627: if (cp)
628: *cp = '\0';
629: if (parse_dispatch_command(fd_in, fd_out, cmd, &pwd))
630: break;
631: }
632: xfree(pwd);
633: }