diff options
author | Andrew Cady <d@jerkface.net> | 2016-05-01 05:25:14 -0400 |
---|---|---|
committer | Andrew Cady <d@jerkface.net> | 2016-05-01 05:28:22 -0400 |
commit | a8e19d5d8057e82cbda2705d755f3d4e1d3da20a (patch) | |
tree | 84449f8ac6e45a5727b0abbb64eeb578c20628fd /src/samizdat-pinentry.c | |
parent | 4854ffec94f70705dc95c5657e43c5f69c270a1a (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.c | 455 |
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 | |||
31 | static int | ||
32 | open2(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 | |||
87 | typedef struct { | ||
88 | FILE *in, *out; | ||
89 | pid_t pid; | ||
90 | } pipe2_t; | ||
91 | |||
92 | |||
93 | pipe2_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 | |||
105 | void 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 | } | ||
111 | int 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 | |||
131 | void 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 | |||
139 | void 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 | |||
147 | static const char *arg_prompt = "Passphrase:"; | ||
148 | static const char *arg_desc = "Enter your passphrase to unlock the secret key. "; | ||
149 | |||
150 | int 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 | |||
200 | pipe2_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 | |||
207 | int 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 | |||
244 | FILE *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 | |||
263 | const char *arg_socket = NULL; | ||
264 | const char *arg_resocket = NULL; | ||
265 | const char *arg_tellsecret = NULL; | ||
266 | char secret[MAXLINE]; // TODO: secure memory | ||
267 | |||
268 | void 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 | |||
279 | void 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 | |||
320 | void 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 | |||
328 | void signal_term(int val) | ||
329 | { | ||
330 | // TODO: Wipe secret | ||
331 | exit(0); | ||
332 | } | ||
333 | |||
334 | int 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 | } | ||