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