Annotation of src/usr.bin/ssh/sftp-int.c, Revision 1.12
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.12 ! djm 31: RCSID("$OpenBSD: sftp-int.c,v 1.11 2001/02/07 00:10:18 markus 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");
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");
126: printf("!command Execute 'command' in local shell\n");
127: printf("! Escape to local shell\n");
128: }
129:
130: void
131: local_do_shell(const char *args)
132: {
133: int ret, status;
134: char *shell;
135: pid_t pid;
1.3 stevesk 136:
1.1 djm 137: if (!*args)
138: args = NULL;
1.3 stevesk 139:
1.1 djm 140: if ((shell = getenv("SHELL")) == NULL)
141: shell = _PATH_BSHELL;
142:
143: if ((pid = fork()) == -1)
144: fatal("Couldn't fork: %s", strerror(errno));
145:
146: if (pid == 0) {
147: /* XXX: child has pipe fds to ssh subproc open - issue? */
148: if (args) {
149: debug3("Executing %s -c \"%s\"", shell, args);
150: ret = execl(shell, shell, "-c", args, NULL);
151: } else {
152: debug3("Executing %s", shell);
153: ret = execl(shell, shell, NULL);
154: }
1.3 stevesk 155: fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
1.1 djm 156: strerror(errno));
157: _exit(1);
158: }
159: if (waitpid(pid, &status, 0) == -1)
160: fatal("Couldn't wait for child: %s", strerror(errno));
161: if (!WIFEXITED(status))
162: error("Shell exited abormally");
163: else if (WEXITSTATUS(status))
164: error("Shell exited with status %d", WEXITSTATUS(status));
165: }
166:
1.3 stevesk 167: void
1.1 djm 168: local_do_ls(const char *args)
169: {
170: if (!args || !*args)
171: local_do_shell("ls");
172: else {
173: char *buf = xmalloc(8 + strlen(args) + 1);
174:
175: /* XXX: quoting - rip quoting code from ftp? */
176: sprintf(buf, "/bin/ls %s", args);
177: local_do_shell(buf);
178: }
179: }
180:
181: char *
182: make_absolute(char *p, char *pwd)
183: {
184: char buf[2048];
185:
186: /* Derelativise */
187: if (p && p[0] != '/') {
188: snprintf(buf, sizeof(buf), "%s/%s", pwd, p);
189: xfree(p);
190: p = xstrdup(buf);
191: }
192:
193: return(p);
194: }
195:
196: int
197: parse_getput_flags(const char **cpp, int *pflag)
198: {
199: const char *cp = *cpp;
200:
201: /* Check for flags */
202: if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
203: switch (*cp) {
204: case 'P':
205: *pflag = 1;
206: break;
207: default:
208: error("Invalid flag -%c", *cp);
209: return(-1);
210: }
211: cp += 2;
212: *cpp = cp + strspn(cp, WHITESPACE);
213: }
214:
215: return(0);
216: }
217:
218: int
219: get_pathname(const char **cpp, char **path)
220: {
1.8 provos 221: const char *cp = *cpp, *end;
222: char quot;
1.1 djm 223: int i;
224:
225: cp += strspn(cp, WHITESPACE);
226: if (!*cp) {
227: *cpp = cp;
228: *path = NULL;
1.8 provos 229:
230: return (0);
1.1 djm 231: }
232:
233: /* Check for quoted filenames */
234: if (*cp == '\"' || *cp == '\'') {
1.8 provos 235: quot = *cp++;
236:
237: end = strchr(cp, quot);
238: if (end == NULL) {
1.1 djm 239: error("Unterminated quote");
1.8 provos 240: goto fail;
1.1 djm 241: }
1.8 provos 242:
243: if (cp == end) {
1.1 djm 244: error("Empty quotes");
1.8 provos 245: goto fail;
1.1 djm 246: }
1.8 provos 247:
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:
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:
264: return(0);
1.8 provos 265:
266: fail:
267: *path = NULL;
268:
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:
281: if (cp == NULL) {
282: *ifp = xstrdup(p);
283: return(0);
284: }
285:
286: if (!cp[1]) {
287: error("Invalid path");
288: return(-1);
289: }
290:
291: *ifp = xstrdup(cp + 1);
292: return(0);
293: }
294:
295: int
296: parse_args(const char **cpp, int *pflag, unsigned long *n_arg,
297: char **path1, char **path2)
298: {
299: const char *cmd, *cp = *cpp;
1.4 markus 300: int base = 0;
1.1 djm 301: int i, cmdnum;
302:
303: /* Skip leading whitespace */
304: cp = cp + strspn(cp, WHITESPACE);
305:
306: /* Ignore blank lines */
307: if (!*cp)
308: return(-1);
309:
310: /* Figure out which command we have */
311: for(i = 0; cmds[i].c; i++) {
312: int cmdlen = strlen(cmds[i].c);
313:
314: /* Check for command followed by whitespace */
315: if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
316: strchr(WHITESPACE, cp[cmdlen])) {
317: cp += cmdlen;
318: cp = cp + strspn(cp, WHITESPACE);
319: break;
320: }
321: }
322: cmdnum = cmds[i].n;
323: cmd = cmds[i].c;
324:
325: /* Special case */
326: if (*cp == '!') {
327: cp++;
328: cmdnum = I_SHELL;
329: } else if (cmdnum == -1) {
330: error("Invalid command.");
331: return(-1);
332: }
333:
334: /* Get arguments and parse flags */
335: *pflag = *n_arg = 0;
336: *path1 = *path2 = NULL;
337: switch (cmdnum) {
338: case I_GET:
339: case I_PUT:
340: if (parse_getput_flags(&cp, pflag))
341: return(-1);
342: /* Get first pathname (mandatory) */
343: if (get_pathname(&cp, path1))
344: return(-1);
345: if (*path1 == NULL) {
346: error("You must specify at least one path after a "
347: "%s command.", cmd);
348: return(-1);
349: }
350: /* Try to get second pathname (optional) */
351: if (get_pathname(&cp, path2))
352: return(-1);
353: /* Otherwise try to guess it from first path */
354: if (*path2 == NULL && infer_path(*path1, path2))
355: return(-1);
356: break;
357: case I_RENAME:
358: /* Get first pathname (mandatory) */
359: if (get_pathname(&cp, path1))
360: return(-1);
361: if (get_pathname(&cp, path2))
362: return(-1);
363: if (!*path1 || !*path2) {
364: error("You must specify two paths after a %s "
365: "command.", cmd);
366: return(-1);
367: }
368: break;
369: case I_RM:
370: case I_MKDIR:
371: case I_RMDIR:
372: case I_CHDIR:
373: case I_LCHDIR:
374: case I_LMKDIR:
375: /* Get pathname (mandatory) */
376: if (get_pathname(&cp, path1))
377: return(-1);
378: if (*path1 == NULL) {
1.3 stevesk 379: error("You must specify a path after a %s command.",
1.1 djm 380: cmd);
381: return(-1);
382: }
383: break;
384: case I_LS:
385: /* Path is optional */
386: if (get_pathname(&cp, path1))
387: return(-1);
388: break;
389: case I_LLS:
390: case I_SHELL:
391: /* Uses the rest of the line */
392: break;
393: case I_LUMASK:
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) */
399: if (*cp < '0' && *cp > '9') {
400: error("You must supply a numeric argument "
401: "to the %s command.", cmd);
402: return(-1);
403: }
1.4 markus 404: *n_arg = strtoul(cp, (char**)&cp, base);
1.1 djm 405: if (!*cp || !strchr(WHITESPACE, *cp)) {
406: error("You must supply a numeric argument "
407: "to the %s command.", cmd);
408: return(-1);
409: }
410: cp += strspn(cp, WHITESPACE);
411:
412: /* Get pathname (mandatory) */
413: if (get_pathname(&cp, path1))
414: return(-1);
415: if (*path1 == NULL) {
1.3 stevesk 416: error("You must specify a path after a %s command.",
1.1 djm 417: cmd);
418: return(-1);
419: }
420: break;
421: case I_QUIT:
422: case I_PWD:
423: case I_LPWD:
424: case I_HELP:
425: break;
426: default:
427: fatal("Command not implemented");
428: }
429:
430: *cpp = cp;
431:
432: return(cmdnum);
433: }
434:
435: int
436: parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
437: {
1.8 provos 438: char *path1, *path2, *tmp;
1.1 djm 439: int pflag, cmdnum;
440: unsigned long n_arg;
441: Attrib a, *aa;
442: char path_buf[PATH_MAX];
443:
444: path1 = path2 = NULL;
445: cmdnum = parse_args(&cmd, &pflag, &n_arg, &path1, &path2);
446:
447: /* Perform command */
448: switch (cmdnum) {
449: case -1:
450: break;
451: case I_GET:
452: path1 = make_absolute(path1, *pwd);
453: do_download(in, out, path1, path2, pflag);
454: break;
455: case I_PUT:
456: path2 = make_absolute(path2, *pwd);
457: do_upload(in, out, path1, path2, pflag);
458: break;
459: case I_RENAME:
460: path1 = make_absolute(path1, *pwd);
461: path2 = make_absolute(path2, *pwd);
462: do_rename(in, out, path1, path2);
463: break;
464: case I_RM:
465: path1 = make_absolute(path1, *pwd);
466: do_rm(in, out, path1);
467: break;
468: case I_MKDIR:
469: path1 = make_absolute(path1, *pwd);
470: attrib_clear(&a);
471: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
472: a.perm = 0777;
473: do_mkdir(in, out, path1, &a);
474: break;
475: case I_RMDIR:
476: path1 = make_absolute(path1, *pwd);
477: do_rmdir(in, out, path1);
478: break;
479: case I_CHDIR:
480: path1 = make_absolute(path1, *pwd);
1.11 markus 481: if ((tmp = do_realpath(in, out, path1)) == NULL)
482: break;
483: if ((aa = do_stat(in, out, tmp)) == NULL) {
484: xfree(tmp);
485: break;
486: }
1.9 djm 487: if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
488: error("Can't change directory: Can't check target");
489: xfree(tmp);
490: break;
491: }
492: if (!S_ISDIR(aa->perm)) {
493: error("Can't change directory: \"%s\" is not "
494: "a directory", tmp);
495: xfree(tmp);
496: break;
497: }
1.11 markus 498: xfree(*pwd);
499: *pwd = tmp;
1.1 djm 500: break;
501: case I_LS:
1.12 ! djm 502: if (!path1) {
! 503: do_ls(in, out, *pwd);
! 504: break;
! 505: }
1.1 djm 506: path1 = make_absolute(path1, *pwd);
1.12 ! djm 507: if ((tmp = do_realpath(in, out, path1)) == NULL)
! 508: break;
! 509: xfree(path1);
! 510: path1 = tmp;
! 511: if ((aa = do_stat(in, out, path1)) == NULL)
! 512: break;
! 513: if ((aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
! 514: !S_ISDIR(aa->perm)) {
! 515: error("Can't ls: \"%s\" is not a directory", path1);
! 516: break;
! 517: }
! 518: do_ls(in, out, path1);
1.1 djm 519: break;
520: case I_LCHDIR:
521: if (chdir(path1) == -1)
522: error("Couldn't change local directory to "
523: "\"%s\": %s", path1, strerror(errno));
524: break;
525: case I_LMKDIR:
526: if (mkdir(path1, 0777) == -1)
527: error("Couldn't create local directory to "
528: "\"%s\": %s", path1, strerror(errno));
529: break;
530: case I_LLS:
531: local_do_ls(cmd);
532: break;
533: case I_SHELL:
534: local_do_shell(cmd);
535: break;
536: case I_LUMASK:
537: umask(n_arg);
538: break;
539: case I_CHMOD:
540: path1 = make_absolute(path1, *pwd);
541: attrib_clear(&a);
542: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
543: a.perm = n_arg;
544: do_setstat(in, out, path1, &a);
1.5 stevesk 545: break;
1.1 djm 546: case I_CHOWN:
547: path1 = make_absolute(path1, *pwd);
548: aa = do_stat(in, out, path1);
1.5 stevesk 549: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1.1 djm 550: error("Can't get current ownership of "
551: "remote file \"%s\"", path1);
552: break;
553: }
554: aa->uid = n_arg;
555: do_setstat(in, out, path1, aa);
556: break;
557: case I_CHGRP:
558: path1 = make_absolute(path1, *pwd);
559: aa = do_stat(in, out, path1);
1.5 stevesk 560: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1.1 djm 561: error("Can't get current ownership of "
562: "remote file \"%s\"", path1);
563: break;
564: }
565: aa->gid = n_arg;
566: do_setstat(in, out, path1, aa);
567: break;
568: case I_PWD:
569: printf("Remote working directory: %s\n", *pwd);
570: break;
571: case I_LPWD:
572: if (!getcwd(path_buf, sizeof(path_buf)))
573: error("Couldn't get local cwd: %s\n",
574: strerror(errno));
575: else
576: printf("Local working directory: %s\n",
577: path_buf);
578: break;
579: case I_QUIT:
580: return(-1);
581: case I_HELP:
582: help();
583: break;
584: default:
585: fatal("%d is not implemented", cmdnum);
586: }
587:
588: if (path1)
589: xfree(path1);
590: if (path2)
591: xfree(path2);
592:
593: return(0);
594: }
595:
596: void
597: interactive_loop(int fd_in, int fd_out)
598: {
599: char *pwd;
600: char cmd[2048];
601:
602: pwd = do_realpath(fd_in, fd_out, ".");
603: if (pwd == NULL)
604: fatal("Need cwd");
605:
606: setlinebuf(stdout);
607: setlinebuf(stdin);
608:
609: for(;;) {
610: char *cp;
611:
612: printf("sftp> ");
613:
614: /* XXX: use libedit */
615: if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
616: printf("\n");
617: break;
618: }
619: cp = strrchr(cmd, '\n');
620: if (cp)
621: *cp = '\0';
622: if (parse_dispatch_command(fd_in, fd_out, cmd, &pwd))
623: break;
624: }
625: xfree(pwd);
626: }