#include #include #include #include #include #include #include #include #include #include // TODO: implement multiple ttys // TODO: inotify on a password file (kills pinentry) // TODO: secure memory /* Buffers: * * assuan info page "The implementation is line based with a maximum line size * of 1000 octets. The default IPC mechanism are Unix Domain Sockets. * * pinentry info page "Although it is called a PIN-Entry, it does allow to * enter reasonably long strings (at least 2048 characters are supported by * every pinentry). The client using the PIN-Entry has to check for * correctness." */ #define MAXLINE 1000 // It's assumed this is > 2. static int open2(const char *argv[], int *result_in, int *result_out, pid_t *result_child) { int pipe_out[2]; /* out of parent */ int pipe_in[2]; /* into parent */ pid_t cpid; if (pipe(pipe_out) < 0) return -1; if (pipe(pipe_in) < 0) return -1; cpid = fork(); if (cpid < 0) return -1; if (cpid == 0) { /* child */ int *input = pipe_out; /* for sanity */ int *output = pipe_in; close(output[0]); close(input[1]); /* see http://unixwiz.net/techtips/remap-pipe-fds.c.txt */ if (output[1] == 0) if ((output[1] = dup(output[1]) < 0)) error(1, errno, "in child process: dup"); if (output[0] == 1) if ((output[0] = dup(output[0]) < 0)) error(1, errno, "in child process: dup"); if (dup2(output[1], 1) < 0) error(1, errno, "in child process: dup2"); if (dup2(input[0], 0) < 0) error(1, errno, "in child process: dup2"); if (output[1] != 1) close(output[1]); if (input[0] != 0) close(input[0]); execv(argv[0],(char *const*) &argv[1]); error(1, errno, "in child process: exec"); return -1; /* fucking warning */ } else { /* parent */ close(pipe_out[0]); close(pipe_in[1]); *result_in = pipe_in[0]; *result_out = pipe_out[1]; if (*result_child) *result_child = cpid; return 0; } } typedef struct { FILE *in, *out; pid_t pid; } pipe2_t; pipe2_t fopen2(const char *argv[]) { pipe2_t res = { 0, 0, 0 }; int in = 0; int out = 0; if( open2(argv, &in, &out, &res.pid) < 0 ) error(1,0, "open2"); res.out = fdopen(out, "w"); res.in = fdopen(in, "r"); return res; } void pinentry_send(pipe2_t pinentry, const char *msg) { fprintf(pinentry.out, "%s\n", msg); fflush(pinentry.out); // printf("C: %s\n", msg); } int pinentry_get(pipe2_t pinentry, char *data, size_t len) { char *ret=fgets(data,len,pinentry.in); char *p=ret; int gotnewline=0; for(;*p;p++); if(p!=ret && p[-1]=='\n') { p[-1]='\0'; gotnewline=1; } if( !gotnewline ) { int c = ' '; do { c=fgetc(pinentry.in); } while( c!=-1 && c!='\n' ); } // printf("S: %s%s", ret, gotnewline?"\n":" \n"); return !gotnewline; } void pinentry_seterror(pipe2_t pinentry, const char *s) { fprintf(pinentry.out,"SETERROR "); pinentry_send( pinentry, s ); char data[4] = { 0 }; pinentry_get(pinentry,data,4); // OK } void pinentry_set(pipe2_t pinentry, const char *k, const char *v) { fprintf(pinentry.out,"SET%s %s\n", k, v); fflush(pinentry.out); char data[4] = { 0 }; pinentry_get(pinentry,data,4); // OK } static const char *arg_prompt = "Passphrase:"; static const char *arg_desc = "Enter your passphrase to unlock the secret key. "; int pinentry_getpin(pipe2_t pinentry, char *secret, size_t maxline) { char data[MAXLINE] = { 0 }; // pinentry_send( pinentry, "OPTION lc-ctype=en_US.UTF-8"); // pinentry_get( pinentry, data, MAXLINE); // OK if (strlen(arg_prompt)) pinentry_set( pinentry, "PROMPT", arg_prompt ); if (strlen(arg_desc)) pinentry_set( pinentry, "DESC", arg_desc ); pinentry_send( pinentry, "GETPIN"); int e = pinentry_get(pinentry, data, MAXLINE); if( strncmp(data,"D ",2)==0 ) { if(!e && strlen(data+2)<=maxline-1 ) { // D snprintf(secret,maxline,"%s", data+2); //printf("secret = %s\n", secret); pinentry_get(pinentry,data,MAXLINE); // OK return 0; } else { // SECRET TOO BIG, discarding pinentry_seterror(pinentry, "Too big."); return 4; } } else if( strncmp(data,"OK",2)==0 ) { // EMPTY (not an error) secret[0]='\0'; //printf("empty secret\n"); return 0; } else if( strncmp(data,"ERR ",4)==0 ) { // CANCELED (ERR 83886179 canceled) // ERR 83886246 locale problem // xxx printf("canceled.\n"); return 2; } else { // WTF? // xxx printf("Unexpected: %s\n", data); char buf[1024]; snprintf(buf,1024,"Unexpected: %s", data ); pinentry_seterror(pinentry, buf); return 3; } } pipe2_t do_shell(const char *shell_code) { pipe2_t ret; const char *argv[] = { "/bin/sh", "/bin/sh","-c",NULL,NULL }; argv[3]=shell_code; return fopen2(argv); } int validate(pipe2_t pinentry, const char *secret, const char *arg_validate) { int retv = 0; if( !arg_validate ) return 1; pipe2_t validate_script = do_shell( arg_validate ); fprintf( validate_script.out, "%s\n", secret ); fclose( validate_script.out ); char error_string[1024] = "Error%0Aunspecified error"; int c; char *p = error_string; while( p!=&error_string[1024-4] && -1!=(c=getc( validate_script.in )) ) switch(c){ case '%': case '\n': snprintf(p,4, "%%%0.02X", c); p+=3; break; default: *p++ = c; } *p='\0'; // pinentry ruins its display if this message ends with a newline while(p-3>error_string && memcmp(&p[-3],"%0A",3)==0 ) p-=3; *p='\0'; fclose( validate_script.in ); waitpid(validate_script.pid, &retv, 0 ); if( retv ) { fprintf(stderr, "error_string: %s\n", error_string); pinentry_seterror(pinentry, error_string ); } return !retv; } FILE *open_socket(const char *socket_path) { int sok = socket(PF_LOCAL, SOCK_STREAM, 0); if( sok<0 ) { return NULL; } struct sockaddr_un address; memset(&address, 0, sizeof(struct sockaddr_un)); address.sun_family = AF_UNIX; snprintf(address.sun_path, 108, socket_path); if(connect(sok, (struct sockaddr *) &address, sizeof(struct sockaddr_un)) != 0) { return NULL; } return fdopen(sok,"r+"); } const char *arg_socket = NULL; const char *arg_resocket = NULL; const char *arg_tellsecret = NULL; char secret[MAXLINE]; // TODO: secure memory void tell_encoded_secret(FILE *out) { char *encoded_secret = malloc(strlen(secret)*2 + 1); for (char *p = secret; *p; ++p) snprintf(encoded_secret + 2*(p - secret), 3, "%0.02X", *p); fprintf(out, "%s\n", encoded_secret); fflush(out); free(encoded_secret); } void signal_usr1(int val) { if( !arg_tellsecret ) { fprintf(stderr, "USR1, no action\n"); return; } else fprintf(stderr, "Received SIGUSR1; will tell secret.\n"); pipe2_t tellsecret = do_shell( arg_tellsecret ); tell_encoded_secret(tellsecret.out); FILE *sock = NULL; if (arg_socket) sock=open_socket(arg_socket); for (;;) { char *response = 0; size_t len; if (sock && (getline(&response, &len, sock) > 0)) { fprintf(tellsecret.out, "%s", response); fflush(tellsecret.out); } if (getline(&response, &len, tellsecret.in) > 0) { if (sock) { fprintf(sock, "%s", response); fflush(sock); } else { fprintf(stderr, "?> %s", response); } } else { break; } } if (sock) fclose(sock); fclose(tellsecret.out); fclose(tellsecret.in); int status = 0; waitpid(tellsecret.pid, &status, 0 ); } void signal_usr2(int wat) { fprintf(stderr, "Received SIGUSR2; will change socket and emulate SIGUSR1.\n"); if (arg_resocket) arg_socket = arg_resocket; signal_usr1(wat); } void signal_term(int val) { // TODO: Wipe secret exit(0); } int main(int argc, char **argv) { pipe2_t pinentry = { 0 }; char tty_name_buf[256]; if( ttyname_r(1, tty_name_buf, sizeof(tty_name_buf)) ) { tty_name_buf[0]='\0'; //error(2,0,"ttyname_r"); } const char *arg_ttyname = tty_name_buf; const char *arg_pinentry = "/usr/bin/pinentry-curses"; const char *arg_validate = NULL; const char *arg_pidfile = NULL; //same as "/dev/stdout"; int tell_at_start=0; const char **expect = NULL; int i=0; for(i=1;i