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