Annotation of src/usr.bin/ssh/sftp-int.c, Revision 1.13
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.13 ! stevesk 31: RCSID("$OpenBSD: sftp-int.c,v 1.12 2001/02/07 13:12:29 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.6 deraadt 74: { "CD", I_CHDIR },
75: { "CHDIR", I_CHDIR },
76: { "CHGRP", I_CHGRP },
77: { "CHMOD", I_CHMOD },
78: { "CHOWN", I_CHOWN },
1.10 markus 79: { "DIR", I_LS },
1.6 deraadt 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 },
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)
172: local_do_shell("ls");
173: else {
174: char *buf = xmalloc(8 + strlen(args) + 1);
175:
176: /* XXX: quoting - rip quoting code from ftp? */
177: sprintf(buf, "/bin/ls %s", args);
178: local_do_shell(buf);
179: }
180: }
181:
182: char *
183: make_absolute(char *p, char *pwd)
184: {
185: char buf[2048];
186:
187: /* Derelativise */
188: if (p && p[0] != '/') {
189: snprintf(buf, sizeof(buf), "%s/%s", pwd, p);
190: xfree(p);
191: p = xstrdup(buf);
192: }
193:
194: return(p);
195: }
196:
197: int
198: parse_getput_flags(const char **cpp, int *pflag)
199: {
200: const char *cp = *cpp;
201:
202: /* Check for flags */
203: if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
204: switch (*cp) {
205: case 'P':
206: *pflag = 1;
207: break;
208: default:
209: error("Invalid flag -%c", *cp);
210: return(-1);
211: }
212: cp += 2;
213: *cpp = cp + strspn(cp, WHITESPACE);
214: }
215:
216: return(0);
217: }
218:
219: int
220: get_pathname(const char **cpp, char **path)
221: {
1.8 provos 222: const char *cp = *cpp, *end;
223: char quot;
1.1 djm 224: int i;
225:
226: cp += strspn(cp, WHITESPACE);
227: if (!*cp) {
228: *cpp = cp;
229: *path = NULL;
1.8 provos 230:
231: return (0);
1.1 djm 232: }
233:
234: /* Check for quoted filenames */
235: if (*cp == '\"' || *cp == '\'') {
1.8 provos 236: quot = *cp++;
237:
238: end = strchr(cp, quot);
239: if (end == NULL) {
1.1 djm 240: error("Unterminated quote");
1.8 provos 241: goto fail;
1.1 djm 242: }
1.8 provos 243:
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:
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:
256: *cpp = end + strspn(end, WHITESPACE);
1.1 djm 257: }
258:
1.8 provos 259: i = end - cp;
1.1 djm 260:
261: *path = xmalloc(i + 1);
262: memcpy(*path, cp, i);
263: (*path)[i] = '\0';
264:
265: return(0);
1.8 provos 266:
267: fail:
268: *path = NULL;
269:
270: return (-1);
1.1 djm 271: }
272:
273: int
274: infer_path(const char *p, char **ifp)
275: {
276: char *cp;
277:
278: debug("XXX: P = \"%s\"", p);
279:
280: cp = strrchr(p, '/');
281:
282: if (cp == NULL) {
283: *ifp = xstrdup(p);
284: return(0);
285: }
286:
287: if (!cp[1]) {
288: error("Invalid path");
289: return(-1);
290: }
291:
292: *ifp = xstrdup(cp + 1);
293: return(0);
294: }
295:
296: int
297: parse_args(const char **cpp, int *pflag, unsigned long *n_arg,
298: char **path1, char **path2)
299: {
300: const char *cmd, *cp = *cpp;
1.4 markus 301: int base = 0;
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:
395: case I_CHMOD:
1.4 markus 396: base = 8;
1.1 djm 397: case I_CHOWN:
398: case I_CHGRP:
399: /* Get numeric arg (mandatory) */
400: if (*cp < '0' && *cp > '9') {
401: error("You must supply a numeric argument "
402: "to the %s command.", cmd);
403: return(-1);
404: }
1.4 markus 405: *n_arg = strtoul(cp, (char**)&cp, base);
1.1 djm 406: if (!*cp || !strchr(WHITESPACE, *cp)) {
407: error("You must supply a numeric argument "
408: "to the %s command.", cmd);
409: return(-1);
410: }
411: cp += strspn(cp, WHITESPACE);
412:
413: /* Get pathname (mandatory) */
414: if (get_pathname(&cp, path1))
415: return(-1);
416: if (*path1 == NULL) {
1.3 stevesk 417: error("You must specify a path after a %s command.",
1.1 djm 418: cmd);
419: return(-1);
420: }
421: break;
422: case I_QUIT:
423: case I_PWD:
424: case I_LPWD:
425: case I_HELP:
426: break;
427: default:
428: fatal("Command not implemented");
429: }
430:
431: *cpp = cp;
432:
433: return(cmdnum);
434: }
435:
436: int
437: parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
438: {
1.8 provos 439: char *path1, *path2, *tmp;
1.1 djm 440: int pflag, cmdnum;
441: unsigned long n_arg;
442: Attrib a, *aa;
443: char path_buf[PATH_MAX];
444:
445: path1 = path2 = NULL;
446: cmdnum = parse_args(&cmd, &pflag, &n_arg, &path1, &path2);
447:
448: /* Perform command */
449: switch (cmdnum) {
450: case -1:
451: break;
452: case I_GET:
453: path1 = make_absolute(path1, *pwd);
454: do_download(in, out, path1, path2, pflag);
455: break;
456: case I_PUT:
457: path2 = make_absolute(path2, *pwd);
458: do_upload(in, out, path1, path2, pflag);
459: break;
460: case I_RENAME:
461: path1 = make_absolute(path1, *pwd);
462: path2 = make_absolute(path2, *pwd);
463: do_rename(in, out, path1, path2);
464: break;
465: case I_RM:
466: path1 = make_absolute(path1, *pwd);
467: do_rm(in, out, path1);
468: break;
469: case I_MKDIR:
470: path1 = make_absolute(path1, *pwd);
471: attrib_clear(&a);
472: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
473: a.perm = 0777;
474: do_mkdir(in, out, path1, &a);
475: break;
476: case I_RMDIR:
477: path1 = make_absolute(path1, *pwd);
478: do_rmdir(in, out, path1);
479: break;
480: case I_CHDIR:
481: path1 = make_absolute(path1, *pwd);
1.11 markus 482: if ((tmp = do_realpath(in, out, path1)) == NULL)
483: break;
484: if ((aa = do_stat(in, out, tmp)) == NULL) {
485: xfree(tmp);
486: break;
487: }
1.9 djm 488: if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
489: error("Can't change directory: Can't check target");
490: xfree(tmp);
491: break;
492: }
493: if (!S_ISDIR(aa->perm)) {
494: error("Can't change directory: \"%s\" is not "
495: "a directory", tmp);
496: xfree(tmp);
497: break;
498: }
1.11 markus 499: xfree(*pwd);
500: *pwd = tmp;
1.1 djm 501: break;
502: case I_LS:
1.12 djm 503: if (!path1) {
504: do_ls(in, out, *pwd);
505: break;
506: }
1.1 djm 507: path1 = make_absolute(path1, *pwd);
1.12 djm 508: if ((tmp = do_realpath(in, out, path1)) == NULL)
509: break;
510: xfree(path1);
511: path1 = tmp;
512: if ((aa = do_stat(in, out, path1)) == NULL)
513: break;
514: if ((aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
515: !S_ISDIR(aa->perm)) {
516: error("Can't ls: \"%s\" is not a directory", path1);
517: break;
518: }
519: do_ls(in, out, path1);
1.1 djm 520: break;
521: case I_LCHDIR:
522: if (chdir(path1) == -1)
523: error("Couldn't change local directory to "
524: "\"%s\": %s", path1, strerror(errno));
525: break;
526: case I_LMKDIR:
527: if (mkdir(path1, 0777) == -1)
528: error("Couldn't create local directory to "
529: "\"%s\": %s", path1, strerror(errno));
530: break;
531: case I_LLS:
532: local_do_ls(cmd);
533: break;
534: case I_SHELL:
535: local_do_shell(cmd);
536: break;
537: case I_LUMASK:
538: umask(n_arg);
539: break;
540: case I_CHMOD:
541: path1 = make_absolute(path1, *pwd);
542: attrib_clear(&a);
543: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
544: a.perm = n_arg;
545: do_setstat(in, out, path1, &a);
1.5 stevesk 546: break;
1.1 djm 547: case I_CHOWN:
548: path1 = make_absolute(path1, *pwd);
549: aa = do_stat(in, out, path1);
1.5 stevesk 550: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1.1 djm 551: error("Can't get current ownership of "
552: "remote file \"%s\"", path1);
553: break;
554: }
555: aa->uid = n_arg;
556: do_setstat(in, out, path1, aa);
557: break;
558: case I_CHGRP:
559: path1 = make_absolute(path1, *pwd);
560: aa = do_stat(in, out, path1);
1.5 stevesk 561: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1.1 djm 562: error("Can't get current ownership of "
563: "remote file \"%s\"", path1);
564: break;
565: }
566: aa->gid = n_arg;
567: do_setstat(in, out, path1, aa);
568: break;
569: case I_PWD:
570: printf("Remote working directory: %s\n", *pwd);
571: break;
572: case I_LPWD:
573: if (!getcwd(path_buf, sizeof(path_buf)))
574: error("Couldn't get local cwd: %s\n",
575: strerror(errno));
576: else
577: printf("Local working directory: %s\n",
578: path_buf);
579: break;
580: case I_QUIT:
581: return(-1);
582: case I_HELP:
583: help();
584: break;
585: default:
586: fatal("%d is not implemented", cmdnum);
587: }
588:
589: if (path1)
590: xfree(path1);
591: if (path2)
592: xfree(path2);
593:
594: return(0);
595: }
596:
597: void
598: interactive_loop(int fd_in, int fd_out)
599: {
600: char *pwd;
601: char cmd[2048];
602:
603: pwd = do_realpath(fd_in, fd_out, ".");
604: if (pwd == NULL)
605: fatal("Need cwd");
606:
607: setlinebuf(stdout);
608: setlinebuf(stdin);
609:
610: for(;;) {
611: char *cp;
612:
613: printf("sftp> ");
614:
615: /* XXX: use libedit */
616: if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
617: printf("\n");
618: break;
619: }
620: cp = strrchr(cmd, '\n');
621: if (cp)
622: *cp = '\0';
623: if (parse_dispatch_command(fd_in, fd_out, cmd, &pwd))
624: break;
625: }
626: xfree(pwd);
627: }