Improve daemonize - fix trac issue 178, debian bug http://bugs.debian.org/cgi-bin...
[guacd.git] / src / daemon.c
1
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is guacd.
16  *
17  * The Initial Developer of the Original Code is
18  * Michael Jumper.
19  * Portions created by the Initial Developer are Copyright (C) 2010
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  * David PHAM-VAN <d.pham-van@ulteo.com> Ulteo SAS - http://www.ulteo.com
24  *
25  * Alternatively, the contents of this file may be used under the terms of
26  * either the GNU General Public License Version 2 or later (the "GPL"), or
27  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28  * in which case the provisions of the GPL or the LGPL are applicable instead
29  * of those above. If you wish to allow use of your version of this file only
30  * under the terms of either the GPL or the LGPL, and not to allow others to
31  * use your version of this file under the terms of the MPL, indicate your
32  * decision by deleting the provisions above and replace them with the notice
33  * and other provisions required by the GPL or the LGPL. If you do not delete
34  * the provisions above, a recipient may use your version of this file under
35  * the terms of any one of the MPL, the GPL or the LGPL.
36  *
37  * ***** END LICENSE BLOCK ***** */
38
39 #include <unistd.h>
40 #include <stdlib.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <signal.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <fcntl.h>
47 #include <ctype.h>
48
49 #include <sys/socket.h>
50 #include <netdb.h>
51 #include <netinet/in.h>
52
53 #include <errno.h>
54 #include <syslog.h>
55 #include <libgen.h>
56
57 #include <guacamole/client.h>
58 #include <guacamole/error.h>
59
60 #include "client.h"
61 #include "log.h"
62
63 /* XML */
64 #include <libxml2/libxml/parser.h>
65 #include <libxml2/libxml/tree.h>
66 #include <libxml2/libxml/xpath.h>
67 #include <libxml2/libxml/xpathInternals.h>
68
69 void xml_init () {
70     /* check the version. This calls xmlInitParser() */
71     /* braces to stop indent getting confused */
72     {LIBXML_TEST_VERSION}
73 }
74
75 void xml_deinit () {
76     xmlCleanupParser ();
77 }
78
79 xmlNodePtr xml_get_node (xmlDoc * pDoc, const char *xpathexpr) {
80     xmlChar *xpath_expr = (xmlChar *) xpathexpr;
81     xmlXPathContextPtr xpathCtx = NULL;
82     xmlXPathObjectPtr xpathObj = NULL;
83     xmlNodeSetPtr nodeSet = NULL;
84     int size;
85     xmlNodePtr myNode = NULL;
86
87     /* Create xpath evaluation context */
88     if (NULL == (xpathCtx = xmlXPathNewContext (pDoc)))
89         return NULL;
90         
91     /* Evaluate xpath expression */
92     if (NULL == (xpathObj = xmlXPathEvalExpression (xpath_expr, xpathCtx))) {
93         xmlXPathFreeContext (xpathCtx);
94         return NULL;
95     }
96
97     nodeSet = xpathObj->nodesetval;
98     size = (nodeSet) ? nodeSet->nodeNr : 0;
99     if (size == 1)
100         myNode = nodeSet->nodeTab[0];
101
102     xmlXPathFreeObject (xpathObj);
103     xmlXPathFreeContext (xpathCtx);
104     return myNode;
105 }
106
107 char * xml_get_string (xmlDoc * pDoc, char *xpathexpr) {
108     xmlNodePtr config_node = NULL;
109     xmlChar *propval = NULL;
110     
111     /* Find the node in question beneath the config node */
112     if (NULL == (config_node = xml_get_node (pDoc, xpathexpr)))
113         return NULL;
114     
115     /* Find the property attached to that node; if it's not there, return 0 */
116     if (NULL == (propval = xmlNodeGetContent (config_node)))
117         return NULL;
118
119     /* We would like to just return propval here, but that's an xmlChar * allocated by                                                                                                                    
120      * libxml, and thus the caller can't just free() it - it would need to be xmlFree()'d.                                                                                                                
121      * so we'll fiddle around and generate our own copy allocated with libc                                                                                                                               
122      */
123     char *value = strdup ((char *) propval);
124     xmlFree (propval);            /* as xmlGetProp makes a copy of the string */
125     return value;                 /* caller's responsibility to free() this */
126 }
127
128 void guacd_handle_connection(int fd) {
129
130     guac_client* client;
131     guac_client_plugin* plugin;
132     guac_instruction* select;
133     guac_instruction* connect;
134
135     /* Open guac_socket */
136     guac_socket* socket = guac_socket_open(fd);
137
138     /* Get protocol from select instruction */
139     select = guac_protocol_expect_instruction(
140             socket, GUACD_USEC_TIMEOUT, "select");
141     if (select == NULL) {
142
143         /* Log error */
144         guacd_log_guac_error("Error reading \"select\"");
145
146         /* Free resources */
147         guac_socket_close(socket);
148         return;
149     }
150
151     /* Validate args to select */
152     if (select->argc != 1) {
153
154         /* Log error */
155         guacd_log_error("Bad number of arguments to \"select\" (%i)",
156                 select->argc);
157
158         /* Free resources */
159         guac_socket_close(socket);
160         return;
161     }
162
163     guacd_log_info("Protocol \"%s\" selected", select->argv[0]);
164
165     /* Get plugin from protocol in select */
166     plugin = guac_client_plugin_open(select->argv[0]);
167     guac_instruction_free(select);
168
169     if (plugin == NULL) {
170
171         /* Log error */
172         guacd_log_guac_error("Error loading client plugin");
173
174         /* Free resources */
175         guac_socket_close(socket);
176         return;
177     }
178
179     /* Send args response */
180     if (guac_protocol_send_args(socket, plugin->args)
181             || guac_socket_flush(socket)) {
182
183         /* Log error */
184         guacd_log_guac_error("Error sending \"args\"");
185
186         if (guac_client_plugin_close(plugin))
187             guacd_log_guac_error("Error closing client plugin");
188
189         guac_socket_close(socket);
190         return;
191     }
192
193     /* Get args from connect instruction */
194     connect = guac_protocol_expect_instruction(
195             socket, GUACD_USEC_TIMEOUT, "connect");
196     if (connect == NULL) {
197
198         /* Log error */
199         guacd_log_guac_error("Error reading \"connect\"");
200
201         if (guac_client_plugin_close(plugin))
202             guacd_log_guac_error("Error closing client plugin");
203
204         guac_socket_close(socket);
205         return;
206     }
207
208     /* Load and init client */
209     client = guac_client_plugin_get_client(plugin, socket,
210             connect->argc, connect->argv,
211             guacd_client_log_info, guacd_client_log_error);
212
213     guac_instruction_free(connect);
214
215     if (client == NULL) {
216
217         guacd_log_guac_error("Error instantiating client");
218
219         if (guac_client_plugin_close(plugin))
220             guacd_log_guac_error("Error closing client plugin");
221
222         guac_socket_close(socket);
223         return;
224     }
225
226     /* Start client threads */
227     guacd_log_info("Starting client");
228     if (guacd_client_start(client))
229         guacd_log_error("Client finished abnormally");
230     else
231         guacd_log_info("Client finished normally");
232
233     /* Clean up */
234     guac_client_free(client);
235     if (guac_client_plugin_close(plugin))
236         guacd_log_error("Error closing client plugin");
237
238     /* Close socket */
239     guac_socket_close(socket);
240
241     return;
242
243 }
244
245 void guacd_handle_connection_xml(int fd, char* xmlconfig) {
246
247     guac_client* client = NULL;
248     guac_client_plugin* plugin = NULL;
249     char ** protocol_argv = NULL;
250     int protocol_argc = 0;
251     xmlDoc * pDoc = NULL;
252     char * protocol = NULL;
253     guac_socket* socket = NULL;
254
255     if (NULL == (socket = guac_socket_open(fd))) {
256         guacd_log_guac_error("Could not open socket");
257         goto error;
258     }
259
260     if (NULL == (pDoc = xmlParseMemory (xmlconfig, strlen(xmlconfig)))) {
261         guacd_log_guac_error("Could not parse XML");
262         goto error;
263     }
264
265     if (NULL == (protocol = xml_get_string(pDoc, "/params/protocol"))) {
266         guacd_log_guac_error("Could not parse XML");
267         goto error;
268     }
269
270     guacd_log_info("Opening protocol '%s'", protocol);
271
272     /* Get plugin from protocol in select */
273     if (NULL == (plugin = guac_client_plugin_open(protocol))) {
274         guacd_log_guac_error("Error loading client plugin");
275         goto error;
276     }
277
278     /* Now parse protocol strings */
279     const char ** arg;
280     const char * params = "/params/";
281     int lparams = strlen(params);
282     for (arg = plugin->args; *arg && **arg; arg++)
283         protocol_argc++;
284     if (NULL == (protocol_argv = calloc(sizeof(char *), protocol_argc+1))) {
285         guacd_log_guac_error("Cannot allocate protocol arguments");
286         goto error;
287     }
288
289     int i;
290     for (i=0; i<protocol_argc; i++) {
291         const char * p;
292         char * q;
293         int l = strlen(plugin->args[i]);
294         char * argname = malloc(lparams+l+1);
295         if (!argname) {
296             guacd_log_guac_error("Error duplicating argument list");
297             goto error;
298         }
299         strncpy(argname, params, lparams);
300         /* replace non-alpha characters by '_' for XML */
301         for (p = plugin->args[i], q = argname+lparams; *p; p++, q++)
302             *q = isalnum(*p)?*p:'_';
303         *q='\0';
304         char * value = xml_get_string(pDoc, argname);
305         if (!value)
306             value = strdup("");
307         guacd_log_info("Argument '%s' set to '%s'", plugin->args[i], value);
308         protocol_argv[i]=value;
309     }
310
311     guacd_log_info("Starting protocol %s, %d arguments", protocol, protocol_argc);
312
313     /* Load and init client */
314     if (NULL == (client = guac_client_plugin_get_client(plugin, socket,
315                                                         protocol_argc, protocol_argv,
316                                                         guacd_client_log_info, guacd_client_log_error))) {
317         guacd_log_guac_error("Error instantiating client");
318         goto error;
319     }
320
321     /* Start client threads */
322     guacd_log_info("Starting client");
323     if (guacd_client_start(client))
324         guacd_log_error("Client finished abnormally");
325     else
326         guacd_log_info("Client finished normally");
327
328   error:
329     /* Clean up */
330     if (client)
331         guac_client_free(client);
332
333     if (plugin && guac_client_plugin_close(plugin))
334         guacd_log_error("Error closing client plugin");
335
336     if (protocol_argv) {
337         char **parg;
338         for (parg = protocol_argv ; *parg; parg++)
339             free(*parg);
340         free(protocol_argv);
341     }
342     if (pDoc)
343         xmlFreeDoc(pDoc);
344     if (protocol)
345         free (protocol);
346     if (socket)
347         guac_socket_close(socket);
348
349     return;
350 }
351
352 void daemonize () {
353     const char *devnull = "/dev/null";
354
355     /* Fork once to ensure we aren't the process group leader */
356     int i = fork ();
357     if (i < 0) {
358         fprintf (stderr, "Unable to fork\n");
359         _exit (1);
360     }
361
362     /* Exit if we are the parent */
363     if (i > 0)
364        _exit (0);                  /* parent exits */
365
366     /* Start a new session */
367     setsid ();
368
369     /* Fork again so the session group leader exits */
370     i = fork ();
371     if (i < 0) {
372         fprintf (stderr, "Unable to fork\n");
373         _exit (1);
374     }
375
376     /* Exit if we are the parent */
377     if (i > 0)
378        _exit (0);                  /* parent exits */
379
380     if (chdir ("/") <0) {
381         fprintf (stderr, "Unable to chdir /\n");
382         _exit (1);
383     }
384
385     /* Now close all FDs and reopen the 3 stdxxx to /dev/null */
386     for (i = getdtablesize () - 1; i >= 0; i--)
387         close (i);
388
389     i = open (devnull, O_RDWR);
390     if (i == -1) {
391         fprintf (stderr, "Unable to open /dev/null\n");
392         _exit (1);
393     }
394     i = open (devnull, O_RDONLY);
395     if (i != 0) {
396         dup2 (i, 0);
397         close (i);
398     }
399     i = open (devnull, O_WRONLY);
400     if (i != 1) {
401         dup2 (i, 1);
402         close (i);
403     }
404     i = open (devnull, O_WRONLY);
405     if (i != 2) {
406         dup2 (i, 2);
407         close (i);
408     }
409 }
410
411
412
413 int main(int argc, char* argv[]) {
414
415     /* Server */
416     int socket_fd;
417     struct addrinfo* addresses;
418     struct addrinfo* current_address;
419     char bound_address[1024];
420     char bound_port[64];
421     int opt_on = 1;
422
423     struct addrinfo hints = {
424         .ai_family   = AF_UNSPEC,
425         .ai_socktype = SOCK_STREAM,
426         .ai_protocol = IPPROTO_TCP
427     };
428
429     /* Client */
430     struct sockaddr_in client_addr;
431     socklen_t client_addr_len;
432     int connected_socket_fd;
433
434     /* Arguments */
435     char* listen_address = NULL; /* Default address of INADDR_ANY */
436     char* listen_port = "4822";  /* Default port */
437     char* pidfile = NULL;
438     int opt;
439     int foreground = 0;
440     char * xmlconfig = NULL;
441
442     /* General */
443     int retval;
444
445     xml_init();
446
447     /* Parse arguments */
448     while ((opt = getopt(argc, argv, "l:b:p:x:f")) != -1) {
449         if (opt == 'l') {
450             listen_port = strdup(optarg);
451         }
452         else if (opt == 'b') {
453             listen_address = strdup(optarg);
454         }
455         else if (opt == 'f') {
456             foreground++;
457         }
458         else if (opt == 'p') {
459             pidfile = strdup(optarg);
460         }
461         else if (opt == 'x') {
462             xmlconfig = strdup (optarg);
463         }
464         else {
465
466             fprintf(stderr, "USAGE: %s"
467                     " [-l LISTENPORT]"
468                     " [-b LISTENADDRESS]"
469                     " [-p PIDFILE]"
470                     " [-f]"
471                     " [-x XMLCONFIG]\n", argv[0]);
472
473             exit(EXIT_FAILURE);
474         }
475     }
476
477     /* Daemonize before we start opening sockets, as this closes FDs */
478     if (!foreground)
479         daemonize();
480
481     if (pidfile != NULL) {
482         /* Attempt to open pidfile and write PID */
483         FILE* pidf = fopen(pidfile, "w");
484         if (pidf) {
485             fprintf(pidf, "%d\n", getpid());
486             fclose(pidf);
487         } else {
488             /* Warn on failure */
489             guacd_log_error("Could not write PID file: %s", strerror(errno));
490             exit(EXIT_FAILURE);
491         }
492     }
493
494     /* Set up logging prefix */
495     strncpy(log_prefix, basename(argv[0]), sizeof(log_prefix));
496
497     /* Open log as early as we can */
498     openlog(NULL, LOG_PID, LOG_DAEMON);
499
500     /* Get addresses for binding */
501     if ((retval = getaddrinfo(listen_address, listen_port, &hints, &addresses))) {
502         guacd_log_error("Error parsing given address or port: %s",
503                 gai_strerror(retval));
504         exit(EXIT_FAILURE);
505     }
506
507     /* Get socket */
508     socket_fd = socket(AF_INET, SOCK_STREAM, 0);
509     if (socket_fd < 0) {
510         guacd_log_error("Error opening socket: %s", strerror(errno));
511         exit(EXIT_FAILURE);
512     }
513
514     /* Allow socket reuse */
515     if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &opt_on, sizeof(opt_on))) {
516         guacd_log_info("Unable to set socket options for reuse: %s", strerror(errno));
517     }
518
519     /* Attempt binding of each address until success */
520     current_address = addresses;
521     while (current_address != NULL) {
522
523         int retval;
524
525         /* Resolve hostname */
526         if ((retval = getnameinfo(current_address->ai_addr,
527                 current_address->ai_addrlen,
528                 bound_address, sizeof(bound_address),
529                 bound_port, sizeof(bound_port),
530                 NI_NUMERICHOST | NI_NUMERICSERV)))
531             guacd_log_error("Unable to resolve host: %s",
532                     gai_strerror(retval));
533
534         /* Attempt to bind socket to address */
535         if (bind(socket_fd,
536                     current_address->ai_addr,
537                     current_address->ai_addrlen) == 0) {
538
539             guacd_log_info("Successfully bound socket to "
540                     "host %s, port %s", bound_address, bound_port);
541
542             /* Done if successful bind */
543             break;
544
545         }
546
547         /* Otherwise log information regarding bind failure */
548         else
549             guacd_log_info("Unable to bind socket to "
550                     "host %s, port %s: %s",
551                     bound_address, bound_port, strerror(errno));
552
553         current_address = current_address->ai_next;
554
555     }
556
557     /* If unable to bind to anything, fail */
558     if (current_address == NULL) {
559         guacd_log_error("Unable to bind socket to any addresses.");
560         exit(EXIT_FAILURE);
561     }
562
563     /* Ignore SIGPIPE */
564     if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
565         guacd_log_info("Could not set handler for SIGPIPE to ignore. SIGPIPE may cause termination of the daemon.");
566     }
567
568     /* Ignore SIGCHLD (force automatic removal of children) */
569     if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
570         guacd_log_info("Could not set handler for SIGCHLD to ignore. Child processes may pile up in the process table.");
571     }
572
573     /* Log listening status */
574     syslog(LOG_INFO,
575             "Listening on host %s, port %s", bound_address, bound_port);
576
577     /* Free addresses */
578     freeaddrinfo(addresses);
579
580     /* Daemon loop */
581     for (;;) {
582
583         pid_t child_pid;
584
585         /* Listen for connections */
586         if (listen(socket_fd, 5) < 0) {
587             guacd_log_error("Could not listen on socket: %s", strerror(errno));
588             return 3;
589         }
590
591         /* Accept connection */
592         client_addr_len = sizeof(client_addr);
593         connected_socket_fd = accept(socket_fd, (struct sockaddr*) &client_addr, &client_addr_len);
594         if (connected_socket_fd < 0) {
595             guacd_log_error("Could not accept client connection: %s", strerror(errno));
596             return 3;
597         }
598
599         /* 
600          * Once connection is accepted, send child into background.
601          *
602          * Note that we prefer fork() over threads for connection-handling
603          * processes as they give each connection its own memory area, and
604          * isolate the main daemon and other connections from errors in any
605          * particular client plugin.
606          */
607
608         child_pid = (foreground>1)?0:fork();
609
610         /* If error, log */
611         if (child_pid == -1)
612             guacd_log_error("Error forking child process: %s", strerror(errno));
613
614         /* If child, start client, and exit when finished */
615         else if (child_pid == 0) {
616             if (xmlconfig)
617                 guacd_handle_connection_xml(connected_socket_fd, xmlconfig);
618             else
619                 guacd_handle_connection(connected_socket_fd);
620             close(connected_socket_fd);
621             return 0;
622         }
623
624         /* If parent, close reference to child's descriptor */
625         else if (close(connected_socket_fd) < 0) {
626             guacd_log_error("Error closing daemon reference to child descriptor: %s", strerror(errno));
627         }
628
629     }
630
631     /* Close socket */
632     if (close(socket_fd) < 0) {
633         guacd_log_error("Could not close socket: %s", strerror(errno));
634         return 3;
635     }
636
637     xml_deinit();
638     return 0;
639
640 }
641