Initial commit.
[pwmd.git] / acctd.c
blobf572d51ce8683d03c83a231030631dbbeb4b96c5
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <err.h>
5 #include <errno.h>
6 #include <libxml/tree.h>
7 #include <libxml/parser.h>
8 #include <libxml/xmlreader.h>
9 #include <ctype.h>
10 #include <string.h>
11 #include <sys/socket.h>
12 #include <netinet/in.h>
13 #include <signal.h>
14 #include <stdarg.h>
15 #include <string.h>
17 enum {
18 STATE_CONNECTED,
19 STATE_AUTH
22 struct client_s {
23 xmlDocPtr doc;
24 xmlTextReaderPtr reader;
25 char *filename;
26 int fd;
27 xmlChar **req;
28 int state;
31 enum {
32 P_OK,
33 P_GET,
34 P_AUTH,
35 P_QUIT
38 void catchsig(int sig)
40 switch (sig) {
41 case SIGCHLD:
42 break;
43 default:
44 break;
48 xmlNodePtr find_node(xmlNodePtr root, xmlChar *name)
50 xmlNodePtr n;
52 for (n = root->children; n; n = n->next) {
53 if (strcmp((char *)name, (char *)n->name) == 0)
54 return n;
57 return NULL;
60 xmlNodePtr create_doc(xmlDocPtr *doc, const char *root)
62 xmlDocPtr d = xmlNewDoc((xmlChar *)"1.0");
63 xmlNodePtr n = xmlNewNode(NULL, (xmlChar *)root);
65 *doc = d;
66 xmlDocSetRootElement(d, n);
67 return n;
70 int add_node(xmlNodePtr root, char **exp)
72 char **p;
73 xmlNodePtr n = NULL, l = root;
75 for (p = exp; *p; p++) {
76 n = find_node(l, (xmlChar *)*p);
78 // non-matching element.
79 if (!n) {
80 if (!*(p + 1)) {
81 xmlNodeAddContent(n, (xmlChar *)*p);
82 break;
84 else {
85 xmlNewChild(l, NULL, (xmlChar *)*p, NULL);
86 l->next->type = XML_ELEMENT_NODE;
89 n = l;
90 continue;
93 l = n;
96 #if 0
97 if (!exp && p && n) {
98 xmlNodeAddContent(n, (xmlChar *)p);
99 return 0;
102 if (!n && exp) {
103 warnx("add: parent node \"%s\" does not exist", p);
104 return 1;
106 else
107 xmlNewChild(n, NULL, (xmlChar *)p, (xmlChar *)"value");
108 #endif
110 return 0;
113 void usage(char *pn)
115 printf("Usage: %s [-h]\n", pn);
116 exit(EXIT_SUCCESS);
119 int find_account(xmlTextReaderPtr reader, xmlChar *acct)
121 int type;
122 xmlNodePtr n;
124 while (xmlTextReaderRead(reader) == 1) {
125 if ((n = xmlTextReaderCurrentNode(reader)) == NULL)
126 return 1;
129 * "name" is the element that holds the account name. It would be nice
130 * to have an element the is the account name but I wouldn't know how
131 * to do it with the DTD.
133 if (xmlTextReaderDepth(reader) == 1 &&
134 xmlStrcmp(n->name, (xmlChar *)"name") &&
135 xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) {
136 do {
137 if (xmlTextReaderNext(reader) != 1)
138 return 1;
139 type = xmlTextReaderNodeType(reader);
140 } while (type != -1 && type != XML_READER_TYPE_TEXT);
142 if ((n = xmlTextReaderCurrentNode(reader)) == NULL)
143 return 1;
145 if (xmlStrcmp(n->content, acct) == 0)
146 return 0;
150 return 1;
153 int find_element(xmlTextReaderPtr reader, xmlChar *e, int more)
155 int type;
156 xmlNodePtr n;
158 while (xmlTextReaderRead(reader) == 1) {
159 if ((n = xmlTextReaderCurrentNode(reader)) == NULL)
160 return 1;
162 if (xmlStrcmp(n->name, e) == 0 &&
163 xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) {
164 if (more)
165 return 0;
168 * FIXME account/service without details (account/service/host).
170 do {
171 if (xmlTextReaderNext(reader) != 1)
172 return 1;
173 type = xmlTextReaderNodeType(reader);
174 } while (type != -1 && type != XML_READER_TYPE_TEXT);
175 return (type == XML_READER_TYPE_TEXT) ? 0 : 1;
179 return 1;
182 void send_to_client(int fd, char *fmt, ...)
184 va_list ap;
185 char *buf;
187 va_start(ap, fmt);
188 vasprintf(&buf, fmt, ap);
189 va_end(ap);
191 if (send(fd, buf, strlen(buf), 0) == -1)
192 warn("send()");
194 free(buf);
197 int parser()
199 #if 0
200 errno = 0;
202 if (access(argv[optind], R_OK) != 0 && errno == ENOENT) {
203 root = create_doc(&doc, "accounts");
205 else if (errno)
206 err(EXIT_FAILURE, "%s", argv[optind]);
208 if (!doc) {
209 if ((doc = xmlReadFile(argv[optind++], NULL, 0)) == NULL)
210 errx(EXIT_FAILURE, "parse error");
212 root = doc->children;
215 if ((reader = xmlReaderWalker(doc)) == NULL)
216 errx(EXIT_FAILURE, "xmlReaderWalker() failed");
218 s = argv[optind];
220 while ((p = strsep(&s, "/")) != NULL) {
221 exp = realloc(exp, (i + 2) * sizeof(xmlChar *));
222 exp[i++] = xmlStrdup((xmlChar *)p);
223 exp[i] = NULL;
226 if (!i)
227 errx(EXIT_FAILURE, "account expression parse error");
229 i = 0;
231 if (find_account(reader, exp[i++]))
232 errx(EXIT_FAILURE, "account not found");
235 * We are at the position in the document where the account was found.
236 * Search through the account for the wanted elements.
238 for (; exp[i]; i++) {
239 xmlNodePtr n;
241 if (find_element(reader, exp[i], exp[i+1] != NULL))
242 errx(EXIT_FAILURE, "could not find element %s", exp[i]);
245 * We are at the end of the element list. Save the result.
247 if (!exp[i+1]) {
248 n = xmlTextReaderCurrentNode(reader);
249 result = xmlStrdup(n->content);
253 if (exp) {
254 for (i = 0; exp[i]; i++)
255 xmlFree(exp[i]);
257 xmlFree(exp);
260 xmlFreeTextReader(reader);
261 xmlFreeDoc(doc);
262 xmlFree(result);
263 #endif
264 exit(EXIT_SUCCESS);
267 int parse_account(struct client_s *cl, char *str)
269 char *p;
270 int i = 0;
272 while ((p = strsep(&str, "/")) != NULL) {
273 cl->req = realloc(cl->req, (i + 2) * sizeof(xmlChar *));
274 cl->req[i++] = xmlStrdup((xmlChar *)p);
275 cl->req[i] = NULL;
278 if (!i)
279 return 1;
281 i = 0;
283 if (find_account(cl->reader, cl->req[i++])) {
284 send_to_client(cl->fd, "000 account \"%s\" not found\n", cl->req[0]);
285 return 1;
289 * We are at the position in the document where the account was found.
290 * Search through the account for the wanted elements.
292 for (; cl->req[i]; i++) {
293 xmlNodePtr n;
295 if (find_element(cl->reader, cl->req[i], cl->req[i+1] != NULL)) {
296 send_to_client(cl->fd, "could not find element \"%s\"",
297 cl->req[i]);
298 return 1;
302 * We are at the end of the element list. Save the result.
304 if (!cl->req[i+1]) {
305 n = xmlTextReaderCurrentNode(cl->reader);
306 send_to_client(cl->fd, "%s\n", n->content);
310 return 0;
313 void client_help(int fd, const char *what)
315 char *line;
317 if (!*what)
318 line =
319 "000 Try 'help topic' for details\n"
320 "000 auth get quit\n";
321 else if (strcasecmp(what, "get") == 0)
322 line =
323 "000 syntax: get account[/element[/...]]\n"
324 "000 <account> is the account to work on and <element>\n"
325 "000 is the account elements wanted.\n"
326 "000 -\n"
327 "000 Example: get isp/imap/port\n"
328 "000 get isp/username\n";
329 else if (strcasecmp(what, "quit") == 0)
330 line =
331 "000 syntax: quit\n"
332 "000 close the connection\n";
333 else if (strcasecmp(what, "auth") == 0)
334 line =
335 "000 syntax: auth username password\n";
336 else
337 line = "000 unknown command\n";
339 send_to_client(fd, line);
342 char *skip_space(char *str)
344 while (isspace(*str))
345 str++;
347 return str;
350 int input_parser(int rfd, char *str, char **dst)
352 char *p;
354 if (strcasecmp(str, "quit") == 0)
355 return P_QUIT;
356 if (strncasecmp(str, "help", 4) == 0) {
357 p = str + 4;
358 p = skip_space(p);
359 client_help(rfd, p);
361 else if (strcasecmp(str, "get") == 0) {
362 p = str + 3;
363 p = skip_space(p);
364 *dst = p;
365 return P_GET;
367 else if (strncasecmp(str, "auth", 4) == 0) {
368 p = str + 4;
369 p = skip_space(p);
370 *dst = p;
371 return P_AUTH;
374 return P_OK;
378 * The filename will be username.xml.
380 int authenticate_client(struct client_s *cl, char *str)
382 char *user = NULL, *pass = NULL;
383 char buf[FILENAME_MAX];
385 if ((user = strsep(&str, " ")) == NULL)
386 return 1;
388 pass = str;
389 snprintf(buf, sizeof(buf), "%s.xml", user);
392 if (access(argv[optind], R_OK) != 0 && errno == ENOENT) {
393 root = create_doc(&doc, "accounts");
395 else if (errno)
396 err(EXIT_FAILURE, "%s", argv[optind]);
399 if ((cl->doc = xmlReadFile(buf, NULL, 0)) == NULL) {
400 send_to_client(cl->fd, "000 error while parsing XML\n");
401 return 1;
404 if ((cl->reader = xmlReaderWalker(cl->doc)) == NULL) {
405 send_to_client(cl->fd, "xmlReaderWalker() failed");
406 return 1;
409 cl->filename = strdup(buf);
410 cl->state = STATE_AUTH;
411 return 0;
415 * Called every time a connection is made.
416 * FIXME protocol
417 * FIXME SSL
419 void doit(int rfd)
421 struct client_s *cl = calloc(1, sizeof(struct client_s));
423 cl->state = STATE_CONNECTED;
424 cl->fd = rfd;
425 send_to_client(rfd, "000 Type help for available commands\n");
427 while (1) {
428 fd_set rfds, wfds;
429 char buf[LINE_MAX] = {0};
430 struct timeval tv;
431 char *dst = NULL;
433 FD_ZERO(&rfds);
434 FD_SET(cl->fd, &rfds);
435 FD_ZERO(&wfds);
436 FD_SET(cl->fd, &wfds);
438 tv.tv_sec = 1;
439 tv.tv_usec = 0;
441 switch (select(cl->fd + 1, &rfds, &wfds, NULL, NULL)) {
442 case -1:
443 warn("select()");
444 break;
445 case 0:
446 // timeout
447 continue;
448 default:
449 break;
452 if (FD_ISSET(cl->fd, &rfds)) {
453 int len = recv(cl->fd, buf, sizeof(buf), 0);
455 buf[len - 1] = 0;
457 if (len == -1) {
458 warn("recv()");
459 continue;
461 else if (len == 0)
462 break;
464 buf[strlen(buf) - 1] = 0;
466 switch (input_parser(cl->fd, buf, &dst)) {
467 case P_QUIT:
468 goto done;
469 case P_GET:
470 if (cl->state != STATE_AUTH) {
471 send_to_client(cl->fd, "000 not authenticated\n");
472 break;
475 if (parse_account(cl, dst)) {
476 send_to_client(cl->fd, "000 parse error\n");
477 break;
479 break;
480 case P_AUTH:
481 if (authenticate_client(cl, dst))
482 goto done;
484 send_to_client(cl->fd, "000 authenticated\n");
485 break;
486 default:
487 break;
492 done:
493 close(cl->fd);
494 exit(EXIT_SUCCESS);
497 int main(int argc, char *argv[])
499 xmlDocPtr doc = NULL;
500 xmlTextReaderPtr reader = NULL;
501 xmlNodePtr root;
502 int opt;
503 xmlChar **exp = NULL;
504 int i = 0;
505 char *p, *s;
506 xmlChar *result = NULL;
507 int sfd, rfd;
508 struct sockaddr_in laddr, raddr;
509 socklen_t slen;
510 int yes = 0;
512 while ((opt = getopt(argc, argv, "h")) != EOF) {
513 switch (opt) {
514 default:
515 usage(argv[0]);
519 if ((sfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
520 err(EXIT_FAILURE, "socket()");
522 if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
523 err(EXIT_FAILURE, "setsockopt()");
525 laddr.sin_family = AF_INET;
526 laddr.sin_port = htons(5555);
527 laddr.sin_addr.s_addr = INADDR_ANY;
528 memset(&(laddr.sin_zero), 0, 8);
530 if (bind(sfd, (struct sockaddr *)&laddr, sizeof(struct sockaddr)) == -1)
531 err(EXIT_FAILURE, "bind()");
533 if (listen(sfd, 10) == -1)
534 err(EXIT_FAILURE, "listen()");
536 signal(SIGCHLD, catchsig);
538 while (1) {
539 slen = sizeof(struct sockaddr_in);
541 if ((rfd = accept(sfd, (struct sockaddr_in *)&raddr, &slen)) == -1) {
542 warn("accept");
543 continue;
546 switch (fork()) {
547 case -1:
548 warn("fork()");
549 break;
550 case 0:
551 close(sfd);
552 doit(rfd);
553 break;
554 default:
555 break;
559 exit(EXIT_SUCCESS);