Annotation of src/usr.bin/ssh/sftp.c, Revision 1.49
1.1 djm 1: /*
1.42 djm 2: * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
1.1 djm 3: *
1.42 djm 4: * Permission to use, copy, modify, and distribute this software for any
5: * purpose with or without fee is hereby granted, provided that the above
6: * copyright notice and this permission notice appear in all copies.
1.1 djm 7: *
1.42 djm 8: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1.1 djm 15: */
16:
17: #include "includes.h"
18:
1.49 ! dtucker 19: RCSID("$OpenBSD: sftp.c,v 1.48 2004/06/03 12:22:20 pedro Exp $");
1.44 djm 20:
21: #include <glob.h>
1.1 djm 22:
23: #include "buffer.h"
24: #include "xmalloc.h"
25: #include "log.h"
26: #include "pathnames.h"
1.16 mouring 27: #include "misc.h"
1.1 djm 28:
29: #include "sftp.h"
30: #include "sftp-common.h"
31: #include "sftp-client.h"
1.43 djm 32:
1.44 djm 33: /* File to read commands from */
34: FILE* infile;
1.15 mouring 35:
1.44 djm 36: /* Are we in batchfile mode? */
1.39 djm 37: int batchmode = 0;
1.44 djm 38:
39: /* Size of buffer used when copying files */
1.24 djm 40: size_t copy_buffer_len = 32768;
1.44 djm 41:
42: /* Number of concurrent outstanding requests */
1.26 djm 43: size_t num_requests = 16;
1.44 djm 44:
45: /* PID of ssh transport process */
1.36 djm 46: static pid_t sshpid = -1;
1.7 markus 47:
1.44 djm 48: /* This is set to 0 if the progressmeter is not desired. */
1.45 djm 49: int showprogress = 1;
1.44 djm 50:
1.46 djm 51: /* SIGINT received during command processing */
52: volatile sig_atomic_t interrupted = 0;
53:
1.44 djm 54: int remote_glob(struct sftp_conn *, const char *, int,
55: int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
56:
57: /* Separators for interactive commands */
58: #define WHITESPACE " \t\r\n"
59:
60: /* Define what type of ls view (0 - multi-column) */
61: #define LONG_VIEW 1 /* Full view ala ls -l */
62: #define SHORT_VIEW 2 /* Single row view ala ls -1 */
63:
64: /* Commands for interactive mode */
65: #define I_CHDIR 1
66: #define I_CHGRP 2
67: #define I_CHMOD 3
68: #define I_CHOWN 4
69: #define I_GET 5
70: #define I_HELP 6
71: #define I_LCHDIR 7
72: #define I_LLS 8
73: #define I_LMKDIR 9
74: #define I_LPWD 10
75: #define I_LS 11
76: #define I_LUMASK 12
77: #define I_MKDIR 13
78: #define I_PUT 14
79: #define I_PWD 15
80: #define I_QUIT 16
81: #define I_RENAME 17
82: #define I_RM 18
83: #define I_RMDIR 19
84: #define I_SHELL 20
85: #define I_SYMLINK 21
86: #define I_VERSION 22
87: #define I_PROGRESS 23
88:
89: struct CMD {
90: const char *c;
91: const int n;
92: };
93:
94: static const struct CMD cmds[] = {
95: { "bye", I_QUIT },
96: { "cd", I_CHDIR },
97: { "chdir", I_CHDIR },
98: { "chgrp", I_CHGRP },
99: { "chmod", I_CHMOD },
100: { "chown", I_CHOWN },
101: { "dir", I_LS },
102: { "exit", I_QUIT },
103: { "get", I_GET },
104: { "mget", I_GET },
105: { "help", I_HELP },
106: { "lcd", I_LCHDIR },
107: { "lchdir", I_LCHDIR },
108: { "lls", I_LLS },
109: { "lmkdir", I_LMKDIR },
110: { "ln", I_SYMLINK },
111: { "lpwd", I_LPWD },
112: { "ls", I_LS },
113: { "lumask", I_LUMASK },
114: { "mkdir", I_MKDIR },
115: { "progress", I_PROGRESS },
116: { "put", I_PUT },
117: { "mput", I_PUT },
118: { "pwd", I_PWD },
119: { "quit", I_QUIT },
120: { "rename", I_RENAME },
121: { "rm", I_RM },
122: { "rmdir", I_RMDIR },
123: { "symlink", I_SYMLINK },
124: { "version", I_VERSION },
125: { "!", I_SHELL },
126: { "?", I_HELP },
127: { NULL, -1}
128: };
129:
130: int interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
131:
132: static void
1.46 djm 133: killchild(int signo)
134: {
135: if (sshpid > 1)
136: kill(sshpid, SIGTERM);
137:
138: _exit(1);
139: }
140:
141: static void
142: cmd_interrupt(int signo)
143: {
144: const char msg[] = "\rInterrupt \n";
145:
146: write(STDERR_FILENO, msg, sizeof(msg) - 1);
147: interrupted = 1;
148: }
149:
150: static void
1.44 djm 151: help(void)
152: {
153: printf("Available commands:\n");
154: printf("cd path Change remote directory to 'path'\n");
155: printf("lcd path Change local directory to 'path'\n");
156: printf("chgrp grp path Change group of file 'path' to 'grp'\n");
157: printf("chmod mode path Change permissions of file 'path' to 'mode'\n");
158: printf("chown own path Change owner of file 'path' to 'own'\n");
159: printf("help Display this help text\n");
160: printf("get remote-path [local-path] Download file\n");
161: printf("lls [ls-options [path]] Display local directory listing\n");
162: printf("ln oldpath newpath Symlink remote file\n");
163: printf("lmkdir path Create local directory\n");
164: printf("lpwd Print local working directory\n");
165: printf("ls [path] Display remote directory listing\n");
166: printf("lumask umask Set local umask to 'umask'\n");
167: printf("mkdir path Create remote directory\n");
168: printf("progress Toggle display of progress meter\n");
169: printf("put local-path [remote-path] Upload file\n");
170: printf("pwd Display remote working directory\n");
171: printf("exit Quit sftp\n");
172: printf("quit Quit sftp\n");
173: printf("rename oldpath newpath Rename remote file\n");
174: printf("rmdir path Remove remote directory\n");
175: printf("rm path Delete remote file\n");
176: printf("symlink oldpath newpath Symlink remote file\n");
177: printf("version Show SFTP version\n");
178: printf("!command Execute 'command' in local shell\n");
179: printf("! Escape to local shell\n");
180: printf("? Synonym for help\n");
181: }
182:
183: static void
184: local_do_shell(const char *args)
185: {
186: int status;
187: char *shell;
188: pid_t pid;
189:
190: if (!*args)
191: args = NULL;
192:
193: if ((shell = getenv("SHELL")) == NULL)
194: shell = _PATH_BSHELL;
195:
196: if ((pid = fork()) == -1)
197: fatal("Couldn't fork: %s", strerror(errno));
198:
199: if (pid == 0) {
200: /* XXX: child has pipe fds to ssh subproc open - issue? */
201: if (args) {
202: debug3("Executing %s -c \"%s\"", shell, args);
203: execl(shell, shell, "-c", args, (char *)NULL);
204: } else {
205: debug3("Executing %s", shell);
206: execl(shell, shell, (char *)NULL);
207: }
208: fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
209: strerror(errno));
210: _exit(1);
211: }
212: while (waitpid(pid, &status, 0) == -1)
213: if (errno != EINTR)
214: fatal("Couldn't wait for child: %s", strerror(errno));
215: if (!WIFEXITED(status))
216: error("Shell exited abormally");
217: else if (WEXITSTATUS(status))
218: error("Shell exited with status %d", WEXITSTATUS(status));
219: }
220:
221: static void
222: local_do_ls(const char *args)
223: {
224: if (!args || !*args)
225: local_do_shell(_PATH_LS);
226: else {
227: int len = strlen(_PATH_LS " ") + strlen(args) + 1;
228: char *buf = xmalloc(len);
229:
230: /* XXX: quoting - rip quoting code from ftp? */
231: snprintf(buf, len, _PATH_LS " %s", args);
232: local_do_shell(buf);
233: xfree(buf);
234: }
235: }
236:
237: /* Strip one path (usually the pwd) from the start of another */
238: static char *
239: path_strip(char *path, char *strip)
240: {
241: size_t len;
242:
243: if (strip == NULL)
244: return (xstrdup(path));
245:
246: len = strlen(strip);
247: if (strip != NULL && strncmp(path, strip, len) == 0) {
248: if (strip[len - 1] != '/' && path[len] == '/')
249: len++;
250: return (xstrdup(path + len));
251: }
252:
253: return (xstrdup(path));
254: }
255:
256: static char *
257: path_append(char *p1, char *p2)
258: {
259: char *ret;
260: int len = strlen(p1) + strlen(p2) + 2;
261:
262: ret = xmalloc(len);
263: strlcpy(ret, p1, len);
264: if (p1[strlen(p1) - 1] != '/')
265: strlcat(ret, "/", len);
266: strlcat(ret, p2, len);
267:
268: return(ret);
269: }
270:
271: static char *
272: make_absolute(char *p, char *pwd)
273: {
274: char *abs;
275:
276: /* Derelativise */
277: if (p && p[0] != '/') {
278: abs = path_append(pwd, p);
279: xfree(p);
280: return(abs);
281: } else
282: return(p);
283: }
284:
285: static int
286: infer_path(const char *p, char **ifp)
287: {
288: char *cp;
289:
290: cp = strrchr(p, '/');
291: if (cp == NULL) {
292: *ifp = xstrdup(p);
293: return(0);
294: }
295:
296: if (!cp[1]) {
297: error("Invalid path");
298: return(-1);
299: }
300:
301: *ifp = xstrdup(cp + 1);
302: return(0);
303: }
304:
305: static int
306: parse_getput_flags(const char **cpp, int *pflag)
307: {
308: const char *cp = *cpp;
309:
310: /* Check for flags */
311: if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
312: switch (cp[1]) {
313: case 'p':
314: case 'P':
315: *pflag = 1;
316: break;
317: default:
318: error("Invalid flag -%c", cp[1]);
319: return(-1);
320: }
321: cp += 2;
322: *cpp = cp + strspn(cp, WHITESPACE);
323: }
324:
325: return(0);
326: }
327:
328: static int
329: parse_ls_flags(const char **cpp, int *lflag)
330: {
331: const char *cp = *cpp;
332:
333: /* Check for flags */
334: if (cp++[0] == '-') {
335: for(; strchr(WHITESPACE, *cp) == NULL; cp++) {
336: switch (*cp) {
337: case 'l':
338: *lflag = LONG_VIEW;
339: break;
340: case '1':
341: *lflag = SHORT_VIEW;
342: break;
343: default:
344: error("Invalid flag -%c", *cp);
345: return(-1);
346: }
347: }
348: *cpp = cp + strspn(cp, WHITESPACE);
349: }
350:
351: return(0);
352: }
353:
354: static int
355: get_pathname(const char **cpp, char **path)
356: {
357: const char *cp = *cpp, *end;
358: char quot;
359: int i, j;
360:
361: cp += strspn(cp, WHITESPACE);
362: if (!*cp) {
363: *cpp = cp;
364: *path = NULL;
365: return (0);
366: }
367:
368: *path = xmalloc(strlen(cp) + 1);
369:
370: /* Check for quoted filenames */
371: if (*cp == '\"' || *cp == '\'') {
372: quot = *cp++;
373:
374: /* Search for terminating quote, unescape some chars */
375: for (i = j = 0; i <= strlen(cp); i++) {
376: if (cp[i] == quot) { /* Found quote */
377: i++;
378: (*path)[j] = '\0';
379: break;
380: }
381: if (cp[i] == '\0') { /* End of string */
382: error("Unterminated quote");
383: goto fail;
384: }
385: if (cp[i] == '\\') { /* Escaped characters */
386: i++;
387: if (cp[i] != '\'' && cp[i] != '\"' &&
388: cp[i] != '\\') {
389: error("Bad escaped character '\%c'",
390: cp[i]);
391: goto fail;
392: }
393: }
394: (*path)[j++] = cp[i];
395: }
396:
397: if (j == 0) {
398: error("Empty quotes");
399: goto fail;
400: }
401: *cpp = cp + i + strspn(cp + i, WHITESPACE);
402: } else {
403: /* Read to end of filename */
404: end = strpbrk(cp, WHITESPACE);
405: if (end == NULL)
406: end = strchr(cp, '\0');
407: *cpp = end + strspn(end, WHITESPACE);
408:
409: memcpy(*path, cp, end - cp);
410: (*path)[end - cp] = '\0';
411: }
412: return (0);
413:
414: fail:
415: xfree(*path);
416: *path = NULL;
417: return (-1);
418: }
419:
420: static int
421: is_dir(char *path)
422: {
423: struct stat sb;
424:
425: /* XXX: report errors? */
426: if (stat(path, &sb) == -1)
427: return(0);
428:
429: return(sb.st_mode & S_IFDIR);
430: }
431:
432: static int
433: is_reg(char *path)
434: {
435: struct stat sb;
436:
437: if (stat(path, &sb) == -1)
438: fatal("stat %s: %s", path, strerror(errno));
439:
440: return(S_ISREG(sb.st_mode));
441: }
442:
443: static int
444: remote_is_dir(struct sftp_conn *conn, char *path)
445: {
446: Attrib *a;
447:
448: /* XXX: report errors? */
449: if ((a = do_stat(conn, path, 1)) == NULL)
450: return(0);
451: if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
452: return(0);
453: return(a->perm & S_IFDIR);
454: }
455:
456: static int
457: process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
458: {
459: char *abs_src = NULL;
460: char *abs_dst = NULL;
461: char *tmp;
462: glob_t g;
463: int err = 0;
464: int i;
465:
466: abs_src = xstrdup(src);
467: abs_src = make_absolute(abs_src, pwd);
468:
469: memset(&g, 0, sizeof(g));
470: debug3("Looking up %s", abs_src);
471: if (remote_glob(conn, abs_src, 0, NULL, &g)) {
472: error("File \"%s\" not found.", abs_src);
473: err = -1;
474: goto out;
475: }
476:
477: /* If multiple matches, dst must be a directory or unspecified */
478: if (g.gl_matchc > 1 && dst && !is_dir(dst)) {
479: error("Multiple files match, but \"%s\" is not a directory",
480: dst);
481: err = -1;
482: goto out;
483: }
484:
1.46 djm 485: for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1.44 djm 486: if (infer_path(g.gl_pathv[i], &tmp)) {
487: err = -1;
488: goto out;
489: }
490:
491: if (g.gl_matchc == 1 && dst) {
492: /* If directory specified, append filename */
493: if (is_dir(dst)) {
494: if (infer_path(g.gl_pathv[0], &tmp)) {
495: err = 1;
496: goto out;
497: }
498: abs_dst = path_append(dst, tmp);
499: xfree(tmp);
500: } else
501: abs_dst = xstrdup(dst);
502: } else if (dst) {
503: abs_dst = path_append(dst, tmp);
504: xfree(tmp);
505: } else
506: abs_dst = tmp;
507:
508: printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
509: if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
510: err = -1;
511: xfree(abs_dst);
512: abs_dst = NULL;
513: }
514:
515: out:
516: xfree(abs_src);
517: if (abs_dst)
518: xfree(abs_dst);
519: globfree(&g);
520: return(err);
521: }
522:
523: static int
524: process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
525: {
526: char *tmp_dst = NULL;
527: char *abs_dst = NULL;
528: char *tmp;
529: glob_t g;
530: int err = 0;
531: int i;
532:
533: if (dst) {
534: tmp_dst = xstrdup(dst);
535: tmp_dst = make_absolute(tmp_dst, pwd);
536: }
537:
538: memset(&g, 0, sizeof(g));
539: debug3("Looking up %s", src);
540: if (glob(src, 0, NULL, &g)) {
541: error("File \"%s\" not found.", src);
542: err = -1;
543: goto out;
544: }
545:
546: /* If multiple matches, dst may be directory or unspecified */
547: if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) {
548: error("Multiple files match, but \"%s\" is not a directory",
549: tmp_dst);
550: err = -1;
551: goto out;
552: }
553:
1.46 djm 554: for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1.44 djm 555: if (!is_reg(g.gl_pathv[i])) {
556: error("skipping non-regular file %s",
557: g.gl_pathv[i]);
558: continue;
559: }
560: if (infer_path(g.gl_pathv[i], &tmp)) {
561: err = -1;
562: goto out;
563: }
564:
565: if (g.gl_matchc == 1 && tmp_dst) {
566: /* If directory specified, append filename */
567: if (remote_is_dir(conn, tmp_dst)) {
568: if (infer_path(g.gl_pathv[0], &tmp)) {
569: err = 1;
570: goto out;
571: }
572: abs_dst = path_append(tmp_dst, tmp);
573: xfree(tmp);
574: } else
575: abs_dst = xstrdup(tmp_dst);
576:
577: } else if (tmp_dst) {
578: abs_dst = path_append(tmp_dst, tmp);
579: xfree(tmp);
580: } else
581: abs_dst = make_absolute(tmp, pwd);
582:
583: printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
584: if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
585: err = -1;
586: }
587:
588: out:
589: if (abs_dst)
590: xfree(abs_dst);
591: if (tmp_dst)
592: xfree(tmp_dst);
593: globfree(&g);
594: return(err);
595: }
596:
597: static int
598: sdirent_comp(const void *aa, const void *bb)
599: {
600: SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
601: SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
602:
603: return (strcmp(a->filename, b->filename));
604: }
605:
606: /* sftp ls.1 replacement for directories */
607: static int
608: do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
609: {
610: int n, c = 1, colspace = 0, columns = 1;
611: SFTP_DIRENT **d;
612:
613: if ((n = do_readdir(conn, path, &d)) != 0)
614: return (n);
615:
616: if (!(lflag & SHORT_VIEW)) {
617: int m = 0, width = 80;
618: struct winsize ws;
619: char *tmp;
620:
621: /* Count entries for sort and find longest filename */
622: for (n = 0; d[n] != NULL; n++)
623: m = MAX(m, strlen(d[n]->filename));
624:
625: /* Add any subpath that also needs to be counted */
626: tmp = path_strip(path, strip_path);
627: m += strlen(tmp);
628: xfree(tmp);
629:
630: if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
631: width = ws.ws_col;
632:
633: columns = width / (m + 2);
634: columns = MAX(columns, 1);
635: colspace = width / columns;
636: colspace = MIN(colspace, width);
637: }
638:
639: qsort(d, n, sizeof(*d), sdirent_comp);
640:
1.46 djm 641: for (n = 0; d[n] != NULL && !interrupted; n++) {
1.44 djm 642: char *tmp, *fname;
643:
644: tmp = path_append(path, d[n]->filename);
645: fname = path_strip(tmp, strip_path);
646: xfree(tmp);
647:
648: if (lflag & LONG_VIEW) {
649: char *lname;
650: struct stat sb;
651:
652: memset(&sb, 0, sizeof(sb));
653: attrib_to_stat(&d[n]->a, &sb);
654: lname = ls_file(fname, &sb, 1);
655: printf("%s\n", lname);
656: xfree(lname);
657: } else {
658: printf("%-*s", colspace, fname);
659: if (c >= columns) {
660: printf("\n");
661: c = 1;
662: } else
663: c++;
664: }
665:
666: xfree(fname);
667: }
668:
669: if (!(lflag & LONG_VIEW) && (c != 1))
670: printf("\n");
671:
672: free_sftp_dirents(d);
673: return (0);
674: }
675:
676: /* sftp ls.1 replacement which handles path globs */
677: static int
678: do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
679: int lflag)
680: {
681: glob_t g;
682: int i, c = 1, colspace = 0, columns = 1;
683: Attrib *a;
684:
685: memset(&g, 0, sizeof(g));
686:
687: if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
688: NULL, &g)) {
689: error("Can't ls: \"%s\" not found", path);
690: return (-1);
691: }
692:
1.46 djm 693: if (interrupted)
694: goto out;
695:
1.44 djm 696: /*
697: * If the glob returns a single match, which is the same as the
698: * input glob, and it is a directory, then just list its contents
699: */
700: if (g.gl_pathc == 1 &&
701: strncmp(path, g.gl_pathv[0], strlen(g.gl_pathv[0]) - 1) == 0) {
702: if ((a = do_lstat(conn, path, 1)) == NULL) {
703: globfree(&g);
704: return (-1);
705: }
706: if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
707: S_ISDIR(a->perm)) {
708: globfree(&g);
709: return (do_ls_dir(conn, path, strip_path, lflag));
710: }
711: }
712:
713: if (!(lflag & SHORT_VIEW)) {
714: int m = 0, width = 80;
715: struct winsize ws;
716:
717: /* Count entries for sort and find longest filename */
718: for (i = 0; g.gl_pathv[i]; i++)
719: m = MAX(m, strlen(g.gl_pathv[i]));
720:
721: if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
722: width = ws.ws_col;
723:
724: columns = width / (m + 2);
725: columns = MAX(columns, 1);
726: colspace = width / columns;
727: }
728:
1.46 djm 729: for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1.44 djm 730: char *fname;
731:
732: fname = path_strip(g.gl_pathv[i], strip_path);
733:
734: if (lflag & LONG_VIEW) {
735: char *lname;
736: struct stat sb;
737:
738: /*
739: * XXX: this is slow - 1 roundtrip per path
740: * A solution to this is to fork glob() and
741: * build a sftp specific version which keeps the
742: * attribs (which currently get thrown away)
743: * that the server returns as well as the filenames.
744: */
745: memset(&sb, 0, sizeof(sb));
746: a = do_lstat(conn, g.gl_pathv[i], 1);
747: if (a != NULL)
748: attrib_to_stat(a, &sb);
749: lname = ls_file(fname, &sb, 1);
750: printf("%s\n", lname);
751: xfree(lname);
752: } else {
753: printf("%-*s", colspace, fname);
754: if (c >= columns) {
755: printf("\n");
756: c = 1;
757: } else
758: c++;
759: }
760: xfree(fname);
761: }
762:
763: if (!(lflag & LONG_VIEW) && (c != 1))
764: printf("\n");
765:
1.46 djm 766: out:
1.44 djm 767: if (g.gl_pathc)
768: globfree(&g);
769:
770: return (0);
771: }
772:
773: static int
774: parse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
775: unsigned long *n_arg, char **path1, char **path2)
776: {
777: const char *cmd, *cp = *cpp;
778: char *cp2;
779: int base = 0;
780: long l;
781: int i, cmdnum;
782:
783: /* Skip leading whitespace */
784: cp = cp + strspn(cp, WHITESPACE);
785:
786: /* Ignore blank lines and lines which begin with comment '#' char */
787: if (*cp == '\0' || *cp == '#')
788: return (0);
789:
790: /* Check for leading '-' (disable error processing) */
791: *iflag = 0;
792: if (*cp == '-') {
793: *iflag = 1;
794: cp++;
795: }
796:
797: /* Figure out which command we have */
798: for (i = 0; cmds[i].c; i++) {
799: int cmdlen = strlen(cmds[i].c);
800:
801: /* Check for command followed by whitespace */
802: if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
803: strchr(WHITESPACE, cp[cmdlen])) {
804: cp += cmdlen;
805: cp = cp + strspn(cp, WHITESPACE);
806: break;
807: }
808: }
809: cmdnum = cmds[i].n;
810: cmd = cmds[i].c;
811:
812: /* Special case */
813: if (*cp == '!') {
814: cp++;
815: cmdnum = I_SHELL;
816: } else if (cmdnum == -1) {
817: error("Invalid command.");
818: return (-1);
819: }
820:
821: /* Get arguments and parse flags */
822: *lflag = *pflag = *n_arg = 0;
823: *path1 = *path2 = NULL;
824: switch (cmdnum) {
825: case I_GET:
826: case I_PUT:
827: if (parse_getput_flags(&cp, pflag))
828: return(-1);
829: /* Get first pathname (mandatory) */
830: if (get_pathname(&cp, path1))
831: return(-1);
832: if (*path1 == NULL) {
833: error("You must specify at least one path after a "
834: "%s command.", cmd);
835: return(-1);
836: }
837: /* Try to get second pathname (optional) */
838: if (get_pathname(&cp, path2))
839: return(-1);
840: break;
841: case I_RENAME:
842: case I_SYMLINK:
843: if (get_pathname(&cp, path1))
844: return(-1);
845: if (get_pathname(&cp, path2))
846: return(-1);
847: if (!*path1 || !*path2) {
848: error("You must specify two paths after a %s "
849: "command.", cmd);
850: return(-1);
851: }
852: break;
853: case I_RM:
854: case I_MKDIR:
855: case I_RMDIR:
856: case I_CHDIR:
857: case I_LCHDIR:
858: case I_LMKDIR:
859: /* Get pathname (mandatory) */
860: if (get_pathname(&cp, path1))
861: return(-1);
862: if (*path1 == NULL) {
863: error("You must specify a path after a %s command.",
864: cmd);
865: return(-1);
866: }
867: break;
868: case I_LS:
869: if (parse_ls_flags(&cp, lflag))
870: return(-1);
871: /* Path is optional */
872: if (get_pathname(&cp, path1))
873: return(-1);
874: break;
875: case I_LLS:
876: case I_SHELL:
877: /* Uses the rest of the line */
878: break;
879: case I_LUMASK:
880: base = 8;
881: case I_CHMOD:
882: base = 8;
883: case I_CHOWN:
884: case I_CHGRP:
885: /* Get numeric arg (mandatory) */
886: l = strtol(cp, &cp2, base);
887: if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) &&
888: errno == ERANGE) || l < 0) {
889: error("You must supply a numeric argument "
890: "to the %s command.", cmd);
891: return(-1);
892: }
893: cp = cp2;
894: *n_arg = l;
895: if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp))
896: break;
897: if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
898: error("You must supply a numeric argument "
899: "to the %s command.", cmd);
900: return(-1);
901: }
902: cp += strspn(cp, WHITESPACE);
903:
904: /* Get pathname (mandatory) */
905: if (get_pathname(&cp, path1))
906: return(-1);
907: if (*path1 == NULL) {
908: error("You must specify a path after a %s command.",
909: cmd);
910: return(-1);
911: }
912: break;
913: case I_QUIT:
914: case I_PWD:
915: case I_LPWD:
916: case I_HELP:
917: case I_VERSION:
918: case I_PROGRESS:
919: break;
920: default:
921: fatal("Command not implemented");
922: }
923:
924: *cpp = cp;
925: return(cmdnum);
926: }
927:
928: static int
929: parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
930: int err_abort)
931: {
932: char *path1, *path2, *tmp;
933: int pflag, lflag, iflag, cmdnum, i;
934: unsigned long n_arg;
935: Attrib a, *aa;
936: char path_buf[MAXPATHLEN];
937: int err = 0;
938: glob_t g;
939:
940: path1 = path2 = NULL;
941: cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &n_arg,
942: &path1, &path2);
943:
944: if (iflag != 0)
945: err_abort = 0;
946:
947: memset(&g, 0, sizeof(g));
948:
949: /* Perform command */
950: switch (cmdnum) {
951: case 0:
952: /* Blank line */
953: break;
954: case -1:
955: /* Unrecognized command */
956: err = -1;
957: break;
958: case I_GET:
959: err = process_get(conn, path1, path2, *pwd, pflag);
960: break;
961: case I_PUT:
962: err = process_put(conn, path1, path2, *pwd, pflag);
963: break;
964: case I_RENAME:
965: path1 = make_absolute(path1, *pwd);
966: path2 = make_absolute(path2, *pwd);
967: err = do_rename(conn, path1, path2);
968: break;
969: case I_SYMLINK:
970: path2 = make_absolute(path2, *pwd);
971: err = do_symlink(conn, path1, path2);
972: break;
973: case I_RM:
974: path1 = make_absolute(path1, *pwd);
975: remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1.46 djm 976: for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1.44 djm 977: printf("Removing %s\n", g.gl_pathv[i]);
978: err = do_rm(conn, g.gl_pathv[i]);
979: if (err != 0 && err_abort)
980: break;
981: }
982: break;
983: case I_MKDIR:
984: path1 = make_absolute(path1, *pwd);
985: attrib_clear(&a);
986: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
987: a.perm = 0777;
988: err = do_mkdir(conn, path1, &a);
989: break;
990: case I_RMDIR:
991: path1 = make_absolute(path1, *pwd);
992: err = do_rmdir(conn, path1);
993: break;
994: case I_CHDIR:
995: path1 = make_absolute(path1, *pwd);
996: if ((tmp = do_realpath(conn, path1)) == NULL) {
997: err = 1;
998: break;
999: }
1000: if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1001: xfree(tmp);
1002: err = 1;
1003: break;
1004: }
1005: if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1006: error("Can't change directory: Can't check target");
1007: xfree(tmp);
1008: err = 1;
1009: break;
1010: }
1011: if (!S_ISDIR(aa->perm)) {
1012: error("Can't change directory: \"%s\" is not "
1013: "a directory", tmp);
1014: xfree(tmp);
1015: err = 1;
1016: break;
1017: }
1018: xfree(*pwd);
1019: *pwd = tmp;
1020: break;
1021: case I_LS:
1022: if (!path1) {
1023: do_globbed_ls(conn, *pwd, *pwd, lflag);
1024: break;
1025: }
1026:
1027: /* Strip pwd off beginning of non-absolute paths */
1028: tmp = NULL;
1029: if (*path1 != '/')
1030: tmp = *pwd;
1031:
1032: path1 = make_absolute(path1, *pwd);
1033: err = do_globbed_ls(conn, path1, tmp, lflag);
1034: break;
1035: case I_LCHDIR:
1036: if (chdir(path1) == -1) {
1037: error("Couldn't change local directory to "
1038: "\"%s\": %s", path1, strerror(errno));
1039: err = 1;
1040: }
1041: break;
1042: case I_LMKDIR:
1043: if (mkdir(path1, 0777) == -1) {
1044: error("Couldn't create local directory "
1045: "\"%s\": %s", path1, strerror(errno));
1046: err = 1;
1047: }
1048: break;
1049: case I_LLS:
1050: local_do_ls(cmd);
1051: break;
1052: case I_SHELL:
1053: local_do_shell(cmd);
1054: break;
1055: case I_LUMASK:
1056: umask(n_arg);
1057: printf("Local umask: %03lo\n", n_arg);
1058: break;
1059: case I_CHMOD:
1060: path1 = make_absolute(path1, *pwd);
1061: attrib_clear(&a);
1062: a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1063: a.perm = n_arg;
1064: remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1.46 djm 1065: for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1.44 djm 1066: printf("Changing mode on %s\n", g.gl_pathv[i]);
1067: err = do_setstat(conn, g.gl_pathv[i], &a);
1068: if (err != 0 && err_abort)
1069: break;
1070: }
1071: break;
1072: case I_CHOWN:
1073: case I_CHGRP:
1074: path1 = make_absolute(path1, *pwd);
1075: remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1.46 djm 1076: for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1.44 djm 1077: if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
1078: if (err != 0 && err_abort)
1079: break;
1080: else
1081: continue;
1082: }
1083: if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1084: error("Can't get current ownership of "
1085: "remote file \"%s\"", g.gl_pathv[i]);
1086: if (err != 0 && err_abort)
1087: break;
1088: else
1089: continue;
1090: }
1091: aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1092: if (cmdnum == I_CHOWN) {
1093: printf("Changing owner on %s\n", g.gl_pathv[i]);
1094: aa->uid = n_arg;
1095: } else {
1096: printf("Changing group on %s\n", g.gl_pathv[i]);
1097: aa->gid = n_arg;
1098: }
1099: err = do_setstat(conn, g.gl_pathv[i], aa);
1100: if (err != 0 && err_abort)
1101: break;
1102: }
1103: break;
1104: case I_PWD:
1105: printf("Remote working directory: %s\n", *pwd);
1106: break;
1107: case I_LPWD:
1108: if (!getcwd(path_buf, sizeof(path_buf))) {
1109: error("Couldn't get local cwd: %s", strerror(errno));
1110: err = -1;
1111: break;
1112: }
1113: printf("Local working directory: %s\n", path_buf);
1114: break;
1115: case I_QUIT:
1116: /* Processed below */
1117: break;
1118: case I_HELP:
1119: help();
1120: break;
1121: case I_VERSION:
1122: printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1123: break;
1124: case I_PROGRESS:
1125: showprogress = !showprogress;
1126: if (showprogress)
1127: printf("Progress meter enabled\n");
1128: else
1129: printf("Progress meter disabled\n");
1130: break;
1131: default:
1132: fatal("%d is not implemented", cmdnum);
1133: }
1134:
1135: if (g.gl_pathc)
1136: globfree(&g);
1137: if (path1)
1138: xfree(path1);
1139: if (path2)
1140: xfree(path2);
1141:
1142: /* If an unignored error occurs in batch mode we should abort. */
1143: if (err_abort && err != 0)
1144: return (-1);
1145: else if (cmdnum == I_QUIT)
1146: return (1);
1147:
1148: return (0);
1149: }
1150:
1151: int
1152: interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
1153: {
1154: char *pwd;
1155: char *dir = NULL;
1156: char cmd[2048];
1157: struct sftp_conn *conn;
1158: int err;
1159:
1160: conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
1161: if (conn == NULL)
1162: fatal("Couldn't initialise connection to server");
1163:
1164: pwd = do_realpath(conn, ".");
1165: if (pwd == NULL)
1166: fatal("Need cwd");
1167:
1168: if (file1 != NULL) {
1169: dir = xstrdup(file1);
1170: dir = make_absolute(dir, pwd);
1171:
1172: if (remote_is_dir(conn, dir) && file2 == NULL) {
1173: printf("Changing to: %s\n", dir);
1174: snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
1175: if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0)
1176: return (-1);
1177: } else {
1178: if (file2 == NULL)
1179: snprintf(cmd, sizeof cmd, "get %s", dir);
1180: else
1181: snprintf(cmd, sizeof cmd, "get %s %s", dir,
1182: file2);
1183:
1184: err = parse_dispatch_command(conn, cmd, &pwd, 1);
1185: xfree(dir);
1186: xfree(pwd);
1187: return (err);
1188: }
1189: xfree(dir);
1190: }
1191:
1192: setvbuf(stdout, NULL, _IOLBF, 0);
1193: setvbuf(infile, NULL, _IOLBF, 0);
1194:
1195: err = 0;
1196: for (;;) {
1197: char *cp;
1198:
1.46 djm 1199: signal(SIGINT, SIG_IGN);
1200:
1.44 djm 1201: printf("sftp> ");
1202:
1203: /* XXX: use libedit */
1204: if (fgets(cmd, sizeof(cmd), infile) == NULL) {
1205: printf("\n");
1206: break;
1207: }
1208:
1209: if (batchmode) /* Echo command */
1210: printf("%s", cmd);
1211:
1212: cp = strrchr(cmd, '\n');
1213: if (cp)
1214: *cp = '\0';
1215:
1.46 djm 1216: /* Handle user interrupts gracefully during commands */
1217: interrupted = 0;
1218: signal(SIGINT, cmd_interrupt);
1219:
1.44 djm 1220: err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
1221: if (err != 0)
1222: break;
1223: }
1224: xfree(pwd);
1225:
1226: /* err == 1 signifies normal "quit" exit */
1227: return (err >= 0 ? 0 : -1);
1228: }
1.34 fgsch 1229:
1.18 itojun 1230: static void
1.36 djm 1231: connect_to_server(char *path, char **args, int *in, int *out)
1.1 djm 1232: {
1233: int c_in, c_out;
1.30 deraadt 1234:
1.1 djm 1235: #ifdef USE_PIPES
1236: int pin[2], pout[2];
1.30 deraadt 1237:
1.1 djm 1238: if ((pipe(pin) == -1) || (pipe(pout) == -1))
1239: fatal("pipe: %s", strerror(errno));
1240: *in = pin[0];
1241: *out = pout[1];
1242: c_in = pout[0];
1243: c_out = pin[1];
1244: #else /* USE_PIPES */
1245: int inout[2];
1.30 deraadt 1246:
1.1 djm 1247: if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
1248: fatal("socketpair: %s", strerror(errno));
1249: *in = *out = inout[0];
1250: c_in = c_out = inout[1];
1251: #endif /* USE_PIPES */
1252:
1.36 djm 1253: if ((sshpid = fork()) == -1)
1.1 djm 1254: fatal("fork: %s", strerror(errno));
1.36 djm 1255: else if (sshpid == 0) {
1.1 djm 1256: if ((dup2(c_in, STDIN_FILENO) == -1) ||
1257: (dup2(c_out, STDOUT_FILENO) == -1)) {
1258: fprintf(stderr, "dup2: %s\n", strerror(errno));
1.47 djm 1259: _exit(1);
1.1 djm 1260: }
1261: close(*in);
1262: close(*out);
1263: close(c_in);
1264: close(c_out);
1.46 djm 1265:
1266: /*
1267: * The underlying ssh is in the same process group, so we must
1268: * ignore SIGINT if we want to gracefully abort commands,
1269: * otherwise the signal will make it to the ssh process and
1270: * kill it too
1271: */
1272: signal(SIGINT, SIG_IGN);
1.49 ! dtucker 1273: execvp(path, args);
1.23 djm 1274: fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
1.47 djm 1275: _exit(1);
1.1 djm 1276: }
1277:
1.36 djm 1278: signal(SIGTERM, killchild);
1279: signal(SIGINT, killchild);
1280: signal(SIGHUP, killchild);
1.1 djm 1281: close(c_in);
1282: close(c_out);
1283: }
1284:
1.18 itojun 1285: static void
1.1 djm 1286: usage(void)
1287: {
1.25 mpech 1288: extern char *__progname;
1.27 markus 1289:
1.19 stevesk 1290: fprintf(stderr,
1.38 jmc 1291: "usage: %s [-1Cv] [-B buffer_size] [-b batchfile] [-F ssh_config]\n"
1292: " [-o ssh_option] [-P sftp_server_path] [-R num_requests]\n"
1293: " [-S program] [-s subsystem | sftp_server] host\n"
1294: " %s [[user@]host[:file [file]]]\n"
1295: " %s [[user@]host[:dir[/]]]\n"
1296: " %s -b batchfile [user@]host\n", __progname, __progname, __progname, __progname);
1.1 djm 1297: exit(1);
1298: }
1299:
1.2 stevesk 1300: int
1.1 djm 1301: main(int argc, char **argv)
1302: {
1.33 djm 1303: int in, out, ch, err;
1.48 pedro 1304: char *host, *userhost, *cp, *file2 = NULL;
1.17 mouring 1305: int debug_level = 0, sshver = 2;
1306: char *file1 = NULL, *sftp_server = NULL;
1.23 djm 1307: char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
1.17 mouring 1308: LogLevel ll = SYSLOG_LEVEL_INFO;
1309: arglist args;
1.3 djm 1310: extern int optind;
1311: extern char *optarg;
1.1 djm 1312:
1.17 mouring 1313: args.list = NULL;
1.22 deraadt 1314: addargs(&args, "ssh"); /* overwritten with ssh_program */
1.17 mouring 1315: addargs(&args, "-oForwardX11 no");
1316: addargs(&args, "-oForwardAgent no");
1.21 stevesk 1317: addargs(&args, "-oClearAllForwardings yes");
1.40 djm 1318:
1.17 mouring 1319: ll = SYSLOG_LEVEL_INFO;
1.40 djm 1320: infile = stdin;
1.3 djm 1321:
1.26 djm 1322: while ((ch = getopt(argc, argv, "1hvCo:s:S:b:B:F:P:R:")) != -1) {
1.3 djm 1323: switch (ch) {
1324: case 'C':
1.17 mouring 1325: addargs(&args, "-C");
1.3 djm 1326: break;
1327: case 'v':
1.17 mouring 1328: if (debug_level < 3) {
1329: addargs(&args, "-v");
1330: ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
1331: }
1332: debug_level++;
1.3 djm 1333: break;
1.19 stevesk 1334: case 'F':
1.3 djm 1335: case 'o':
1.19 stevesk 1336: addargs(&args, "-%c%s", ch, optarg);
1.7 markus 1337: break;
1338: case '1':
1.17 mouring 1339: sshver = 1;
1.7 markus 1340: if (sftp_server == NULL)
1341: sftp_server = _PATH_SFTP_SERVER;
1342: break;
1343: case 's':
1344: sftp_server = optarg;
1345: break;
1346: case 'S':
1347: ssh_program = optarg;
1.3 djm 1348: break;
1.10 deraadt 1349: case 'b':
1.39 djm 1350: if (batchmode)
1351: fatal("Batch file already specified.");
1352:
1353: /* Allow "-" as stdin */
1354: if (strcmp(optarg, "-") != 0 &&
1355: (infile = fopen(optarg, "r")) == NULL)
1356: fatal("%s (%s).", strerror(errno), optarg);
1.34 fgsch 1357: showprogress = 0;
1.39 djm 1358: batchmode = 1;
1.10 deraadt 1359: break;
1.23 djm 1360: case 'P':
1361: sftp_direct = optarg;
1.24 djm 1362: break;
1363: case 'B':
1364: copy_buffer_len = strtol(optarg, &cp, 10);
1365: if (copy_buffer_len == 0 || *cp != '\0')
1366: fatal("Invalid buffer size \"%s\"", optarg);
1.26 djm 1367: break;
1368: case 'R':
1369: num_requests = strtol(optarg, &cp, 10);
1370: if (num_requests == 0 || *cp != '\0')
1.27 markus 1371: fatal("Invalid number of requests \"%s\"",
1.26 djm 1372: optarg);
1.23 djm 1373: break;
1.3 djm 1374: case 'h':
1375: default:
1.1 djm 1376: usage();
1377: }
1378: }
1.45 djm 1379:
1380: if (!isatty(STDERR_FILENO))
1381: showprogress = 0;
1.1 djm 1382:
1.29 markus 1383: log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
1384:
1.23 djm 1385: if (sftp_direct == NULL) {
1386: if (optind == argc || argc > (optind + 2))
1387: usage();
1388:
1389: userhost = xstrdup(argv[optind]);
1390: file2 = argv[optind+1];
1.1 djm 1391:
1.32 markus 1392: if ((host = strrchr(userhost, '@')) == NULL)
1.23 djm 1393: host = userhost;
1394: else {
1395: *host++ = '\0';
1396: if (!userhost[0]) {
1397: fprintf(stderr, "Missing username\n");
1398: usage();
1399: }
1400: addargs(&args, "-l%s",userhost);
1.41 djm 1401: }
1402:
1403: if ((cp = colon(host)) != NULL) {
1404: *cp++ = '\0';
1405: file1 = cp;
1.23 djm 1406: }
1.3 djm 1407:
1.23 djm 1408: host = cleanhostname(host);
1409: if (!*host) {
1410: fprintf(stderr, "Missing hostname\n");
1.1 djm 1411: usage();
1412: }
1413:
1.23 djm 1414: addargs(&args, "-oProtocol %d", sshver);
1415:
1416: /* no subsystem if the server-spec contains a '/' */
1417: if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
1418: addargs(&args, "-s");
1419:
1420: addargs(&args, "%s", host);
1.27 markus 1421: addargs(&args, "%s", (sftp_server != NULL ?
1.23 djm 1422: sftp_server : "sftp"));
1423: args.list[0] = ssh_program;
1424:
1.39 djm 1425: if (!batchmode)
1426: fprintf(stderr, "Connecting to %s...\n", host);
1.36 djm 1427: connect_to_server(ssh_program, args.list, &in, &out);
1.23 djm 1428: } else {
1429: args.list = NULL;
1430: addargs(&args, "sftp-server");
1431:
1.39 djm 1432: if (!batchmode)
1433: fprintf(stderr, "Attaching to %s...\n", sftp_direct);
1.36 djm 1434: connect_to_server(sftp_direct, args.list, &in, &out);
1.1 djm 1435: }
1436:
1.33 djm 1437: err = interactive_loop(in, out, file1, file2);
1.1 djm 1438:
1439: close(in);
1440: close(out);
1.39 djm 1441: if (batchmode)
1.10 deraadt 1442: fclose(infile);
1.1 djm 1443:
1.28 markus 1444: while (waitpid(sshpid, NULL, 0) == -1)
1445: if (errno != EINTR)
1446: fatal("Couldn't wait for ssh process: %s",
1447: strerror(errno));
1.1 djm 1448:
1.33 djm 1449: exit(err == 0 ? 0 : 1);
1.1 djm 1450: }