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