main.c 14 KB

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