2 * Copyright (c) 2001 Damien Miller. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
25 /* XXX: finish implementation of all commands */
26 /* XXX: do fnmatch() instead of using raw pathname */
28 /* XXX: recursive operations */
31 RCSID("$OpenBSD: sftp-int.c,v 1.25 2001/03/06 06:11:44 deraadt Exp $");
36 #include "pathnames.h"
39 #include "sftp-common.h"
40 #include "sftp-client.h"
45 /* Seperators for interactive commands */
46 #define WHITESPACE " \t\r\n"
48 /* Commands for interactive mode */
75 const struct CMD cmds[] = {
86 { "lchdir", I_LCHDIR },
88 { "lmkdir", I_LMKDIR },
91 { "lumask", I_LUMASK },
96 { "rename", I_RENAME },
107 printf("Available commands:\n");
108 printf("cd path Change remote directory to 'path'\n");
109 printf("lcd path Change local directory to 'path'\n");
110 printf("chgrp grp path Change group of file 'path' to 'grp'\n");
111 printf("chmod mode path Change permissions of file 'path' to 'mode'\n");
112 printf("chown own path Change owner of file 'path' to 'own'\n");
113 printf("help Display this help text\n");
114 printf("get remote-path [local-path] Download file\n");
115 printf("lls [ls-options [path]] Display local directory listing\n");
116 printf("lmkdir path Create local directory\n");
117 printf("lpwd Print local working directory\n");
118 printf("ls [path] Display remote directory listing\n");
119 printf("lumask umask Set local umask to 'umask'\n");
120 printf("mkdir path Create remote directory\n");
121 printf("put local-path [remote-path] Upload file\n");
122 printf("pwd Display remote working directory\n");
123 printf("exit Quit sftp\n");
124 printf("quit Quit sftp\n");
125 printf("rename oldpath newpath Rename remote file\n");
126 printf("rmdir path Remove remote directory\n");
127 printf("rm path Delete remote file\n");
128 printf("!command Execute 'command' in local shell\n");
129 printf("! Escape to local shell\n");
130 printf("? Synonym for help\n");
134 local_do_shell(const char *args)
143 if ((shell = getenv("SHELL")) == NULL)
144 shell = _PATH_BSHELL;
146 if ((pid = fork()) == -1)
147 fatal("Couldn't fork: %s", strerror(errno));
150 /* XXX: child has pipe fds to ssh subproc open - issue? */
152 debug3("Executing %s -c \"%s\"", shell, args);
153 ret = execl(shell, shell, "-c", args, NULL);
155 debug3("Executing %s", shell);
156 ret = execl(shell, shell, NULL);
158 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
162 if (waitpid(pid, &status, 0) == -1)
163 fatal("Couldn't wait for child: %s", strerror(errno));
164 if (!WIFEXITED(status))
165 error("Shell exited abormally");
166 else if (WEXITSTATUS(status))
167 error("Shell exited with status %d", WEXITSTATUS(status));
171 local_do_ls(const char *args)
174 local_do_shell(_PATH_LS);
176 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
177 char *buf = xmalloc(len);
179 /* XXX: quoting - rip quoting code from ftp? */
180 snprintf(buf, len, _PATH_LS " %s", args);
187 make_absolute(char *p, char *pwd)
192 if (p && p[0] != '/') {
193 snprintf(buf, sizeof(buf), "%s/%s", pwd, p);
202 parse_getput_flags(const char **cpp, int *pflag)
204 const char *cp = *cpp;
206 /* Check for flags */
207 if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
214 error("Invalid flag -%c", cp[1]);
218 *cpp = cp + strspn(cp, WHITESPACE);
225 get_pathname(const char **cpp, char **path)
227 const char *cp = *cpp, *end;
231 cp += strspn(cp, WHITESPACE);
238 /* Check for quoted filenames */
239 if (*cp == '\"' || *cp == '\'') {
242 end = strchr(cp, quot);
244 error("Unterminated quote");
248 error("Empty quotes");
251 *cpp = end + 1 + strspn(end + 1, WHITESPACE);
253 /* Read to end of filename */
254 end = strpbrk(cp, WHITESPACE);
256 end = strchr(cp, '\0');
257 *cpp = end + strspn(end, WHITESPACE);
262 *path = xmalloc(i + 1);
263 memcpy(*path, cp, i);
273 infer_path(const char *p, char **ifp)
277 debug("XXX: P = \"%s\"", p);
279 cp = strrchr(p, '/');
286 error("Invalid path");
290 *ifp = xstrdup(cp + 1);
295 parse_args(const char **cpp, int *pflag, unsigned long *n_arg,
296 char **path1, char **path2)
298 const char *cmd, *cp = *cpp;
304 /* Skip leading whitespace */
305 cp = cp + strspn(cp, WHITESPACE);
307 /* Ignore blank lines */
311 /* Figure out which command we have */
312 for(i = 0; cmds[i].c; i++) {
313 int cmdlen = strlen(cmds[i].c);
315 /* Check for command followed by whitespace */
316 if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
317 strchr(WHITESPACE, cp[cmdlen])) {
319 cp = cp + strspn(cp, WHITESPACE);
330 } else if (cmdnum == -1) {
331 error("Invalid command.");
335 /* Get arguments and parse flags */
337 *path1 = *path2 = NULL;
341 if (parse_getput_flags(&cp, pflag))
343 /* Get first pathname (mandatory) */
344 if (get_pathname(&cp, path1))
346 if (*path1 == NULL) {
347 error("You must specify at least one path after a "
351 /* Try to get second pathname (optional) */
352 if (get_pathname(&cp, path2))
354 /* Otherwise try to guess it from first path */
355 if (*path2 == NULL && infer_path(*path1, path2))
359 /* Get first pathname (mandatory) */
360 if (get_pathname(&cp, path1))
362 if (get_pathname(&cp, path2))
364 if (!*path1 || !*path2) {
365 error("You must specify two paths after a %s "
376 /* Get pathname (mandatory) */
377 if (get_pathname(&cp, path1))
379 if (*path1 == NULL) {
380 error("You must specify a path after a %s command.",
386 /* Path is optional */
387 if (get_pathname(&cp, path1))
392 /* Uses the rest of the line */
400 /* Get numeric arg (mandatory) */
401 l = strtol(cp, &cp2, base);
402 if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) &&
403 errno == ERANGE) || l < 0) {
404 error("You must supply a numeric argument "
405 "to the %s command.", cmd);
410 if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp))
412 if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
413 error("You must supply a numeric argument "
414 "to the %s command.", cmd);
417 cp += strspn(cp, WHITESPACE);
419 /* Get pathname (mandatory) */
420 if (get_pathname(&cp, path1))
422 if (*path1 == NULL) {
423 error("You must specify a path after a %s command.",
434 fatal("Command not implemented");
442 parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
444 char *path1, *path2, *tmp;
448 char path_buf[MAXPATHLEN];
451 path1 = path2 = NULL;
452 cmdnum = parse_args(&cmd, &pflag, &n_arg, &path1, &path2);
454 /* Perform command */
459 path1 = make_absolute(path1, *pwd);
460 err = do_download(in, out, path1, path2, pflag);
463 path2 = make_absolute(path2, *pwd);
464 err = do_upload(in, out, path1, path2, pflag);
467 path1 = make_absolute(path1, *pwd);
468 path2 = make_absolute(path2, *pwd);
469 err = do_rename(in, out, path1, path2);
472 path1 = make_absolute(path1, *pwd);
473 err = do_rm(in, out, path1);
476 path1 = make_absolute(path1, *pwd);
478 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
480 err = do_mkdir(in, out, path1, &a);
483 path1 = make_absolute(path1, *pwd);
484 err = do_rmdir(in, out, path1);
487 path1 = make_absolute(path1, *pwd);
488 if ((tmp = do_realpath(in, out, path1)) == NULL) {
492 if ((aa = do_stat(in, out, tmp)) == NULL) {
497 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
498 error("Can't change directory: Can't check target");
503 if (!S_ISDIR(aa->perm)) {
504 error("Can't change directory: \"%s\" is not "
515 do_ls(in, out, *pwd);
518 path1 = make_absolute(path1, *pwd);
519 if ((tmp = do_realpath(in, out, path1)) == NULL)
523 if ((aa = do_stat(in, out, path1)) == NULL)
525 if ((aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
526 !S_ISDIR(aa->perm)) {
527 error("Can't ls: \"%s\" is not a directory", path1);
530 do_ls(in, out, path1);
533 if (chdir(path1) == -1) {
534 error("Couldn't change local directory to "
535 "\"%s\": %s", path1, strerror(errno));
540 if (mkdir(path1, 0777) == -1) {
541 error("Couldn't create local directory "
542 "\"%s\": %s", path1, strerror(errno));
554 printf("Local umask: %03lo\n", n_arg);
557 path1 = make_absolute(path1, *pwd);
559 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
561 do_setstat(in, out, path1, &a);
564 path1 = make_absolute(path1, *pwd);
565 if (!(aa = do_stat(in, out, path1)))
567 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
568 error("Can't get current ownership of "
569 "remote file \"%s\"", path1);
572 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
574 do_setstat(in, out, path1, aa);
577 path1 = make_absolute(path1, *pwd);
578 if (!(aa = do_stat(in, out, path1)))
580 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
581 error("Can't get current ownership of "
582 "remote file \"%s\"", path1);
585 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
587 do_setstat(in, out, path1, aa);
590 printf("Remote working directory: %s\n", *pwd);
593 if (!getcwd(path_buf, sizeof(path_buf)))
594 error("Couldn't get local cwd: %s",
597 printf("Local working directory: %s\n",
606 fatal("%d is not implemented", cmdnum);
614 /* If an error occurs in batch mode we should abort. */
615 if (infile != stdin && err > 0)
622 interactive_loop(int fd_in, int fd_out)
627 pwd = do_realpath(fd_in, fd_out, ".");
631 setvbuf(stdout, NULL, _IOLBF, 0);
632 setvbuf(infile, NULL, _IOLBF, 0);
639 /* XXX: use libedit */
640 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
643 } else if (infile != stdin) /* Bluff typing */
646 cp = strrchr(cmd, '\n');
650 if (parse_dispatch_command(fd_in, fd_out, cmd, &pwd))