summaryrefslogtreecommitdiff
path: root/src/samizdat-pinentry.c
diff options
context:
space:
mode:
authorAndrew Cady <d@jerkface.net>2016-05-01 05:25:14 -0400
committerAndrew Cady <d@jerkface.net>2016-05-01 05:28:22 -0400
commita8e19d5d8057e82cbda2705d755f3d4e1d3da20a (patch)
tree84449f8ac6e45a5727b0abbb64eeb578c20628fd /src/samizdat-pinentry.c
parent4854ffec94f70705dc95c5657e43c5f69c270a1a (diff)
remove references to files outside of this repo
(commit the files into this repo)
Diffstat (limited to 'src/samizdat-pinentry.c')
-rw-r--r--src/samizdat-pinentry.c455
1 files changed, 455 insertions, 0 deletions
diff --git a/src/samizdat-pinentry.c b/src/samizdat-pinentry.c
new file mode 100644
index 0000000..e1f7fee
--- /dev/null
+++ b/src/samizdat-pinentry.c
@@ -0,0 +1,455 @@
1#include <error.h>
2#include <unistd.h>
3#include <stdlib.h>
4#include <stdio.h>
5#include <errno.h>
6#include <string.h>
7#include <sys/types.h>
8#include <sys/wait.h>
9#include <sys/socket.h>
10#include <sys/un.h>
11
12
13// TODO: implement multiple ttys
14// TODO: inotify on a password file (kills pinentry)
15// TODO: secure memory
16
17
18/* Buffers:
19 *
20 * assuan info page "The implementation is line based with a maximum line size
21 * of 1000 octets. The default IPC mechanism are Unix Domain Sockets.
22 *
23 * pinentry info page "Although it is called a PIN-Entry, it does allow to
24 * enter reasonably long strings (at least 2048 characters are supported by
25 * every pinentry). The client using the PIN-Entry has to check for
26 * correctness."
27 */
28#define MAXLINE 1000 // It's assumed this is > 2.
29
30
31static int
32open2(const char *argv[], int *result_in, int *result_out, pid_t *result_child)
33{
34 int pipe_out[2]; /* out of parent */
35 int pipe_in[2]; /* into parent */
36 pid_t cpid;
37
38 if (pipe(pipe_out) < 0)
39 return -1;
40 if (pipe(pipe_in) < 0)
41 return -1;
42
43 cpid = fork();
44 if (cpid < 0)
45 return -1;
46
47 if (cpid == 0) { /* child */
48 int *input = pipe_out; /* for sanity */
49 int *output = pipe_in;
50 close(output[0]);
51 close(input[1]);
52
53 /* see http://unixwiz.net/techtips/remap-pipe-fds.c.txt */
54 if (output[1] == 0)
55 if ((output[1] = dup(output[1]) < 0))
56 error(1, errno, "in child process: dup");
57 if (output[0] == 1)
58 if ((output[0] = dup(output[0]) < 0))
59 error(1, errno, "in child process: dup");
60
61 if (dup2(output[1], 1) < 0)
62 error(1, errno, "in child process: dup2");
63 if (dup2(input[0], 0) < 0)
64 error(1, errno, "in child process: dup2");
65
66 if (output[1] != 1)
67 close(output[1]);
68 if (input[0] != 0)
69 close(input[0]);
70
71 execv(argv[0],(char *const*) &argv[1]);
72 error(1, errno, "in child process: exec");
73 return -1; /* fucking warning */
74
75 } else { /* parent */
76 close(pipe_out[0]);
77 close(pipe_in[1]);
78
79 *result_in = pipe_in[0];
80 *result_out = pipe_out[1];
81 if (*result_child)
82 *result_child = cpid;
83 return 0;
84 }
85}
86
87typedef struct {
88 FILE *in, *out;
89 pid_t pid;
90} pipe2_t;
91
92
93pipe2_t fopen2(const char *argv[])
94{
95 pipe2_t res = { 0, 0, 0 };
96 int in = 0;
97 int out = 0;
98 if( open2(argv, &in, &out, &res.pid) < 0 )
99 error(1,0, "open2");
100 res.out = fdopen(out, "w");
101 res.in = fdopen(in, "r");
102 return res;
103}
104
105void pinentry_send(pipe2_t pinentry, const char *msg)
106{
107 fprintf(pinentry.out, "%s\n", msg);
108 fflush(pinentry.out);
109 // printf("C: %s\n", msg);
110}
111int pinentry_get(pipe2_t pinentry, char *data, size_t len)
112{
113 char *ret=fgets(data,len,pinentry.in);
114 char *p=ret;
115 int gotnewline=0;
116 for(;*p;p++);
117 if(p!=ret && p[-1]=='\n') {
118 p[-1]='\0';
119 gotnewline=1;
120 }
121 if( !gotnewline ) {
122 int c = ' ';
123 do {
124 c=fgetc(pinentry.in);
125 } while( c!=-1 && c!='\n' );
126 }
127 // printf("S: %s%s", ret, gotnewline?"\n":" <truncated>\n");
128 return !gotnewline;
129}
130
131void pinentry_seterror(pipe2_t pinentry, const char *s)
132{
133 fprintf(pinentry.out,"SETERROR ");
134 pinentry_send( pinentry, s );
135 char data[4] = { 0 };
136 pinentry_get(pinentry,data,4); // OK
137}
138
139void pinentry_set(pipe2_t pinentry, const char *k, const char *v)
140{
141 fprintf(pinentry.out,"SET%s %s\n", k, v);
142 fflush(pinentry.out);
143 char data[4] = { 0 };
144 pinentry_get(pinentry,data,4); // OK
145}
146
147static const char *arg_prompt = "Passphrase:";
148static const char *arg_desc = "Enter your passphrase to unlock the secret key. ";
149
150int pinentry_getpin(pipe2_t pinentry, char *secret, size_t maxline)
151{
152 char data[MAXLINE] = { 0 };
153
154 // pinentry_send( pinentry, "OPTION lc-ctype=en_US.UTF-8");
155 // pinentry_get( pinentry, data, MAXLINE); // OK
156
157 if (strlen(arg_prompt))
158 pinentry_set( pinentry, "PROMPT", arg_prompt );
159 if (strlen(arg_desc))
160 pinentry_set( pinentry, "DESC", arg_desc );
161
162 pinentry_send( pinentry, "GETPIN");
163 int e = pinentry_get(pinentry, data, MAXLINE);
164 if( strncmp(data,"D ",2)==0 ) {
165 if(!e && strlen(data+2)<=maxline-1 ) {
166 // D <secret>
167 snprintf(secret,maxline,"%s", data+2);
168 //printf("secret = %s\n", secret);
169 pinentry_get(pinentry,data,MAXLINE); // OK
170 return 0;
171 }
172 else {
173 // SECRET TOO BIG, discarding
174 pinentry_seterror(pinentry, "Too big.");
175 return 4;
176 }
177 }
178 else if( strncmp(data,"OK",2)==0 ) {
179 // EMPTY (not an error)
180 secret[0]='\0';
181 //printf("empty secret\n");
182 return 0;
183 }
184 else if( strncmp(data,"ERR ",4)==0 ) {
185 // CANCELED (ERR 83886179 canceled)
186 // ERR 83886246 locale problem
187 // xxx printf("canceled.\n");
188 return 2;
189 }
190 else {
191 // WTF?
192 // xxx printf("Unexpected: %s\n", data);
193 char buf[1024];
194 snprintf(buf,1024,"Unexpected: %s", data );
195 pinentry_seterror(pinentry, buf);
196 return 3;
197 }
198}
199
200pipe2_t do_shell(const char *shell_code) {
201 pipe2_t ret;
202 const char *argv[] = { "/bin/sh", "/bin/sh","-c",NULL,NULL };
203 argv[3]=shell_code;
204 return fopen2(argv);
205}
206
207int validate(pipe2_t pinentry, const char *secret, const char *arg_validate)
208{
209 int retv = 0;
210 if( !arg_validate ) return 1;
211 pipe2_t validate_script = do_shell( arg_validate );
212
213 fprintf( validate_script.out, "%s\n", secret );
214 fclose( validate_script.out );
215 char error_string[1024] = "Error%0Aunspecified error";
216
217 int c;
218 char *p = error_string;
219 while( p!=&error_string[1024-4] && -1!=(c=getc( validate_script.in )) )
220 switch(c){
221 case '%':
222 case '\n':
223 snprintf(p,4, "%%%0.02X", c);
224 p+=3;
225 break;
226 default:
227 *p++ = c;
228 }
229 *p='\0';
230
231 // pinentry ruins its display if this message ends with a newline
232 while(p-3>error_string && memcmp(&p[-3],"%0A",3)==0 ) p-=3;
233 *p='\0';
234
235 fclose( validate_script.in );
236 waitpid(validate_script.pid, &retv, 0 );
237 if( retv ) {
238 fprintf(stderr, "error_string: %s\n", error_string);
239 pinentry_seterror(pinentry, error_string );
240 }
241 return !retv;
242}
243
244FILE *open_socket(const char *socket_path)
245{
246 int sok = socket(PF_LOCAL, SOCK_STREAM, 0);
247 if( sok<0 ) {
248 return NULL;
249 }
250 struct sockaddr_un address;
251 memset(&address, 0, sizeof(struct sockaddr_un));
252 address.sun_family = AF_UNIX;
253 snprintf(address.sun_path, 108, socket_path);
254 if(connect(sok,
255 (struct sockaddr *) &address,
256 sizeof(struct sockaddr_un)) != 0)
257 {
258 return NULL;
259 }
260 return fdopen(sok,"r+");
261}
262
263const char *arg_socket = NULL;
264const char *arg_resocket = NULL;
265const char *arg_tellsecret = NULL;
266char secret[MAXLINE]; // TODO: secure memory
267
268void tell_encoded_secret(FILE *out)
269{
270 char *encoded_secret = malloc(strlen(secret)*2 + 1);
271 for (char *p = secret; *p; ++p)
272 snprintf(encoded_secret + 2*(p - secret), 3, "%0.02X", *p);
273
274 fprintf(out, "%s\n", encoded_secret);
275 fflush(out);
276 free(encoded_secret);
277}
278
279void signal_usr1(int val)
280{
281 if( !arg_tellsecret ) {
282 fprintf(stderr, "USR1, no action\n");
283 return;
284 } else
285 fprintf(stderr, "Received SIGUSR1; will tell secret.\n");
286
287 pipe2_t tellsecret = do_shell( arg_tellsecret );
288 tell_encoded_secret(tellsecret.out);
289
290 FILE *sock = NULL;
291 if (arg_socket) sock=open_socket(arg_socket);
292
293 for (;;) {
294 char *response = 0;
295 size_t len;
296 if (sock && (getline(&response, &len, sock) > 0)) {
297 fprintf(tellsecret.out, "%s", response);
298 fflush(tellsecret.out);
299 }
300
301 if (getline(&response, &len, tellsecret.in) > 0) {
302 if (sock) {
303 fprintf(sock, "%s", response);
304 fflush(sock);
305 } else {
306 fprintf(stderr, "?> %s", response);
307 }
308 } else {
309 break;
310 }
311 }
312
313 if (sock) fclose(sock);
314 fclose(tellsecret.out);
315 fclose(tellsecret.in);
316 int status = 0;
317 waitpid(tellsecret.pid, &status, 0 );
318}
319
320void signal_usr2(int wat)
321{
322 fprintf(stderr, "Received SIGUSR2; will change socket and emulate SIGUSR1.\n");
323 if (arg_resocket)
324 arg_socket = arg_resocket;
325 signal_usr1(wat);
326}
327
328void signal_term(int val)
329{
330 // TODO: Wipe secret
331 exit(0);
332}
333
334int main(int argc, char **argv)
335{
336 pipe2_t pinentry = { 0 };
337
338 char tty_name_buf[256];
339 if( ttyname_r(1, tty_name_buf, sizeof(tty_name_buf)) ) {
340 tty_name_buf[0]='\0';
341 //error(2,0,"ttyname_r");
342 }
343 const char *arg_ttyname = tty_name_buf;
344 const char *arg_pinentry = "/usr/bin/pinentry-curses";
345 const char *arg_validate = NULL;
346 const char *arg_pidfile = NULL; //same as "/dev/stdout";
347 int tell_at_start=0;
348
349 const char **expect = NULL;
350 int i=0;
351 for(i=1;i<argc;i++) {
352 if(expect) {
353 *expect = argv[i];
354 expect = NULL;
355 }
356 else if( strcmp(argv[i],"--setdesc")==0 )
357 expect = &arg_desc;
358 else if( strcmp(argv[i],"--setprompt")==0 )
359 expect = &arg_prompt;
360 else if( strcmp(argv[i],"--validate")==0 )
361 expect = &arg_validate;
362 else if( strcmp(argv[i],"--ttyname")==0 )
363 expect = &arg_ttyname;
364 else if( strcmp(argv[i],"--pinentry")==0 )
365 expect = &arg_pinentry;
366 else if( strcmp(argv[i],"--pidfile")==0 )
367 expect = &arg_pidfile;
368 else if( strcmp(argv[i],"--tell")==0 )
369 expect = &arg_tellsecret;
370 else if( strcmp(argv[i],"--tell-immediately")==0 ) {
371 expect = &arg_tellsecret;
372 tell_at_start=1;
373 }
374 else if( strcmp(argv[i],"--socket")==0 )
375 expect = &arg_socket;
376 else if( strcmp(argv[i],"--resocket")==0 )
377 expect = &arg_resocket;
378 }
379
380 if(!arg_ttyname || !arg_ttyname[0]) {
381 error(2,0,"unknown tty, use --ttyname");
382 }
383
384 // xxx printf("validate script = \n%s\n", arg_validate );
385 // xxx printf("ttyname = %s\n", arg_ttyname );
386
387 char dummy[123];
388 //fgets(dummy,sizeof(dummy),stdin);
389
390 // pinentry-curses --lc-ctype en_US.UTF-8 --ttyname /dev/pts/5
391 const char *cmd[] = {
392 NULL /* pinentry command */,
393 "pinentry-curses",
394 "--lc-ctype", "en_US.UTF-8",
395 "--ttyname", NULL,
396 0 };
397 cmd[0] = arg_pinentry;
398 cmd[sizeof(cmd)/sizeof(cmd[0]) - 2] = arg_ttyname;
399 for(i=0; cmd[i]; i++ ) {
400 // xxx printf("cmd[%d]=%s\n", i, cmd[i] );
401 }
402
403 char data[MAXLINE];
404
405 pinentry = fopen2(cmd);
406 pinentry_get(pinentry,data,MAXLINE); // OK
407
408 int e=0;
409 while( 0!=(e=pinentry_getpin(pinentry,secret,MAXLINE))
410 || !validate(pinentry,secret, arg_validate) ) {
411 //printf("e=%d\n",e);
412 if(e==2) break; // Canceled
413 }
414
415 fclose(pinentry.out);
416 fclose(pinentry.in);
417
418 if(e) return e;
419
420 if( tell_at_start ) {
421 signal_usr1(0);
422 }
423
424 int pid = fork();
425 if( !pid ) {
426
427 if (isatty(0)) fclose(stdin);
428 if (isatty(1)) fclose(stdout);
429 if (isatty(2)) fclose(stderr);
430 setsid();
431
432 struct sigaction act = { 0 };
433 sigemptyset(&act.sa_mask);
434 sigaddset(&act.sa_mask, SIGTERM);
435 sigaddset(&act.sa_mask, SIGUSR1);
436 sigaddset(&act.sa_mask, SIGUSR2);
437 act.sa_handler = signal_usr1;
438 sigaction( SIGUSR1, &act, NULL );
439 act.sa_handler = signal_usr2;
440 sigaction( SIGUSR2, &act, NULL );
441 act.sa_handler = signal_term;
442 sigaction( SIGTERM, &act, NULL );
443
444 for(;;) {
445 pause();
446 }
447 return 0;
448 }
449
450 FILE *pidfile = arg_pidfile ? fopen(arg_pidfile,"w") : stdout;
451 fprintf(pidfile, "%d\n", pid );
452 if(arg_pidfile) fclose(pidfile);
453
454 return 0;
455}