1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
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
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",
72 "Attribute not found",
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
)
89 for (p
= list
; *p
; p
++)
95 unsigned char *pwmd_base64_decode(const char *str
, size_t *size
)
98 guchar
*buf
= g_base64_decode(str
, &len
);
104 char *pwmd_base64_encode(const char *str
, size_t len
)
106 gchar
*buf
= g_base64_encode((const guchar
*)str
, len
);
112 dst
= strdup((char *)buf
);
113 memset(buf
, 0, strlen((char *)buf
));
118 static int parse_protocol(char *data
, size_t total
, char **result
)
124 * Set the pointer to the end of the buffer.
126 p
= data
+ total
- 1;
128 if (*p
!= '\n' || total
< 3)
134 * "Rewind" the pointer to the second to last newline character.
136 for (i
= total
- 1; i
; i
--, p
--) {
143 if (strncmp(p
, "ERR ", 4) == 0) {
148 if (strncmp(p
, "OK ", 3) == 0) {
153 if (strncmp(p
, "BEGIN ", 6) == 0) {
156 while (*p
&& isdigit(*p
))
170 static int get_daemon_result(pwm_t
*pwm
, char **result
)
179 FD_SET(pwm
->fd
, &fds
);
180 n
= select(pwm
->fd
+ 1, &fds
, NULL
, NULL
, NULL
);
185 if (FD_ISSET(pwm
->fd
, &fds
)) {
187 ssize_t len
= recv(pwm
->fd
, buf
, sizeof(buf
), 0);
191 memset(data
, 0, total
);
200 memset(data
, 0, total
);
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
);
215 switch (parse_protocol(data
, total
, result
)) {
219 memset(data
, 0, total
);
223 memset(data
, 0, total
);
234 memset(data
, 0, total
);
239 pwm_t
*pwmd_connect(const char *path
, int *error
)
242 struct sockaddr_un addr
;
246 char *socketdir
= NULL
, *socketname
= NULL
, *socketarg
;
252 pw
= getpwuid(getuid());
253 snprintf(cwd
, sizeof(cwd
), "%s/.pwmd/socket", pw
->pw_dir
);
254 socketarg
= strdup(cwd
);
257 socketarg
= strdup(path
);
259 if (getcwd(cwd
, sizeof(cwd
)) == NULL
) {
265 if (strchr(socketarg
, '/') == NULL
) {
266 socketdir
= strdup(cwd
);
267 p
= strdup(socketarg
);
271 p
= strdup(strrchr(socketarg
, '/'));
273 socketarg
[strlen(socketarg
) - strlen(socketname
) -1] = 0;
274 socketdir
= strdup(socketarg
);
277 if ((fd
= socket(PF_UNIX
, SOCK_STREAM
, 0)) == -1) {
285 if (chdir(socketdir
) == -1) {
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) {
311 if ((pwm
= (pwm_t
*)calloc(1, sizeof(pwm_t
))) == NULL
) {
322 * Clear the initial OK response
324 get_daemon_result(pwm
, &result
);
331 void pwmd_close(pwm_t
*pwm
)
333 if (!pwm
|| pwm
->fd
< 0)
336 shutdown(pwm
->fd
, SHUT_RDWR
);
357 static void secure_free(void *p
, size_t len
)
363 static int send_to_daemon(pwm_t
*pwm
, char *fmt
, ...)
373 bufsize
= vasprintf(&buf
, fmt
, ap
);
377 FD_SET(pwm
->fd
, &fds
);
378 n
= select(pwm
->fd
+ 1, NULL
, &fds
, NULL
, NULL
);
385 if (FD_ISSET(pwm
->fd
, &fds
)) {
386 size_t t
= strlen(buf
);
387 ssize_t len
= send(pwm
->fd
, buf
, t
, 0);
399 secure_free(buf
, bufsize
);
403 static void init_cache_id(pwm_t
*pwm
)
408 snprintf(t
, sizeof(t
), "%li", n
);
410 if (strncmp(t
, (char *)pwm
->cache_id
, sizeof(t
)) == 0)
413 strncpy((char *)pwm
->cache_id
, t
, sizeof(pwm
->cache_id
));
416 static gpg_error_t
connect_to_agent(pwm_t
*pwm
)
422 char path
[PATH_MAX
], *t
;
423 assuan_context_t ctx
;
425 if ((env
= getenv("GPG_AGENT_INFO")) == NULL
)
430 for (p
= env
, t
= path
; *p
; p
++) {
442 rc
= assuan_socket_connect(&ctx
, path
, pid
);
445 if (gpg_err_code(rc
) == GPG_ERR_ASS_CONNECT_FAILED
)
451 pwm
->title
= strdup("LibPWMD");
454 pwm
->prompt
= strdup("Password:");
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. */
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
);
493 static int mem_realloc_cb(void *data
, const void *buffer
, size_t len
)
495 membuf_t
*mem
= (membuf_t
*)data
;
501 if ((p
= realloc(mem
->buf
, mem
->len
+ len
)) == NULL
)
505 memcpy((char *)mem
->buf
+ mem
->len
, buffer
, len
);
511 static int get_agent_password(pwm_t
*pwm
, char **password
)
513 char cmd
[] = "GET_PASSPHRASE --data -- ";
520 if (connect_to_agent(pwm
))
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
);
532 p
= percent_plus_escape(p
, pwm
->desc
);
534 p
= percent_plus_escape(p
, pwm
->prompt
);
536 p
= percent_plus_escape(p
, pwm
->title
);
539 rc
= assuan_transact((assuan_context_t
)pwm
->ctx
, line
, mem_realloc_cb
, &data
, NULL
, NULL
, NULL
, NULL
);
543 memset(data
.buf
, 0, data
.len
);
548 mem_realloc_cb(&data
, "", 1);
549 *password
= (char *)data
.buf
;
553 assuan_disconnect((assuan_context_t
)pwm
->ctx
);
558 static int clear_agent_password(pwm_t
*pwm
)
560 char *buf
= "CLEAR_PASSPHRASE";
562 size_t len
= strlen(buf
) + sizeof(pwm
->cache_id
) + 2;
566 if ((line
= (char *)malloc(len
)) == NULL
)
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
);
580 int pwmd_command(pwm_t
*pwm
, char **result
, int *error
, pwmd_cmd cmd
, ...)
585 char *filename
= NULL
;
587 char *password
= NULL
;
588 char *tresult
= NULL
;
597 *error
= EPWMD_ERROR
;
602 arg1
= va_arg(ap
, char *);
604 if (send_to_daemon(pwm
, "%s", arg1
)) {
611 filename
= va_arg(ap
, char *);
614 * Avoid calling gpg-agent if the password is cached on the
617 snprintf(buf
, sizeof(buf
), "CACHE ISCACHED %s\n", filename
);
618 n
= pwmd_command(pwm
, &tresult
, &terror
, PWMD_COMMAND
, buf
);
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
;
634 return PWMD_AGENT_ERROR
;
643 p
= strdup(password
);
644 secure_free(password
, strlen(password
));
648 * We use pwmd's cache from now on.
650 clear_agent_password(pwm
);
655 * Not using gpg-agent and the file was not found
658 if (pwm
->password
== NULL
) {
664 password
= strdup(pwm
->password
);
667 else if (terror
!= EPWMD_FILE_NOT_FOUND
) {
677 if (send_to_daemon(pwm
, "OPEN %s %s\n", filename
, (password
) ? password
: "")) {
679 secure_free(password
, strlen(password
));
686 secure_free(password
, strlen(password
));
692 case PWMD_OPTION_USEAGENT
:
695 if (n
!= 0 && n
!= 1) {
702 case PWMD_OPTION_PASSWORD
:
703 arg1
= va_arg(ap
, char *);
706 memset(pwm
->password
, 0, strlen(pwm
->password
));
710 pwm
->password
= strdup(arg1
);
712 case PWMD_OPTION_TITLE
:
715 pwm
->title
= strdup(va_arg(ap
, char *));
717 case PWMD_OPTION_PROMPT
:
720 pwm
->prompt
= strdup(va_arg(ap
, char *));
722 case PWMD_OPTION_DESC
:
725 pwm
->desc
= strdup(va_arg(ap
, char *));
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
);
746 if (terror
== EPWMD_CACHE_NOT_FOUND
||
747 terror
== EPWMD_FILE_NOT_FOUND
) {
748 if (get_agent_password(pwm
, &password
)) {
751 return PWMD_AGENT_ERROR
;
754 if (!password
|| !*password
) {
760 p
= strdup(password
);
761 secure_free(password
, strlen(password
));
765 * We use pwmd's cache from now on.
767 clear_agent_password(pwm
);
780 password
= pwm
->password
;
782 if (send_to_daemon(pwm
, "SAVE %s\n", (password
) ? password
: "")) {
784 secure_free(password
, strlen(password
));
792 secure_free(password
, strlen(password
));
794 pwm
->password
= NULL
;
803 n
= get_daemon_result(pwm
, &res
);
819 if (res
[strlen(res
) - 1] == '\n')
820 res
[strlen(res
) - 1] = 0;
829 pwm
->filename
= strdup(filename
);
833 memset(res
, 0, strlen(res
));