Annotation of src/usr.bin/ssh/sftp-int.c, Revision 1.8
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 */
27: /* XXX: recursive operations */
28:
29: #include "includes.h"
1.8 ! provos 30: RCSID("$OpenBSD: sftp-int.c,v 1.7 2001/02/05 00:02:32 deraadt Exp $");
1.1 djm 31:
32: #include "buffer.h"
33: #include "xmalloc.h"
34: #include "log.h"
35: #include "pathnames.h"
36:
37: #include "sftp.h"
38: #include "sftp-common.h"
39: #include "sftp-client.h"
40: #include "sftp-int.h"
41:
42: /* Seperators for interactive commands */
43: #define WHITESPACE " \t\r\n"
44:
45: /* Commands for interactive mode */
46: #define I_CHDIR 1
47: #define I_CHGRP 2
48: #define I_CHMOD 3
49: #define I_CHOWN 4
50: #define I_GET 5
51: #define I_HELP 6
52: #define I_LCHDIR 7
53: #define I_LLS 8
54: #define I_LMKDIR 9
55: #define I_LPWD 10
56: #define I_LS 11
57: #define I_LUMASK 12
58: #define I_MKDIR 13
59: #define I_PUT 14
60: #define I_PWD 15
61: #define I_QUIT 16
62: #define I_RENAME 17
63: #define I_RM 18
64: #define I_RMDIR 19
65: #define I_SHELL 20
66:
67: struct CMD {
1.6 deraadt 68: const char *c;
1.1 djm 69: const int n;
70: };
71:
72: const struct CMD cmds[] = {
1.6 deraadt 73: { "CD", I_CHDIR },
74: { "CHDIR", I_CHDIR },
75: { "CHGRP", I_CHGRP },
76: { "CHMOD", I_CHMOD },
77: { "CHOWN", I_CHOWN },
78: { "EXIT", I_QUIT },
79: { "GET", I_GET },
80: { "HELP", I_HELP },
81: { "LCD", I_LCHDIR },
82: { "LCHDIR", I_LCHDIR },
83: { "LLS", I_LLS },
84: { "LMKDIR", I_LMKDIR },
85: { "LPWD", I_LPWD },
86: { "LS", I_LS },
87: { "LUMASK", I_LUMASK },
88: { "MKDIR", I_MKDIR },
89: { "PUT", I_PUT },
90: { "PWD", I_PWD },
91: { "QUIT", I_QUIT },
92: { "RENAME", I_RENAME },
93: { "RM", I_RM },
94: { "RMDIR", I_RMDIR },
95: { "!", I_SHELL },
1.7 deraadt 96: { "?", I_HELP },
1.6 deraadt 97: { NULL, -1}
1.1 djm 98: };
99:
100: void
101: help(void)
102: {
103: printf("Available commands:\n");
104: printf("CD path Change remote directory to 'path'\n");
105: printf("LCD path Change local directory to 'path'\n");
106: printf("CHGRP grp path Change group of file 'path' to 'grp'\n");
107: printf("CHMOD mode path Change permissions of file 'path' to 'mode'\n");
108: printf("CHOWN own path Change owner of file 'path' to 'own'\n");
109: printf("HELP Display this help text\n");
110: printf("GET remote-path [local-path] Download file\n");
111: printf("LLS [ls options] [path] Display local directory listing\n");
112: printf("LMKDIR path Create local directory\n");
113: printf("LPWD Print local working directory\n");
114: printf("LS [path] Display remote directory listing\n");
115: printf("LUMASK umask Set local umask to 'umask'\n");
116: printf("MKDIR path Create remote directory\n");
117: printf("PUT local-path [remote-path] Upload file\n");
118: printf("PWD Display remote working directory\n");
119: printf("EXIT Quit sftp\n");
120: printf("QUIT Quit sftp\n");
121: printf("RENAME oldpath newpath Rename remote file\n");
122: printf("RMDIR path Remove remote directory\n");
123: printf("RM path Delete remote file\n");
124: printf("!command Execute 'command' in local shell\n");
125: printf("! Escape to local shell\n");
126: }
127:
128: void
129: local_do_shell(const char *args)
130: {
131: int ret, status;
132: char *shell;
133: pid_t pid;
1.3 stevesk 134:
1.1 djm 135: if (!*args)
136: args = NULL;
1.3 stevesk 137:
1.1 djm 138: if ((shell = getenv("SHELL")) == NULL)
139: shell = _PATH_BSHELL;
140:
141: if ((pid = fork()) == -1)
142: fatal("Couldn't fork: %s", strerror(errno));
143:
144: if (pid == 0) {
145: /* XXX: child has pipe fds to ssh subproc open - issue? */
146: if (args) {
147: debug3("Executing %s -c \"%s\"", shell, args);
148: ret = execl(shell, shell, "-c", args, NULL);
149: } else {
150: debug3("Executing %s", shell);
151: ret = execl(shell, shell, NULL);
152: }
1.3 stevesk 153: fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
1.1 djm 154: strerror(errno));
155: _exit(1);
156: }
157: if (waitpid(pid, &status, 0) == -1)
158: fatal("Couldn't wait for child: %s", strerror(errno));
159: if (!WIFEXITED(status))
160: error("Shell exited abormally");
161: else if (WEXITSTATUS(status))
162: error("Shell exited with status %d", WEXITSTATUS(status));
163: }
164:
1.3 stevesk 165: void
1.1 djm 166: local_do_ls(const char *args)
167: {
168: if (!args || !*args)
169: local_do_shell("ls");
170: else {
171: char *buf = xmalloc(8 + strlen(args) + 1);
172:
173: /* XXX: quoting - rip quoting code from ftp? */
174: sprintf(buf, "/bin/ls %s", args);
175: local_do_shell(buf);
176: }
177: }
178:
179: char *
180: make_absolute(char *p, char *pwd)
181: {
182: char buf[2048];
183:
184: /* Derelativise */
185: if (p && p[0] != '/') {
186: snprintf(buf, sizeof(buf), "%s/%s", pwd, p);
187: xfree(p);
188: p = xstrdup(buf);
189: }
190:
191: return(p);
192: }
193:
194: int
195: parse_getput_flags(const char **cpp, int *pflag)
196: {
197: const char *cp = *cpp;
198:
199: /* Check for flags */
200: if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
201: switch (*cp) {
202: case 'P':
203: *pflag = 1;
204: break;
205: default:
206: error("Invalid flag -%c", *cp);
207: return(-1);
208: }
209: cp += 2;
210: *cpp = cp + strspn(cp, WHITESPACE);
211: }
212:
213: return(0);
214: }
215:
216: int
217: get_pathname(const char **cpp, char **path)
218: {
1.8 ! provos 219: const char *cp = *cpp, *end;
! 220: char quot;
1.1 djm 221: int i;
222:
223: cp += strspn(cp, WHITESPACE);
224: if (!*cp) {
225: *cpp = cp;
226: *path = NULL;
1.8 ! provos 227:
! 228: return (0);
1.1 djm 229: }
230:
231: /* Check for quoted filenames */
232: if (*cp == '\"' || *cp == '\'') {
1.8 ! provos 233: quot = *cp++;
! 234:
! 235: end = strchr(cp, quot);
! 236: if (end == NULL) {
1.1 djm 237: error("Unterminated quote");
1.8 ! provos 238: goto fail;
1.1 djm 239: }
1.8 ! provos 240:
! 241: if (cp == end) {
1.1 djm 242: error("Empty quotes");
1.8 ! provos 243: goto fail;
1.1 djm 244: }
1.8 ! provos 245:
! 246: *cpp = end + 1 + strspn(end + 1, WHITESPACE);
! 247: } else {
! 248: /* Read to end of filename */
! 249: end = strpbrk(cp, WHITESPACE);
! 250: if (end == NULL)
! 251: end = strchr(cp, '\0');
! 252:
! 253: *cpp = end + strspn(end, WHITESPACE);
1.1 djm 254: }
255:
1.8 ! provos 256: i = end - cp;
1.1 djm 257:
258: *path = xmalloc(i + 1);
259: memcpy(*path, cp, i);
260: (*path)[i] = '\0';
261:
262: return(0);
1.8 ! provos 263:
! 264: fail:
! 265: *path = NULL;
! 266:
! 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:
279: if (cp == NULL) {
280: *ifp = xstrdup(p);
281: return(0);
282: }
283:
284: if (!cp[1]) {
285: error("Invalid path");
286: return(-1);
287: }
288:
289: *ifp = xstrdup(cp + 1);
290: return(0);
291: }
292:
293: int
294: parse_args(const char **cpp, int *pflag, unsigned long *n_arg,
295: char **path1, char **path2)
296: {
297: const char *cmd, *cp = *cpp;
1.4 markus 298: int base = 0;
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:
392: case I_CHMOD:
1.4 markus 393: base = 8;
1.1 djm 394: case I_CHOWN:
395: case I_CHGRP:
396: /* Get numeric arg (mandatory) */
397: if (*cp < '0' && *cp > '9') {
398: error("You must supply a numeric argument "
399: "to the %s command.", cmd);
400: return(-1);
401: }
1.4 markus 402: *n_arg = strtoul(cp, (char**)&cp, base);
1.1 djm 403: if (!*cp || !strchr(WHITESPACE, *cp)) {
404: error("You must supply a numeric argument "
405: "to the %s command.", cmd);
406: return(-1);
407: }
408: cp += strspn(cp, WHITESPACE);
409:
410: /* Get pathname (mandatory) */
411: if (get_pathname(&cp, path1))
412: return(-1);
413: if (*path1 == NULL) {
1.3 stevesk 414: error("You must specify a path after a %s command.",
1.1 djm 415: cmd);
416: return(-1);
417: }
418: break;
419: case I_QUIT:
420: case I_PWD:
421: case I_LPWD:
422: case I_HELP:
423: break;
424: default:
425: fatal("Command not implemented");
426: }
427:
428: *cpp = cp;
429:
430: return(cmdnum);
431: }
432:
433: int
434: parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
435: {
1.8 ! provos 436: char *path1, *path2, *tmp;
1.1 djm 437: int pflag, cmdnum;
438: unsigned long n_arg;
439: Attrib a, *aa;
440: char path_buf[PATH_MAX];
441:
442: path1 = path2 = NULL;
443: cmdnum = parse_args(&cmd, &pflag, &n_arg, &path1, &path2);
444:
445: /* Perform command */
446: switch (cmdnum) {
447: case -1:
448: break;
449: case I_GET:
450: path1 = make_absolute(path1, *pwd);
451: do_download(in, out, path1, path2, pflag);
452: break;
453: case I_PUT:
454: path2 = make_absolute(path2, *pwd);
455: do_upload(in, out, path1, path2, pflag);
456: break;
457: case I_RENAME:
458: path1 = make_absolute(path1, *pwd);
459: path2 = make_absolute(path2, *pwd);
460: do_rename(in, out, path1, path2);
461: break;
462: case I_RM:
463: path1 = make_absolute(path1, *pwd);
464: do_rm(in, out, path1);
465: break;
466: case I_MKDIR:
467: path1 = make_absolute(path1, *pwd);
468: attrib_clear(&a);
469: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
470: a.perm = 0777;
471: do_mkdir(in, out, path1, &a);
472: break;
473: case I_RMDIR:
474: path1 = make_absolute(path1, *pwd);
475: do_rmdir(in, out, path1);
476: break;
477: case I_CHDIR:
478: path1 = make_absolute(path1, *pwd);
1.8 ! provos 479: tmp = do_realpath(in, out, path1);
! 480: if (tmp) {
! 481: xfree(*pwd);
! 482: *pwd = tmp;
! 483: }
1.1 djm 484: break;
485: case I_LS:
486: path1 = make_absolute(path1, *pwd);
487: do_ls(in, out, path1?path1:*pwd);
488: break;
489: case I_LCHDIR:
490: if (chdir(path1) == -1)
491: error("Couldn't change local directory to "
492: "\"%s\": %s", path1, strerror(errno));
493: break;
494: case I_LMKDIR:
495: if (mkdir(path1, 0777) == -1)
496: error("Couldn't create local directory to "
497: "\"%s\": %s", path1, strerror(errno));
498: break;
499: case I_LLS:
500: local_do_ls(cmd);
501: break;
502: case I_SHELL:
503: local_do_shell(cmd);
504: break;
505: case I_LUMASK:
506: umask(n_arg);
507: break;
508: case I_CHMOD:
509: path1 = make_absolute(path1, *pwd);
510: attrib_clear(&a);
511: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
512: a.perm = n_arg;
513: do_setstat(in, out, path1, &a);
1.5 stevesk 514: break;
1.1 djm 515: case I_CHOWN:
516: path1 = make_absolute(path1, *pwd);
517: aa = do_stat(in, out, path1);
1.5 stevesk 518: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1.1 djm 519: error("Can't get current ownership of "
520: "remote file \"%s\"", path1);
521: break;
522: }
523: aa->uid = n_arg;
524: do_setstat(in, out, path1, aa);
525: break;
526: case I_CHGRP:
527: path1 = make_absolute(path1, *pwd);
528: aa = do_stat(in, out, path1);
1.5 stevesk 529: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1.1 djm 530: error("Can't get current ownership of "
531: "remote file \"%s\"", path1);
532: break;
533: }
534: aa->gid = n_arg;
535: do_setstat(in, out, path1, aa);
536: break;
537: case I_PWD:
538: printf("Remote working directory: %s\n", *pwd);
539: break;
540: case I_LPWD:
541: if (!getcwd(path_buf, sizeof(path_buf)))
542: error("Couldn't get local cwd: %s\n",
543: strerror(errno));
544: else
545: printf("Local working directory: %s\n",
546: path_buf);
547: break;
548: case I_QUIT:
549: return(-1);
550: case I_HELP:
551: help();
552: break;
553: default:
554: fatal("%d is not implemented", cmdnum);
555: }
556:
557: if (path1)
558: xfree(path1);
559: if (path2)
560: xfree(path2);
561:
562: return(0);
563: }
564:
565: void
566: interactive_loop(int fd_in, int fd_out)
567: {
568: char *pwd;
569: char cmd[2048];
570:
571: pwd = do_realpath(fd_in, fd_out, ".");
572: if (pwd == NULL)
573: fatal("Need cwd");
574:
575: setlinebuf(stdout);
576: setlinebuf(stdin);
577:
578: for(;;) {
579: char *cp;
580:
581: printf("sftp> ");
582:
583: /* XXX: use libedit */
584: if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
585: printf("\n");
586: break;
587: }
588: cp = strrchr(cmd, '\n');
589: if (cp)
590: *cp = '\0';
591: if (parse_dispatch_command(fd_in, fd_out, cmd, &pwd))
592: break;
593: }
594: xfree(pwd);
595: }