Annotation of src/usr.bin/ssh/sftp-int.c, Revision 1.5
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.5 ! stevesk 30: RCSID("$OpenBSD: sftp-int.c,v 1.4 2001/02/04 21:41:21 markus 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 {
68: const int n;
69: const char *c;
70: };
71:
72: const struct CMD cmds[] = {
73: { I_CHDIR, "CD" },
74: { I_CHDIR, "CHDIR" },
75: { I_CHGRP, "CHGRP" },
76: { I_CHMOD, "CHMOD" },
77: { I_CHOWN, "CHOWN" },
78: { I_HELP, "HELP" },
79: { I_GET, "GET" },
1.2 markus 80: { I_LCHDIR, "LCD" },
1.1 djm 81: { I_LCHDIR, "LCHDIR" },
82: { I_LLS, "LLS" },
83: { I_LMKDIR, "LMKDIR" },
84: { I_LPWD, "LPWD" },
85: { I_LS, "LS" },
86: { I_LUMASK, "LUMASK" },
87: { I_MKDIR, "MKDIR" },
88: { I_PUT, "PUT" },
89: { I_PWD, "PWD" },
90: { I_QUIT, "EXIT" },
91: { I_QUIT, "QUIT" },
92: { I_RENAME, "RENAME" },
93: { I_RMDIR, "RMDIR" },
94: { I_RM, "RM" },
95: { I_SHELL, "!" },
96: { -1, NULL}
97: };
98:
99: void
100: help(void)
101: {
102: printf("Available commands:\n");
103: printf("CD path Change remote directory to 'path'\n");
104: printf("LCD path Change local directory to 'path'\n");
105: printf("CHGRP grp path Change group of file 'path' to 'grp'\n");
106: printf("CHMOD mode path Change permissions of file 'path' to 'mode'\n");
107: printf("CHOWN own path Change owner of file 'path' to 'own'\n");
108: printf("HELP Display this help text\n");
109: printf("GET remote-path [local-path] Download file\n");
110: printf("LLS [ls options] [path] Display local directory listing\n");
111: printf("LMKDIR path Create local directory\n");
112: printf("LPWD Print local working directory\n");
113: printf("LS [path] Display remote directory listing\n");
114: printf("LUMASK umask Set local umask to 'umask'\n");
115: printf("MKDIR path Create remote directory\n");
116: printf("PUT local-path [remote-path] Upload file\n");
117: printf("PWD Display remote working directory\n");
118: printf("EXIT Quit sftp\n");
119: printf("QUIT Quit sftp\n");
120: printf("RENAME oldpath newpath Rename remote file\n");
121: printf("RMDIR path Remove remote directory\n");
122: printf("RM path Delete remote file\n");
123: printf("!command Execute 'command' in local shell\n");
124: printf("! Escape to local shell\n");
125: }
126:
127: void
128: local_do_shell(const char *args)
129: {
130: int ret, status;
131: char *shell;
132: pid_t pid;
1.3 stevesk 133:
1.1 djm 134: if (!*args)
135: args = NULL;
1.3 stevesk 136:
1.1 djm 137: if ((shell = getenv("SHELL")) == NULL)
138: shell = _PATH_BSHELL;
139:
140: if ((pid = fork()) == -1)
141: fatal("Couldn't fork: %s", strerror(errno));
142:
143: if (pid == 0) {
144: /* XXX: child has pipe fds to ssh subproc open - issue? */
145: if (args) {
146: debug3("Executing %s -c \"%s\"", shell, args);
147: ret = execl(shell, shell, "-c", args, NULL);
148: } else {
149: debug3("Executing %s", shell);
150: ret = execl(shell, shell, NULL);
151: }
1.3 stevesk 152: fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
1.1 djm 153: strerror(errno));
154: _exit(1);
155: }
156: if (waitpid(pid, &status, 0) == -1)
157: fatal("Couldn't wait for child: %s", strerror(errno));
158: if (!WIFEXITED(status))
159: error("Shell exited abormally");
160: else if (WEXITSTATUS(status))
161: error("Shell exited with status %d", WEXITSTATUS(status));
162: }
163:
1.3 stevesk 164: void
1.1 djm 165: local_do_ls(const char *args)
166: {
167: if (!args || !*args)
168: local_do_shell("ls");
169: else {
170: char *buf = xmalloc(8 + strlen(args) + 1);
171:
172: /* XXX: quoting - rip quoting code from ftp? */
173: sprintf(buf, "/bin/ls %s", args);
174: local_do_shell(buf);
175: }
176: }
177:
178: char *
179: make_absolute(char *p, char *pwd)
180: {
181: char buf[2048];
182:
183: /* Derelativise */
184: if (p && p[0] != '/') {
185: snprintf(buf, sizeof(buf), "%s/%s", pwd, p);
186: xfree(p);
187: p = xstrdup(buf);
188: }
189:
190: return(p);
191: }
192:
193: int
194: parse_getput_flags(const char **cpp, int *pflag)
195: {
196: const char *cp = *cpp;
197:
198: /* Check for flags */
199: if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
200: switch (*cp) {
201: case 'P':
202: *pflag = 1;
203: break;
204: default:
205: error("Invalid flag -%c", *cp);
206: return(-1);
207: }
208: cp += 2;
209: *cpp = cp + strspn(cp, WHITESPACE);
210: }
211:
212: return(0);
213: }
214:
215: int
216: get_pathname(const char **cpp, char **path)
217: {
218: const char *quot, *cp = *cpp;
219: int i;
220:
221: cp += strspn(cp, WHITESPACE);
222: if (!*cp) {
223: *cpp = cp;
224: *path = NULL;
225: return(0);
226: }
227:
228: /* Check for quoted filenames */
229: if (*cp == '\"' || *cp == '\'') {
230: quot = cp++;
231: for(i = 0; cp[i] && cp[i] != *quot; i++)
232: ;
233: if (!cp[i]) {
234: error("Unterminated quote");
235: *path = NULL;
236: return(-1);
237: }
238: if (i == 0) {
239: error("Empty quotes");
240: *path = NULL;
241: return(-1);
242: }
243: *path = xmalloc(i + 1);
244: memcpy(*path, cp, i);
245: (*path)[i] = '\0';
246: cp += i + 1;
247: *cpp = cp + strspn(cp, WHITESPACE);
248: return(0);
249: }
250:
251: /* Read to end of filename */
252: for(i = 0; cp[i] && cp[i] != ' '; i++)
253: ;
254:
255: *path = xmalloc(i + 1);
256: memcpy(*path, cp, i);
257: (*path)[i] = '\0';
258: cp += i;
259: *cpp = cp + strspn(cp, WHITESPACE);
260:
261: return(0);
262: }
263:
264: int
265: infer_path(const char *p, char **ifp)
266: {
267: char *cp;
268:
269: debug("XXX: P = \"%s\"", p);
270:
271: cp = strrchr(p, '/');
272:
273: if (cp == NULL) {
274: *ifp = xstrdup(p);
275: return(0);
276: }
277:
278: if (!cp[1]) {
279: error("Invalid path");
280: return(-1);
281: }
282:
283: *ifp = xstrdup(cp + 1);
284: return(0);
285: }
286:
287: int
288: parse_args(const char **cpp, int *pflag, unsigned long *n_arg,
289: char **path1, char **path2)
290: {
291: const char *cmd, *cp = *cpp;
1.4 markus 292: int base = 0;
1.1 djm 293: int i, cmdnum;
294:
295: /* Skip leading whitespace */
296: cp = cp + strspn(cp, WHITESPACE);
297:
298: /* Ignore blank lines */
299: if (!*cp)
300: return(-1);
301:
302: /* Figure out which command we have */
303: for(i = 0; cmds[i].c; i++) {
304: int cmdlen = strlen(cmds[i].c);
305:
306: /* Check for command followed by whitespace */
307: if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
308: strchr(WHITESPACE, cp[cmdlen])) {
309: cp += cmdlen;
310: cp = cp + strspn(cp, WHITESPACE);
311: break;
312: }
313: }
314: cmdnum = cmds[i].n;
315: cmd = cmds[i].c;
316:
317: /* Special case */
318: if (*cp == '!') {
319: cp++;
320: cmdnum = I_SHELL;
321: } else if (cmdnum == -1) {
322: error("Invalid command.");
323: return(-1);
324: }
325:
326: /* Get arguments and parse flags */
327: *pflag = *n_arg = 0;
328: *path1 = *path2 = NULL;
329: switch (cmdnum) {
330: case I_GET:
331: case I_PUT:
332: if (parse_getput_flags(&cp, pflag))
333: return(-1);
334: /* Get first pathname (mandatory) */
335: if (get_pathname(&cp, path1))
336: return(-1);
337: if (*path1 == NULL) {
338: error("You must specify at least one path after a "
339: "%s command.", cmd);
340: return(-1);
341: }
342: /* Try to get second pathname (optional) */
343: if (get_pathname(&cp, path2))
344: return(-1);
345: /* Otherwise try to guess it from first path */
346: if (*path2 == NULL && infer_path(*path1, path2))
347: return(-1);
348: break;
349: case I_RENAME:
350: /* Get first pathname (mandatory) */
351: if (get_pathname(&cp, path1))
352: return(-1);
353: if (get_pathname(&cp, path2))
354: return(-1);
355: if (!*path1 || !*path2) {
356: error("You must specify two paths after a %s "
357: "command.", cmd);
358: return(-1);
359: }
360: break;
361: case I_RM:
362: case I_MKDIR:
363: case I_RMDIR:
364: case I_CHDIR:
365: case I_LCHDIR:
366: case I_LMKDIR:
367: /* Get pathname (mandatory) */
368: if (get_pathname(&cp, path1))
369: return(-1);
370: if (*path1 == NULL) {
1.3 stevesk 371: error("You must specify a path after a %s command.",
1.1 djm 372: cmd);
373: return(-1);
374: }
375: break;
376: case I_LS:
377: /* Path is optional */
378: if (get_pathname(&cp, path1))
379: return(-1);
380: break;
381: case I_LLS:
382: case I_SHELL:
383: /* Uses the rest of the line */
384: break;
385: case I_LUMASK:
386: case I_CHMOD:
1.4 markus 387: base = 8;
1.1 djm 388: case I_CHOWN:
389: case I_CHGRP:
390: /* Get numeric arg (mandatory) */
391: if (*cp < '0' && *cp > '9') {
392: error("You must supply a numeric argument "
393: "to the %s command.", cmd);
394: return(-1);
395: }
1.4 markus 396: *n_arg = strtoul(cp, (char**)&cp, base);
1.1 djm 397: if (!*cp || !strchr(WHITESPACE, *cp)) {
398: error("You must supply a numeric argument "
399: "to the %s command.", cmd);
400: return(-1);
401: }
402: cp += strspn(cp, WHITESPACE);
403:
404: /* Get pathname (mandatory) */
405: if (get_pathname(&cp, path1))
406: return(-1);
407: if (*path1 == NULL) {
1.3 stevesk 408: error("You must specify a path after a %s command.",
1.1 djm 409: cmd);
410: return(-1);
411: }
412: break;
413: case I_QUIT:
414: case I_PWD:
415: case I_LPWD:
416: case I_HELP:
417: break;
418: default:
419: fatal("Command not implemented");
420: }
421:
422: *cpp = cp;
423:
424: return(cmdnum);
425: }
426:
427: int
428: parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
429: {
430: char *path1, *path2;
431: int pflag, cmdnum;
432: unsigned long n_arg;
433: Attrib a, *aa;
434: char path_buf[PATH_MAX];
435:
436: path1 = path2 = NULL;
437: cmdnum = parse_args(&cmd, &pflag, &n_arg, &path1, &path2);
438:
439: /* Perform command */
440: switch (cmdnum) {
441: case -1:
442: break;
443: case I_GET:
444: path1 = make_absolute(path1, *pwd);
445: do_download(in, out, path1, path2, pflag);
446: break;
447: case I_PUT:
448: path2 = make_absolute(path2, *pwd);
449: do_upload(in, out, path1, path2, pflag);
450: break;
451: case I_RENAME:
452: path1 = make_absolute(path1, *pwd);
453: path2 = make_absolute(path2, *pwd);
454: do_rename(in, out, path1, path2);
455: break;
456: case I_RM:
457: path1 = make_absolute(path1, *pwd);
458: do_rm(in, out, path1);
459: break;
460: case I_MKDIR:
461: path1 = make_absolute(path1, *pwd);
462: attrib_clear(&a);
463: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
464: a.perm = 0777;
465: do_mkdir(in, out, path1, &a);
466: break;
467: case I_RMDIR:
468: path1 = make_absolute(path1, *pwd);
469: do_rmdir(in, out, path1);
470: break;
471: case I_CHDIR:
472: path1 = make_absolute(path1, *pwd);
473: xfree(*pwd);
474: *pwd = do_realpath(in, out, path1);
475: break;
476: case I_LS:
477: path1 = make_absolute(path1, *pwd);
478: do_ls(in, out, path1?path1:*pwd);
479: break;
480: case I_LCHDIR:
481: if (chdir(path1) == -1)
482: error("Couldn't change local directory to "
483: "\"%s\": %s", path1, strerror(errno));
484: break;
485: case I_LMKDIR:
486: if (mkdir(path1, 0777) == -1)
487: error("Couldn't create local directory to "
488: "\"%s\": %s", path1, strerror(errno));
489: break;
490: case I_LLS:
491: local_do_ls(cmd);
492: break;
493: case I_SHELL:
494: local_do_shell(cmd);
495: break;
496: case I_LUMASK:
497: umask(n_arg);
498: break;
499: case I_CHMOD:
500: path1 = make_absolute(path1, *pwd);
501: attrib_clear(&a);
502: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
503: a.perm = n_arg;
504: do_setstat(in, out, path1, &a);
1.5 ! stevesk 505: break;
1.1 djm 506: case I_CHOWN:
507: path1 = make_absolute(path1, *pwd);
508: aa = do_stat(in, out, path1);
1.5 ! stevesk 509: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1.1 djm 510: error("Can't get current ownership of "
511: "remote file \"%s\"", path1);
512: break;
513: }
514: aa->uid = n_arg;
515: do_setstat(in, out, path1, aa);
516: break;
517: case I_CHGRP:
518: path1 = make_absolute(path1, *pwd);
519: aa = do_stat(in, out, path1);
1.5 ! stevesk 520: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1.1 djm 521: error("Can't get current ownership of "
522: "remote file \"%s\"", path1);
523: break;
524: }
525: aa->gid = n_arg;
526: do_setstat(in, out, path1, aa);
527: break;
528: case I_PWD:
529: printf("Remote working directory: %s\n", *pwd);
530: break;
531: case I_LPWD:
532: if (!getcwd(path_buf, sizeof(path_buf)))
533: error("Couldn't get local cwd: %s\n",
534: strerror(errno));
535: else
536: printf("Local working directory: %s\n",
537: path_buf);
538: break;
539: case I_QUIT:
540: return(-1);
541: case I_HELP:
542: help();
543: break;
544: default:
545: fatal("%d is not implemented", cmdnum);
546: }
547:
548: if (path1)
549: xfree(path1);
550: if (path2)
551: xfree(path2);
552:
553: return(0);
554: }
555:
556: void
557: interactive_loop(int fd_in, int fd_out)
558: {
559: char *pwd;
560: char cmd[2048];
561:
562: pwd = do_realpath(fd_in, fd_out, ".");
563: if (pwd == NULL)
564: fatal("Need cwd");
565:
566: setlinebuf(stdout);
567: setlinebuf(stdin);
568:
569: for(;;) {
570: char *cp;
571:
572: printf("sftp> ");
573:
574: /* XXX: use libedit */
575: if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
576: printf("\n");
577: break;
578: }
579: cp = strrchr(cmd, '\n');
580: if (cp)
581: *cp = '\0';
582: if (parse_dispatch_command(fd_in, fd_out, cmd, &pwd))
583: break;
584: }
585: xfree(pwd);
586: }