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