diff --git a/.gitignore b/.gitignore index 838c9da..7ba8947 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,15 @@ config.status *.o sshpass stamp-h1 +INSTALL +Makefile.in +aclocal.m4 +autom4te.cache/ +compile +config.guess +config.h.in +config.sub +configure +depcomp +install-sh +missing diff --git a/README.md b/README.md index 5b2f090..023d0ca 100644 --- a/README.md +++ b/README.md @@ -5,26 +5,44 @@ For ssh servers with 2FA, with a normal password and time-based one time passwor ## Usage -Create a file containing your ssh password. ex: `~/.ssh/pw` +Create a file containing your ssh password, ex: `~/.ssh/pw` or better, create a command, ex: `~/.ssh/pwd`, that +prints the password after having it safely obtained from a password manager, like 1Password. -Create an shell executable file printing your One time password. ex: `~/.ssh/totp` +Create a command that prints your password using the 1Password CLI, ex: `~/.ssh/pwd`: + +```shell=1 +#!/bin/sh + +op read op://Private/my-account/password +``` + +Create a command that prints your One Time Password, ex: `~/.ssh/totp`: ```shell=1 #!/bin/sh oathtool --totp -b YOUR-SECRET-KEY -``` +``` + +or using 1Password CLI: + +```shell=1 +#!/bin/sh + +op read op://Private/my-account/one-time\ password?attribute=otp +``` Remember setting executable bit: ``` +chmod +x ~/.ssh/pwd chmod +x ~/.ssh/totp ``` -Run sshpass with -f and -c parameters: +Run sshpass with -x and -c parameters: ``` -sshpass -f ~/.ssh/pw -c ~/.ssh/totp ssh your-ssh-server +sshpass -x ~/.ssh/pwd -c ~/.ssh/totp ssh your-ssh-server ``` You can use -v parameter if something wrong. @@ -36,7 +54,7 @@ You can use sshpass and ssh as a proxy command for connecting beyond severs. Edi ``` Host beyond - ProxyCommand sshpass -f ~/.ssh/pw -c ~/.ssh/totp ssh bastion -qW %h:%p + ProxyCommand sshpass -x ~/.ssh/pwd -c ~/.ssh/totp ssh bastion -qW %h:%p ``` Then run ssh. @@ -51,12 +69,13 @@ ssh beyond Added parameters: ``` +-x command executable file name printing the password -o OTP One time password -c command executable file name printing one time password -O OTP prompt Which string should sshpass search for the one time password prompt ``` --O option's default is `Verification code:`. +`-O` option's default is `Verification code:`. ## Build diff --git a/main.c b/main.c index 158ec89..b7c79cb 100644 --- a/main.c +++ b/main.c @@ -52,6 +52,7 @@ enum program_return_codes { RETURN_HOST_KEY_CHANGED, RETURN_INCORRECT_OTP, RETURN_OTP_COMMAND_ERROR, + RETURN_PWT_COMMAND_ERROR }; // Some systems don't define posix_openpt @@ -63,6 +64,7 @@ posix_openpt(int flags) } #endif +void run_pw_command(); void run_otp_command(); int runprogram( int argc, char *argv[] ); void reliable_write( int fd, const void *data, size_t size ); @@ -75,11 +77,12 @@ void write_pass( int fd ); void write_otp( int fd ); struct { - enum { PWT_STDIN, PWT_FILE, PWT_FD, PWT_PASS } pwtype; + enum { PWT_STDIN, PWT_FILE, PWT_FD, PWT_COMMAND, PWT_PASS } pwtype; union { const char *filename; int fd; const char *password; + const char *pwcommand; } pwsrc; const char *pwprompt; @@ -97,6 +100,7 @@ static void show_help() " -f filename Take password to use from file\n" " -d number Use number as file descriptor for getting password\n" " -p password Provide password as argument (security unwise)\n" + " -x command executable file name printing password\n" " -e Password is passed as env-var \"SSHPASS\"\n" " With no parameters - password will be taken from stdin\n\n" " -P prompt Which string should sshpass search for to detect a password prompt\n" @@ -106,7 +110,7 @@ static void show_help() " -o OTP One time password\n" " -c command executable file name printing one time password\n" " -O OTP prompt Which string should sshpass search for the one time password prompt\n" - "At most one of -f, -d, -p or -e should be used\n"); + "At most one of -f, -d, -p, -x or -e should be used\n"); } // Parse the command line. Fill in the "args" global struct with the results. Return argv offset @@ -132,7 +136,7 @@ static int parse_options( int argc, char *argv[] ) optarg[i]='z'; \ } while(0) - while( (opt=getopt(argc, argv, "+f:d:p:P:o:c:O:heVv"))!=-1 && error==-1 ) { + while( (opt=getopt(argc, argv, "+f:d:p:x:P:o:c:O:heVv"))!=-1 && error==-1 ) { switch( opt ) { case 'f': // Password should come from a file @@ -163,6 +167,13 @@ static int parse_options( int argc, char *argv[] ) for( i=0; optarg[i]!='\0'; ++i ) optarg[i]='z'; } + break; + case 'x': + // Password is provided by command + VIRGIN_PWTYPE; + args.pwtype=PWT_COMMAND; + args.pwsrc.pwcommand=strdup(optarg); + HIDE_OPTARG; break; case 'P': args.pwprompt=optarg; @@ -249,6 +260,10 @@ int main( int argc, char *argv[] ) } } + if( args.pwtype == PWT_COMMAND ) { + run_pw_command(); + } + if( args.otptype == OTP_COMMAND ) { run_otp_command(); } @@ -580,6 +595,7 @@ void write_pass( int fd ) } break; case PWT_PASS: + case PWT_COMMAND: reliable_write( fd, args.pwsrc.password, strlen( args.pwsrc.password ) ); reliable_write( fd, "\n", 1 ); break; @@ -682,3 +698,29 @@ void run_otp_command() pclose( fp ); } + +void run_pw_command() +{ + if( args.verbose ) { + fprintf(stderr, "SSHPASS popen(%s)\n", args.pwsrc.pwcommand); + } + + FILE *fp = popen( args.pwsrc.pwcommand, "r" ); + if( fp == NULL ) { + if( args.verbose ) { + perror( args.pwsrc.pwcommand ); + } + exit(RETURN_PWT_COMMAND_ERROR); + } + + char buf[256]; + if( fgets( buf, sizeof(buf), fp ) != NULL ) { + char *p = strchr(buf, '\n'); + if( p != NULL ) { + *p='\0'; + } + args.pwsrc.password = strdup(buf); + } + + pclose( fp ); +} diff --git a/sshpass.1 b/sshpass.1 index 854c6af..106b178 100644 --- a/sshpass.1 +++ b/sshpass.1 @@ -4,7 +4,7 @@ sshpass \- noninteractive ssh password provider .SH SYNOPSIS .B sshpass -.RB [ -f\fIfilename | -d\fInum | -p\fIpassword | -e ] +.RB [ -f \fIfilename | -d \fInum | -p \fIpassword | -x \fIcommand | -e ] [ -o \fIotp | -c \fIcommand ] .RI [ options ] " command arguments" .br .SH DESCRIPTION @@ -24,27 +24,44 @@ prompt used by ssh is, however, currently hardcoded into sshpass. If no option is given, sshpass reads the password from the standard input. The user may give at most one alternative source for the password: .TP -.B \-p\fIpassword\fP +.B \-p \fIpassword\fP The password is given on the command line. Please note the section titled "\fBSECURITY CONSIDERATIONS\fP". .TP -.B \-f\fIfilename\fP +.B \-f \fIfilename\fP The password is the first line of the file \fIfilename\fP. .TP -.B \-d\fInumber\fP +.B \-x \fIcommand\fP +The password is printed by executing the \fIcommand\fP. This is useful +when the password can be safely retrieved from a password manager (like 1Password). +.TP +.B \-d \fInumber\fP \fInumber\fP is a file descriptor inherited by sshpass from the runner. The password is read from the open file descriptor. .TP .B \-e The password is taken from the environment variable "SSHPASS". .TP -.B \-P +.B \-P \fIprompt\fP Set the password prompt. Sshpass searched for this prompt in the program's output to the TTY as an indication when to send the password. By default sshpass looks for the string "assword:" (which matches both "Password:" and "password:"). If your client's prompt does not fall under either of these, you can override the default with this option. .TP +.B \-o \fIotp\fP +\fIotp\fP is the One Time Password coming from a OTP code generator. +.TP +.B \-c \fIcommand\fP +The OTP (One Time Password) is printed by executing the \fIcommand\fP. This is useful +when the OTP can be safely retrieved from a password manager (like 1Password). +.TP +.B \-O \fIprompt\fP +Set the OTP prompt. Sshpass searched for this prompt in the program's +output to the TTY as an indication when to send the OTP code. By default +sshpass looks for the string "Verification code:". If your client's prompt does +not fall under either of these, you can override the default with this option. +.TP .B \-v Be verbose. sshpass will output to stderr information that should help debug cases where the connection hangs, seemingly for no good reason.