Annotation of src/usr.bin/ssh/sftp.c, Revision 1.106
1.106 ! sobrado 1: /* $OpenBSD: sftp.c,v 1.105 2008/12/09 03:04:39 djm Exp $ */
1.1 djm 2: /*
1.42 djm 3: * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
1.1 djm 4: *
1.42 djm 5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the above
7: * copyright notice and this permission notice appear in all copies.
1.1 djm 8: *
1.42 djm 9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1.1 djm 16: */
17:
1.91 deraadt 18: #include <sys/types.h>
1.72 stevesk 19: #include <sys/ioctl.h>
1.73 stevesk 20: #include <sys/wait.h>
1.75 stevesk 21: #include <sys/stat.h>
1.83 stevesk 22: #include <sys/socket.h>
1.88 stevesk 23: #include <sys/param.h>
1.100 djm 24: #include <sys/statvfs.h>
1.44 djm 25:
1.97 djm 26: #include <ctype.h>
1.85 stevesk 27: #include <errno.h>
1.44 djm 28: #include <glob.h>
1.57 djm 29: #include <histedit.h>
1.71 stevesk 30: #include <paths.h>
1.74 stevesk 31: #include <signal.h>
1.89 stevesk 32: #include <stdlib.h>
1.90 stevesk 33: #include <stdio.h>
1.87 stevesk 34: #include <string.h>
1.86 stevesk 35: #include <unistd.h>
1.100 djm 36: #include <util.h>
1.91 deraadt 37: #include <stdarg.h>
1.1 djm 38:
39: #include "xmalloc.h"
40: #include "log.h"
41: #include "pathnames.h"
1.16 mouring 42: #include "misc.h"
1.1 djm 43:
44: #include "sftp.h"
1.91 deraadt 45: #include "buffer.h"
1.1 djm 46: #include "sftp-common.h"
47: #include "sftp-client.h"
1.43 djm 48:
1.44 djm 49: /* File to read commands from */
50: FILE* infile;
1.15 mouring 51:
1.44 djm 52: /* Are we in batchfile mode? */
1.39 djm 53: int batchmode = 0;
1.44 djm 54:
55: /* Size of buffer used when copying files */
1.24 djm 56: size_t copy_buffer_len = 32768;
1.44 djm 57:
58: /* Number of concurrent outstanding requests */
1.103 djm 59: size_t num_requests = 64;
1.44 djm 60:
61: /* PID of ssh transport process */
1.36 djm 62: static pid_t sshpid = -1;
1.7 markus 63:
1.44 djm 64: /* This is set to 0 if the progressmeter is not desired. */
1.45 djm 65: int showprogress = 1;
1.44 djm 66:
1.46 djm 67: /* SIGINT received during command processing */
68: volatile sig_atomic_t interrupted = 0;
69:
1.52 djm 70: /* I wish qsort() took a separate ctx for the comparison function...*/
71: int sort_flag;
72:
1.44 djm 73: int remote_glob(struct sftp_conn *, const char *, int,
74: int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
75:
76: /* Separators for interactive commands */
77: #define WHITESPACE " \t\r\n"
78:
1.52 djm 79: /* ls flags */
1.53 djm 80: #define LS_LONG_VIEW 0x01 /* Full view ala ls -l */
81: #define LS_SHORT_VIEW 0x02 /* Single row view ala ls -1 */
82: #define LS_NUMERIC_VIEW 0x04 /* Long view with numeric uid/gid */
83: #define LS_NAME_SORT 0x08 /* Sort by name (default) */
84: #define LS_TIME_SORT 0x10 /* Sort by mtime */
85: #define LS_SIZE_SORT 0x20 /* Sort by file size */
86: #define LS_REVERSE_SORT 0x40 /* Reverse sort order */
1.54 djm 87: #define LS_SHOW_ALL 0x80 /* Don't skip filenames starting with '.' */
1.52 djm 88:
1.53 djm 89: #define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW)
90: #define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
1.44 djm 91:
92: /* Commands for interactive mode */
93: #define I_CHDIR 1
94: #define I_CHGRP 2
95: #define I_CHMOD 3
96: #define I_CHOWN 4
1.100 djm 97: #define I_DF 24
1.44 djm 98: #define I_GET 5
99: #define I_HELP 6
100: #define I_LCHDIR 7
101: #define I_LLS 8
102: #define I_LMKDIR 9
103: #define I_LPWD 10
104: #define I_LS 11
105: #define I_LUMASK 12
106: #define I_MKDIR 13
107: #define I_PUT 14
108: #define I_PWD 15
109: #define I_QUIT 16
110: #define I_RENAME 17
111: #define I_RM 18
112: #define I_RMDIR 19
113: #define I_SHELL 20
114: #define I_SYMLINK 21
115: #define I_VERSION 22
116: #define I_PROGRESS 23
117:
118: struct CMD {
119: const char *c;
120: const int n;
121: };
122:
123: static const struct CMD cmds[] = {
124: { "bye", I_QUIT },
125: { "cd", I_CHDIR },
126: { "chdir", I_CHDIR },
127: { "chgrp", I_CHGRP },
128: { "chmod", I_CHMOD },
129: { "chown", I_CHOWN },
1.100 djm 130: { "df", I_DF },
1.44 djm 131: { "dir", I_LS },
132: { "exit", I_QUIT },
133: { "get", I_GET },
134: { "mget", I_GET },
135: { "help", I_HELP },
136: { "lcd", I_LCHDIR },
137: { "lchdir", I_LCHDIR },
138: { "lls", I_LLS },
139: { "lmkdir", I_LMKDIR },
140: { "ln", I_SYMLINK },
141: { "lpwd", I_LPWD },
142: { "ls", I_LS },
143: { "lumask", I_LUMASK },
144: { "mkdir", I_MKDIR },
145: { "progress", I_PROGRESS },
146: { "put", I_PUT },
147: { "mput", I_PUT },
148: { "pwd", I_PWD },
149: { "quit", I_QUIT },
150: { "rename", I_RENAME },
151: { "rm", I_RM },
152: { "rmdir", I_RMDIR },
153: { "symlink", I_SYMLINK },
154: { "version", I_VERSION },
155: { "!", I_SHELL },
156: { "?", I_HELP },
157: { NULL, -1}
158: };
159:
160: int interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
161:
1.96 stevesk 162: /* ARGSUSED */
1.44 djm 163: static void
1.46 djm 164: killchild(int signo)
165: {
1.61 dtucker 166: if (sshpid > 1) {
1.46 djm 167: kill(sshpid, SIGTERM);
1.61 dtucker 168: waitpid(sshpid, NULL, 0);
169: }
1.46 djm 170:
171: _exit(1);
172: }
173:
1.96 stevesk 174: /* ARGSUSED */
1.46 djm 175: static void
176: cmd_interrupt(int signo)
177: {
178: const char msg[] = "\rInterrupt \n";
1.59 djm 179: int olderrno = errno;
1.46 djm 180:
181: write(STDERR_FILENO, msg, sizeof(msg) - 1);
182: interrupted = 1;
1.59 djm 183: errno = olderrno;
1.46 djm 184: }
185:
186: static void
1.44 djm 187: help(void)
188: {
1.106 ! sobrado 189: printf("Available commands:\n"
! 190: "bye Quit sftp\n"
! 191: "cd path Change remote directory to 'path'\n"
! 192: "chgrp grp path Change group of file 'path' to 'grp'\n"
! 193: "chmod mode path Change permissions of file 'path' to 'mode'\n"
! 194: "chown own path Change owner of file 'path' to 'own'\n"
! 195: "df [-hi] [path] Display statistics for current directory or\n"
! 196: " filesystem containing 'path'\n"
! 197: "exit Quit sftp\n"
! 198: "get [-P] remote-path [local-path] Download file\n"
! 199: "help Display this help text\n"
! 200: "lcd path Change local directory to 'path'\n"
! 201: "lls [ls-options [path]] Display local directory listing\n"
! 202: "lmkdir path Create local directory\n"
! 203: "ln oldpath newpath Symlink remote file\n"
! 204: "lpwd Print local working directory\n"
! 205: "ls [-1aflnrSt] [path] Display remote directory listing\n"
! 206: "lumask umask Set local umask to 'umask'\n"
! 207: "mkdir path Create remote directory\n"
! 208: "progress Toggle display of progress meter\n"
! 209: "put [-P] local-path [remote-path] Upload file\n"
! 210: "pwd Display remote working directory\n"
! 211: "quit Quit sftp\n"
! 212: "rename oldpath newpath Rename remote file\n"
! 213: "rm path Delete remote file\n"
! 214: "rmdir path Remove remote directory\n"
! 215: "symlink oldpath newpath Symlink remote file\n"
! 216: "version Show SFTP version\n"
! 217: "!command Execute 'command' in local shell\n"
! 218: "! Escape to local shell\n"
! 219: "? Synonym for help\n");
1.44 djm 220: }
221:
222: static void
223: local_do_shell(const char *args)
224: {
225: int status;
226: char *shell;
227: pid_t pid;
228:
229: if (!*args)
230: args = NULL;
231:
232: if ((shell = getenv("SHELL")) == NULL)
233: shell = _PATH_BSHELL;
234:
235: if ((pid = fork()) == -1)
236: fatal("Couldn't fork: %s", strerror(errno));
237:
238: if (pid == 0) {
239: /* XXX: child has pipe fds to ssh subproc open - issue? */
240: if (args) {
241: debug3("Executing %s -c \"%s\"", shell, args);
242: execl(shell, shell, "-c", args, (char *)NULL);
243: } else {
244: debug3("Executing %s", shell);
245: execl(shell, shell, (char *)NULL);
246: }
247: fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
248: strerror(errno));
249: _exit(1);
250: }
251: while (waitpid(pid, &status, 0) == -1)
252: if (errno != EINTR)
253: fatal("Couldn't wait for child: %s", strerror(errno));
254: if (!WIFEXITED(status))
1.78 djm 255: error("Shell exited abnormally");
1.44 djm 256: else if (WEXITSTATUS(status))
257: error("Shell exited with status %d", WEXITSTATUS(status));
258: }
259:
260: static void
261: local_do_ls(const char *args)
262: {
263: if (!args || !*args)
264: local_do_shell(_PATH_LS);
265: else {
266: int len = strlen(_PATH_LS " ") + strlen(args) + 1;
267: char *buf = xmalloc(len);
268:
269: /* XXX: quoting - rip quoting code from ftp? */
270: snprintf(buf, len, _PATH_LS " %s", args);
271: local_do_shell(buf);
272: xfree(buf);
273: }
274: }
275:
276: /* Strip one path (usually the pwd) from the start of another */
277: static char *
278: path_strip(char *path, char *strip)
279: {
280: size_t len;
281:
282: if (strip == NULL)
283: return (xstrdup(path));
284:
285: len = strlen(strip);
1.59 djm 286: if (strncmp(path, strip, len) == 0) {
1.44 djm 287: if (strip[len - 1] != '/' && path[len] == '/')
288: len++;
289: return (xstrdup(path + len));
290: }
291:
292: return (xstrdup(path));
293: }
294:
295: static char *
296: path_append(char *p1, char *p2)
297: {
298: char *ret;
1.94 ray 299: size_t len = strlen(p1) + strlen(p2) + 2;
1.44 djm 300:
301: ret = xmalloc(len);
302: strlcpy(ret, p1, len);
1.94 ray 303: if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
1.44 djm 304: strlcat(ret, "/", len);
305: strlcat(ret, p2, len);
306:
307: return(ret);
308: }
309:
310: static char *
311: make_absolute(char *p, char *pwd)
312: {
1.51 avsm 313: char *abs_str;
1.44 djm 314:
315: /* Derelativise */
316: if (p && p[0] != '/') {
1.51 avsm 317: abs_str = path_append(pwd, p);
1.44 djm 318: xfree(p);
1.51 avsm 319: return(abs_str);
1.44 djm 320: } else
321: return(p);
322: }
323:
324: static int
325: infer_path(const char *p, char **ifp)
326: {
327: char *cp;
328:
329: cp = strrchr(p, '/');
330: if (cp == NULL) {
331: *ifp = xstrdup(p);
332: return(0);
333: }
334:
335: if (!cp[1]) {
336: error("Invalid path");
337: return(-1);
338: }
339:
340: *ifp = xstrdup(cp + 1);
341: return(0);
342: }
343:
344: static int
1.97 djm 345: parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag)
1.44 djm 346: {
1.102 martynas 347: extern int opterr, optind, optopt, optreset;
1.97 djm 348: int ch;
1.44 djm 349:
1.97 djm 350: optind = optreset = 1;
351: opterr = 0;
352:
353: *pflag = 0;
354: while ((ch = getopt(argc, argv, "Pp")) != -1) {
355: switch (ch) {
1.44 djm 356: case 'p':
357: case 'P':
358: *pflag = 1;
359: break;
360: default:
1.102 martynas 361: error("%s: Invalid flag -%c", cmd, optopt);
1.97 djm 362: return -1;
1.44 djm 363: }
364: }
365:
1.97 djm 366: return optind;
1.44 djm 367: }
368:
369: static int
1.97 djm 370: parse_ls_flags(char **argv, int argc, int *lflag)
1.44 djm 371: {
1.102 martynas 372: extern int opterr, optind, optopt, optreset;
1.97 djm 373: int ch;
374:
375: optind = optreset = 1;
376: opterr = 0;
1.44 djm 377:
1.53 djm 378: *lflag = LS_NAME_SORT;
1.97 djm 379: while ((ch = getopt(argc, argv, "1Saflnrt")) != -1) {
380: switch (ch) {
381: case '1':
382: *lflag &= ~VIEW_FLAGS;
383: *lflag |= LS_SHORT_VIEW;
384: break;
385: case 'S':
386: *lflag &= ~SORT_FLAGS;
387: *lflag |= LS_SIZE_SORT;
388: break;
389: case 'a':
390: *lflag |= LS_SHOW_ALL;
391: break;
392: case 'f':
393: *lflag &= ~SORT_FLAGS;
394: break;
395: case 'l':
396: *lflag &= ~VIEW_FLAGS;
397: *lflag |= LS_LONG_VIEW;
398: break;
399: case 'n':
400: *lflag &= ~VIEW_FLAGS;
401: *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
402: break;
403: case 'r':
404: *lflag |= LS_REVERSE_SORT;
405: break;
406: case 't':
407: *lflag &= ~SORT_FLAGS;
408: *lflag |= LS_TIME_SORT;
409: break;
410: default:
1.102 martynas 411: error("ls: Invalid flag -%c", optopt);
1.97 djm 412: return -1;
1.44 djm 413: }
414: }
415:
1.97 djm 416: return optind;
1.44 djm 417: }
418:
419: static int
1.100 djm 420: parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
421: {
1.102 martynas 422: extern int opterr, optind, optopt, optreset;
1.100 djm 423: int ch;
424:
425: optind = optreset = 1;
426: opterr = 0;
427:
428: *hflag = *iflag = 0;
429: while ((ch = getopt(argc, argv, "hi")) != -1) {
430: switch (ch) {
431: case 'h':
432: *hflag = 1;
433: break;
434: case 'i':
435: *iflag = 1;
436: break;
437: default:
1.102 martynas 438: error("%s: Invalid flag -%c", cmd, optopt);
1.100 djm 439: return -1;
440: }
441: }
442:
443: return optind;
444: }
445:
446: static int
1.44 djm 447: is_dir(char *path)
448: {
449: struct stat sb;
450:
451: /* XXX: report errors? */
452: if (stat(path, &sb) == -1)
453: return(0);
454:
1.92 otto 455: return(S_ISDIR(sb.st_mode));
1.44 djm 456: }
457:
458: static int
459: remote_is_dir(struct sftp_conn *conn, char *path)
460: {
461: Attrib *a;
462:
463: /* XXX: report errors? */
464: if ((a = do_stat(conn, path, 1)) == NULL)
465: return(0);
466: if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
467: return(0);
1.92 otto 468: return(S_ISDIR(a->perm));
1.44 djm 469: }
470:
471: static int
472: process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
473: {
474: char *abs_src = NULL;
475: char *abs_dst = NULL;
476: char *tmp;
477: glob_t g;
478: int err = 0;
479: int i;
480:
481: abs_src = xstrdup(src);
482: abs_src = make_absolute(abs_src, pwd);
483:
484: memset(&g, 0, sizeof(g));
485: debug3("Looking up %s", abs_src);
486: if (remote_glob(conn, abs_src, 0, NULL, &g)) {
487: error("File \"%s\" not found.", abs_src);
488: err = -1;
489: goto out;
490: }
491:
492: /* If multiple matches, dst must be a directory or unspecified */
493: if (g.gl_matchc > 1 && dst && !is_dir(dst)) {
494: error("Multiple files match, but \"%s\" is not a directory",
495: dst);
496: err = -1;
497: goto out;
498: }
499:
1.46 djm 500: for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1.44 djm 501: if (infer_path(g.gl_pathv[i], &tmp)) {
502: err = -1;
503: goto out;
504: }
505:
506: if (g.gl_matchc == 1 && dst) {
507: /* If directory specified, append filename */
1.82 markus 508: xfree(tmp);
1.44 djm 509: if (is_dir(dst)) {
510: if (infer_path(g.gl_pathv[0], &tmp)) {
511: err = 1;
512: goto out;
513: }
514: abs_dst = path_append(dst, tmp);
515: xfree(tmp);
516: } else
517: abs_dst = xstrdup(dst);
518: } else if (dst) {
519: abs_dst = path_append(dst, tmp);
520: xfree(tmp);
521: } else
522: abs_dst = tmp;
523:
524: printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
525: if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
526: err = -1;
527: xfree(abs_dst);
528: abs_dst = NULL;
529: }
530:
531: out:
532: xfree(abs_src);
533: globfree(&g);
534: return(err);
535: }
536:
537: static int
538: process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
539: {
540: char *tmp_dst = NULL;
541: char *abs_dst = NULL;
542: char *tmp;
543: glob_t g;
544: int err = 0;
545: int i;
1.99 djm 546: struct stat sb;
1.44 djm 547:
548: if (dst) {
549: tmp_dst = xstrdup(dst);
550: tmp_dst = make_absolute(tmp_dst, pwd);
551: }
552:
553: memset(&g, 0, sizeof(g));
554: debug3("Looking up %s", src);
1.99 djm 555: if (glob(src, GLOB_NOCHECK, NULL, &g)) {
1.44 djm 556: error("File \"%s\" not found.", src);
557: err = -1;
558: goto out;
559: }
560:
561: /* If multiple matches, dst may be directory or unspecified */
562: if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) {
563: error("Multiple files match, but \"%s\" is not a directory",
564: tmp_dst);
565: err = -1;
566: goto out;
567: }
568:
1.46 djm 569: for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1.99 djm 570: if (stat(g.gl_pathv[i], &sb) == -1) {
571: err = -1;
572: error("stat %s: %s", g.gl_pathv[i], strerror(errno));
573: continue;
574: }
575:
576: if (!S_ISREG(sb.st_mode)) {
1.44 djm 577: error("skipping non-regular file %s",
578: g.gl_pathv[i]);
579: continue;
580: }
581: if (infer_path(g.gl_pathv[i], &tmp)) {
582: err = -1;
583: goto out;
584: }
585:
586: if (g.gl_matchc == 1 && tmp_dst) {
587: /* If directory specified, append filename */
588: if (remote_is_dir(conn, tmp_dst)) {
589: if (infer_path(g.gl_pathv[0], &tmp)) {
590: err = 1;
591: goto out;
592: }
593: abs_dst = path_append(tmp_dst, tmp);
594: xfree(tmp);
595: } else
596: abs_dst = xstrdup(tmp_dst);
597:
598: } else if (tmp_dst) {
599: abs_dst = path_append(tmp_dst, tmp);
600: xfree(tmp);
601: } else
602: abs_dst = make_absolute(tmp, pwd);
603:
604: printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
605: if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
606: err = -1;
607: }
608:
609: out:
610: if (abs_dst)
611: xfree(abs_dst);
612: if (tmp_dst)
613: xfree(tmp_dst);
614: globfree(&g);
615: return(err);
616: }
617:
618: static int
619: sdirent_comp(const void *aa, const void *bb)
620: {
621: SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
622: SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
1.53 djm 623: int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
1.44 djm 624:
1.52 djm 625: #define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
1.53 djm 626: if (sort_flag & LS_NAME_SORT)
1.52 djm 627: return (rmul * strcmp(a->filename, b->filename));
1.53 djm 628: else if (sort_flag & LS_TIME_SORT)
1.52 djm 629: return (rmul * NCMP(a->a.mtime, b->a.mtime));
1.53 djm 630: else if (sort_flag & LS_SIZE_SORT)
1.52 djm 631: return (rmul * NCMP(a->a.size, b->a.size));
632:
633: fatal("Unknown ls sort type");
1.44 djm 634: }
635:
636: /* sftp ls.1 replacement for directories */
637: static int
638: do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
639: {
1.64 djm 640: int n;
641: u_int c = 1, colspace = 0, columns = 1;
1.44 djm 642: SFTP_DIRENT **d;
643:
644: if ((n = do_readdir(conn, path, &d)) != 0)
645: return (n);
646:
1.53 djm 647: if (!(lflag & LS_SHORT_VIEW)) {
1.64 djm 648: u_int m = 0, width = 80;
1.44 djm 649: struct winsize ws;
650: char *tmp;
651:
652: /* Count entries for sort and find longest filename */
1.54 djm 653: for (n = 0; d[n] != NULL; n++) {
654: if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
655: m = MAX(m, strlen(d[n]->filename));
656: }
1.44 djm 657:
658: /* Add any subpath that also needs to be counted */
659: tmp = path_strip(path, strip_path);
660: m += strlen(tmp);
661: xfree(tmp);
662:
663: if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
664: width = ws.ws_col;
665:
666: columns = width / (m + 2);
667: columns = MAX(columns, 1);
668: colspace = width / columns;
669: colspace = MIN(colspace, width);
670: }
671:
1.52 djm 672: if (lflag & SORT_FLAGS) {
1.68 dtucker 673: for (n = 0; d[n] != NULL; n++)
674: ; /* count entries */
1.53 djm 675: sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
1.52 djm 676: qsort(d, n, sizeof(*d), sdirent_comp);
677: }
1.44 djm 678:
1.46 djm 679: for (n = 0; d[n] != NULL && !interrupted; n++) {
1.44 djm 680: char *tmp, *fname;
1.54 djm 681:
682: if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
683: continue;
1.44 djm 684:
685: tmp = path_append(path, d[n]->filename);
686: fname = path_strip(tmp, strip_path);
687: xfree(tmp);
688:
1.53 djm 689: if (lflag & LS_LONG_VIEW) {
690: if (lflag & LS_NUMERIC_VIEW) {
1.50 djm 691: char *lname;
692: struct stat sb;
693:
694: memset(&sb, 0, sizeof(sb));
695: attrib_to_stat(&d[n]->a, &sb);
696: lname = ls_file(fname, &sb, 1);
697: printf("%s\n", lname);
698: xfree(lname);
699: } else
700: printf("%s\n", d[n]->longname);
1.44 djm 701: } else {
702: printf("%-*s", colspace, fname);
703: if (c >= columns) {
704: printf("\n");
705: c = 1;
706: } else
707: c++;
708: }
709:
710: xfree(fname);
711: }
712:
1.53 djm 713: if (!(lflag & LS_LONG_VIEW) && (c != 1))
1.44 djm 714: printf("\n");
715:
716: free_sftp_dirents(d);
717: return (0);
718: }
719:
720: /* sftp ls.1 replacement which handles path globs */
721: static int
722: do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
723: int lflag)
724: {
725: glob_t g;
1.64 djm 726: u_int i, c = 1, colspace = 0, columns = 1;
1.60 fgsch 727: Attrib *a = NULL;
1.44 djm 728:
729: memset(&g, 0, sizeof(g));
730:
731: if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
1.60 fgsch 732: NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
733: if (g.gl_pathc)
734: globfree(&g);
1.44 djm 735: error("Can't ls: \"%s\" not found", path);
736: return (-1);
737: }
738:
1.46 djm 739: if (interrupted)
740: goto out;
741:
1.44 djm 742: /*
1.60 fgsch 743: * If the glob returns a single match and it is a directory,
744: * then just list its contents.
1.44 djm 745: */
1.60 fgsch 746: if (g.gl_matchc == 1) {
747: if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
1.44 djm 748: globfree(&g);
749: return (-1);
750: }
751: if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
752: S_ISDIR(a->perm)) {
1.60 fgsch 753: int err;
754:
755: err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
1.44 djm 756: globfree(&g);
1.60 fgsch 757: return (err);
1.44 djm 758: }
759: }
760:
1.53 djm 761: if (!(lflag & LS_SHORT_VIEW)) {
1.64 djm 762: u_int m = 0, width = 80;
1.44 djm 763: struct winsize ws;
764:
765: /* Count entries for sort and find longest filename */
766: for (i = 0; g.gl_pathv[i]; i++)
767: m = MAX(m, strlen(g.gl_pathv[i]));
768:
769: if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
770: width = ws.ws_col;
771:
772: columns = width / (m + 2);
773: columns = MAX(columns, 1);
774: colspace = width / columns;
775: }
776:
1.60 fgsch 777: for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
1.44 djm 778: char *fname;
779:
780: fname = path_strip(g.gl_pathv[i], strip_path);
781:
1.53 djm 782: if (lflag & LS_LONG_VIEW) {
1.44 djm 783: char *lname;
784: struct stat sb;
785:
786: /*
787: * XXX: this is slow - 1 roundtrip per path
788: * A solution to this is to fork glob() and
789: * build a sftp specific version which keeps the
790: * attribs (which currently get thrown away)
791: * that the server returns as well as the filenames.
792: */
793: memset(&sb, 0, sizeof(sb));
1.60 fgsch 794: if (a == NULL)
795: a = do_lstat(conn, g.gl_pathv[i], 1);
1.44 djm 796: if (a != NULL)
797: attrib_to_stat(a, &sb);
798: lname = ls_file(fname, &sb, 1);
799: printf("%s\n", lname);
800: xfree(lname);
801: } else {
802: printf("%-*s", colspace, fname);
803: if (c >= columns) {
804: printf("\n");
805: c = 1;
806: } else
807: c++;
808: }
809: xfree(fname);
810: }
811:
1.53 djm 812: if (!(lflag & LS_LONG_VIEW) && (c != 1))
1.44 djm 813: printf("\n");
814:
1.46 djm 815: out:
1.44 djm 816: if (g.gl_pathc)
817: globfree(&g);
818:
819: return (0);
820: }
821:
1.100 djm 822: static int
823: do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
824: {
1.101 dtucker 825: struct sftp_statvfs st;
1.100 djm 826: char s_used[FMT_SCALED_STRSIZE];
827: char s_avail[FMT_SCALED_STRSIZE];
828: char s_root[FMT_SCALED_STRSIZE];
829: char s_total[FMT_SCALED_STRSIZE];
830:
831: if (do_statvfs(conn, path, &st, 1) == -1)
832: return -1;
833: if (iflag) {
834: printf(" Inodes Used Avail "
835: "(root) %%Capacity\n");
836: printf("%11llu %11llu %11llu %11llu %3llu%%\n",
837: (unsigned long long)st.f_files,
838: (unsigned long long)(st.f_files - st.f_ffree),
839: (unsigned long long)st.f_favail,
840: (unsigned long long)st.f_ffree,
841: (unsigned long long)(100 * (st.f_files - st.f_ffree) /
842: st.f_files));
843: } else if (hflag) {
844: strlcpy(s_used, "error", sizeof(s_used));
845: strlcpy(s_avail, "error", sizeof(s_avail));
846: strlcpy(s_root, "error", sizeof(s_root));
847: strlcpy(s_total, "error", sizeof(s_total));
848: fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
849: fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
850: fmt_scaled(st.f_bfree * st.f_frsize, s_root);
851: fmt_scaled(st.f_blocks * st.f_frsize, s_total);
852: printf(" Size Used Avail (root) %%Capacity\n");
853: printf("%7sB %7sB %7sB %7sB %3llu%%\n",
854: s_total, s_used, s_avail, s_root,
855: (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
856: st.f_blocks));
857: } else {
858: printf(" Size Used Avail "
859: "(root) %%Capacity\n");
860: printf("%12llu %12llu %12llu %12llu %3llu%%\n",
861: (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
862: (unsigned long long)(st.f_frsize *
863: (st.f_blocks - st.f_bfree) / 1024),
864: (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
865: (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
866: (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
867: st.f_blocks));
868: }
869: return 0;
870: }
871:
1.97 djm 872: /*
873: * Undo escaping of glob sequences in place. Used to undo extra escaping
874: * applied in makeargv() when the string is destined for a function that
875: * does not glob it.
876: */
877: static void
878: undo_glob_escape(char *s)
879: {
880: size_t i, j;
881:
882: for (i = j = 0;;) {
883: if (s[i] == '\0') {
884: s[j] = '\0';
885: return;
886: }
887: if (s[i] != '\\') {
888: s[j++] = s[i++];
889: continue;
890: }
891: /* s[i] == '\\' */
892: ++i;
893: switch (s[i]) {
894: case '?':
895: case '[':
896: case '*':
897: case '\\':
898: s[j++] = s[i++];
899: break;
900: case '\0':
901: s[j++] = '\\';
902: s[j] = '\0';
903: return;
904: default:
905: s[j++] = '\\';
906: s[j++] = s[i++];
907: break;
908: }
909: }
910: }
911:
912: /*
913: * Split a string into an argument vector using sh(1)-style quoting,
914: * comment and escaping rules, but with some tweaks to handle glob(3)
915: * wildcards.
916: * Returns NULL on error or a NULL-terminated array of arguments.
917: */
918: #define MAXARGS 128
919: #define MAXARGLEN 8192
920: static char **
921: makeargv(const char *arg, int *argcp)
922: {
923: int argc, quot;
924: size_t i, j;
925: static char argvs[MAXARGLEN];
926: static char *argv[MAXARGS + 1];
927: enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
928:
929: *argcp = argc = 0;
930: if (strlen(arg) > sizeof(argvs) - 1) {
931: args_too_longs:
932: error("string too long");
933: return NULL;
934: }
935: state = MA_START;
936: i = j = 0;
937: for (;;) {
938: if (isspace(arg[i])) {
939: if (state == MA_UNQUOTED) {
940: /* Terminate current argument */
941: argvs[j++] = '\0';
942: argc++;
943: state = MA_START;
944: } else if (state != MA_START)
945: argvs[j++] = arg[i];
946: } else if (arg[i] == '"' || arg[i] == '\'') {
947: q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
948: if (state == MA_START) {
949: argv[argc] = argvs + j;
950: state = q;
951: } else if (state == MA_UNQUOTED)
952: state = q;
953: else if (state == q)
954: state = MA_UNQUOTED;
955: else
956: argvs[j++] = arg[i];
957: } else if (arg[i] == '\\') {
958: if (state == MA_SQUOTE || state == MA_DQUOTE) {
959: quot = state == MA_SQUOTE ? '\'' : '"';
960: /* Unescape quote we are in */
961: /* XXX support \n and friends? */
962: if (arg[i + 1] == quot) {
963: i++;
964: argvs[j++] = arg[i];
965: } else if (arg[i + 1] == '?' ||
966: arg[i + 1] == '[' || arg[i + 1] == '*') {
967: /*
968: * Special case for sftp: append
969: * double-escaped glob sequence -
970: * glob will undo one level of
971: * escaping. NB. string can grow here.
972: */
973: if (j >= sizeof(argvs) - 5)
974: goto args_too_longs;
975: argvs[j++] = '\\';
976: argvs[j++] = arg[i++];
977: argvs[j++] = '\\';
978: argvs[j++] = arg[i];
979: } else {
980: argvs[j++] = arg[i++];
981: argvs[j++] = arg[i];
982: }
983: } else {
984: if (state == MA_START) {
985: argv[argc] = argvs + j;
986: state = MA_UNQUOTED;
987: }
988: if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
989: arg[i + 1] == '*' || arg[i + 1] == '\\') {
990: /*
991: * Special case for sftp: append
992: * escaped glob sequence -
993: * glob will undo one level of
994: * escaping.
995: */
996: argvs[j++] = arg[i++];
997: argvs[j++] = arg[i];
998: } else {
999: /* Unescape everything */
1000: /* XXX support \n and friends? */
1001: i++;
1002: argvs[j++] = arg[i];
1003: }
1004: }
1005: } else if (arg[i] == '#') {
1006: if (state == MA_SQUOTE || state == MA_DQUOTE)
1007: argvs[j++] = arg[i];
1008: else
1009: goto string_done;
1010: } else if (arg[i] == '\0') {
1011: if (state == MA_SQUOTE || state == MA_DQUOTE) {
1012: error("Unterminated quoted argument");
1013: return NULL;
1014: }
1015: string_done:
1016: if (state == MA_UNQUOTED) {
1017: argvs[j++] = '\0';
1018: argc++;
1019: }
1020: break;
1021: } else {
1022: if (state == MA_START) {
1023: argv[argc] = argvs + j;
1024: state = MA_UNQUOTED;
1025: }
1026: if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1027: (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1028: /*
1029: * Special case for sftp: escape quoted
1030: * glob(3) wildcards. NB. string can grow
1031: * here.
1032: */
1033: if (j >= sizeof(argvs) - 3)
1034: goto args_too_longs;
1035: argvs[j++] = '\\';
1036: argvs[j++] = arg[i];
1037: } else
1038: argvs[j++] = arg[i];
1039: }
1040: i++;
1041: }
1042: *argcp = argc;
1043: return argv;
1044: }
1045:
1.44 djm 1046: static int
1.100 djm 1047: parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, int *hflag,
1.44 djm 1048: unsigned long *n_arg, char **path1, char **path2)
1049: {
1050: const char *cmd, *cp = *cpp;
1.97 djm 1051: char *cp2, **argv;
1.44 djm 1052: int base = 0;
1053: long l;
1.97 djm 1054: int i, cmdnum, optidx, argc;
1.44 djm 1055:
1056: /* Skip leading whitespace */
1057: cp = cp + strspn(cp, WHITESPACE);
1058:
1059: /* Ignore blank lines and lines which begin with comment '#' char */
1060: if (*cp == '\0' || *cp == '#')
1061: return (0);
1062:
1063: /* Check for leading '-' (disable error processing) */
1064: *iflag = 0;
1065: if (*cp == '-') {
1066: *iflag = 1;
1067: cp++;
1068: }
1069:
1.97 djm 1070: if ((argv = makeargv(cp, &argc)) == NULL)
1071: return -1;
1072:
1.44 djm 1073: /* Figure out which command we have */
1.97 djm 1074: for (i = 0; cmds[i].c != NULL; i++) {
1075: if (strcasecmp(cmds[i].c, argv[0]) == 0)
1.44 djm 1076: break;
1077: }
1078: cmdnum = cmds[i].n;
1079: cmd = cmds[i].c;
1080:
1081: /* Special case */
1082: if (*cp == '!') {
1083: cp++;
1084: cmdnum = I_SHELL;
1085: } else if (cmdnum == -1) {
1086: error("Invalid command.");
1.97 djm 1087: return -1;
1.44 djm 1088: }
1089:
1090: /* Get arguments and parse flags */
1.100 djm 1091: *lflag = *pflag = *hflag = *n_arg = 0;
1.44 djm 1092: *path1 = *path2 = NULL;
1.97 djm 1093: optidx = 1;
1.44 djm 1094: switch (cmdnum) {
1095: case I_GET:
1096: case I_PUT:
1.97 djm 1097: if ((optidx = parse_getput_flags(cmd, argv, argc, pflag)) == -1)
1098: return -1;
1.44 djm 1099: /* Get first pathname (mandatory) */
1.97 djm 1100: if (argc - optidx < 1) {
1.44 djm 1101: error("You must specify at least one path after a "
1102: "%s command.", cmd);
1.97 djm 1103: return -1;
1104: }
1105: *path1 = xstrdup(argv[optidx]);
1106: /* Get second pathname (optional) */
1107: if (argc - optidx > 1) {
1108: *path2 = xstrdup(argv[optidx + 1]);
1109: /* Destination is not globbed */
1110: undo_glob_escape(*path2);
1.44 djm 1111: }
1112: break;
1113: case I_RENAME:
1114: case I_SYMLINK:
1.97 djm 1115: if (argc - optidx < 2) {
1.44 djm 1116: error("You must specify two paths after a %s "
1117: "command.", cmd);
1.97 djm 1118: return -1;
1.44 djm 1119: }
1.97 djm 1120: *path1 = xstrdup(argv[optidx]);
1121: *path2 = xstrdup(argv[optidx + 1]);
1122: /* Paths are not globbed */
1123: undo_glob_escape(*path1);
1124: undo_glob_escape(*path2);
1.44 djm 1125: break;
1126: case I_RM:
1127: case I_MKDIR:
1128: case I_RMDIR:
1129: case I_CHDIR:
1130: case I_LCHDIR:
1131: case I_LMKDIR:
1132: /* Get pathname (mandatory) */
1.97 djm 1133: if (argc - optidx < 1) {
1.44 djm 1134: error("You must specify a path after a %s command.",
1135: cmd);
1.97 djm 1136: return -1;
1.44 djm 1137: }
1.97 djm 1138: *path1 = xstrdup(argv[optidx]);
1139: /* Only "rm" globs */
1140: if (cmdnum != I_RM)
1141: undo_glob_escape(*path1);
1.44 djm 1142: break;
1.100 djm 1143: case I_DF:
1144: if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1145: iflag)) == -1)
1146: return -1;
1147: /* Default to current directory if no path specified */
1148: if (argc - optidx < 1)
1149: *path1 = NULL;
1150: else {
1151: *path1 = xstrdup(argv[optidx]);
1152: undo_glob_escape(*path1);
1153: }
1154: break;
1.44 djm 1155: case I_LS:
1.97 djm 1156: if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
1.44 djm 1157: return(-1);
1158: /* Path is optional */
1.97 djm 1159: if (argc - optidx > 0)
1160: *path1 = xstrdup(argv[optidx]);
1.44 djm 1161: break;
1162: case I_LLS:
1.98 djm 1163: /* Skip ls command and following whitespace */
1164: cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
1.44 djm 1165: case I_SHELL:
1166: /* Uses the rest of the line */
1167: break;
1168: case I_LUMASK:
1169: case I_CHMOD:
1170: base = 8;
1171: case I_CHOWN:
1172: case I_CHGRP:
1173: /* Get numeric arg (mandatory) */
1.97 djm 1174: if (argc - optidx < 1)
1175: goto need_num_arg;
1.93 ray 1176: errno = 0;
1.97 djm 1177: l = strtol(argv[optidx], &cp2, base);
1178: if (cp2 == argv[optidx] || *cp2 != '\0' ||
1179: ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1180: l < 0) {
1181: need_num_arg:
1.44 djm 1182: error("You must supply a numeric argument "
1183: "to the %s command.", cmd);
1.97 djm 1184: return -1;
1.44 djm 1185: }
1186: *n_arg = l;
1.97 djm 1187: if (cmdnum == I_LUMASK)
1.44 djm 1188: break;
1189: /* Get pathname (mandatory) */
1.97 djm 1190: if (argc - optidx < 2) {
1.44 djm 1191: error("You must specify a path after a %s command.",
1192: cmd);
1.97 djm 1193: return -1;
1.44 djm 1194: }
1.97 djm 1195: *path1 = xstrdup(argv[optidx + 1]);
1.44 djm 1196: break;
1197: case I_QUIT:
1198: case I_PWD:
1199: case I_LPWD:
1200: case I_HELP:
1201: case I_VERSION:
1202: case I_PROGRESS:
1203: break;
1204: default:
1205: fatal("Command not implemented");
1206: }
1207:
1208: *cpp = cp;
1209: return(cmdnum);
1210: }
1211:
1212: static int
1213: parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1214: int err_abort)
1215: {
1216: char *path1, *path2, *tmp;
1.100 djm 1217: int pflag, lflag, iflag, hflag, cmdnum, i;
1.44 djm 1218: unsigned long n_arg;
1219: Attrib a, *aa;
1220: char path_buf[MAXPATHLEN];
1221: int err = 0;
1222: glob_t g;
1223:
1224: path1 = path2 = NULL;
1.100 djm 1225: cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &hflag, &n_arg,
1.44 djm 1226: &path1, &path2);
1227:
1228: if (iflag != 0)
1229: err_abort = 0;
1230:
1231: memset(&g, 0, sizeof(g));
1232:
1233: /* Perform command */
1234: switch (cmdnum) {
1235: case 0:
1236: /* Blank line */
1237: break;
1238: case -1:
1239: /* Unrecognized command */
1240: err = -1;
1241: break;
1242: case I_GET:
1243: err = process_get(conn, path1, path2, *pwd, pflag);
1244: break;
1245: case I_PUT:
1246: err = process_put(conn, path1, path2, *pwd, pflag);
1247: break;
1248: case I_RENAME:
1249: path1 = make_absolute(path1, *pwd);
1250: path2 = make_absolute(path2, *pwd);
1251: err = do_rename(conn, path1, path2);
1252: break;
1253: case I_SYMLINK:
1254: path2 = make_absolute(path2, *pwd);
1255: err = do_symlink(conn, path1, path2);
1256: break;
1257: case I_RM:
1258: path1 = make_absolute(path1, *pwd);
1259: remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1.46 djm 1260: for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1.44 djm 1261: printf("Removing %s\n", g.gl_pathv[i]);
1262: err = do_rm(conn, g.gl_pathv[i]);
1263: if (err != 0 && err_abort)
1264: break;
1265: }
1266: break;
1267: case I_MKDIR:
1268: path1 = make_absolute(path1, *pwd);
1269: attrib_clear(&a);
1270: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1271: a.perm = 0777;
1272: err = do_mkdir(conn, path1, &a);
1273: break;
1274: case I_RMDIR:
1275: path1 = make_absolute(path1, *pwd);
1276: err = do_rmdir(conn, path1);
1277: break;
1278: case I_CHDIR:
1279: path1 = make_absolute(path1, *pwd);
1280: if ((tmp = do_realpath(conn, path1)) == NULL) {
1281: err = 1;
1282: break;
1283: }
1284: if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1285: xfree(tmp);
1286: err = 1;
1287: break;
1288: }
1289: if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1290: error("Can't change directory: Can't check target");
1291: xfree(tmp);
1292: err = 1;
1293: break;
1294: }
1295: if (!S_ISDIR(aa->perm)) {
1296: error("Can't change directory: \"%s\" is not "
1297: "a directory", tmp);
1298: xfree(tmp);
1299: err = 1;
1300: break;
1301: }
1302: xfree(*pwd);
1303: *pwd = tmp;
1304: break;
1305: case I_LS:
1306: if (!path1) {
1307: do_globbed_ls(conn, *pwd, *pwd, lflag);
1308: break;
1309: }
1310:
1311: /* Strip pwd off beginning of non-absolute paths */
1312: tmp = NULL;
1313: if (*path1 != '/')
1314: tmp = *pwd;
1315:
1316: path1 = make_absolute(path1, *pwd);
1317: err = do_globbed_ls(conn, path1, tmp, lflag);
1.100 djm 1318: break;
1319: case I_DF:
1320: /* Default to current directory if no path specified */
1321: if (path1 == NULL)
1322: path1 = xstrdup(*pwd);
1323: path1 = make_absolute(path1, *pwd);
1324: err = do_df(conn, path1, hflag, iflag);
1.44 djm 1325: break;
1326: case I_LCHDIR:
1327: if (chdir(path1) == -1) {
1328: error("Couldn't change local directory to "
1329: "\"%s\": %s", path1, strerror(errno));
1330: err = 1;
1331: }
1332: break;
1333: case I_LMKDIR:
1334: if (mkdir(path1, 0777) == -1) {
1335: error("Couldn't create local directory "
1336: "\"%s\": %s", path1, strerror(errno));
1337: err = 1;
1338: }
1339: break;
1340: case I_LLS:
1341: local_do_ls(cmd);
1342: break;
1343: case I_SHELL:
1344: local_do_shell(cmd);
1345: break;
1346: case I_LUMASK:
1347: umask(n_arg);
1348: printf("Local umask: %03lo\n", n_arg);
1349: break;
1350: case I_CHMOD:
1351: path1 = make_absolute(path1, *pwd);
1352: attrib_clear(&a);
1353: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1354: a.perm = n_arg;
1355: remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1.46 djm 1356: for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1.44 djm 1357: printf("Changing mode on %s\n", g.gl_pathv[i]);
1358: err = do_setstat(conn, g.gl_pathv[i], &a);
1359: if (err != 0 && err_abort)
1360: break;
1361: }
1362: break;
1363: case I_CHOWN:
1364: case I_CHGRP:
1365: path1 = make_absolute(path1, *pwd);
1366: remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1.46 djm 1367: for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1.44 djm 1368: if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
1.104 djm 1369: if (err_abort) {
1370: err = -1;
1.44 djm 1371: break;
1.104 djm 1372: } else
1.44 djm 1373: continue;
1374: }
1375: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1376: error("Can't get current ownership of "
1377: "remote file \"%s\"", g.gl_pathv[i]);
1.104 djm 1378: if (err_abort) {
1379: err = -1;
1.44 djm 1380: break;
1.104 djm 1381: } else
1.44 djm 1382: continue;
1383: }
1384: aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1385: if (cmdnum == I_CHOWN) {
1386: printf("Changing owner on %s\n", g.gl_pathv[i]);
1387: aa->uid = n_arg;
1388: } else {
1389: printf("Changing group on %s\n", g.gl_pathv[i]);
1390: aa->gid = n_arg;
1391: }
1392: err = do_setstat(conn, g.gl_pathv[i], aa);
1393: if (err != 0 && err_abort)
1394: break;
1395: }
1396: break;
1397: case I_PWD:
1398: printf("Remote working directory: %s\n", *pwd);
1399: break;
1400: case I_LPWD:
1401: if (!getcwd(path_buf, sizeof(path_buf))) {
1402: error("Couldn't get local cwd: %s", strerror(errno));
1403: err = -1;
1404: break;
1405: }
1406: printf("Local working directory: %s\n", path_buf);
1407: break;
1408: case I_QUIT:
1409: /* Processed below */
1410: break;
1411: case I_HELP:
1412: help();
1413: break;
1414: case I_VERSION:
1415: printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1416: break;
1417: case I_PROGRESS:
1418: showprogress = !showprogress;
1419: if (showprogress)
1420: printf("Progress meter enabled\n");
1421: else
1422: printf("Progress meter disabled\n");
1423: break;
1424: default:
1425: fatal("%d is not implemented", cmdnum);
1426: }
1427:
1428: if (g.gl_pathc)
1429: globfree(&g);
1430: if (path1)
1431: xfree(path1);
1432: if (path2)
1433: xfree(path2);
1434:
1435: /* If an unignored error occurs in batch mode we should abort. */
1436: if (err_abort && err != 0)
1437: return (-1);
1438: else if (cmdnum == I_QUIT)
1439: return (1);
1440:
1441: return (0);
1442: }
1443:
1.57 djm 1444: static char *
1445: prompt(EditLine *el)
1446: {
1447: return ("sftp> ");
1448: }
1449:
1.44 djm 1450: int
1451: interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
1452: {
1453: char *pwd;
1454: char *dir = NULL;
1455: char cmd[2048];
1456: struct sftp_conn *conn;
1.66 jaredy 1457: int err, interactive;
1.57 djm 1458: EditLine *el = NULL;
1459: History *hl = NULL;
1460: HistEvent hev;
1461: extern char *__progname;
1462:
1463: if (!batchmode && isatty(STDIN_FILENO)) {
1464: if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1465: fatal("Couldn't initialise editline");
1466: if ((hl = history_init()) == NULL)
1467: fatal("Couldn't initialise editline history");
1468: history(hl, &hev, H_SETSIZE, 100);
1469: el_set(el, EL_HIST, history, hl);
1470:
1471: el_set(el, EL_PROMPT, prompt);
1472: el_set(el, EL_EDITOR, "emacs");
1473: el_set(el, EL_TERMINAL, NULL);
1474: el_set(el, EL_SIGNAL, 1);
1475: el_source(el, NULL);
1476: }
1.44 djm 1477:
1478: conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
1479: if (conn == NULL)
1480: fatal("Couldn't initialise connection to server");
1481:
1482: pwd = do_realpath(conn, ".");
1483: if (pwd == NULL)
1484: fatal("Need cwd");
1485:
1486: if (file1 != NULL) {
1487: dir = xstrdup(file1);
1488: dir = make_absolute(dir, pwd);
1489:
1490: if (remote_is_dir(conn, dir) && file2 == NULL) {
1491: printf("Changing to: %s\n", dir);
1492: snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
1.58 markus 1493: if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) {
1494: xfree(dir);
1495: xfree(pwd);
1.76 djm 1496: xfree(conn);
1.44 djm 1497: return (-1);
1.58 markus 1498: }
1.44 djm 1499: } else {
1500: if (file2 == NULL)
1501: snprintf(cmd, sizeof cmd, "get %s", dir);
1502: else
1503: snprintf(cmd, sizeof cmd, "get %s %s", dir,
1504: file2);
1505:
1506: err = parse_dispatch_command(conn, cmd, &pwd, 1);
1507: xfree(dir);
1508: xfree(pwd);
1.76 djm 1509: xfree(conn);
1.44 djm 1510: return (err);
1511: }
1512: xfree(dir);
1513: }
1514:
1515: setvbuf(stdout, NULL, _IOLBF, 0);
1516: setvbuf(infile, NULL, _IOLBF, 0);
1517:
1.66 jaredy 1518: interactive = !batchmode && isatty(STDIN_FILENO);
1.44 djm 1519: err = 0;
1520: for (;;) {
1521: char *cp;
1.57 djm 1522: const char *line;
1523: int count = 0;
1.44 djm 1524:
1.46 djm 1525: signal(SIGINT, SIG_IGN);
1526:
1.57 djm 1527: if (el == NULL) {
1.66 jaredy 1528: if (interactive)
1529: printf("sftp> ");
1.57 djm 1530: if (fgets(cmd, sizeof(cmd), infile) == NULL) {
1.66 jaredy 1531: if (interactive)
1532: printf("\n");
1.57 djm 1533: break;
1534: }
1.66 jaredy 1535: if (!interactive) { /* Echo command */
1536: printf("sftp> %s", cmd);
1537: if (strlen(cmd) > 0 &&
1538: cmd[strlen(cmd) - 1] != '\n')
1539: printf("\n");
1540: }
1.57 djm 1541: } else {
1.66 jaredy 1542: if ((line = el_gets(el, &count)) == NULL || count <= 0) {
1543: printf("\n");
1.57 djm 1544: break;
1.66 jaredy 1545: }
1.57 djm 1546: history(hl, &hev, H_ENTER, line);
1547: if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1548: fprintf(stderr, "Error: input line too long\n");
1549: continue;
1550: }
1.44 djm 1551: }
1552:
1553: cp = strrchr(cmd, '\n');
1554: if (cp)
1555: *cp = '\0';
1556:
1.46 djm 1557: /* Handle user interrupts gracefully during commands */
1558: interrupted = 0;
1559: signal(SIGINT, cmd_interrupt);
1560:
1.44 djm 1561: err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
1562: if (err != 0)
1563: break;
1564: }
1565: xfree(pwd);
1.76 djm 1566: xfree(conn);
1.66 jaredy 1567:
1568: if (el != NULL)
1569: el_end(el);
1.44 djm 1570:
1571: /* err == 1 signifies normal "quit" exit */
1572: return (err >= 0 ? 0 : -1);
1573: }
1.34 fgsch 1574:
1.18 itojun 1575: static void
1.36 djm 1576: connect_to_server(char *path, char **args, int *in, int *out)
1.1 djm 1577: {
1578: int c_in, c_out;
1.30 deraadt 1579:
1.1 djm 1580: int inout[2];
1.30 deraadt 1581:
1.1 djm 1582: if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
1583: fatal("socketpair: %s", strerror(errno));
1584: *in = *out = inout[0];
1585: c_in = c_out = inout[1];
1586:
1.36 djm 1587: if ((sshpid = fork()) == -1)
1.1 djm 1588: fatal("fork: %s", strerror(errno));
1.36 djm 1589: else if (sshpid == 0) {
1.1 djm 1590: if ((dup2(c_in, STDIN_FILENO) == -1) ||
1591: (dup2(c_out, STDOUT_FILENO) == -1)) {
1592: fprintf(stderr, "dup2: %s\n", strerror(errno));
1.47 djm 1593: _exit(1);
1.1 djm 1594: }
1595: close(*in);
1596: close(*out);
1597: close(c_in);
1598: close(c_out);
1.46 djm 1599:
1600: /*
1601: * The underlying ssh is in the same process group, so we must
1.56 deraadt 1602: * ignore SIGINT if we want to gracefully abort commands,
1603: * otherwise the signal will make it to the ssh process and
1.46 djm 1604: * kill it too
1605: */
1606: signal(SIGINT, SIG_IGN);
1.49 dtucker 1607: execvp(path, args);
1.23 djm 1608: fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
1.47 djm 1609: _exit(1);
1.1 djm 1610: }
1611:
1.36 djm 1612: signal(SIGTERM, killchild);
1613: signal(SIGINT, killchild);
1614: signal(SIGHUP, killchild);
1.1 djm 1615: close(c_in);
1616: close(c_out);
1617: }
1618:
1.18 itojun 1619: static void
1.1 djm 1620: usage(void)
1621: {
1.25 mpech 1622: extern char *__progname;
1.27 markus 1623:
1.19 stevesk 1624: fprintf(stderr,
1.38 jmc 1625: "usage: %s [-1Cv] [-B buffer_size] [-b batchfile] [-F ssh_config]\n"
1626: " [-o ssh_option] [-P sftp_server_path] [-R num_requests]\n"
1627: " [-S program] [-s subsystem | sftp_server] host\n"
1.105 djm 1628: " %s [user@]host[:file ...]\n"
1629: " %s [user@]host[:dir[/]]\n"
1.38 jmc 1630: " %s -b batchfile [user@]host\n", __progname, __progname, __progname, __progname);
1.1 djm 1631: exit(1);
1632: }
1633:
1.2 stevesk 1634: int
1.1 djm 1635: main(int argc, char **argv)
1636: {
1.33 djm 1637: int in, out, ch, err;
1.48 pedro 1638: char *host, *userhost, *cp, *file2 = NULL;
1.17 mouring 1639: int debug_level = 0, sshver = 2;
1640: char *file1 = NULL, *sftp_server = NULL;
1.23 djm 1641: char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
1.17 mouring 1642: LogLevel ll = SYSLOG_LEVEL_INFO;
1643: arglist args;
1.3 djm 1644: extern int optind;
1645: extern char *optarg;
1.67 djm 1646:
1647: /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
1648: sanitise_stdfd();
1.1 djm 1649:
1.70 djm 1650: memset(&args, '\0', sizeof(args));
1.17 mouring 1651: args.list = NULL;
1.80 djm 1652: addargs(&args, "%s", ssh_program);
1.17 mouring 1653: addargs(&args, "-oForwardX11 no");
1654: addargs(&args, "-oForwardAgent no");
1.69 reyk 1655: addargs(&args, "-oPermitLocalCommand no");
1.21 stevesk 1656: addargs(&args, "-oClearAllForwardings yes");
1.40 djm 1657:
1.17 mouring 1658: ll = SYSLOG_LEVEL_INFO;
1.40 djm 1659: infile = stdin;
1.3 djm 1660:
1.26 djm 1661: while ((ch = getopt(argc, argv, "1hvCo:s:S:b:B:F:P:R:")) != -1) {
1.3 djm 1662: switch (ch) {
1663: case 'C':
1.17 mouring 1664: addargs(&args, "-C");
1.3 djm 1665: break;
1666: case 'v':
1.17 mouring 1667: if (debug_level < 3) {
1668: addargs(&args, "-v");
1669: ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
1670: }
1671: debug_level++;
1.3 djm 1672: break;
1.19 stevesk 1673: case 'F':
1.3 djm 1674: case 'o':
1.19 stevesk 1675: addargs(&args, "-%c%s", ch, optarg);
1.7 markus 1676: break;
1677: case '1':
1.17 mouring 1678: sshver = 1;
1.7 markus 1679: if (sftp_server == NULL)
1680: sftp_server = _PATH_SFTP_SERVER;
1681: break;
1682: case 's':
1683: sftp_server = optarg;
1684: break;
1685: case 'S':
1686: ssh_program = optarg;
1.70 djm 1687: replacearg(&args, 0, "%s", ssh_program);
1.3 djm 1688: break;
1.10 deraadt 1689: case 'b':
1.39 djm 1690: if (batchmode)
1691: fatal("Batch file already specified.");
1692:
1693: /* Allow "-" as stdin */
1.56 deraadt 1694: if (strcmp(optarg, "-") != 0 &&
1.65 djm 1695: (infile = fopen(optarg, "r")) == NULL)
1.39 djm 1696: fatal("%s (%s).", strerror(errno), optarg);
1.34 fgsch 1697: showprogress = 0;
1.39 djm 1698: batchmode = 1;
1.62 djm 1699: addargs(&args, "-obatchmode yes");
1.10 deraadt 1700: break;
1.23 djm 1701: case 'P':
1702: sftp_direct = optarg;
1.24 djm 1703: break;
1704: case 'B':
1705: copy_buffer_len = strtol(optarg, &cp, 10);
1706: if (copy_buffer_len == 0 || *cp != '\0')
1707: fatal("Invalid buffer size \"%s\"", optarg);
1.26 djm 1708: break;
1709: case 'R':
1710: num_requests = strtol(optarg, &cp, 10);
1711: if (num_requests == 0 || *cp != '\0')
1.27 markus 1712: fatal("Invalid number of requests \"%s\"",
1.26 djm 1713: optarg);
1.23 djm 1714: break;
1.3 djm 1715: case 'h':
1716: default:
1.1 djm 1717: usage();
1718: }
1719: }
1.45 djm 1720:
1721: if (!isatty(STDERR_FILENO))
1722: showprogress = 0;
1.1 djm 1723:
1.29 markus 1724: log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
1725:
1.23 djm 1726: if (sftp_direct == NULL) {
1727: if (optind == argc || argc > (optind + 2))
1728: usage();
1729:
1730: userhost = xstrdup(argv[optind]);
1731: file2 = argv[optind+1];
1.1 djm 1732:
1.32 markus 1733: if ((host = strrchr(userhost, '@')) == NULL)
1.23 djm 1734: host = userhost;
1735: else {
1736: *host++ = '\0';
1737: if (!userhost[0]) {
1738: fprintf(stderr, "Missing username\n");
1739: usage();
1740: }
1.95 stevesk 1741: addargs(&args, "-l%s", userhost);
1.41 djm 1742: }
1743:
1744: if ((cp = colon(host)) != NULL) {
1745: *cp++ = '\0';
1746: file1 = cp;
1.23 djm 1747: }
1.3 djm 1748:
1.23 djm 1749: host = cleanhostname(host);
1750: if (!*host) {
1751: fprintf(stderr, "Missing hostname\n");
1.1 djm 1752: usage();
1753: }
1754:
1.23 djm 1755: addargs(&args, "-oProtocol %d", sshver);
1756:
1757: /* no subsystem if the server-spec contains a '/' */
1758: if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
1759: addargs(&args, "-s");
1760:
1761: addargs(&args, "%s", host);
1.27 markus 1762: addargs(&args, "%s", (sftp_server != NULL ?
1.23 djm 1763: sftp_server : "sftp"));
1764:
1.39 djm 1765: if (!batchmode)
1766: fprintf(stderr, "Connecting to %s...\n", host);
1.36 djm 1767: connect_to_server(ssh_program, args.list, &in, &out);
1.23 djm 1768: } else {
1769: args.list = NULL;
1770: addargs(&args, "sftp-server");
1771:
1.39 djm 1772: if (!batchmode)
1773: fprintf(stderr, "Attaching to %s...\n", sftp_direct);
1.36 djm 1774: connect_to_server(sftp_direct, args.list, &in, &out);
1.1 djm 1775: }
1.70 djm 1776: freeargs(&args);
1.1 djm 1777:
1.33 djm 1778: err = interactive_loop(in, out, file1, file2);
1.1 djm 1779:
1780: close(in);
1781: close(out);
1.39 djm 1782: if (batchmode)
1.10 deraadt 1783: fclose(infile);
1.1 djm 1784:
1.28 markus 1785: while (waitpid(sshpid, NULL, 0) == -1)
1786: if (errno != EINTR)
1787: fatal("Couldn't wait for ssh process: %s",
1788: strerror(errno));
1.1 djm 1789:
1.33 djm 1790: exit(err == 0 ? 0 : 1);
1.1 djm 1791: }