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