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