"Fossies" - the Fresh Open Source Software Archive

Member "sshpass-1.09/main.c" (29 Jan 2021, 17703 Bytes) of package /linux/privat/sshpass-1.09.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "main.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.07_vs_1.09.

    1 /*  This file is part of "sshpass", a tool for batch running password ssh authentication
    2  *  Copyright (C) 2006 Lingnu Open Source Consulting Ltd.
    3  *  Copyright (C) 2015-2016, 2021 Shachar Shemesh
    4  *
    5  *  This program is free software; you can redistribute it and/or modify
    6  *  it under the terms of the GNU General Public License as published by
    7  *  the Free Software Foundation; either version 2 of the License, or
    8  *  (at your option) any later version, provided that it was accepted by
    9  *  Lingnu Open Source Consulting Ltd. as an acceptable license for its
   10  *  projects. Consult http://www.lingnu.com/licenses.html
   11  *
   12  *  This program is distributed in the hope that it will be useful,
   13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   15  *  GNU General Public License for more details.
   16  *
   17  *  You should have received a copy of the GNU General Public License
   18  *  along with this program; if not, write to the Free Software
   19  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   20  */
   21 
   22 #if HAVE_CONFIG_H
   23 #include "config.h"
   24 #endif
   25 
   26 #include <sys/types.h>
   27 #include <sys/stat.h>
   28 #include <sys/wait.h>
   29 #include <sys/ioctl.h>
   30 #include <sys/select.h>
   31 
   32 #include <unistd.h>
   33 #include <fcntl.h>
   34 #include <signal.h>
   35 #if HAVE_TERMIOS_H
   36 #include <termios.h>
   37 #endif
   38 
   39 #include <stdio.h>
   40 #include <stdlib.h>
   41 #include <errno.h>
   42 #include <string.h>
   43 
   44 enum program_return_codes {
   45     RETURN_NOERROR,
   46     RETURN_INVALID_ARGUMENTS,
   47     RETURN_CONFLICTING_ARGUMENTS,
   48     RETURN_RUNTIME_ERROR,
   49     RETURN_PARSE_ERRROR,
   50     RETURN_INCORRECT_PASSWORD,
   51     RETURN_HOST_KEY_UNKNOWN,
   52     RETURN_HOST_KEY_CHANGED,
   53 };
   54 
   55 // Some systems don't define posix_openpt
   56 #ifndef HAVE_POSIX_OPENPT
   57 int
   58 posix_openpt(int flags)
   59 {
   60     return open("/dev/ptmx", flags);
   61 }
   62 #endif
   63 
   64 int runprogram( int argc, char *argv[] );
   65 void reliable_write( int fd, const void *data, size_t size );
   66 int handleoutput( int fd );
   67 void window_resize_handler(int signum);
   68 void sigchld_handler(int signum);
   69 void term_handler(int signum);
   70 int match( const char *reference, const char *buffer, ssize_t bufsize, int state );
   71 void write_pass( int fd );
   72 
   73 struct {
   74     enum { PWT_STDIN, PWT_FILE, PWT_FD, PWT_PASS } pwtype;
   75     union {
   76         const char *filename;
   77         int fd;
   78         const char *password;
   79     } pwsrc;
   80 
   81     const char *pwprompt;
   82     int verbose;
   83     char *orig_password;
   84 } args;
   85 
   86 static void show_help()
   87 {
   88     printf("Usage: " PACKAGE_NAME " [-f|-d|-p|-e] [-hV] command parameters\n"
   89             "   -f filename   Take password to use from file\n"
   90             "   -d number     Use number as file descriptor for getting password\n"
   91             "   -p password   Provide password as argument (security unwise)\n"
   92             "   -e            Password is passed as env-var \"SSHPASS\"\n"
   93             "   With no parameters - password will be taken from stdin\n\n"
   94             "   -P prompt     Which string should sshpass search for to detect a password prompt\n"
   95             "   -v            Be verbose about what you're doing\n"
   96             "   -h            Show help (this screen)\n"
   97             "   -V            Print version information\n"
   98             "At most one of -f, -d, -p or -e should be used\n");
   99 }
  100 
  101 // Parse the command line. Fill in the "args" global struct with the results. Return argv offset
  102 // on success, and a negative number on failure
  103 static int parse_options( int argc, char *argv[] )
  104 {
  105     int error=-1;
  106     int opt;
  107 
  108     // Set the default password source to stdin
  109     args.pwtype=PWT_STDIN;
  110     args.pwsrc.fd=0;
  111 
  112 #define VIRGIN_PWTYPE if( args.pwtype!=PWT_STDIN ) { \
  113     fprintf(stderr, "Conflicting password source\n"); \
  114     error=RETURN_CONFLICTING_ARGUMENTS; }
  115 
  116     while( (opt=getopt(argc, argv, "+f:d:p:P:heVv"))!=-1 && error==-1 ) {
  117         switch( opt ) {
  118         case 'f':
  119             // Password should come from a file
  120             VIRGIN_PWTYPE;
  121 
  122             args.pwtype=PWT_FILE;
  123             args.pwsrc.filename=optarg;
  124             break;
  125         case 'd':
  126             // Password should come from an open file descriptor
  127             VIRGIN_PWTYPE;
  128 
  129             args.pwtype=PWT_FD;
  130             args.pwsrc.fd=atoi(optarg);
  131             break;
  132         case 'p':
  133             // Password is given on the command line
  134             VIRGIN_PWTYPE;
  135 
  136             args.pwtype=PWT_PASS;
  137             args.orig_password=optarg;
  138             break;
  139         case 'P':
  140             args.pwprompt=optarg;
  141             break;
  142         case 'v':
  143             args.verbose++;
  144             break;
  145         case 'e':
  146             VIRGIN_PWTYPE;
  147 
  148             args.pwtype=PWT_PASS;
  149             args.orig_password=getenv("SSHPASS");
  150             if( args.orig_password==NULL ) {
  151                 fprintf(stderr, "SSHPASS: -e option given but SSHPASS environment variable not set\n");
  152 
  153                 error=RETURN_INVALID_ARGUMENTS;
  154             }
  155             break;
  156         case '?':
  157         case ':':
  158             error=RETURN_INVALID_ARGUMENTS;
  159             break;
  160         case 'h':
  161             error=RETURN_NOERROR;
  162             break;
  163         case 'V':
  164             printf("%s\n"
  165                     "(C) 2006-2011 Lingnu Open Source Consulting Ltd.\n"
  166                     "(C) 2015-2016, 2021 Shachar Shemesh\n"
  167                     "This program is free software, and can be distributed under the terms of the GPL\n"
  168                     "See the COPYING file for more information.\n"
  169                     "\n"
  170                     "Using \"%s\" as the default password prompt indicator.\n", PACKAGE_STRING, PASSWORD_PROMPT );
  171             exit(0);
  172             break;
  173         }
  174     }
  175 
  176     if( error>=0 )
  177         return -(error+1);
  178     else
  179         return optind;
  180 }
  181 
  182 int main( int argc, char *argv[] )
  183 {
  184     int opt_offset=parse_options( argc, argv );
  185 
  186     if( opt_offset<0 ) {
  187         // There was some error
  188         show_help();
  189 
  190         return -(opt_offset+1); // -1 becomes 0, -2 becomes 1 etc.
  191     }
  192 
  193     if( argc-opt_offset<1 ) {
  194         show_help();
  195 
  196         return 0;
  197     }
  198 
  199     if( args.orig_password!=NULL ) {
  200         args.pwsrc.password = strdup(args.orig_password);
  201 
  202         // Hide the original password from prying eyes
  203         while( *args.orig_password != '\0' ) {
  204             *args.orig_password = 'x';
  205             ++args.orig_password;
  206         }
  207     }
  208 
  209     return runprogram( argc-opt_offset, argv+opt_offset );
  210 }
  211 
  212 /* Global variables so that this information be shared with the signal handler */
  213 static int ourtty; // Our own tty
  214 static int masterpt;
  215 
  216 int childpid;
  217 int term;
  218 
  219 int runprogram( int argc, char *argv[] )
  220 {
  221     struct winsize ttysize; // The size of our tty
  222 
  223     // We need to interrupt a select with a SIGCHLD. In order to do so, we need a SIGCHLD handler
  224     signal( SIGCHLD, sigchld_handler );
  225 
  226     // Create a pseudo terminal for our process
  227     masterpt=posix_openpt(O_RDWR);
  228 
  229     if( masterpt==-1 ) {
  230         perror("Failed to get a pseudo terminal");
  231 
  232         return RETURN_RUNTIME_ERROR;
  233     }
  234 
  235     fcntl(masterpt, F_SETFL, O_NONBLOCK);
  236 
  237     if( grantpt( masterpt )!=0 ) {
  238         perror("Failed to change pseudo terminal's permission");
  239 
  240         return RETURN_RUNTIME_ERROR;
  241     }
  242     if( unlockpt( masterpt )!=0 ) {
  243         perror("Failed to unlock pseudo terminal");
  244 
  245         return RETURN_RUNTIME_ERROR;
  246     }
  247 
  248     ourtty=open("/dev/tty", 0);
  249     if( ourtty!=-1 && ioctl( ourtty, TIOCGWINSZ, &ttysize )==0 ) {
  250         signal(SIGWINCH, window_resize_handler);
  251 
  252         ioctl( masterpt, TIOCSWINSZ, &ttysize );
  253     }
  254 
  255     const char *name=ptsname(masterpt);
  256     int slavept;
  257     /*
  258        Comment no. 3.14159
  259 
  260        This comment documents the history of code.
  261 
  262        We need to open the slavept inside the child process, after "setsid", so that it becomes the controlling
  263        TTY for the process. We do not, otherwise, need the file descriptor open. The original approach was to
  264        close the fd immediately after, as it is no longer needed.
  265 
  266        It turns out that (at least) the Linux kernel considers a master ptty fd that has no open slave fds
  267        to be unused, and causes "select" to return with "error on fd". The subsequent read would fail, causing us
  268        to go into an infinite loop. This is a bug in the kernel, as the fact that a master ptty fd has no slaves
  269        is not a permenant problem. As long as processes exist that have the slave end as their controlling TTYs,
  270        new slave fds can be created by opening /dev/tty, which is exactly what ssh is, in fact, doing.
  271 
  272        Our attempt at solving this problem, then, was to have the child process not close its end of the slave
  273        ptty fd. We do, essentially, leak this fd, but this was a small price to pay. This worked great up until
  274        openssh version 5.6.
  275 
  276        Openssh version 5.6 looks at all of its open file descriptors, and closes any that it does not know what
  277        they are for. While entirely within its prerogative, this breaks our fix, causing sshpass to either
  278        hang, or do the infinite loop again.
  279 
  280        Our solution is to keep the slave end open in both parent AND child, at least until the handshake is
  281        complete, at which point we no longer need to monitor the TTY anyways.
  282      */
  283 
  284     sigset_t sigmask, sigmask_select;
  285 
  286     // Set the signal mask during the select
  287     sigemptyset(&sigmask_select);
  288 
  289     // And during the regular run
  290     sigemptyset(&sigmask);
  291     sigaddset(&sigmask, SIGCHLD);
  292     sigaddset(&sigmask, SIGHUP);
  293     sigaddset(&sigmask, SIGTERM);
  294     sigaddset(&sigmask, SIGINT);
  295     sigaddset(&sigmask, SIGTSTP);
  296 
  297     sigprocmask( SIG_SETMASK, &sigmask, NULL );
  298 
  299     signal(SIGHUP, term_handler);
  300     signal(SIGTERM, term_handler);
  301     signal(SIGINT, term_handler);
  302     signal(SIGTSTP, term_handler);
  303 
  304     childpid=fork();
  305     if( childpid==0 ) {
  306         // Child
  307 
  308         // Re-enable all signals to child
  309         sigprocmask( SIG_SETMASK, &sigmask_select, NULL );
  310 
  311         // Detach us from the current TTY
  312         setsid();
  313         // This line makes the ptty our controlling tty. We do not otherwise need it open
  314         slavept=open(name, O_RDWR );
  315 #ifdef TIOCSCTTY
  316         // On some systems, an open(2) is insufficient to set the
  317         // controlling tty (see the documentation for TIOCSCTTY in
  318         // tty(4)).
  319         if (ioctl(slavept, TIOCSCTTY) == -1) {
  320             perror("sshpass: Failed to set controlling terminal in child (TIOCSCTTY)");
  321             exit(RETURN_RUNTIME_ERROR);
  322         }
  323 #endif
  324         close( slavept );
  325 
  326         close( masterpt );
  327 
  328         char **new_argv=malloc(sizeof(char *)*(argc+1));
  329 
  330         int i;
  331 
  332         for( i=0; i<argc; ++i ) {
  333             new_argv[i]=argv[i];
  334         }
  335 
  336         new_argv[i]=NULL;
  337 
  338         execvp( new_argv[0], new_argv );
  339 
  340         perror("SSHPASS: Failed to run command");
  341 
  342         exit(RETURN_RUNTIME_ERROR);
  343     } else if( childpid<0 ) {
  344         perror("SSHPASS: Failed to create child process");
  345 
  346         return RETURN_RUNTIME_ERROR;
  347     }
  348 
  349     // We are the parent
  350     slavept=open(name, O_RDWR|O_NOCTTY );
  351 
  352     int status=0;
  353     int terminate=0;
  354     pid_t wait_id;
  355 
  356     do {
  357         if( !terminate ) {
  358             fd_set readfd;
  359 
  360             FD_ZERO(&readfd);
  361             FD_SET(masterpt, &readfd);
  362 
  363             int selret=pselect( masterpt+1, &readfd, NULL, NULL, NULL, &sigmask_select );
  364 
  365             if( selret>0 ) {
  366                 if( FD_ISSET( masterpt, &readfd ) ) {
  367                     int ret;
  368                     if( (ret=handleoutput( masterpt )) ) {
  369                         // Authentication failed or any other error
  370 
  371                         // handleoutput returns positive error number in case of some error, and a negative value
  372                         // if all that happened is that the slave end of the pt is closed.
  373                         if( ret>0 ) {
  374                             close( masterpt ); // Signal ssh that it's controlling TTY is now closed
  375                             close(slavept);
  376                         }
  377 
  378                         terminate=ret;
  379 
  380                         if( terminate ) {
  381                             close( slavept );
  382                         }
  383                     }
  384                 }
  385             }
  386             wait_id=waitpid( childpid, &status, WNOHANG );
  387         } else {
  388             wait_id=waitpid( childpid, &status, 0 );
  389         }
  390     } while( wait_id==0 || (!WIFEXITED( status ) && !WIFSIGNALED( status )) );
  391 
  392     if( terminate>0 )
  393         return terminate;
  394     else if( WIFEXITED( status ) )
  395         return WEXITSTATUS(status);
  396     else
  397         return 255;
  398 }
  399 
  400 int handleoutput( int fd )
  401 {
  402     // We are looking for the string
  403     static int prevmatch=0; // If the "password" prompt is repeated, we have the wrong password.
  404     static int state1, state2, state3;
  405     static int firsttime = 1;
  406     static const char *compare1=PASSWORD_PROMPT; // Asking for a password
  407     static const char compare2[]="The authenticity of host "; // Asks to authenticate host
  408     static const char compare3[] = "differs from the key for the IP address"; // Key changes
  409     // static const char compare3[]="WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!"; // Warns about man in the middle attack
  410     // The remote identification changed error is sent to stderr, not the tty, so we do not handle it.
  411     // This is not a problem, as ssh exists immediately in such a case
  412     char buffer[256];
  413     int ret=0;
  414 
  415     if( args.pwprompt ) {
  416         compare1 = args.pwprompt;
  417     }
  418 
  419     if( args.verbose && firsttime ) {
  420         firsttime=0;
  421         fprintf(stderr, "SSHPASS: searching for password prompt using match \"%s\"\n", compare1);
  422     }
  423 
  424     int numread=read(fd, buffer, sizeof(buffer)-1 );
  425     buffer[numread] = '\0';
  426     if( args.verbose ) {
  427         fprintf(stderr, "SSHPASS: read: %s\n", buffer);
  428     }
  429 
  430     state1=match( compare1, buffer, numread, state1 );
  431 
  432     // Are we at a password prompt?
  433     if( compare1[state1]=='\0' ) {
  434         if( !prevmatch ) {
  435             if( args.verbose )
  436                 fprintf(stderr, "SSHPASS: detected prompt. Sending password.\n");
  437             write_pass( fd );
  438             state1=0;
  439             prevmatch=1;
  440         } else {
  441             // Wrong password - terminate with proper error code
  442             if( args.verbose )
  443                 fprintf(stderr, "SSHPASS: detected prompt, again. Wrong password. Terminating.\n");
  444             ret=RETURN_INCORRECT_PASSWORD;
  445         }
  446     }
  447 
  448     if( ret==0 ) {
  449         state2=match( compare2, buffer, numread, state2 );
  450 
  451         // Are we being prompted to authenticate the host?
  452         if( compare2[state2]=='\0' ) {
  453             if( args.verbose )
  454                 fprintf(stderr, "SSHPASS: detected host authentication prompt. Exiting.\n");
  455             ret=RETURN_HOST_KEY_UNKNOWN;
  456         } else {
  457             state3 = match( compare3, buffer, numread, state3 );
  458             // Host key changed
  459             if ( compare3[state3]=='\0' ) {
  460                 ret=RETURN_HOST_KEY_CHANGED;
  461             }
  462         }
  463     }
  464 
  465     return ret;
  466 }
  467 
  468 int match( const char *reference, const char *buffer, ssize_t bufsize, int state )
  469 {
  470     // This is a highly simplisic implementation. It's good enough for matching "Password: ", though.
  471     int i;
  472     for( i=0;reference[state]!='\0' && i<bufsize; ++i ) {
  473         if( reference[state]==buffer[i] )
  474             state++;
  475         else {
  476             state=0;
  477             if( reference[state]==buffer[i] )
  478                 state++;
  479         }
  480     }
  481 
  482     return state;
  483 }
  484 
  485 void write_pass_fd( int srcfd, int dstfd );
  486 
  487 void write_pass( int fd )
  488 {
  489     switch( args.pwtype ) {
  490     case PWT_STDIN:
  491         write_pass_fd( STDIN_FILENO, fd );
  492         break;
  493     case PWT_FD:
  494         write_pass_fd( args.pwsrc.fd, fd );
  495         break;
  496     case PWT_FILE:
  497         {
  498             int srcfd=open( args.pwsrc.filename, O_RDONLY );
  499             if( srcfd!=-1 ) {
  500                 write_pass_fd( srcfd, fd );
  501                 close( srcfd );
  502             } else {
  503                 fprintf(stderr, "SSHPASS: Failed to open password file \"%s\": %s\n", args.pwsrc.filename, strerror(errno));
  504             }
  505         }
  506         break;
  507     case PWT_PASS:
  508         reliable_write( fd, args.pwsrc.password, strlen( args.pwsrc.password ) );
  509         reliable_write( fd, "\n", 1 );
  510         break;
  511     }
  512 }
  513 
  514 void write_pass_fd( int srcfd, int dstfd )
  515 {
  516 
  517     int done=0;
  518 
  519     while( !done ) {
  520         char buffer[40];
  521         int i;
  522         int numread=read( srcfd, buffer, sizeof(buffer) );
  523         done=(numread<1);
  524         for( i=0; i<numread && !done; ++i ) {
  525             if( buffer[i]!='\n' )
  526                 reliable_write( dstfd, buffer+i, 1 );
  527             else
  528                 done=1;
  529         }
  530     }
  531 
  532     reliable_write( dstfd, "\n", 1 );
  533 }
  534 
  535 void window_resize_handler(int signum)
  536 {
  537     struct winsize ttysize; // The size of our tty
  538 
  539     if( ioctl( ourtty, TIOCGWINSZ, &ttysize )==0 )
  540         ioctl( masterpt, TIOCSWINSZ, &ttysize );
  541 }
  542 
  543 // Do nothing handler - makes sure the select will terminate if the signal arrives, though.
  544 void sigchld_handler(int signum)
  545 {
  546 }
  547 
  548 void term_handler(int signum)
  549 {
  550     fflush(stdout);
  551     switch(signum) {
  552     case SIGINT:
  553         reliable_write(masterpt, "\x03", 1);
  554         break;
  555     case SIGTSTP:
  556         reliable_write(masterpt, "\x1a", 1);
  557         break;
  558     default:
  559         if( childpid>0 ) {
  560             kill( childpid, signum );
  561         }
  562     }
  563 
  564     term = 1;
  565 }
  566 
  567 void reliable_write( int fd, const void *data, size_t size )
  568 {
  569     ssize_t result = write( fd, data, size );
  570     if( result!=size ) {
  571         if( result<0 ) {
  572             perror("SSHPASS: write failed");
  573         } else {
  574             fprintf(stderr, "SSHPASS: Short write. Tried to write %lu, only wrote %ld\n", size, result);
  575         }
  576     }
  577 }