Annotation of src/usr.bin/ssh/sftp-int.c, Revision 1.18
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.18 ! stevesk 31: RCSID("$OpenBSD: sftp-int.c,v 1.17 2001/02/08 17:57:59 stevesk 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])) {
206: switch (*cp) {
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.4 markus 296: int base = 0;
1.1 djm 297: int i, cmdnum;
298:
299: /* Skip leading whitespace */
300: cp = cp + strspn(cp, WHITESPACE);
301:
302: /* Ignore blank lines */
303: if (!*cp)
304: return(-1);
305:
306: /* Figure out which command we have */
307: for(i = 0; cmds[i].c; i++) {
308: int cmdlen = strlen(cmds[i].c);
309:
310: /* Check for command followed by whitespace */
311: if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
312: strchr(WHITESPACE, cp[cmdlen])) {
313: cp += cmdlen;
314: cp = cp + strspn(cp, WHITESPACE);
315: break;
316: }
317: }
318: cmdnum = cmds[i].n;
319: cmd = cmds[i].c;
320:
321: /* Special case */
322: if (*cp == '!') {
323: cp++;
324: cmdnum = I_SHELL;
325: } else if (cmdnum == -1) {
326: error("Invalid command.");
327: return(-1);
328: }
329:
330: /* Get arguments and parse flags */
331: *pflag = *n_arg = 0;
332: *path1 = *path2 = NULL;
333: switch (cmdnum) {
334: case I_GET:
335: case I_PUT:
336: if (parse_getput_flags(&cp, pflag))
337: return(-1);
338: /* Get first pathname (mandatory) */
339: if (get_pathname(&cp, path1))
340: return(-1);
341: if (*path1 == NULL) {
342: error("You must specify at least one path after a "
343: "%s command.", cmd);
344: return(-1);
345: }
346: /* Try to get second pathname (optional) */
347: if (get_pathname(&cp, path2))
348: return(-1);
349: /* Otherwise try to guess it from first path */
350: if (*path2 == NULL && infer_path(*path1, path2))
351: return(-1);
352: break;
353: case I_RENAME:
354: /* Get first pathname (mandatory) */
355: if (get_pathname(&cp, path1))
356: return(-1);
357: if (get_pathname(&cp, path2))
358: return(-1);
359: if (!*path1 || !*path2) {
360: error("You must specify two paths after a %s "
361: "command.", cmd);
362: return(-1);
363: }
364: break;
365: case I_RM:
366: case I_MKDIR:
367: case I_RMDIR:
368: case I_CHDIR:
369: case I_LCHDIR:
370: case I_LMKDIR:
371: /* Get pathname (mandatory) */
372: if (get_pathname(&cp, path1))
373: return(-1);
374: if (*path1 == NULL) {
1.3 stevesk 375: error("You must specify a path after a %s command.",
1.1 djm 376: cmd);
377: return(-1);
378: }
379: break;
380: case I_LS:
381: /* Path is optional */
382: if (get_pathname(&cp, path1))
383: return(-1);
384: break;
385: case I_LLS:
386: case I_SHELL:
387: /* Uses the rest of the line */
388: break;
389: case I_LUMASK:
390: case I_CHMOD:
1.4 markus 391: base = 8;
1.1 djm 392: case I_CHOWN:
393: case I_CHGRP:
394: /* Get numeric arg (mandatory) */
395: if (*cp < '0' && *cp > '9') {
396: error("You must supply a numeric argument "
397: "to the %s command.", cmd);
398: return(-1);
399: }
1.4 markus 400: *n_arg = strtoul(cp, (char**)&cp, base);
1.1 djm 401: if (!*cp || !strchr(WHITESPACE, *cp)) {
402: error("You must supply a numeric argument "
403: "to the %s command.", cmd);
404: return(-1);
405: }
406: cp += strspn(cp, WHITESPACE);
407:
408: /* Get pathname (mandatory) */
409: if (get_pathname(&cp, path1))
410: return(-1);
411: if (*path1 == NULL) {
1.3 stevesk 412: error("You must specify a path after a %s command.",
1.1 djm 413: cmd);
414: return(-1);
415: }
416: break;
417: case I_QUIT:
418: case I_PWD:
419: case I_LPWD:
420: case I_HELP:
421: break;
422: default:
423: fatal("Command not implemented");
424: }
425:
426: *cpp = cp;
427: return(cmdnum);
428: }
429:
430: int
431: parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
432: {
1.8 provos 433: char *path1, *path2, *tmp;
1.1 djm 434: int pflag, cmdnum;
435: unsigned long n_arg;
436: Attrib a, *aa;
437: char path_buf[PATH_MAX];
438:
439: path1 = path2 = NULL;
440: cmdnum = parse_args(&cmd, &pflag, &n_arg, &path1, &path2);
441:
442: /* Perform command */
443: switch (cmdnum) {
444: case -1:
445: break;
446: case I_GET:
447: path1 = make_absolute(path1, *pwd);
448: do_download(in, out, path1, path2, pflag);
449: break;
450: case I_PUT:
451: path2 = make_absolute(path2, *pwd);
452: do_upload(in, out, path1, path2, pflag);
453: break;
454: case I_RENAME:
455: path1 = make_absolute(path1, *pwd);
456: path2 = make_absolute(path2, *pwd);
457: do_rename(in, out, path1, path2);
458: break;
459: case I_RM:
460: path1 = make_absolute(path1, *pwd);
461: do_rm(in, out, path1);
462: break;
463: case I_MKDIR:
464: path1 = make_absolute(path1, *pwd);
465: attrib_clear(&a);
466: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
467: a.perm = 0777;
468: do_mkdir(in, out, path1, &a);
469: break;
470: case I_RMDIR:
471: path1 = make_absolute(path1, *pwd);
472: do_rmdir(in, out, path1);
473: break;
474: case I_CHDIR:
475: path1 = make_absolute(path1, *pwd);
1.11 markus 476: if ((tmp = do_realpath(in, out, path1)) == NULL)
477: break;
478: if ((aa = do_stat(in, out, tmp)) == NULL) {
479: xfree(tmp);
480: break;
481: }
1.9 djm 482: if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
483: error("Can't change directory: Can't check target");
484: xfree(tmp);
485: break;
486: }
487: if (!S_ISDIR(aa->perm)) {
488: error("Can't change directory: \"%s\" is not "
489: "a directory", tmp);
490: xfree(tmp);
491: break;
492: }
1.11 markus 493: xfree(*pwd);
494: *pwd = tmp;
1.1 djm 495: break;
496: case I_LS:
1.12 djm 497: if (!path1) {
498: do_ls(in, out, *pwd);
499: break;
500: }
1.1 djm 501: path1 = make_absolute(path1, *pwd);
1.12 djm 502: if ((tmp = do_realpath(in, out, path1)) == NULL)
503: break;
504: xfree(path1);
505: path1 = tmp;
506: if ((aa = do_stat(in, out, path1)) == NULL)
507: break;
508: if ((aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
509: !S_ISDIR(aa->perm)) {
510: error("Can't ls: \"%s\" is not a directory", path1);
511: break;
512: }
513: do_ls(in, out, path1);
1.1 djm 514: break;
515: case I_LCHDIR:
516: if (chdir(path1) == -1)
517: error("Couldn't change local directory to "
518: "\"%s\": %s", path1, strerror(errno));
519: break;
520: case I_LMKDIR:
521: if (mkdir(path1, 0777) == -1)
1.17 stevesk 522: error("Couldn't create local directory "
1.1 djm 523: "\"%s\": %s", path1, strerror(errno));
524: break;
525: case I_LLS:
526: local_do_ls(cmd);
527: break;
528: case I_SHELL:
529: local_do_shell(cmd);
530: break;
531: case I_LUMASK:
532: umask(n_arg);
533: break;
534: case I_CHMOD:
535: path1 = make_absolute(path1, *pwd);
536: attrib_clear(&a);
537: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
538: a.perm = n_arg;
539: do_setstat(in, out, path1, &a);
1.5 stevesk 540: break;
1.1 djm 541: case I_CHOWN:
542: path1 = make_absolute(path1, *pwd);
543: aa = do_stat(in, out, path1);
1.5 stevesk 544: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1.1 djm 545: error("Can't get current ownership of "
546: "remote file \"%s\"", path1);
547: break;
548: }
549: aa->uid = n_arg;
550: do_setstat(in, out, path1, aa);
551: break;
552: case I_CHGRP:
553: path1 = make_absolute(path1, *pwd);
554: aa = do_stat(in, out, path1);
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: }
560: aa->gid = n_arg;
561: do_setstat(in, out, path1, aa);
562: break;
563: case I_PWD:
564: printf("Remote working directory: %s\n", *pwd);
565: break;
566: case I_LPWD:
567: if (!getcwd(path_buf, sizeof(path_buf)))
568: error("Couldn't get local cwd: %s\n",
569: strerror(errno));
570: else
571: printf("Local working directory: %s\n",
572: path_buf);
573: break;
574: case I_QUIT:
575: return(-1);
576: case I_HELP:
577: help();
578: break;
579: default:
580: fatal("%d is not implemented", cmdnum);
581: }
582:
583: if (path1)
584: xfree(path1);
585: if (path2)
586: xfree(path2);
587: return(0);
588: }
589:
590: void
591: interactive_loop(int fd_in, int fd_out)
592: {
593: char *pwd;
594: char cmd[2048];
595:
596: pwd = do_realpath(fd_in, fd_out, ".");
597: if (pwd == NULL)
598: fatal("Need cwd");
599:
1.14 stevesk 600: setvbuf(stdout, NULL, _IOLBF, 0);
601: setvbuf(stdin, NULL, _IOLBF, 0);
1.1 djm 602:
603: for(;;) {
604: char *cp;
605:
606: printf("sftp> ");
607:
608: /* XXX: use libedit */
609: if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
610: printf("\n");
611: break;
612: }
613: cp = strrchr(cmd, '\n');
614: if (cp)
615: *cp = '\0';
616: if (parse_dispatch_command(fd_in, fd_out, cmd, &pwd))
617: break;
618: }
619: xfree(pwd);
620: }