Add write_dtd(). Unused ATM.
[pwmd.git] / src / pwmserver.c
blobba17f7fe0c0e1de88f7e86c18f35d90e0057254a
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 #define DEFAULT_PORT 5555
19 enum {
20 STATE_CONNECTED,
21 STATE_AUTH
24 struct client_s {
25 xmlDocPtr doc;
26 xmlNodePtr root;
27 xmlTextReaderPtr reader;
28 char *filename;
29 int fd;
30 xmlChar **req;
31 int state;
34 enum {
35 P_ERROR = -1,
36 P_OK,
37 P_GET,
38 P_AUTH,
39 P_QUIT
42 void write_dtd(FILE *fp)
44 fprintf(fp, "%s",
45 "<?xml version=\"1.0\"?>\n"
46 "<!DOCTYPE accounts [\n"
47 "!ELEMENT accounts (account*)>\n"
48 "!ELEMENT account (name, username?, password?, smtp?, pop?, imap?)>\n"
49 "!ELEMENT name (#PCDATA)>\n"
50 "!ELEMENT username (#PCDATA)>\n"
51 "!ELEMENT password (#PCDATA)>\n"
52 "!ELEMENT smtp (host, port, ssl)>\n"
53 "!ELEMENT pop (host, port, ssl)>\n"
54 "!ELEMENT imap (host, port, ssl)>\n"
55 "!ELEMENT host (#PCDATA)>\n"
56 "!ELEMENT port (#PCDATA)>\n"
57 "!ELEMENT ssl (#PCDATA)>\n"
58 "]>\n"
62 void catchsig(int sig)
64 switch (sig) {
65 case SIGCHLD:
66 break;
67 default:
68 break;
72 xmlNodePtr find_node(xmlNodePtr root, xmlChar *name)
74 xmlNodePtr n;
76 for (n = root->children; n; n = n->next) {
77 if (strcmp((char *)name, (char *)n->name) == 0)
78 return n;
81 return NULL;
84 xmlNodePtr create_doc(xmlDocPtr *doc, const char *root)
86 xmlDocPtr d = xmlNewDoc((xmlChar *)"1.0");
87 xmlNodePtr n = xmlNewNode(NULL, (xmlChar *)root);
89 *doc = d;
90 xmlDocSetRootElement(d, n);
91 return n;
94 int add_node(xmlNodePtr root, char **exp)
96 char **p;
97 xmlNodePtr n = NULL, l = root;
99 for (p = exp; *p; p++) {
100 n = find_node(l, (xmlChar *)*p);
102 // non-matching element.
103 if (!n) {
104 if (!*(p + 1)) {
105 xmlNodeAddContent(n, (xmlChar *)*p);
106 break;
108 else {
109 xmlNewChild(l, NULL, (xmlChar *)*p, NULL);
110 l->next->type = XML_ELEMENT_NODE;
113 n = l;
114 continue;
117 l = n;
120 #if 0
121 if (!exp && p && n) {
122 xmlNodeAddContent(n, (xmlChar *)p);
123 return 0;
126 if (!n && exp) {
127 warnx("add: parent node \"%s\" does not exist", p);
128 return 1;
130 else
131 xmlNewChild(n, NULL, (xmlChar *)p, (xmlChar *)"value");
132 #endif
134 return 0;
137 void usage(char *pn)
139 printf(
140 "Usage: %s [-h] [-p <port>]\n"
141 " -p alternate port (%i)\n"
142 " -h this help text\n",
143 pn, DEFAULT_PORT);
144 exit(EXIT_SUCCESS);
147 int find_account(xmlTextReaderPtr reader, xmlChar *name)
149 int type;
150 xmlNodePtr n;
152 while (xmlTextReaderRead(reader) == 1) {
153 if ((n = xmlTextReaderCurrentNode(reader)) == NULL)
154 return 1;
157 * "name" is the element that holds the account name. It would be nice
158 * to have an element the is the account name but I wouldn't know how
159 * to do it with the DTD.
161 if (xmlTextReaderDepth(reader) == 1 &&
162 xmlStrcmp(n->name, (xmlChar *)"name") &&
163 xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) {
164 do {
165 if (xmlTextReaderNext(reader) != 1)
166 return 1;
167 type = xmlTextReaderNodeType(reader);
168 } while (type != -1 && type != XML_READER_TYPE_TEXT);
170 if ((n = xmlTextReaderCurrentNode(reader)) == NULL)
171 return 1;
173 if (xmlStrcmp(n->content, name) == 0)
174 return 0;
178 return 1;
181 int find_element(xmlTextReaderPtr reader, xmlChar *e, int more)
183 int type;
184 xmlNodePtr n;
186 while (xmlTextReaderRead(reader) == 1) {
187 if ((n = xmlTextReaderCurrentNode(reader)) == NULL)
188 return 1;
190 if (xmlStrcmp(n->name, e) == 0 &&
191 xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) {
192 if (more)
193 return 0;
196 * FIXME account/service without details (account/service/host).
198 do {
199 if (xmlTextReaderNext(reader) != 1)
200 return 1;
201 type = xmlTextReaderNodeType(reader);
202 } while (type != -1 && type != XML_READER_TYPE_TEXT);
203 return (type == XML_READER_TYPE_TEXT) ? 0 : 1;
207 return 1;
210 void send_to_client(int fd, char *fmt, ...)
212 va_list ap;
213 char *buf;
215 va_start(ap, fmt);
216 vasprintf(&buf, fmt, ap);
217 va_end(ap);
219 if (send(fd, buf, strlen(buf), 0) == -1)
220 warn("send()");
222 free(buf);
225 int parser()
227 #if 0
228 errno = 0;
230 if (access(argv[optind], R_OK) != 0 && errno == ENOENT) {
231 root = create_doc(&doc, "accounts");
233 else if (errno)
234 err(EXIT_FAILURE, "%s", argv[optind]);
236 if (!doc) {
237 if ((doc = xmlReadFile(argv[optind++], NULL, 0)) == NULL)
238 errx(EXIT_FAILURE, "parse error");
240 root = doc->children;
243 if ((reader = xmlReaderWalker(doc)) == NULL)
244 errx(EXIT_FAILURE, "xmlReaderWalker() failed");
246 s = argv[optind];
248 while ((p = strsep(&s, "/")) != NULL) {
249 exp = realloc(exp, (i + 2) * sizeof(xmlChar *));
250 exp[i++] = xmlStrdup((xmlChar *)p);
251 exp[i] = NULL;
254 if (!i)
255 errx(EXIT_FAILURE, "account expression parse error");
257 i = 0;
259 if (find_account(reader, exp[i++]))
260 errx(EXIT_FAILURE, "account not found");
263 * We are at the position in the document where the account was found.
264 * Search through the account for the wanted elements.
266 for (; exp[i]; i++) {
267 xmlNodePtr n;
269 if (find_element(reader, exp[i], exp[i+1] != NULL))
270 errx(EXIT_FAILURE, "could not find element %s", exp[i]);
273 * We are at the end of the element list. Save the result.
275 if (!exp[i+1]) {
276 n = xmlTextReaderCurrentNode(reader);
277 result = xmlStrdup(n->content);
281 if (exp) {
282 for (i = 0; exp[i]; i++)
283 xmlFree(exp[i]);
285 xmlFree(exp);
288 xmlFreeTextReader(reader);
289 xmlFreeDoc(doc);
290 xmlFree(result);
291 #endif
292 exit(EXIT_SUCCESS);
295 int parse_account(struct client_s *cl, char *str)
297 char *p;
298 int i = 0;
300 if (cl->req) {
301 for (i = 0; cl->req[i]; i++)
302 free(cl->req[i]);
305 i = 0;
307 while ((p = strsep(&str, "/")) != NULL) {
308 cl->req = realloc(cl->req, (i + 2) * sizeof(xmlChar *));
309 cl->req[i++] = xmlStrdup((xmlChar *)p);
310 cl->req[i] = NULL;
313 if (!i)
314 return 1;
316 i = 0;
318 if (find_account(cl->reader, cl->req[i++])) {
319 send_to_client(cl->fd, "000 account \"%s\" not found\n", cl->req[0]);
320 return 1;
324 * We are at the position in the document where the account was found.
325 * Search through the account for the wanted elements.
327 for (; cl->req[i]; i++) {
328 xmlNodePtr n;
330 if (find_element(cl->reader, cl->req[i], cl->req[i+1] != NULL)) {
331 send_to_client(cl->fd, "could not find element \"%s\"",
332 cl->req[i]);
333 return 1;
337 * We are at the end of the element list. Save the result.
339 if (!cl->req[i+1]) {
340 n = xmlTextReaderCurrentNode(cl->reader);
341 send_to_client(cl->fd, "%s\n", n->content);
345 return 0;
348 void client_help(int fd, const char *what)
350 char *line;
352 if (!what || !*what)
353 line =
354 "000 Try 'help topic' for details\n"
355 "000 auth get quit\n";
356 else if (strcasecmp(what, "get") == 0)
357 line =
358 "000 syntax: get account[/element[/...]]\n"
359 "000 <account> is the account to work on and <element>\n"
360 "000 is the account elements wanted.\n"
361 "000 -\n"
362 "000 Example: get isp/imap/port\n"
363 "000 get isp/username\n";
364 else if (strcasecmp(what, "quit") == 0)
365 line =
366 "000 syntax: quit\n"
367 "000 close the connection\n";
368 else if (strcasecmp(what, "auth") == 0)
369 line =
370 "000 syntax: auth username password\n";
371 else
372 line = "000 unknown command\n";
374 send_to_client(fd, line);
377 char *skip_space(char *str)
379 while (isspace(*str))
380 str++;
382 return str;
385 int open_xml(struct client_s *cl)
387 if ((cl->doc = xmlReadFile(cl->filename, NULL, 0)) == NULL) {
388 send_to_client(cl->fd, "000 error while parsing XML\n");
389 return 1;
392 cl->root = xmlDocGetRootElement(cl->doc);
394 if ((cl->reader = xmlReaderWalker(cl->doc)) == NULL) {
395 send_to_client(cl->fd, "xmlReaderWalker() failed");
396 return 1;
399 return 0;
403 * The filename will be username.xml.
405 int authenticate_client(struct client_s *cl, char *str)
407 char *user = NULL, *pass = NULL;
408 char buf[FILENAME_MAX];
410 if ((user = strsep(&str, " ")) == NULL)
411 return 1;
413 pass = str;
414 snprintf(buf, sizeof(buf), "%s.xml", user);
417 if (access(argv[optind], R_OK) != 0 && errno == ENOENT) {
418 root = create_doc(&doc, "accounts");
420 else if (errno)
421 err(EXIT_FAILURE, "%s", argv[optind]);
424 cl->filename = strdup(buf);
425 cl->state = STATE_AUTH;
427 return open_xml(cl);
430 int input_parser(struct client_s *cl, char *str)
432 char *p, *t;
434 while ((p = strsep(&str, "\n")) != NULL) {
435 if (strcasecmp(p, "quit") == 0)
436 return P_QUIT;
437 else if (strcasecmp(p, "help") == 0)
438 client_help(cl->fd, NULL);
439 else if (strncasecmp(p, "help ", 5) == 0) {
440 t = p + 5;
441 t = skip_space(t);
442 client_help(cl->fd, t);
444 else if (strncasecmp(p, "get ", 4) == 0) {
445 t = p + 4;
446 t = skip_space(t);
448 if (cl->state != STATE_AUTH) {
449 send_to_client(cl->fd, "000 not authenticated\n");
452 // FIXME reuse existing cl->doc and cl->reader handles.
453 if (cl->doc) {
454 xmlFreeTextReader(cl->reader);
455 xmlFreeDoc(cl->doc);
457 if (!open_xml(cl)) {
458 cl->doc->children = cl->root;
459 xmlReaderNewWalker(cl->reader, cl->doc);
461 if (parse_account(cl, t)) {
462 send_to_client(cl->fd, "000 parse error\n");
467 else if (strncasecmp(p, "auth ", 5) == 0) {
468 t = p + 5;
469 t = skip_space(t);
471 if (!authenticate_client(cl, t))
472 send_to_client(cl->fd, "000 authenticated\n");
476 return P_OK;
480 * Called every time a connection is made.
481 * FIXME protocol
482 * FIXME SSL
484 void doit(int rfd)
486 struct client_s *cl = calloc(1, sizeof(struct client_s));
488 cl->state = STATE_CONNECTED;
489 cl->fd = rfd;
490 send_to_client(rfd, "000 Type help for available commands\n");
492 while (1) {
493 fd_set rfds, wfds;
494 char buf[LINE_MAX] = {0};
495 struct timeval tv;
497 FD_ZERO(&rfds);
498 FD_SET(cl->fd, &rfds);
499 FD_ZERO(&wfds);
500 FD_SET(cl->fd, &wfds);
502 tv.tv_sec = 1;
503 tv.tv_usec = 0;
505 switch (select(cl->fd + 1, &rfds, &wfds, NULL, NULL)) {
506 case -1:
507 warn("select()");
508 break;
509 case 0:
510 // timeout
511 continue;
512 default:
513 break;
516 if (FD_ISSET(cl->fd, &rfds)) {
517 int len = recv(cl->fd, buf, sizeof(buf), 0);
519 buf[len - 1] = 0;
521 if (len == -1) {
522 warn("recv()");
523 continue;
525 else if (len == 0)
526 break;
528 switch (input_parser(cl, buf)) {
529 case P_QUIT:
530 goto done;
531 default:
532 break;
537 done:
538 shutdown(cl->fd, SHUT_RDWR);
539 _exit(EXIT_SUCCESS);
542 int main(int argc, char *argv[])
544 int opt;
545 int sfd, rfd;
546 struct sockaddr_in laddr, raddr;
547 socklen_t slen;
548 int yes = 0;
549 int port = DEFAULT_PORT;
551 while ((opt = getopt(argc, argv, "hp:")) != EOF) {
552 switch (opt) {
553 case 'p':
554 port = atoi(optarg);
555 break;
556 default:
557 usage(argv[0]);
561 if ((sfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
562 err(EXIT_FAILURE, "socket()");
564 if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
565 err(EXIT_FAILURE, "setsockopt()");
567 laddr.sin_family = AF_INET;
568 laddr.sin_port = htons(port);
569 laddr.sin_addr.s_addr = INADDR_ANY;
570 memset(&(laddr.sin_zero), 0, 8);
572 if (bind(sfd, (struct sockaddr *)&laddr, sizeof(struct sockaddr)) == -1)
573 err(EXIT_FAILURE, "bind()");
575 if (listen(sfd, 10) == -1)
576 err(EXIT_FAILURE, "listen()");
578 signal(SIGCHLD, catchsig);
580 while (1) {
581 slen = sizeof(struct sockaddr_in);
583 if ((rfd = accept(sfd, (struct sockaddr_in *)&raddr, &slen)) == -1) {
584 warn("accept");
585 continue;
588 switch (fork()) {
589 case -1:
590 warn("fork()");
591 break;
592 case 0:
593 doit(rfd);
594 break;
595 default:
596 break;
600 exit(EXIT_SUCCESS);