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