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