-adding single file for testing without autostarts above core level
[oweals/gnunet.git] / src / gns / gnunet-bcd.c
1 /*
2      This file is part of GNUnet.
3      (C) 2013 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      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      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
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 GNUNET_SCHEDULER_TaskIdentifier 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 unsigned int 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, struct MHD_Connection *connection,
94                          const char *url, const char *method,
95                          const char *version, const char *upload_data,
96                          size_t * upload_data_size, void **con_cls)
97 {
98   static int dummy;
99   static const struct Entry map[] = {
100     { "prefix", "prefix" },
101     { "name", "name" },
102     { "suffix", "suffix" },
103     { "street", "street" },
104     { "city", "city" },
105     { "phone", "phone" },
106     { "fax", "fax" },
107     { "email", "email"},
108     { "homepage", "homepage" },
109     { "orga", "orga"},
110     { "departmenti18n", "departmentde"},
111     { "departmenten", "departmenten"},
112     { "subdepartmenti18n", "subdepartmentde"},
113     { "subdepartmenten", "subdepartmenten"},
114     { "jobtitlei18n", "jobtitlegerman"},
115     { "jobtitleen", "jobtitleenglish"},
116     { "subdepartmenten", "subdepartmenten"},
117     { NULL, NULL }
118   };
119
120   if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
121   {
122     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
123                 _("Refusing `%s' request to HTTP server\n"),
124                 method);
125     return MHD_NO;
126   }
127   if (NULL == *con_cls)
128   {
129     (*con_cls) = &dummy;
130     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
131                 "Sending 100 CONTINUE reply\n");
132     return MHD_YES;             /* send 100 continue */
133   }
134   if (0 == strcasecmp (url, "/"))
135     return MHD_queue_response (connection,
136                                MHD_HTTP_OK,
137                                main_response);
138   if (0 == strcasecmp (url, "/submit.pdf"))
139   {
140     unsigned int i;
141     char *p;
142     char *tmp;
143     char *deffile;
144     struct GNUNET_CRYPTO_EcdsaPublicKey pub;
145     size_t slen;
146     FILE *f;
147     struct stat st;
148     struct MHD_Response *response;
149     int fd;
150     int ret;
151
152     const char *gpg_fp = MHD_lookup_connection_value (connection,
153                                                       MHD_GET_ARGUMENT_KIND,
154                                                       "gpgfingerprint");
155     const char *gns_nick = MHD_lookup_connection_value (connection,
156                                                         MHD_GET_ARGUMENT_KIND,
157                                                         "gnsnick");
158     const char *gnskey = MHD_lookup_connection_value (connection,
159                                                       MHD_GET_ARGUMENT_KIND,
160                                                       "gnskey");
161     if ( (NULL == gnskey) ||
162          (GNUNET_OK !=
163           GNUNET_CRYPTO_ecdsa_public_key_from_string (gnskey,
164                                                       strlen (gnskey),
165                                                       &pub)))
166     {
167       return MHD_queue_response (connection,
168                                  MHD_HTTP_OK,
169                                  invalid_gnskey_response);
170     }
171     tmp = GNUNET_DISK_mkdtemp (gnskey);
172     if (NULL == tmp)
173     {
174       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "mktemp", gnskey);
175       return MHD_NO;
176     }
177     GNUNET_asprintf (&deffile,
178                      "%s%s%s",
179                      tmp, DIR_SEPARATOR_STR, "def.tex");
180     f = FOPEN (deffile, "w");
181     if (NULL == f)
182     {
183       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", deffile);
184       GNUNET_free (deffile);
185       GNUNET_DISK_directory_remove (tmp);
186       GNUNET_free (tmp);
187       return MHD_NO;
188     }
189     for (i=0; NULL != map[i].formname; i++)
190     {
191       const char *val =  MHD_lookup_connection_value (connection,
192                                                       MHD_GET_ARGUMENT_KIND,
193                                                       map[i].formname);
194       if (NULL != val)
195         FPRINTF (f,
196                  "\\def\\%s{%s}\n",
197                  map[i].texname, val);
198       else
199         FPRINTF (f,
200                  "\\def\\%s{}\n",
201                  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,
212                "\\def\\gpglineone{%s}\n\\def\\gpglinetwo{%s}\n",
213                gpg1, gpg2);
214       GNUNET_free (gpg2);
215       GNUNET_free (gpg1);
216     }
217     FPRINTF (f,
218              "\\def\\gns{%s/%s}\n",
219              gnskey,
220              (NULL == gns_nick) ? "" : gns_nick);
221     FCLOSE (f);
222     GNUNET_asprintf (&p,
223                      "cd %s; cp %s gns-bcd.tex | pdflatex --enable-write18 gns-bcd.tex > /dev/null 2> /dev/null",
224                      tmp,
225                      resfile);
226     GNUNET_free (deffile);
227     ret = system (p);
228     if (WIFSIGNALED (ret) || (0 != WEXITSTATUS(ret)))
229       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
230                                 "system",
231                                 p);
232     GNUNET_asprintf (&deffile,
233                      "%s%s%s",
234                      tmp, DIR_SEPARATOR_STR, "gns-bcd.pdf");
235     fd = OPEN (deffile, O_RDONLY);
236     if (-1 == fd)
237     {
238       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
239                                 "open",
240                                 deffile);
241       GNUNET_free (deffile);
242       GNUNET_free (p);
243       GNUNET_DISK_directory_remove (tmp);
244       GNUNET_free (tmp);
245       return MHD_NO;
246     }
247     GNUNET_break (0 == STAT (deffile, &st));
248     if (NULL == (response = MHD_create_response_from_fd ((size_t) st.st_size, fd)))
249     {
250       GNUNET_break (0);
251       GNUNET_break (0 == CLOSE (fd));
252       GNUNET_free (deffile);
253       GNUNET_free (p);
254       GNUNET_DISK_directory_remove (tmp);
255       GNUNET_free (tmp);
256       return MHD_NO;
257     }
258     (void) MHD_add_response_header (response,
259                                     MHD_HTTP_HEADER_CONTENT_TYPE,
260                                     "application/pdf");
261     ret = MHD_queue_response (connection,
262                               MHD_HTTP_OK,
263                               response);
264     MHD_destroy_response (response);
265     GNUNET_free (deffile);
266     GNUNET_free (p);
267     GNUNET_DISK_directory_remove (tmp);
268     GNUNET_free (tmp);
269     return ret;
270   }
271   return MHD_queue_response (connection,
272                              MHD_HTTP_NOT_FOUND,
273                              not_found_response);
274 }
275
276
277 /**
278  * Function that queries MHD's select sets and
279  * starts the task waiting for them.
280  */
281 static GNUNET_SCHEDULER_TaskIdentifier
282 prepare_daemon (struct MHD_Daemon *daemon_handle);
283
284
285 /**
286  * Call MHD to process pending requests and then go back
287  * and schedule the next run.
288  */
289 static void
290 run_daemon (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
291 {
292   struct MHD_Daemon *daemon_handle = cls;
293
294   http_task = GNUNET_SCHEDULER_NO_TASK;
295   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
296     return;
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 GNUNET_SCHEDULER_TaskIdentifier
307 prepare_daemon (struct MHD_Daemon *daemon_handle)
308 {
309   GNUNET_SCHEDULER_TaskIdentifier 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) || (port > UINT16_MAX))
353   {
354     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
355                 _("Invalid port number %llu.  Exiting.\n"),
356                 port);
357     return GNUNET_SYSERR;
358   }
359   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
360               _("Businesscard HTTP server starts on %llu\n"),
361               port);
362   daemon_handle = MHD_start_daemon (MHD_USE_DUAL_STACK | MHD_USE_DEBUG,
363                                     (uint16_t) 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 short) 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              const struct GNUNET_SCHEDULER_TaskContext *tc)
389 {
390   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
391               "HTTP server shutdown\n");
392   if (GNUNET_SCHEDULER_NO_TASK != http_task)
393   {
394     GNUNET_SCHEDULER_cancel (http_task);
395     http_task = GNUNET_SCHEDULER_NO_TASK;
396   }
397   if (NULL != daemon_handle)
398   {
399     MHD_stop_daemon (daemon_handle);
400     daemon_handle = NULL;
401   }
402   if (NULL != main_response)
403   {
404     MHD_destroy_response (main_response);
405     main_response = NULL;
406   }
407   if (NULL != invalid_gnskey_response)
408   {
409     MHD_destroy_response (invalid_gnskey_response);
410     invalid_gnskey_response = NULL;
411   }
412   if (NULL != not_found_response)
413   {
414     MHD_destroy_response (not_found_response);
415     not_found_response = NULL;
416   }
417   if (NULL != resfile)
418   {
419     GNUNET_free (resfile);
420     resfile = NULL;
421   }
422 }
423
424
425 /**
426  * Main function that will be run.
427  *
428  * @param cls closure
429  * @param args remaining command-line arguments
430  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
431  * @param c configuration
432  */
433 static void
434 run (void *cls,
435      char *const *args,
436      const char *cfgfile,
437      const struct GNUNET_CONFIGURATION_Handle *c)
438 {
439   struct stat st;
440   char *dir;
441   char *fn;
442   int fd;
443
444   cfg = c;
445   dir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
446   GNUNET_assert (NULL != dir);
447   GNUNET_asprintf (&fn,
448                    "%s%s%s",
449                    dir,
450                    DIR_SEPARATOR_STR,
451                    "gns-bcd.html");
452   GNUNET_asprintf (&resfile,
453                    "%s%s%s",
454                    dir,
455                    DIR_SEPARATOR_STR,
456                    "gns-bcd.tex");
457   GNUNET_free (dir);
458   fd = OPEN (fn, O_RDONLY);
459   if (-1 == fd)
460   {
461     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
462                               "open",
463                               fn);
464     GNUNET_free (fn);
465     return;
466   }
467   if (0 != STAT (fn, &st))
468   {
469     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
470                               "open",
471                               fn);
472     GNUNET_free (fn);
473     CLOSE (fd);
474     return;
475   }
476   GNUNET_free (fn);
477   if (NULL == (main_response = MHD_create_response_from_fd ((size_t) st.st_size, fd)))
478   {
479     GNUNET_break (0);
480     GNUNET_break (0 == CLOSE (fd));
481     return;
482   }
483   (void) MHD_add_response_header (main_response,
484                                   MHD_HTTP_HEADER_CONTENT_TYPE,
485                                   "text/html");
486   invalid_gnskey_response = MHD_create_response_from_buffer (strlen (INVALID_GNSKEY),
487                                                              INVALID_GNSKEY,
488                                                              MHD_RESPMEM_PERSISTENT);
489   (void) MHD_add_response_header (invalid_gnskey_response,
490                                   MHD_HTTP_HEADER_CONTENT_TYPE,
491                                   "text/html");
492   not_found_response = MHD_create_response_from_buffer (strlen (NOT_FOUND),
493                                                         NOT_FOUND,
494                                                         MHD_RESPMEM_PERSISTENT);
495   (void) MHD_add_response_header (not_found_response,
496                                   MHD_HTTP_HEADER_CONTENT_TYPE,
497                                   "text/html");
498   if (GNUNET_OK !=
499       server_start ())
500     return;
501   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
502                                 &server_stop,
503                                 NULL);
504 }
505
506
507 /**
508  * The main function for gnunet-gns.
509  *
510  * @param argc number of arguments from the command line
511  * @param argv command line arguments
512  * @return 0 ok, 1 on error
513  */
514 int
515 main (int argc, char *const *argv)
516 {
517   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
518     {'p', "port", "PORT",
519       gettext_noop ("Run HTTP serve on port PORT (default is 8888)"), 1,
520       &GNUNET_GETOPT_set_uint, &port},
521     GNUNET_GETOPT_OPTION_END
522   };
523   int ret;
524
525   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
526     return 2;
527   GNUNET_log_setup ("gnunet-bcd", "WARNING", NULL);
528   ret =
529       (GNUNET_OK ==
530        GNUNET_PROGRAM_run (argc, argv, "gnunet-bcd",
531                            _("GNUnet HTTP server to create business cards"),
532                            options,
533                            &run, NULL)) ? 0 : 1;
534   GNUNET_free ((void*) argv);
535   return ret;
536 }
537
538
539 /* end of gnunet-bcd.c */