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