remove 'illegal' (non-reentrant) log logic from signal handler
[oweals/gnunet.git] / src / gns / gnunet-bcd.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2013 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      Affero General Public License for more details.
14
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19  */
20
21 /**
22  * @file gns/gnunet-bcd.c
23  * @author Christian Grothoff
24  * @brief HTTP server to create GNS business cards
25  */
26
27 #include "platform.h"
28 #include <microhttpd.h>
29 #include "gnunet_util_lib.h"
30
31 /**
32  * Error page to display if submitted GNS key is invalid.
33  */
34 #define INVALID_GNSKEY \
35   "<html><head><title>Error</title><body>Invalid GNS public key given.</body></html>"
36
37 /**
38  * Error page to display on 404.
39  */
40 #define NOT_FOUND \
41   "<html><head><title>Error</title><body>404 not found</body></html>"
42
43 /**
44  * Handle to the HTTP server as provided by libmicrohttpd
45  */
46 static struct MHD_Daemon *daemon_handle;
47
48 /**
49  * Our configuration.
50  */
51 static const struct GNUNET_CONFIGURATION_Handle *cfg;
52
53 /**
54  * Our primary task for the HTTPD.
55  */
56 static struct GNUNET_SCHEDULER_Task *http_task;
57
58 /**
59  * Our main website.
60  */
61 static struct MHD_Response *main_response;
62
63 /**
64  * Error: invalid gns key.
65  */
66 static struct MHD_Response *invalid_gnskey_response;
67
68 /**
69  * Error: 404
70  */
71 static struct MHD_Response *not_found_response;
72
73 /**
74  * Absolute name of the 'gns-bcd.tex' file.
75  */
76 static char *resfile;
77
78 /**
79  * Port number.
80  */
81 static uint16_t port = 8888;
82
83
84 struct Entry
85 {
86   const char *formname;
87   const char *texname;
88 };
89
90
91 /**
92  * Main request handler.
93  */
94 static int
95 access_handler_callback (void *cls,
96                          struct MHD_Connection *connection,
97                          const char *url,
98                          const char *method,
99                          const char *version,
100                          const char *upload_data,
101                          size_t *upload_data_size,
102                          void **con_cls)
103 {
104   static int dummy;
105   static const struct Entry map[] = { { "prefix", "prefix" },
106                                       { "name", "name" },
107                                       { "suffix", "suffix" },
108                                       { "street", "street" },
109                                       { "city", "city" },
110                                       { "phone", "phone" },
111                                       { "fax", "fax" },
112                                       { "email", "email" },
113                                       { "homepage", "homepage" },
114                                       { "orga", "orga" },
115                                       { "departmenti18n", "departmentde" },
116                                       { "departmenten", "departmenten" },
117                                       { "subdepartmenti18n",
118                                         "subdepartmentde" },
119                                       { "subdepartmenten", "subdepartmenten" },
120                                       { "jobtitlei18n", "jobtitlegerman" },
121                                       { "jobtitleen", "jobtitleenglish" },
122                                       { "subdepartmenten", "subdepartmenten" },
123                                       { NULL, NULL } };
124
125   (void) cls;
126   (void) version;
127   (void) upload_data;
128   (void) upload_data_size;
129   if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
130   {
131     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
132                 _ ("Refusing `%s' request to HTTP server\n"),
133                 method);
134     return MHD_NO;
135   }
136   if (NULL == *con_cls)
137   {
138     (*con_cls) = &dummy;
139     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending 100 CONTINUE reply\n");
140     return MHD_YES;   /* send 100 continue */
141   }
142   if (0 == strcasecmp (url, "/"))
143     return MHD_queue_response (connection, MHD_HTTP_OK, main_response);
144   if (0 == strcasecmp (url, "/submit.pdf"))
145   {
146     unsigned int i;
147     char *p;
148     char *tmp;
149     char *deffile;
150     struct GNUNET_CRYPTO_EcdsaPublicKey pub;
151     size_t slen;
152     FILE *f;
153     struct stat st;
154     struct MHD_Response *response;
155     int fd;
156     int ret;
157
158     const char *gpg_fp = MHD_lookup_connection_value (connection,
159                                                       MHD_GET_ARGUMENT_KIND,
160                                                       "gpgfingerprint");
161     const char *gns_nick = MHD_lookup_connection_value (connection,
162                                                         MHD_GET_ARGUMENT_KIND,
163                                                         "gnsnick");
164     const char *gnskey =
165       MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "gnskey");
166     if ((NULL == gnskey) ||
167         (GNUNET_OK !=
168          GNUNET_CRYPTO_ecdsa_public_key_from_string (gnskey,
169                                                      strlen (gnskey),
170                                                      &pub)))
171     {
172       return MHD_queue_response (connection,
173                                  MHD_HTTP_OK,
174                                  invalid_gnskey_response);
175     }
176     tmp = GNUNET_DISK_mkdtemp (gnskey);
177     if (NULL == tmp)
178     {
179       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "mktemp", gnskey);
180       return MHD_NO;
181     }
182     GNUNET_asprintf (&deffile, "%s%s%s", tmp, DIR_SEPARATOR_STR, "def.tex");
183     f = fopen (deffile, "w");
184     if (NULL == f)
185     {
186       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", deffile);
187       GNUNET_free (deffile);
188       GNUNET_DISK_directory_remove (tmp);
189       GNUNET_free (tmp);
190       return MHD_NO;
191     }
192     for (i = 0; NULL != map[i].formname; i++)
193     {
194       const char *val = MHD_lookup_connection_value (connection,
195                                                      MHD_GET_ARGUMENT_KIND,
196                                                      map[i].formname);
197       if (NULL != val)
198         fprintf (f, "\\def\\%s{%s}\n", map[i].texname, val);
199       else
200         fprintf (f, "\\def\\%s{}\n", map[i].texname);
201     }
202     if (NULL != gpg_fp)
203     {
204       char *gpg1;
205       char *gpg2;
206
207       slen = strlen (gpg_fp);
208       gpg1 = GNUNET_strndup (gpg_fp, slen / 2);
209       gpg2 = GNUNET_strdup (&gpg_fp[slen / 2]);
210       fprintf (f, "\\def\\gpglineone{%s}\n\\def\\gpglinetwo{%s}\n", gpg1, gpg2);
211       GNUNET_free (gpg2);
212       GNUNET_free (gpg1);
213     }
214     fprintf (f,
215              "\\def\\gns{%s/%s}\n",
216              gnskey,
217              (NULL == gns_nick) ? "" : gns_nick);
218     fclose (f);
219     GNUNET_asprintf (
220       &p,
221       "cd %s; cp %s gns-bcd.tex | pdflatex --enable-write18 gns-bcd.tex > /dev/null 2> /dev/null",
222       tmp,
223       resfile);
224     GNUNET_free (deffile);
225     ret = system (p);
226     if (WIFSIGNALED (ret) || (0 != WEXITSTATUS (ret)))
227       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "system", p);
228     GNUNET_asprintf (&deffile, "%s%s%s", tmp, DIR_SEPARATOR_STR, "gns-bcd.pdf");
229     fd = open (deffile, O_RDONLY);
230     if (-1 == fd)
231     {
232       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", deffile);
233       GNUNET_free (deffile);
234       GNUNET_free (p);
235       GNUNET_DISK_directory_remove (tmp);
236       GNUNET_free (tmp);
237       return MHD_NO;
238     }
239     GNUNET_break (0 == stat (deffile, &st));
240     if (NULL ==
241         (response = MHD_create_response_from_fd ((size_t) st.st_size, fd)))
242     {
243       GNUNET_break (0);
244       GNUNET_break (0 == close (fd));
245       GNUNET_free (deffile);
246       GNUNET_free (p);
247       GNUNET_DISK_directory_remove (tmp);
248       GNUNET_free (tmp);
249       return MHD_NO;
250     }
251     (void) MHD_add_response_header (response,
252                                     MHD_HTTP_HEADER_CONTENT_TYPE,
253                                     "application/pdf");
254     ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
255     MHD_destroy_response (response);
256     GNUNET_free (deffile);
257     GNUNET_free (p);
258     GNUNET_DISK_directory_remove (tmp);
259     GNUNET_free (tmp);
260     return ret;
261   }
262   return MHD_queue_response (connection,
263                              MHD_HTTP_NOT_FOUND,
264                              not_found_response);
265 }
266
267
268 /**
269  * Function that queries MHD's select sets and
270  * starts the task waiting for them.
271  */
272 static struct GNUNET_SCHEDULER_Task *
273 prepare_daemon (struct MHD_Daemon *daemon_handle);
274
275
276 /**
277  * Call MHD to process pending requests and then go back
278  * and schedule the next run.
279  */
280 static void
281 run_daemon (void *cls)
282 {
283   struct MHD_Daemon *daemon_handle = cls;
284
285   http_task = NULL;
286   GNUNET_assert (MHD_YES == MHD_run (daemon_handle));
287   http_task = prepare_daemon (daemon_handle);
288 }
289
290
291 /**
292  * Function that queries MHD's select sets and
293  * starts the task waiting for them.
294  */
295 static struct GNUNET_SCHEDULER_Task *
296 prepare_daemon (struct MHD_Daemon *daemon_handle)
297 {
298   struct GNUNET_SCHEDULER_Task *ret;
299   fd_set rs;
300   fd_set ws;
301   fd_set es;
302   struct GNUNET_NETWORK_FDSet *wrs;
303   struct GNUNET_NETWORK_FDSet *wws;
304   int max;
305   MHD_UNSIGNED_LONG_LONG timeout;
306   int haveto;
307   struct GNUNET_TIME_Relative tv;
308
309   FD_ZERO (&rs);
310   FD_ZERO (&ws);
311   FD_ZERO (&es);
312   wrs = GNUNET_NETWORK_fdset_create ();
313   wws = GNUNET_NETWORK_fdset_create ();
314   max = -1;
315   GNUNET_assert (MHD_YES == MHD_get_fdset (daemon_handle, &rs, &ws, &es, &max));
316   haveto = MHD_get_timeout (daemon_handle, &timeout);
317   if (haveto == MHD_YES)
318     tv.rel_value_us = (uint64_t) timeout * 1000LL;
319   else
320     tv = GNUNET_TIME_UNIT_FOREVER_REL;
321   GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max + 1);
322   GNUNET_NETWORK_fdset_copy_native (wws, &ws, max + 1);
323   ret = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
324                                      tv,
325                                      wrs,
326                                      wws,
327                                      &run_daemon,
328                                      daemon_handle);
329   GNUNET_NETWORK_fdset_destroy (wrs);
330   GNUNET_NETWORK_fdset_destroy (wws);
331   return ret;
332 }
333
334
335 /**
336  * Start server offering our hostlist.
337  *
338  * @return #GNUNET_OK on success
339  */
340 static int
341 server_start ()
342 {
343   if (0 == port)
344   {
345     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
346                 _ ("Invalid port number %u.  Exiting.\n"),
347                 port);
348     return GNUNET_SYSERR;
349   }
350   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
351               _ ("Businesscard HTTP server starts on %u\n"),
352               port);
353   daemon_handle = MHD_start_daemon (MHD_USE_DUAL_STACK | MHD_USE_DEBUG,
354                                     port,
355                                     NULL /* accept_policy_callback */,
356                                     NULL,
357                                     &access_handler_callback,
358                                     NULL,
359                                     MHD_OPTION_CONNECTION_LIMIT,
360                                     (unsigned int) 512,
361                                     MHD_OPTION_PER_IP_CONNECTION_LIMIT,
362                                     (unsigned int) 2,
363                                     MHD_OPTION_CONNECTION_TIMEOUT,
364                                     (unsigned int) 60,
365                                     MHD_OPTION_CONNECTION_MEMORY_LIMIT,
366                                     (size_t) (16 * 1024),
367                                     MHD_OPTION_END);
368   if (NULL == daemon_handle)
369   {
370     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
371                 _ ("Could not start businesscard HTTP server on port %u\n"),
372                 (unsigned int) port);
373     return GNUNET_SYSERR;
374   }
375   http_task = prepare_daemon (daemon_handle);
376   return GNUNET_OK;
377 }
378
379
380 /**
381  * Stop HTTP server.
382  */
383 static void
384 server_stop (void *cls)
385 {
386   (void) cls;
387   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "HTTP server shutdown\n");
388   if (NULL != http_task)
389   {
390     GNUNET_SCHEDULER_cancel (http_task);
391     http_task = NULL;
392   }
393   if (NULL != daemon_handle)
394   {
395     MHD_stop_daemon (daemon_handle);
396     daemon_handle = NULL;
397   }
398   if (NULL != main_response)
399   {
400     MHD_destroy_response (main_response);
401     main_response = NULL;
402   }
403   if (NULL != invalid_gnskey_response)
404   {
405     MHD_destroy_response (invalid_gnskey_response);
406     invalid_gnskey_response = NULL;
407   }
408   if (NULL != not_found_response)
409   {
410     MHD_destroy_response (not_found_response);
411     not_found_response = NULL;
412   }
413   if (NULL != resfile)
414   {
415     GNUNET_free (resfile);
416     resfile = NULL;
417   }
418 }
419
420
421 /**
422  * Main function that will be run.
423  *
424  * @param cls closure
425  * @param args remaining command-line arguments
426  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
427  * @param c configuration
428  */
429 static void
430 run (void *cls,
431      char *const *args,
432      const char *cfgfile,
433      const struct GNUNET_CONFIGURATION_Handle *c)
434 {
435   struct stat st;
436   char *dir;
437   char *fn;
438   int fd;
439
440   (void) cls;
441   (void) args;
442   (void) cfgfile;
443   cfg = c;
444   dir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
445   GNUNET_assert (NULL != dir);
446   GNUNET_asprintf (&fn, "%s%s%s", dir, DIR_SEPARATOR_STR, "gns-bcd.html");
447   GNUNET_asprintf (&resfile, "%s%s%s", dir, DIR_SEPARATOR_STR, "gns-bcd.tex");
448   GNUNET_free (dir);
449   fd = open (fn, O_RDONLY);
450   if (-1 == fd)
451   {
452     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", fn);
453     GNUNET_free (fn);
454     return;
455   }
456   if (0 != stat (fn, &st))
457   {
458     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", fn);
459     GNUNET_free (fn);
460     GNUNET_break (0 == close (fd));
461     return;
462   }
463   GNUNET_free (fn);
464   if (NULL ==
465       (main_response = MHD_create_response_from_fd ((size_t) st.st_size, fd)))
466   {
467     GNUNET_break (0);
468     GNUNET_break (0 == close (fd));
469     return;
470   }
471   (void) MHD_add_response_header (main_response,
472                                   MHD_HTTP_HEADER_CONTENT_TYPE,
473                                   "text/html");
474   invalid_gnskey_response =
475     MHD_create_response_from_buffer (strlen (INVALID_GNSKEY),
476                                      INVALID_GNSKEY,
477                                      MHD_RESPMEM_PERSISTENT);
478   (void) MHD_add_response_header (invalid_gnskey_response,
479                                   MHD_HTTP_HEADER_CONTENT_TYPE,
480                                   "text/html");
481   not_found_response = MHD_create_response_from_buffer (strlen (NOT_FOUND),
482                                                         NOT_FOUND,
483                                                         MHD_RESPMEM_PERSISTENT);
484   (void) MHD_add_response_header (not_found_response,
485                                   MHD_HTTP_HEADER_CONTENT_TYPE,
486                                   "text/html");
487   if (GNUNET_OK != server_start ())
488     return;
489   GNUNET_SCHEDULER_add_shutdown (&server_stop, NULL);
490 }
491
492
493 /**
494  * The main function for gnunet-gns.
495  *
496  * @param argc number of arguments from the command line
497  * @param argv command line arguments
498  * @return 0 ok, 1 on error
499  */
500 int
501 main (int argc, char *const *argv)
502 {
503   struct GNUNET_GETOPT_CommandLineOption options[] = {
504     GNUNET_GETOPT_option_uint16 ('p',
505                                  "port",
506                                  "PORT",
507                                  gettext_noop (
508                                    "Run HTTP serve on port PORT (default is 8888)"),
509                                  &port),
510     GNUNET_GETOPT_OPTION_END
511   };
512   int ret;
513
514   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
515     return 2;
516   GNUNET_log_setup ("gnunet-bcd", "WARNING", NULL);
517   ret = (GNUNET_OK ==
518          GNUNET_PROGRAM_run (argc,
519                              argv,
520                              "gnunet-bcd",
521                              _ ("GNUnet HTTP server to create business cards"),
522                              options,
523                              &run,
524                              NULL))
525         ? 0
526         : 1;
527   GNUNET_free ((void *) argv);
528   return ret;
529 }
530
531
532 /* end of gnunet-bcd.c */