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>
43 void catchsig(gint sig
)
49 waitpid(-1, &status
, 0);
52 shutdown(sfd
, SHUT_RDWR
);
62 "Usage: %s [-hv] [-r <directory>]\n"
63 " -r root directory where the socket and files are stored\n"
65 " -h this help text\n",
70 void send_to_client(gchar
**dst
, const gchar
*fmt
, ...)
75 *dst
= g_strdup_vprintf(fmt
, ap
);
79 gboolean
split_input_line(gchar
*str
, gchar
*delim
)
85 cl
->req
= g_strsplit(str
, delim
, 0);
89 void client_help(const gchar
*what
)
95 "NFO Try 'help topic' for details\n"
96 "NFO open list get store delete set save quit\n";
97 else if (g_ascii_strcasecmp(what
, "get") == 0)
99 "NFO syntax: get account <TAB> element [<TAB> element ...]\n"
100 "NFO <account> is the account to work on and <element>\n"
101 "NFO is the element wanted.\n"
103 "NFO Example: get isp <TAB> imap <TAB> port\n"
104 "NFO get isp <TAB> username\n";
105 else if (g_ascii_strcasecmp(what
, "quit") == 0)
108 "NFO close the connection\n";
109 else if (g_ascii_strcasecmp(what
, "delete") == 0)
111 "NFO syntax: delete account <TAB> element [<TAB> element ...]\n";
112 else if (g_ascii_strcasecmp(what
, "store") == 0)
114 "NFO syntax: store account <TAB> element [<TAB> element ...] <TAB> value\n"
115 "NFO <account> is the account to work on and <element>\n"
116 "NFO is the element to create or modify\n"
118 "NFO Example: store isp <TAB> imap <TAB> port <TAB> 993\n"
119 "NFO store isp <TAB> username <TAB> someuser\n";
120 else if (g_ascii_strcasecmp(what
, "open") == 0)
122 "NFO syntax: open filename\n";
123 else if (g_ascii_strcasecmp(what
, "list") == 0)
125 "NFO syntax: list [account]\n"
126 "NFO shows available accounts or account elements\n";
127 else if (g_ascii_strcasecmp(what
, "set") == 0)
129 "NFO syntax: set account option value\n"
130 "NFO -----------------------------------------\n"
131 "NFO name account name\n";
132 else if (g_ascii_strcasecmp(what
, "set") == 0)
135 "NFO save any changes to the opened file\n";
137 line
= "NFO unknown command\n";
139 send_to_client(&cl
->outbuf
, "%sOK \n", line
);
142 static gboolean
parse_xml()
144 switch (open_xml(cl
->xml
, cl
->len
, &cl
->doc
, &cl
->root
, &cl
->reader
)) {
146 send_to_client(&cl
->outbuf
, "%s", "ERR XML parse error\n");
149 send_to_client(&cl
->outbuf
, "%s", "ERR XMLReaderWalker() failed\n");
155 cl
->state
= STATE_OPEN
;
159 static gboolean
valid_filename(gchar
*filename
)
163 for (p
= filename
; *p
; p
++) {
164 if (g_ascii_isalnum(*p
) == FALSE
)
172 * The filename will be ~/.pwmd/filename.xml.gpg.
174 static gboolean
authenticate_client(gchar
*filename
)
181 if (valid_filename(filename
) == FALSE
) {
182 send_to_client(&cl
->outbuf
, "%s", "ERR invalid characters in filename\n");
186 g_snprintf(buf
, sizeof(buf
), "%s/%s.xml.gpg", root_dir
, filename
);
188 if (access(buf
, R_OK
|W_OK
) != 0) {
189 if (errno
!= ENOENT
) {
190 send_to_client(&cl
->outbuf
, "ERR %s\n", strerror(errno
));
194 cl
->filename
= g_strdup(buf
);
196 if ((cl
->xml
= new_document()) == NULL
) {
197 send_to_client(&cl
->outbuf
, "ERR malloc()\n");
201 cl
->len
= xmlStrlen(cl
->xml
);
207 cl
->filename
= g_strdup(buf
);
209 g_snprintf(buf
, sizeof(buf
), "%s -o - -- %s 2>/dev/null", gpg_path
,
212 if ((fp
= popen(buf
, "r")) == NULL
) {
213 send_to_client(&cl
->outbuf
, "ERR popen(): %s\n", strerror(errno
));
218 gsize len
= fread(buf
, 1, sizeof(buf
), fp
);
223 xml
= realloc(xml
, t
+ len
+ 1);
224 memcpy(&xml
[t
], buf
, len
);
234 * Not needed anymore.
238 close(1); // needed for save_xml() and gpg
245 static gboolean
get_elements()
249 if (!cl
->req
|| !cl
->req
[1]) {
250 send_to_client(&cl
->outbuf
, "ERR need an element\n");
254 cl
->doc
= xmlTextReaderCurrentDoc(cl
->reader
);
257 * We are at the position in the document where the account was found.
258 * Search through the account for the wanted elements.
260 for (i
= 1; cl
->req
[i
]; i
++) {
263 switch (find_element(cl
->reader
, cl
->req
[i
], cl
->req
[i
+1] != NULL
)) {
267 send_to_client(&cl
->outbuf
, "ERR could not find element\n");
270 send_to_client(&cl
->outbuf
, "ERR trailing elements in tree\n");
277 * We are at the end of the element list. Save the result.
280 n
= xmlTextReaderCurrentNode(cl
->reader
);
281 send_to_client(&cl
->outbuf
, "BEGIN %li\n%s\nOK \n",
282 xmlStrlen(n
->content
), n
->content
);
289 static gboolean
save_xml()
292 gchar buf
[FILENAME_MAX
];
295 g_snprintf(buf
, sizeof(buf
), "gpg --use-agent --yes -c -o %s --", cl
->filename
);
297 if ((fp
= popen(buf
, "w")) == NULL
)
300 if (xmlDocDump(fp
, cl
->doc
) == -1)
308 * Create a new or modify an existing element.
310 static gboolean
create_elements()
316 r
= xmlTextReaderCurrentNode(cl
->reader
);
318 if (xmlTextReaderDepth(cl
->reader
) > 1)
321 for (i
= 1; cl
->req
[i
]; i
++) {
327 * Prevent creating 'text' elements in the root of the document.
332 b64
= g_base64_encode((guchar
*)cl
->req
[i
],
333 g_utf8_strlen(cl
->req
[i
], -1));
334 xmlNodeSetContent(r
, (xmlChar
*)b64
);
338 if ((n
= find_node(r
, (xmlChar
*)cl
->req
[i
])) == NULL
) {
339 n
= xmlNewNode(NULL
, (xmlChar
*)cl
->req
[i
]);
340 r
= xmlAddChild(r
, n
);
349 static void delete_node(xmlNodePtr n
)
355 static gboolean
delete_command()
360 xmlFreeTextReader(cl
->reader
);
363 if ((cl
->reader
= xmlReaderWalker(cl
->doc
)) == NULL
) {
364 send_to_client(&cl
->outbuf
, "ERR xmlReaderWalker() failed\n");
368 if (find_account(cl
->reader
, cl
->req
[0], &cl
->root
) == FALSE
)
371 n
= xmlTextReaderCurrentNode(cl
->reader
);
374 * No sub-node defined. Remove the entire node (account).
383 * Remove matching sub-nodes starting from the root of the account.
385 while (cl
->req
[i
] && find_element(cl
->reader
, cl
->req
[i
++], 1) == 0)
386 n
= xmlTextReaderCurrentNode(cl
->reader
);
394 static gboolean
store_command()
397 xmlFreeTextReader(cl
->reader
);
400 if ((cl
->reader
= xmlReaderWalker(cl
->doc
)) == NULL
) {
401 send_to_client(&cl
->outbuf
, "ERR xmlReaderWalker() failed\n");
405 if (find_account(cl
->reader
, cl
->req
[0], &cl
->root
) == FALSE
) {
406 if (new_account(cl
->doc
, cl
->req
[0]) == FALSE
) {
407 send_to_client(&cl
->outbuf
, "ERR failed to create account\n");
414 xmlTextReaderNext(cl
->reader
);
415 return create_elements();
418 static gboolean
get_command()
420 xmlFreeTextReader(cl
->reader
);
423 if ((cl
->reader
= xmlReaderWalker(cl
->doc
)) == NULL
) {
424 send_to_client(&cl
->outbuf
, "ERR xmlReaderWalker() failed\n");
428 if (find_account(cl
->reader
, cl
->req
[0], &cl
->root
) == FALSE
) {
429 send_to_client(&cl
->outbuf
, "ERR account not found\n");
433 return get_elements();
436 static gboolean
list_command(gchar
*str
)
442 gchar
**elements
= NULL
;
446 xmlFreeTextReader(cl
->reader
);
449 if ((cl
->reader
= xmlReaderWalker(cl
->doc
)) == NULL
) {
450 send_to_client(&cl
->outbuf
, "ERR xmlReaderWalker() failed\n");
454 if (strchr(p
, ' ') == NULL
) {
456 if (list_accounts(cl
->reader
, &dst
) == FALSE
)
459 send_to_client(&cl
->outbuf
, "BEGIN %i\n%s\nOK \n",
460 g_utf8_strlen(dst
, -1), dst
);
461 memset(dst
, 0, strlen(dst
));
468 while (*p
&& isspace(*p
))
474 if (find_account(cl
->reader
, p
, &n
) == FALSE
)
477 depth
= xmlTextReaderDepth(cl
->reader
);
479 while (xmlTextReaderNext(cl
->reader
) == 1) {
484 if (xmlTextReaderDepth(cl
->reader
) == depth
)
487 n
= xmlTextReaderCurrentNode(cl
->reader
);
489 if (xmlTextReaderNodeType(cl
->reader
) == XML_READER_TYPE_TEXT
) {
490 t
= xmlGetNodePath(n
);
492 for (x
= 0; *t
&& x
< 3; t
++) {
497 t
[xmlStrlen(t
) - 7] = 0;
499 for (x
= 0; t
[x
]; x
++) {
504 buf
= g_malloc(xmlStrlen(t
)+xmlStrlen(n
->content
)+strlen(p
)+3);
505 g_sprintf(buf
, "%s\t%s\t%s", p
, t
, n
->content
);
506 elements
= g_realloc(elements
, (i
+ 2) * sizeof(gchar
*));
515 line
= g_strjoinv("\n", elements
);
516 send_to_client(&cl
->outbuf
, "BEGIN %li\n%s\nOK \n",
517 g_utf8_strlen(line
, -1), line
);
518 g_strfreev(elements
);
525 static gchar
*build_line_from_array(gchar
**src
)
530 return g_strjoinv(NULL
, src
);
533 static gboolean
set_account_attribute()
536 gchar
*name
= cl
->req
[1];
542 if (g_strcasecmp(name
, "name") != 0) {
543 send_to_client(&cl
->outbuf
, "ERR invalid attribute\n");
547 xmlFreeTextReader(cl
->reader
);
550 if ((cl
->reader
= xmlReaderWalker(cl
->doc
)) == NULL
) {
551 send_to_client(&cl
->outbuf
, "ERR xmlReaderWalker() failed\n");
555 if (find_account(cl
->reader
, cl
->req
[0], &cl
->root
) == FALSE
)
558 if ((line
= build_line_from_array(cl
->req
+ 2)) == NULL
)
562 * Don't want spaces in the account name.
564 for (p
= line
; *p
; p
++) {
565 if (g_ascii_isspace(*p
) == TRUE
) {
566 if (g_strcasecmp(name
, "name") == 0) {
573 n
= xmlTextReaderCurrentNode(cl
->reader
);
574 a
= xmlHasProp(n
, (xmlChar
*)name
);
577 a
= xmlNewProp(n
, (xmlChar
*)name
, (xmlChar
*)line
);
579 xmlNodeSetContent(a
->children
, (xmlChar
*)line
);
586 gint
input_parser(gchar
*str
)
590 str
= g_strchug(str
);
595 while ((p
= strsep(&str
, "\n")) != NULL
) {
596 if (g_ascii_strcasecmp(p
, "quit") == 0)
598 else if (g_ascii_strcasecmp(p
, "help") == 0)
600 else if (g_ascii_strncasecmp(p
, "help ", 5) == 0) {
605 else if (g_ascii_strcasecmp(p
, "list") == 0 ||
606 g_ascii_strncasecmp(p
, "list ", 5) == 0) {
607 if (cl
->state
!= STATE_OPEN
)
608 send_to_client(&cl
->outbuf
, "ERR no file opened\n");
610 if (list_command(p
) == FALSE
)
611 send_to_client(&cl
->outbuf
, "ERR \n");
614 else if (g_ascii_strncasecmp(p
, "store ", 6) == 0) {
618 if (cl
->state
!= STATE_OPEN
)
619 send_to_client(&cl
->outbuf
, "ERR no file opened\n");
621 if (split_input_line(t
, "\t") == TRUE
) {
622 if (store_command() == TRUE
)
623 send_to_client(&cl
->outbuf
, "OK \n");
625 send_to_client(&cl
->outbuf
, "ERR \n");
629 else if (g_ascii_strncasecmp(p
, "delete ", 7) == 0) {
632 if (cl
->state
!= STATE_OPEN
)
633 send_to_client(&cl
->outbuf
, "ERR no file opened\n");
635 if (split_input_line(t
, "\t") == TRUE
) {
636 if (delete_command() == TRUE
)
637 send_to_client(&cl
->outbuf
, "OK \n");
639 send_to_client(&cl
->outbuf
, "\nERR \n");
643 else if (g_ascii_strncasecmp(p
, "get ", 4) == 0) {
647 if (cl
->state
!= STATE_OPEN
)
648 send_to_client(&cl
->outbuf
, "ERR no file opened\n");
650 if (split_input_line(t
, "\t") == TRUE
)
654 else if (g_ascii_strncasecmp(p
, "set ", 4) == 0) {
658 if (cl
->state
!= STATE_OPEN
)
659 send_to_client(&cl
->outbuf
, "ERR no file opened\n");
661 if (split_input_line(t
, " ") == TRUE
) {
662 if (set_account_attribute() == FALSE
)
663 send_to_client(&cl
->outbuf
, "ERR \n");
665 send_to_client(&cl
->outbuf
, "OK \n");
669 else if (g_ascii_strncasecmp(p
, "open ", 5) == 0) {
673 if (cl
->state
== STATE_OPEN
)
674 send_to_client(&cl
->outbuf
, "ERR a file is already opened\n");
676 if (authenticate_client(t
) == TRUE
)
677 send_to_client(&cl
->outbuf
, "OK \n");
680 else if (g_ascii_strcasecmp(p
, "save") == 0) {
681 if (cl
->state
!= STATE_OPEN
)
682 send_to_client(&cl
->outbuf
, "ERR no file opened\n");
684 if (save_xml() == FALSE
)
685 send_to_client(&cl
->outbuf
, "ERR \n");
687 send_to_client(&cl
->outbuf
, "OK \n");
691 send_to_client(&cl
->outbuf
, "ERR invalid command or command syntax\n");
697 gboolean
source_prepare(GSource
*src
, gint
*to
)
699 if (cl
->gfd
.revents
& (G_IO_HUP
|G_IO_NVAL
|G_IO_ERR
))
705 gboolean
source_check(GSource
*src
)
707 if (cl
->gfd
.revents
& (G_IO_IN
|G_IO_PRI
))
710 if (cl
->outbuf
&& (cl
->gfd
.revents
& (G_IO_OUT
)))
716 gboolean
source_dispatch(GSource
*src
, GSourceFunc cb
, gpointer data
)
721 gboolean
source_cb(gpointer data
)
726 GError
*gerror
= NULL
;
728 if (cl
->gfd
.revents
& (G_IO_HUP
|G_IO_NVAL
|G_IO_ERR
))
731 if (cl
->outbuf
&& (cl
->gfd
.revents
& (G_IO_OUT
))) {
732 ret
= g_io_channel_write_chars(cl
->ioc
, cl
->outbuf
, -1, &len
,
735 if (ret
== G_IO_STATUS_NORMAL
)
736 g_io_channel_flush(cl
->ioc
, &gerror
);
738 fprintf(stderr
, "%s\n", gerror
->message
);
740 g_clear_error(&gerror
);
745 if (!cl
->gfd
.revents
& (G_IO_IN
))
748 ret
= g_io_channel_read_line(cl
->ioc
, &line
, &len
, NULL
, &gerror
);
750 if (ret
!= G_IO_STATUS_NORMAL
) {
751 fprintf(stderr
, "%s\n", gerror
->message
);
752 g_clear_error(&gerror
);
756 if (ret
== G_IO_STATUS_EOF
)
759 line
[g_utf8_strlen(line
, -1) - 1] = 0;
761 switch (input_parser(line
)) {
764 ret
= g_io_channel_shutdown(cl
->ioc
, FALSE
, &gerror
);
766 if (ret
!= G_IO_STATUS_NORMAL
)
767 fprintf(stderr
, "%s\n", gerror
->message
);
769 g_io_channel_unref(cl
->ioc
);
770 g_clear_error(&gerror
);
773 memset(cl
->xml
, 0, cl
->len
);
781 xmlFreeTextReader(cl
->reader
);
788 g_main_loop_unref(gloop
);
789 g_main_loop_quit(gloop
);
799 * Called every time a connection is made.
803 static GSourceFuncs gsrcf
= {
804 source_prepare
, source_check
, source_dispatch
, NULL
, 0, 0
806 GPollFD gfd
= { rfd
, G_IO_IN
|G_IO_OUT
|G_IO_HUP
|G_IO_ERR
, 0 };
808 gloop
= g_main_loop_new(NULL
, TRUE
);
809 cl
= calloc(1, sizeof(struct client_s
));
810 cl
->src
= g_source_new(&gsrcf
, sizeof(GSource
));
812 cl
->state
= STATE_CONNECTED
;
813 cl
->ioc
= g_io_channel_unix_new(rfd
);
814 g_source_add_poll(cl
->src
, &cl
->gfd
);
815 g_source_set_callback(cl
->src
, source_cb
, NULL
, NULL
);
816 g_source_attach(cl
->src
, NULL
);
817 send_to_client(&cl
->outbuf
, "NFO Type help for available commands\nOK \n");
818 g_main_loop_run(gloop
);
822 int main(int argc
, char *argv
[])
826 struct sockaddr_un addr
;
827 struct passwd
*pw
= getpwuid(getuid());
830 if ((gpg_path
= g_find_program_in_path("gpg")) == NULL
)
831 warnx("Could not find gpg in your path");
832 else if (getenv("GPG_AGENT_INFO") == NULL
)
833 warnx("GPG_AGENT_INFO is not set");
835 while ((opt
= getopt(argc
, argv
, "hr:v")) != EOF
) {
838 root_dir
= strdup(optarg
);
841 printf("%s\n%s\n", PACKAGE_STRING
, PACKAGE_BUGREPORT
);
850 snprintf(buf
, sizeof(buf
), "%s/.pwmd", pw
->pw_dir
);
851 root_dir
= strdup(buf
);
854 strncpy(buf
, root_dir
, sizeof(buf
));
856 if (mkdir(buf
, 0700) == -1 && errno
!= EEXIST
)
857 err(EXIT_FAILURE
, "%s", buf
);
860 * bind() doesn't like the full pathname of the socket or any non alphanum
864 err(EXIT_FAILURE
, "%s", buf
);
866 strncpy(buf
, "socket", sizeof(buf
));
868 if (access(buf
, R_OK
| W_OK
) == 0)
871 if ((sfd
= socket(PF_UNIX
, SOCK_STREAM
, 0)) == -1)
872 err(EXIT_FAILURE
, "socket()");
874 addr
.sun_family
= AF_UNIX
;
875 snprintf(addr
.sun_path
, sizeof(addr
.sun_path
), "%s", buf
);
877 if (bind(sfd
, (struct sockaddr
*)&addr
, sizeof(struct sockaddr
)) == -1)
878 err(EXIT_FAILURE
, "bind()");
880 if (listen(sfd
, 10) == -1)
881 err(EXIT_FAILURE
, "listen()");
883 signal(SIGCHLD
, catchsig
);
884 signal(SIGTERM
, catchsig
);
885 signal(SIGINT
, catchsig
);
888 socklen_t slen
= sizeof(struct sockaddr_un
);
889 struct sockaddr_un raddr
;
894 if ((rfd
= accept(sfd
, (struct sockaddr_un
*)&raddr
, &slen
)) == -1) {
913 unlink(addr
.sun_path
);