1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2006 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
26 #include <sys/socket.h>
51 const char *pwmd_strerror(int pwmd_errno
)
53 const char *pwmd_error_str
[] = {
55 "Invalid characters in filename",
56 "No cache slots available",
59 "Invalid command or command syntax",
62 "Invalid character in element",
63 "Text element in account root element not allowed",
65 "Invalid attribute name or syntax",
66 "Will not overwrite existing account",
69 "Existing file already open",
70 "General LibXML error",
71 "File not found in cache",
74 if (pwmd_errno
< 0 || pwmd_errno
>= EPWMD_MAX
)
75 return "Invalid pwmd_errno";
77 return pwmd_error_str
[pwmd_errno
];
80 char *pwmd_base64_decode(const char *str
)
83 guchar
*buf
= g_base64_decode(str
, &len
);
86 dst
= strdup((char *)buf
);
92 char *pwmd_base64_encode(const char *str
, size_t len
)
94 gchar
*buf
= g_base64_encode((const guchar
*)str
, len
);
100 dst
= strdup((char *)buf
);
101 memset(buf
, 0, strlen((char *)buf
));
106 static int parse_protocol(char *data
, char **result
)
110 size_t total
= strlen(data
);
113 * Set the pointer to the end of the buffer.
115 p
= data
+ strlen(data
) - 1;
117 if (*p
!= '\n' || total
< 3)
123 * "Rewind" the pointer to the second to last newline character.
125 for (i
= total
- 1; i
; i
--, p
--) {
132 if (strncmp(p
, "ERR ", 4) == 0) {
137 if (strncmp(p
, "OK ", 3) == 0) {
142 if (strncmp(p
, "BEGIN ", 6) == 0) {
145 while (*p
&& isdigit(*p
))
159 static int get_daemon_result(pwm_t
*pwm
, char **result
)
168 FD_SET(pwm
->fd
, &fds
);
169 n
= select(pwm
->fd
+ 1, &fds
, NULL
, NULL
, NULL
);
174 if (FD_ISSET(pwm
->fd
, &fds
)) {
176 ssize_t len
= recv(pwm
->fd
, buf
, sizeof(buf
), 0);
187 memset(data
, 0, total
);
195 * Keep appending to data until a valid protocol code is found.
197 data
= realloc(data
, len
+ total
+ 1);
198 memcpy(&(data
[total
]), buf
, len
);
202 switch (parse_protocol(data
, result
)) {
209 memset(data
, 0, total
);
220 memset(data
, 0, total
);
225 pwm_t
*pwmd_connect(const char *path
, int *error
)
228 struct sockaddr_un addr
;
232 char *socketdir
= NULL
, *socketname
= NULL
, *socketarg
;
238 pw
= getpwuid(getuid());
239 snprintf(cwd
, sizeof(cwd
), "%s/.pwmd/socket", pw
->pw_dir
);
240 socketarg
= strdup(cwd
);
243 socketarg
= strdup(path
);
245 if (getcwd(cwd
, sizeof(cwd
)) == NULL
) {
251 if (strchr(socketarg
, '/') == NULL
) {
252 socketdir
= strdup(cwd
);
253 p
= strdup(socketarg
);
257 p
= strdup(strrchr(socketarg
, '/'));
259 socketarg
[strlen(socketarg
) - strlen(socketname
) -1] = 0;
260 socketdir
= strdup(socketarg
);
263 if ((fd
= socket(PF_UNIX
, SOCK_STREAM
, 0)) == -1) {
271 if (chdir(socketdir
) == -1) {
280 addr
.sun_family
= AF_UNIX
;
281 snprintf(addr
.sun_path
, sizeof(addr
.sun_path
), "%s", socketname
);
283 if (connect(fd
, (struct sockaddr
*)&addr
,
284 sizeof(struct sockaddr
)) == -1) {
297 if ((pwm
= calloc(1, sizeof(pwm_t
))) == NULL
) {
308 * Clear the initial OK response
310 get_daemon_result(pwm
, &result
);
314 void pwmd_close(pwm_t
*pwm
)
316 if (!pwm
|| pwm
->fd
< 0)
319 shutdown(pwm
->fd
, SHUT_RDWR
);
324 static void secure_free(void *p
, size_t len
)
330 static int send_to_daemon(pwm_t
*pwm
, char *fmt
, ...)
340 bufsize
= vasprintf(&buf
, fmt
, ap
);
346 FD_SET(pwm
->fd
, &fds
);
347 n
= select(pwm
->fd
+ 1, NULL
, &fds
, NULL
, NULL
);
350 secure_free(buf
, bufsize
);
354 if (FD_ISSET(pwm
->fd
, &fds
)) {
355 size_t t
= strlen(buf
);
356 ssize_t len
= send(pwm
->fd
, buf
, t
, 0);
359 secure_free(buf
, bufsize
);
366 * Keep sending data to the socket until the entire buffer has
370 memmove(&(buf
[0]), buf
+ len
, t
- len
);
379 secure_free(buf
, bufsize
);
383 static char **parse_list_command(char *str
)
389 while ((p
= strsep(&str
, "\n")) != NULL
) {
390 buf
= realloc(buf
, (n
+ 2) * sizeof(char *));
391 buf
[n
++] = strdup(p
);
398 static char *attr_to_str(int opt
)
410 static void init_cache_id(pwm_t
*pwm
)
415 snprintf(t
, sizeof(t
), "%li", n
);
417 if (strncmp(t
, pwm
->cache_id
, sizeof(t
)) == 0)
420 strncpy(pwm
->cache_id
, t
, sizeof(pwm
->cache_id
));
423 static gpg_error_t
connect_to_agent(pwm_t
*pwm
)
429 char path
[PATH_MAX
], *t
;
430 assuan_context_t ctx
;
432 if ((env
= getenv("GPG_AGENT_INFO")) == NULL
)
437 for (p
= env
, t
= path
; *p
; p
++) {
449 rc
= assuan_socket_connect(&ctx
, path
, pid
);
452 if (gpg_err_code(rc
) == GPG_ERR_ASS_CONNECT_FAILED
)
458 pwm
->title
= strdup("LibPWMD");
461 pwm
->prompt
= strdup("Password:");
464 pwm
->desc
= strdup("Enter a password.");
466 return send_pinentry_environment (pwm
->ctx
, GPG_ERR_SOURCE_DEFAULT
,
467 NULL
, NULL
, NULL
, NULL
, NULL
);
470 /* From GnuPG (g10/call-agent.c) */
471 /* Copy the text ATEXT into the buffer P and do plus '+' and percent
472 escaping. Note that the provided buffer needs to be 3 times the
473 size of ATEXT plus 1. Returns a pointer to the leading Nul in P. */
475 percent_plus_escape (char *p
, const char *atext
)
477 const unsigned char *s
;
479 for (s
=atext
; *s
; s
++)
481 if (*s
< ' ' || *s
== '+')
483 sprintf (p
, "%%%02X", *s
);
500 static int mem_realloc_cb(void *data
, const void *buffer
, size_t len
)
502 membuf_t
*mem
= data
;
508 if ((p
= realloc(mem
->buf
, mem
->len
+ len
)) == NULL
)
512 memcpy(mem
->buf
+ mem
->len
, buffer
, len
);
518 static int get_agent_password(pwm_t
*pwm
, char **password
)
520 char cmd
[] = "GET_PASSPHRASE --data -- ";
527 if (connect_to_agent(pwm
))
531 line
= malloc(strlen(cmd
) + 1
532 + (3 * sizeof(pwm
->cache_id
) + 1)
533 + (3 * strlen(pwm
->title
) + 1)
534 + (3 * strlen(pwm
->desc
) + 1)
535 + (3 * strlen(pwm
->prompt
) + 1) + 1);
536 p
= stpcpy(line
, cmd
);
537 p
= percent_plus_escape(p
, pwm
->cache_id
);
539 p
= percent_plus_escape(p
, pwm
->desc
);
541 p
= percent_plus_escape(p
, pwm
->prompt
);
543 p
= percent_plus_escape(p
, pwm
->title
);
546 rc
= assuan_transact(pwm
->ctx
, line
, mem_realloc_cb
, &data
, NULL
, NULL
, NULL
, NULL
);
550 memset(data
.buf
, 0, data
.len
);
555 mem_realloc_cb(&data
, "", 1);
556 *password
= (char *)data
.buf
;
563 static int clear_agent_password(pwm_t
*pwm
)
565 char *buf
= "CLEAR_PASSPHRASE";
567 size_t len
= strlen(buf
) + sizeof(pwm
->cache_id
) + 2;
571 if ((line
= malloc(len
)) == NULL
)
574 sprintf(line
, "%s %s", buf
, pwm
->cache_id
);
575 rc
= assuan_transact(pwm
->ctx
, line
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
);
585 int pwmd_command(pwm_t
*pwm
, void **result
, int *error
, pwmd_cmd cmd
, ...)
590 char *filename
= NULL
;
592 char *password
= NULL
;
593 char *account
= NULL
;
596 void *tresult
= NULL
;
603 *error
= EPWMD_ERROR
;
608 filename
= va_arg(ap
, char *);
611 * Avoid calling gpg-agent if the password is cached on the
614 n
= pwmd_command(pwm
, &tresult
, &terror
, PWMD_CACHE
,
615 PWMD_CACHE_ISCACHED
, filename
);
622 if (terror
== EPWMD_CACHE_NOT_FOUND
) {
624 * Get the password from gpg-agent/pinentry.
626 if (pwm
->use_agent
) {
627 if (get_agent_password(pwm
, &password
)) {
628 *error
= EPWMD_ERROR
;
629 return PWMD_AGENT_ERROR
;
637 p
= pwmd_base64_encode(password
, strlen(password
));
638 secure_free(password
, strlen(password
) + 1);
642 * We use pwmd's cache from now on.
644 clear_agent_password(pwm
);
649 * Not using gpg-agent and the file was not found
652 if (pwm
->password
== NULL
) {
657 password
= strdup(pwm
->password
);
660 else if (terror
!= EPWMD_FILE_NOT_FOUND
) {
669 if (send_to_daemon(pwm
, "OPEN %s %s\n", filename
, (password
) ? password
: "")) {
671 secure_free(password
, strlen(password
) + 1);
677 secure_free(password
, strlen(password
) + 1);
680 if (send_to_daemon(pwm
, "LIST\n")) {
685 case PWMD_LIST_ACCOUNT
:
686 p
= va_arg(ap
, char *);
688 if (send_to_daemon(pwm
, "LIST %s\n", p
)) {
694 if (send_to_daemon(pwm
, "GET %s\n", va_arg(ap
, char *))) {
700 if (send_to_daemon(pwm
, "STORE %s\n", va_arg(ap
, char *))) {
706 if (send_to_daemon(pwm
, "DELETE %s\n", va_arg(ap
, char *))) {
712 account
= va_arg(ap
, char *);
713 opt
= attr_to_str(va_arg(ap
, int));
714 value
= va_arg(ap
, char *);
719 if (send_to_daemon(pwm
, "SETATTR %s %s %s\n", account
, opt
, value
)) {
728 case PWMD_CACHE_ISCACHED
:
729 p
= va_arg(ap
, char *);
731 if (send_to_daemon(pwm
, "CACHE ISCACHED %s\n", p
)) {
736 case PWMD_CACHE_CLEAR
:
737 p
= va_arg(ap
, char *);
739 if (send_to_daemon(pwm
, "CACHE CLEAR %s\n", p
)) {
744 case PWMD_CACHE_CLEARALL
:
745 if (send_to_daemon(pwm
, "CACHE CLEARALL\n")) {
756 case PWMD_OPTION_USEAGENT
:
759 if (n
!= 0 && n
!= 1)
764 case PWMD_OPTION_PASSWORD
:
766 memset(pwm
->password
, 0, strlen(pwm
->password
));
770 p
= va_arg(ap
, char *);
771 pwm
->password
= pwmd_base64_encode(p
, strlen(p
));
773 case PWMD_OPTION_TITLE
:
776 pwm
->title
= strdup(va_arg(ap
, char *));
778 case PWMD_OPTION_PROMPT
:
781 pwm
->prompt
= strdup(va_arg(ap
, char *));
783 case PWMD_OPTION_DESC
:
786 pwm
->desc
= strdup(va_arg(ap
, char *));
795 if (pwm
->use_agent
) {
796 if (get_agent_password(pwm
, &password
)) {
798 return PWMD_AGENT_ERROR
;
801 if (!password
|| !*password
) {
806 p
= pwmd_base64_encode(password
, strlen(password
));
807 secure_free(password
, strlen(password
) + 1);
811 * We use pwmd's cache from now on.
813 clear_agent_password(pwm
);
816 password
= pwm
->password
;
818 if (send_to_daemon(pwm
, "SAVE %s\n", password
)) {
820 secure_free(password
, strlen(password
) + 1);
827 secure_free(password
, strlen(password
) + 1);
830 if (send_to_daemon(pwm
, "QUIT\n")) {
841 n
= get_daemon_result(pwm
, &res
);
855 case PWMD_LIST_ACCOUNT
:
856 *result
= parse_list_command(res
);