Fixed segfault with a NULL result from protocol parser.
[pwmd.git] / libpwmd / libpwmd.c
blob613dfe4746b25314ff9d32ae8e0e1fa394a41b0a
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2006-2007 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <err.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <string.h>
26 #include <sys/socket.h>
27 #include <sys/un.h>
28 #include <signal.h>
29 #include <stdarg.h>
30 #include <string.h>
31 #include <sys/wait.h>
32 #include <fcntl.h>
33 #include <pwd.h>
34 #include <glib.h>
35 #include <libpwmd.h>
37 #ifdef HAVE_CONFIG_H
38 #include <config.h>
39 #endif
41 #ifdef HAVE_ASSUAN_H
42 #include <assuan.h>
43 #endif
45 #ifdef HAVE_SETLOCALE
46 #include <locale.h>
47 #endif
49 #include "asshelp.h"
51 const char *pwmd_strerror(int pwmd_errno)
53 const char *pwmd_error_str[] = {
54 "Unknown error",
55 "Invalid characters in filename",
56 "No cache slots available",
57 "Key required",
58 "Invalid key",
59 "Invalid command or command syntax",
60 "Element not found",
61 "Trailing element",
62 "Invalid character in element",
63 "Text element in account root element not allowed",
64 "Empty",
65 "Invalid attribute name or syntax",
66 "Will not overwrite existing account",
67 "File not found",
68 "No file is open",
69 "Existing file already open",
70 "General LibXML error",
71 "File not found in cache",
72 "Attribute not found",
73 "Not a file",
76 if (pwmd_errno < 0 || pwmd_errno >= EPWMD_MAX)
77 return "Invalid pwmd_errno";
79 return pwmd_error_str[pwmd_errno];
82 void pwmd_list_free(char **list)
84 char **p;
86 if (!list)
87 return;
89 for (p = list; *p; p++)
90 free(*p);
92 free(list);
95 unsigned char *pwmd_base64_decode(const char *str, size_t *size)
97 gsize len;
98 guchar *buf = g_base64_decode(str, &len);
100 *size = len;
101 return buf;
104 char *pwmd_base64_encode(const char *str, size_t len)
106 gchar *buf = g_base64_encode((const guchar *)str, len);
107 char *dst;
109 if (!buf)
110 return NULL;
112 dst = strdup((char *)buf);
113 memset(buf, 0, strlen((char *)buf));
114 g_free(buf);
115 return dst;
118 static int parse_protocol(char *data, size_t total, char **result)
120 char *p;
121 int i;
124 * Set the pointer to the end of the buffer.
126 p = data + total - 1;
128 if (*p != '\n' || total < 3)
129 return -2;
131 p--;
134 * "Rewind" the pointer to the second to last newline character.
136 for (i = total - 1; i; i--, p--) {
137 if (*p == '\n')
138 break;
141 p++;
143 if (strncmp(p, "ERR ", 4) == 0) {
144 *result = strdup(p);
145 return PWMD_PERROR;
148 if (strncmp(p, "OK ", 3) == 0) {
149 //p--;
150 *p = 0;
151 p = data;
153 if (strncmp(p, "BEGIN ", 6) == 0) {
154 p += 6;
156 while (*p && isdigit(*p))
157 p++;
159 if (*p)
160 p++;
163 *result = strdup(p);
164 return PWMD_OK;
167 return -2;
170 static int get_daemon_result(pwm_t *pwm, char **result)
172 fd_set fds;
173 int n;
174 char *data = NULL;
175 size_t total = 0;
177 while (1) {
178 FD_ZERO(&fds);
179 FD_SET(pwm->fd, &fds);
180 n = select(pwm->fd + 1, &fds, NULL, NULL, NULL);
182 if (n == -1)
183 return PWMD_ERROR;
185 if (FD_ISSET(pwm->fd, &fds)) {
186 char buf[4096];
187 ssize_t len = recv(pwm->fd, buf, sizeof(buf), 0);
189 if (len == 0) {
190 if (data) {
191 memset(data, 0, total);
192 free(data);
195 return PWMD_OK;
198 if (len == -1) {
199 if (data) {
200 memset(data, 0, total);
201 free(data);
204 return PWMD_ERROR;
208 * Keep appending to data until a valid protocol code is found.
210 data = (char *)realloc(data, len + total + 1);
211 memcpy(&(data[total]), buf, len);
212 total += len;
213 data[total] = 0;
215 switch (parse_protocol(data, total, result)) {
216 case -2:
217 continue;
218 case PWMD_ERROR:
219 memset(data, 0, total);
220 free(data);
221 return PWMD_ERROR;
222 case PWMD_PERROR:
223 memset(data, 0, total);
224 free(data);
225 return PWMD_PERROR;
226 default:
227 break;
230 break;
234 memset(data, 0, total);
235 free(data);
236 return PWMD_OK;
239 pwm_t *pwmd_connect(const char *path, int *error)
241 int fd;
242 struct sockaddr_un addr;
243 pwm_t *pwm = NULL;
244 char *result = NULL;
245 char cwd[PATH_MAX];
246 char *socketdir = NULL, *socketname = NULL, *socketarg;
247 char *p = NULL;
248 time_t now;
249 struct passwd *pw;
251 if (!path) {
252 pw = getpwuid(getuid());
253 snprintf(cwd, sizeof(cwd), "%s/.pwmd/socket", pw->pw_dir);
254 socketarg = strdup(cwd);
256 else
257 socketarg = strdup(path);
259 if (getcwd(cwd, sizeof(cwd)) == NULL) {
260 *error = errno;
261 free(socketarg);
262 return NULL;
265 if (strchr(socketarg, '/') == NULL) {
266 socketdir = strdup(cwd);
267 p = strdup(socketarg);
268 socketname = p;
270 else {
271 p = strdup(strrchr(socketarg, '/'));
272 socketname = p + 1;
273 socketarg[strlen(socketarg) - strlen(socketname) -1] = 0;
274 socketdir = strdup(socketarg);
277 if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
278 *error = errno;
279 free(socketdir);
280 free(socketarg);
281 free(p);
282 return NULL;
285 if (chdir(socketdir) == -1) {
286 *error = errno;
287 close(fd);
288 free(socketdir);
289 free(socketarg);
290 free(p);
291 return NULL;
294 addr.sun_family = AF_UNIX;
295 snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socketname);
297 if (connect(fd, (struct sockaddr *)&addr,
298 sizeof(struct sockaddr)) == -1) {
299 *error = errno;
300 close(fd);
301 free(socketdir);
302 free(p);
303 free(socketarg);
304 return NULL;
307 free(socketdir);
308 free(socketarg);
309 free(p);
311 if ((pwm = (pwm_t *)calloc(1, sizeof(pwm_t))) == NULL) {
312 *error = errno;
313 close(fd);
314 return NULL;
317 pwm->fd = fd;
318 time(&now);
319 srandom(now);
322 * Clear the initial OK response
324 get_daemon_result(pwm, &result);
326 if (result)
327 free(result);
328 return pwm;
331 void pwmd_close(pwm_t *pwm)
333 if (!pwm || pwm->fd < 0)
334 return;
336 shutdown(pwm->fd, SHUT_RDWR);
337 close(pwm->fd);
339 if (pwm->password)
340 free(pwm->password);
342 if (pwm->title)
343 free(pwm->title);
345 if (pwm->desc)
346 free(pwm->desc);
348 if (pwm->prompt)
349 free(pwm->prompt);
351 if (pwm->filename)
352 free(pwm->filename);
354 free(pwm);
357 static void secure_free(void *p, size_t len)
359 memset(p, 0, len);
360 free(p);
363 static int send_to_daemon(pwm_t *pwm, char *fmt, ...)
365 va_list ap;
366 char *buf;
367 fd_set fds;
368 int n;
369 size_t bufsize = 0;
370 int ret = 1;
372 va_start(ap, fmt);
373 bufsize = vasprintf(&buf, fmt, ap);
374 va_end(ap);
375 bufsize++;
376 FD_ZERO(&fds);
377 FD_SET(pwm->fd, &fds);
378 n = select(pwm->fd + 1, NULL, &fds, NULL, NULL);
380 if (n == -1) {
381 ret = 1;
382 goto done;
385 if (FD_ISSET(pwm->fd, &fds)) {
386 size_t t = strlen(buf);
387 ssize_t len = send(pwm->fd, buf, t, 0);
389 if (len == -1) {
390 ret = 1;
391 goto done;
394 if (len == t)
395 ret = 0;
398 done:
399 secure_free(buf, bufsize);
400 return ret;
403 static void init_cache_id(pwm_t *pwm)
405 char t[16];
406 long n = random();
408 snprintf(t, sizeof(t), "%li", n);
410 if (strncmp(t, (char *)pwm->cache_id, sizeof(t)) == 0)
411 init_cache_id(pwm);
413 strncpy((char *)pwm->cache_id, t, sizeof(pwm->cache_id));
416 static gpg_error_t connect_to_agent(pwm_t *pwm)
418 char *env;
419 char *p;
420 pid_t pid;
421 int rc;
422 char path[PATH_MAX], *t;
423 assuan_context_t ctx;
425 if ((env = getenv("GPG_AGENT_INFO")) == NULL)
426 return 1;
428 env = strdup(env);
430 for (p = env, t = path; *p; p++) {
431 if (*p == ':') {
432 *t = 0;
433 p++;
434 pid = atoi(p);
435 break;
438 *t++ = *p;
441 free(env);
442 rc = assuan_socket_connect(&ctx, path, pid);
443 pwm->ctx = ctx;
445 if (gpg_err_code(rc) == GPG_ERR_ASS_CONNECT_FAILED)
446 return 1;
448 init_cache_id(pwm);
450 if (!pwm->title)
451 pwm->title = strdup("LibPWMD");
453 if (!pwm->prompt)
454 pwm->prompt = strdup("Password:");
456 if (!pwm->desc)
457 pwm->desc = strdup("Enter a password.");
459 return send_pinentry_environment ((assuan_context_t)pwm->ctx, GPG_ERR_SOURCE_DEFAULT,
460 NULL, NULL, NULL, NULL, NULL);
463 /* From GnuPG (g10/call-agent.c) */
464 /* Copy the text ATEXT into the buffer P and do plus '+' and percent
465 escaping. Note that the provided buffer needs to be 3 times the
466 size of ATEXT plus 1. Returns a pointer to the leading Nul in P. */
467 static char *
468 percent_plus_escape (char *p, const char *atext)
470 const unsigned char *s;
472 for (s=(const unsigned char *)atext; *s; s++)
474 if (*s < ' ' || *s == '+')
476 sprintf (p, "%%%02X", *s);
477 p += 3;
479 else if (*s == ' ')
480 *p++ = '+';
481 else
482 *p++ = *s;
484 *p = 0;
485 return p;
488 typedef struct {
489 size_t len;
490 void *buf;
491 } membuf_t;
493 static int mem_realloc_cb(void *data, const void *buffer, size_t len)
495 membuf_t *mem = (membuf_t *)data;
496 void *p;
498 if (!buffer)
499 return 0;
501 if ((p = realloc(mem->buf, mem->len + len)) == NULL)
502 return 1;
504 mem->buf = p;
505 memcpy((char *)mem->buf + mem->len, buffer, len);
506 mem->len += len;
507 return 0;
511 static int get_agent_password(pwm_t *pwm, char **password)
513 char cmd[] = "GET_PASSPHRASE --data -- ";
514 char *line;
515 char *p;
516 membuf_t data;
517 int rc;
519 if (!pwm->ctx) {
520 if (connect_to_agent(pwm))
521 return 1;
524 line = (char *)malloc(strlen(cmd) + 1
525 + (3 * sizeof(pwm->cache_id) + 1)
526 + (3 * strlen(pwm->title) + 1)
527 + (3 * strlen(pwm->desc) + 1)
528 + (3 * strlen(pwm->prompt) + 1) + 1);
529 p = stpcpy(line, cmd);
530 p = percent_plus_escape(p, pwm->cache_id);
531 *p++ = ' ';
532 p = percent_plus_escape(p, pwm->desc);
533 *p++ = ' ';
534 p = percent_plus_escape(p, pwm->prompt);
535 *p++ = ' ';
536 p = percent_plus_escape(p, pwm->title);
537 data.len = 0;
538 data.buf = NULL;
539 rc = assuan_transact((assuan_context_t)pwm->ctx, line, mem_realloc_cb, &data, NULL, NULL, NULL, NULL);
541 if (rc) {
542 if (data.buf) {
543 memset(data.buf, 0, data.len);
544 free(data.buf);
547 else {
548 mem_realloc_cb(&data, "", 1);
549 *password = (char *)data.buf;
552 free(line);
553 assuan_disconnect((assuan_context_t)pwm->ctx);
554 pwm->ctx = NULL;
555 return 0;
558 static int clear_agent_password(pwm_t *pwm)
560 char *buf = "CLEAR_PASSPHRASE";
561 char *line;
562 size_t len = strlen(buf) + sizeof(pwm->cache_id) + 2;
563 int rc;
565 if (pwm->ctx) {
566 if ((line = (char *)malloc(len)) == NULL)
567 return 1;
569 sprintf(line, "%s %s", buf, pwm->cache_id);
570 rc = assuan_transact((assuan_context_t)pwm->ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
571 free(line);
573 if (rc)
574 return 1;
577 return 0;
580 int pwmd_command(pwm_t *pwm, char **result, int *error, pwmd_cmd cmd, ...)
582 va_list ap;
583 char *res = NULL;
584 int n;
585 char *filename = NULL;
586 char *p = NULL;
587 char *password = NULL;
588 char *tresult = NULL;
589 int terror = 0;
590 char *arg1;
591 char buf[LINE_MAX];
593 if (!pwm)
594 return PWMD_ERROR;
596 *result = NULL;
597 *error = EPWMD_ERROR;
598 va_start(ap, cmd);
600 switch (cmd) {
601 case PWMD_COMMAND:
602 arg1 = va_arg(ap, char *);
604 if (send_to_daemon(pwm, "%s", arg1)) {
605 *error = errno;
606 va_end(ap);
607 return PWMD_ERROR;
609 break;
610 case PWMD_OPEN:
611 filename = va_arg(ap, char *);
614 * Avoid calling gpg-agent if the password is cached on the
615 * server.
617 snprintf(buf, sizeof(buf), "CACHE ISCACHED %s\n", filename);
618 n = pwmd_command(pwm, &tresult, &terror, PWMD_COMMAND, buf);
620 switch (n) {
621 case PWMD_ERROR:
622 *error = terror;
623 va_end(ap);
624 return PWMD_ERROR;
625 case PWMD_PERROR:
626 if (terror == EPWMD_CACHE_NOT_FOUND) {
628 * Get the password from gpg-agent/pinentry.
630 if (pwm->use_agent) {
631 if (get_agent_password(pwm, &password)) {
632 *error = EPWMD_ERROR;
633 va_end(ap);
634 return PWMD_AGENT_ERROR;
637 if (!password) {
638 *error = EPWMD_KEY;
639 va_end(ap);
640 return PWMD_PERROR;
643 p = strdup(password);
644 secure_free(password, strlen(password));
645 password = p;
648 * We use pwmd's cache from now on.
650 clear_agent_password(pwm);
651 break;
653 else {
655 * Not using gpg-agent and the file was not found
656 * in the cache.
658 if (pwm->password == NULL) {
659 *error = EPWMD_KEY;
660 va_end(ap);
661 return PWMD_PERROR;
664 password = strdup(pwm->password);
667 else if (terror != EPWMD_FILE_NOT_FOUND) {
668 *error = terror;
669 va_end(ap);
670 return PWMD_PERROR;
672 break;
673 case PWMD_OK:
674 break;
677 if (send_to_daemon(pwm, "OPEN %s %s\n", filename, (password) ? password : "")) {
678 if (password)
679 secure_free(password, strlen(password));
680 *error = errno;
681 va_end(ap);
682 return PWMD_ERROR;
685 if (password)
686 secure_free(password, strlen(password));
687 break;
688 case PWMD_SETOPT:
689 n = va_arg(ap, int);
691 switch (n) {
692 case PWMD_OPTION_USEAGENT:
693 n = va_arg(ap, int);
695 if (n != 0 && n != 1) {
696 va_end(ap);
697 return PWMD_ERROR;
700 pwm->use_agent = n;
701 break;
702 case PWMD_OPTION_PASSWORD:
703 arg1 = va_arg(ap, char *);
705 if (pwm->password) {
706 memset(pwm->password, 0, strlen(pwm->password));
707 free(pwm->password);
710 pwm->password = strdup(arg1);
711 break;
712 case PWMD_OPTION_TITLE:
713 if (pwm->title)
714 free(pwm->title);
715 pwm->title = strdup(va_arg(ap, char *));
716 break;
717 case PWMD_OPTION_PROMPT:
718 if (pwm->prompt)
719 free(pwm->prompt);
720 pwm->prompt = strdup(va_arg(ap, char *));
721 break;
722 case PWMD_OPTION_DESC:
723 if (pwm->desc)
724 free(pwm->desc);
725 pwm->desc = strdup(va_arg(ap, char *));
726 break;
727 default:
728 *error = 0;
729 va_end(ap);
730 return PWMD_ERROR;
733 va_end(ap);
734 return PWMD_OK;
735 case PWMD_SAVE:
736 if (pwm->use_agent) {
737 snprintf(buf, sizeof(buf), "CACHE ISCACHED %s\n", pwm->filename);
738 n = pwmd_command(pwm, &tresult, &terror, PWMD_COMMAND, buf);
740 switch (n) {
741 case PWMD_ERROR:
742 *error = terror;
743 va_end(ap);
744 return PWMD_ERROR;
745 case PWMD_PERROR:
746 if (terror == EPWMD_CACHE_NOT_FOUND ||
747 terror == EPWMD_FILE_NOT_FOUND) {
748 if (get_agent_password(pwm, &password)) {
749 *error = 0;
750 va_end(ap);
751 return PWMD_AGENT_ERROR;
754 if (!password || !*password) {
755 *error = EPWMD_KEY;
756 va_end(ap);
757 return PWMD_PERROR;
760 p = strdup(password);
761 secure_free(password, strlen(password));
762 password = p;
765 * We use pwmd's cache from now on.
767 clear_agent_password(pwm);
769 else {
770 *error = terror;
771 va_end(ap);
772 return PWMD_PERROR;
774 break;
775 case PWMD_OK:
776 break;
779 else
780 password = pwm->password;
782 if (send_to_daemon(pwm, "SAVE %s\n", (password) ? password : "")) {
783 if (password)
784 secure_free(password, strlen(password));
786 *error = errno;
787 va_end(ap);
788 return PWMD_ERROR;
791 if (password)
792 secure_free(password, strlen(password));
794 pwm->password = NULL;
795 break;
796 default:
797 va_end(ap);
798 return PWMD_ERROR;
801 va_end(ap);
802 *result = NULL;
803 n = get_daemon_result(pwm, &res);
805 switch (n) {
806 case PWMD_PERROR:
807 p = res + 4;
808 *error = atoi(p);
809 free(res);
810 return PWMD_PERROR;
811 case PWMD_ERROR:
812 *error = errno;
813 free(res);
814 return PWMD_ERROR;
815 case PWMD_OK:
816 switch (cmd) {
817 case PWMD_COMMAND:
818 if (res) {
819 if (res[strlen(res) - 1] == '\n')
820 res[strlen(res) - 1] = 0;
823 *result = res;
824 break;
825 case PWMD_OPEN:
826 pwm->filename = strdup(filename);
827 break;
828 default:
829 if (res) {
830 memset(res, 0, strlen(res));
831 free(res);
833 break;
835 default:
836 break;
839 return PWMD_OK;