Update command line usage and options.
[pwmd.git] / src / pwmserver.c
blobd6c9d7a5a503e6694463e6e2e50fb103fcee172d
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 catchsig(int sig)
44 switch (sig) {
45 case SIGCHLD:
46 break;
47 default:
48 break;
52 xmlNodePtr find_node(xmlNodePtr root, xmlChar *name)
54 xmlNodePtr n;
56 for (n = root->children; n; n = n->next) {
57 if (strcmp((char *)name, (char *)n->name) == 0)
58 return n;
61 return NULL;
64 xmlNodePtr create_doc(xmlDocPtr *doc, const char *root)
66 xmlDocPtr d = xmlNewDoc((xmlChar *)"1.0");
67 xmlNodePtr n = xmlNewNode(NULL, (xmlChar *)root);
69 *doc = d;
70 xmlDocSetRootElement(d, n);
71 return n;
74 int add_node(xmlNodePtr root, char **exp)
76 char **p;
77 xmlNodePtr n = NULL, l = root;
79 for (p = exp; *p; p++) {
80 n = find_node(l, (xmlChar *)*p);
82 // non-matching element.
83 if (!n) {
84 if (!*(p + 1)) {
85 xmlNodeAddContent(n, (xmlChar *)*p);
86 break;
88 else {
89 xmlNewChild(l, NULL, (xmlChar *)*p, NULL);
90 l->next->type = XML_ELEMENT_NODE;
93 n = l;
94 continue;
97 l = n;
100 #if 0
101 if (!exp && p && n) {
102 xmlNodeAddContent(n, (xmlChar *)p);
103 return 0;
106 if (!n && exp) {
107 warnx("add: parent node \"%s\" does not exist", p);
108 return 1;
110 else
111 xmlNewChild(n, NULL, (xmlChar *)p, (xmlChar *)"value");
112 #endif
114 return 0;
117 void usage(char *pn)
119 printf(
120 "Usage: %s [-h] [-p <port>]\n"
121 " -p alternate port (%i)\n"
122 " -h this help text\n",
123 pn, DEFAULT_PORT);
124 exit(EXIT_SUCCESS);
127 int find_account(xmlTextReaderPtr reader, xmlChar *name)
129 int type;
130 xmlNodePtr n;
132 while (xmlTextReaderRead(reader) == 1) {
133 if ((n = xmlTextReaderCurrentNode(reader)) == NULL)
134 return 1;
137 * "name" is the element that holds the account name. It would be nice
138 * to have an element the is the account name but I wouldn't know how
139 * to do it with the DTD.
141 if (xmlTextReaderDepth(reader) == 1 &&
142 xmlStrcmp(n->name, (xmlChar *)"name") &&
143 xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) {
144 do {
145 if (xmlTextReaderNext(reader) != 1)
146 return 1;
147 type = xmlTextReaderNodeType(reader);
148 } while (type != -1 && type != XML_READER_TYPE_TEXT);
150 if ((n = xmlTextReaderCurrentNode(reader)) == NULL)
151 return 1;
153 if (xmlStrcmp(n->content, name) == 0)
154 return 0;
158 return 1;
161 int find_element(xmlTextReaderPtr reader, xmlChar *e, int more)
163 int type;
164 xmlNodePtr n;
166 while (xmlTextReaderRead(reader) == 1) {
167 if ((n = xmlTextReaderCurrentNode(reader)) == NULL)
168 return 1;
170 if (xmlStrcmp(n->name, e) == 0 &&
171 xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) {
172 if (more)
173 return 0;
176 * FIXME account/service without details (account/service/host).
178 do {
179 if (xmlTextReaderNext(reader) != 1)
180 return 1;
181 type = xmlTextReaderNodeType(reader);
182 } while (type != -1 && type != XML_READER_TYPE_TEXT);
183 return (type == XML_READER_TYPE_TEXT) ? 0 : 1;
187 return 1;
190 void send_to_client(int fd, char *fmt, ...)
192 va_list ap;
193 char *buf;
195 va_start(ap, fmt);
196 vasprintf(&buf, fmt, ap);
197 va_end(ap);
199 if (send(fd, buf, strlen(buf), 0) == -1)
200 warn("send()");
202 free(buf);
205 int parser()
207 #if 0
208 errno = 0;
210 if (access(argv[optind], R_OK) != 0 && errno == ENOENT) {
211 root = create_doc(&doc, "accounts");
213 else if (errno)
214 err(EXIT_FAILURE, "%s", argv[optind]);
216 if (!doc) {
217 if ((doc = xmlReadFile(argv[optind++], NULL, 0)) == NULL)
218 errx(EXIT_FAILURE, "parse error");
220 root = doc->children;
223 if ((reader = xmlReaderWalker(doc)) == NULL)
224 errx(EXIT_FAILURE, "xmlReaderWalker() failed");
226 s = argv[optind];
228 while ((p = strsep(&s, "/")) != NULL) {
229 exp = realloc(exp, (i + 2) * sizeof(xmlChar *));
230 exp[i++] = xmlStrdup((xmlChar *)p);
231 exp[i] = NULL;
234 if (!i)
235 errx(EXIT_FAILURE, "account expression parse error");
237 i = 0;
239 if (find_account(reader, exp[i++]))
240 errx(EXIT_FAILURE, "account not found");
243 * We are at the position in the document where the account was found.
244 * Search through the account for the wanted elements.
246 for (; exp[i]; i++) {
247 xmlNodePtr n;
249 if (find_element(reader, exp[i], exp[i+1] != NULL))
250 errx(EXIT_FAILURE, "could not find element %s", exp[i]);
253 * We are at the end of the element list. Save the result.
255 if (!exp[i+1]) {
256 n = xmlTextReaderCurrentNode(reader);
257 result = xmlStrdup(n->content);
261 if (exp) {
262 for (i = 0; exp[i]; i++)
263 xmlFree(exp[i]);
265 xmlFree(exp);
268 xmlFreeTextReader(reader);
269 xmlFreeDoc(doc);
270 xmlFree(result);
271 #endif
272 exit(EXIT_SUCCESS);
275 int parse_account(struct client_s *cl, char *str)
277 char *p;
278 int i = 0;
280 if (cl->req) {
281 for (i = 0; cl->req[i]; i++)
282 free(cl->req[i]);
285 i = 0;
287 while ((p = strsep(&str, "/")) != NULL) {
288 cl->req = realloc(cl->req, (i + 2) * sizeof(xmlChar *));
289 cl->req[i++] = xmlStrdup((xmlChar *)p);
290 cl->req[i] = NULL;
293 if (!i)
294 return 1;
296 i = 0;
298 if (find_account(cl->reader, cl->req[i++])) {
299 send_to_client(cl->fd, "000 account \"%s\" not found\n", cl->req[0]);
300 return 1;
304 * We are at the position in the document where the account was found.
305 * Search through the account for the wanted elements.
307 for (; cl->req[i]; i++) {
308 xmlNodePtr n;
310 if (find_element(cl->reader, cl->req[i], cl->req[i+1] != NULL)) {
311 send_to_client(cl->fd, "could not find element \"%s\"",
312 cl->req[i]);
313 return 1;
317 * We are at the end of the element list. Save the result.
319 if (!cl->req[i+1]) {
320 n = xmlTextReaderCurrentNode(cl->reader);
321 send_to_client(cl->fd, "%s\n", n->content);
325 return 0;
328 void client_help(int fd, const char *what)
330 char *line;
332 if (!what || !*what)
333 line =
334 "000 Try 'help topic' for details\n"
335 "000 auth get quit\n";
336 else if (strcasecmp(what, "get") == 0)
337 line =
338 "000 syntax: get account[/element[/...]]\n"
339 "000 <account> is the account to work on and <element>\n"
340 "000 is the account elements wanted.\n"
341 "000 -\n"
342 "000 Example: get isp/imap/port\n"
343 "000 get isp/username\n";
344 else if (strcasecmp(what, "quit") == 0)
345 line =
346 "000 syntax: quit\n"
347 "000 close the connection\n";
348 else if (strcasecmp(what, "auth") == 0)
349 line =
350 "000 syntax: auth username password\n";
351 else
352 line = "000 unknown command\n";
354 send_to_client(fd, line);
357 char *skip_space(char *str)
359 while (isspace(*str))
360 str++;
362 return str;
365 int open_xml(struct client_s *cl)
367 if ((cl->doc = xmlReadFile(cl->filename, NULL, 0)) == NULL) {
368 send_to_client(cl->fd, "000 error while parsing XML\n");
369 return 1;
372 cl->root = xmlDocGetRootElement(cl->doc);
374 if ((cl->reader = xmlReaderWalker(cl->doc)) == NULL) {
375 send_to_client(cl->fd, "xmlReaderWalker() failed");
376 return 1;
379 return 0;
383 * The filename will be username.xml.
385 int authenticate_client(struct client_s *cl, char *str)
387 char *user = NULL, *pass = NULL;
388 char buf[FILENAME_MAX];
390 if ((user = strsep(&str, " ")) == NULL)
391 return 1;
393 pass = str;
394 snprintf(buf, sizeof(buf), "%s.xml", user);
397 if (access(argv[optind], R_OK) != 0 && errno == ENOENT) {
398 root = create_doc(&doc, "accounts");
400 else if (errno)
401 err(EXIT_FAILURE, "%s", argv[optind]);
404 cl->filename = strdup(buf);
405 cl->state = STATE_AUTH;
407 return open_xml(cl);
410 int input_parser(struct client_s *cl, char *str)
412 char *p, *t;
414 while ((p = strsep(&str, "\n")) != NULL) {
415 if (strcasecmp(p, "quit") == 0)
416 return P_QUIT;
417 else if (strcasecmp(p, "help") == 0)
418 client_help(cl->fd, NULL);
419 else if (strncasecmp(p, "help ", 5) == 0) {
420 t = p + 5;
421 t = skip_space(t);
422 client_help(cl->fd, t);
424 else if (strncasecmp(p, "get ", 4) == 0) {
425 t = p + 4;
426 t = skip_space(t);
428 if (cl->state != STATE_AUTH) {
429 send_to_client(cl->fd, "000 not authenticated\n");
432 // FIXME reuse existing cl->doc and cl->reader handles.
433 if (cl->doc) {
434 xmlFreeTextReader(cl->reader);
435 xmlFreeDoc(cl->doc);
437 if (!open_xml(cl)) {
438 cl->doc->children = cl->root;
439 xmlReaderNewWalker(cl->reader, cl->doc);
441 if (parse_account(cl, t)) {
442 send_to_client(cl->fd, "000 parse error\n");
447 else if (strncasecmp(p, "auth ", 5) == 0) {
448 t = p + 5;
449 t = skip_space(t);
451 if (!authenticate_client(cl, t))
452 send_to_client(cl->fd, "000 authenticated\n");
456 return P_OK;
460 * Called every time a connection is made.
461 * FIXME protocol
462 * FIXME SSL
464 void doit(int rfd)
466 struct client_s *cl = calloc(1, sizeof(struct client_s));
468 cl->state = STATE_CONNECTED;
469 cl->fd = rfd;
470 send_to_client(rfd, "000 Type help for available commands\n");
472 while (1) {
473 fd_set rfds, wfds;
474 char buf[LINE_MAX] = {0};
475 struct timeval tv;
477 FD_ZERO(&rfds);
478 FD_SET(cl->fd, &rfds);
479 FD_ZERO(&wfds);
480 FD_SET(cl->fd, &wfds);
482 tv.tv_sec = 1;
483 tv.tv_usec = 0;
485 switch (select(cl->fd + 1, &rfds, &wfds, NULL, NULL)) {
486 case -1:
487 warn("select()");
488 break;
489 case 0:
490 // timeout
491 continue;
492 default:
493 break;
496 if (FD_ISSET(cl->fd, &rfds)) {
497 int len = recv(cl->fd, buf, sizeof(buf), 0);
499 buf[len - 1] = 0;
501 if (len == -1) {
502 warn("recv()");
503 continue;
505 else if (len == 0)
506 break;
508 switch (input_parser(cl, buf)) {
509 case P_QUIT:
510 goto done;
511 default:
512 break;
517 done:
518 shutdown(cl->fd, SHUT_RDWR);
519 _exit(EXIT_SUCCESS);
522 int main(int argc, char *argv[])
524 int opt;
525 int sfd, rfd;
526 struct sockaddr_in laddr, raddr;
527 socklen_t slen;
528 int yes = 0;
529 int port = DEFAULT_PORT;
531 while ((opt = getopt(argc, argv, "hp:")) != EOF) {
532 switch (opt) {
533 case 'p':
534 port = atoi(optarg);
535 break;
536 default:
537 usage(argv[0]);
541 if ((sfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
542 err(EXIT_FAILURE, "socket()");
544 if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
545 err(EXIT_FAILURE, "setsockopt()");
547 laddr.sin_family = AF_INET;
548 laddr.sin_port = htons(port);
549 laddr.sin_addr.s_addr = INADDR_ANY;
550 memset(&(laddr.sin_zero), 0, 8);
552 if (bind(sfd, (struct sockaddr *)&laddr, sizeof(struct sockaddr)) == -1)
553 err(EXIT_FAILURE, "bind()");
555 if (listen(sfd, 10) == -1)
556 err(EXIT_FAILURE, "listen()");
558 signal(SIGCHLD, catchsig);
560 while (1) {
561 slen = sizeof(struct sockaddr_in);
563 if ((rfd = accept(sfd, (struct sockaddr_in *)&raddr, &slen)) == -1) {
564 warn("accept");
565 continue;
568 switch (fork()) {
569 case -1:
570 warn("fork()");
571 break;
572 case 0:
573 doit(rfd);
574 break;
575 default:
576 break;
580 exit(EXIT_SUCCESS);