1 /*
2  * nghttp2 - HTTP/2 C Library
3  *
4  * Copyright (c) 2012 Tatsuhiro Tsujikawa
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25 #include "nghttp2_config.h"
26 
27 #ifdef __sgi
28 #  define daemon _daemonize
29 #endif
30 
31 #ifdef HAVE_UNISTD_H
32 #  include <unistd.h>
33 #endif // HAVE_UNISTD_H
34 #include <signal.h>
35 #include <getopt.h>
36 
37 #include <cstdlib>
38 #include <cstring>
39 #include <cassert>
40 #include <string>
41 #include <iostream>
42 #include <string>
43 
44 #include <openssl/ssl.h>
45 #include <openssl/err.h>
46 #include <nghttp2/nghttp2.h>
47 
48 #include "app_helper.h"
49 #include "HttpServer.h"
50 #include "util.h"
51 #include "tls.h"
52 
53 namespace nghttp2 {
54 
55 namespace {
parse_push_config(Config & config,const char * optarg)56 int parse_push_config(Config &config, const char *optarg) {
57   const char *eq = strchr(optarg, '=');
58   if (eq == NULL) {
59     return -1;
60   }
61   auto &paths = config.push[std::string(optarg, eq)];
62   auto optarg_end = optarg + strlen(optarg);
63   auto i = eq + 1;
64   for (;;) {
65     const char *j = strchr(i, ',');
66     if (j == NULL) {
67       j = optarg_end;
68     }
69     paths.emplace_back(i, j);
70     if (j == optarg_end) {
71       break;
72     }
73     i = j;
74     ++i;
75   }
76 
77   return 0;
78 }
79 } // namespace
80 
81 namespace {
print_version(std::ostream & out)82 void print_version(std::ostream &out) {
83   out << "nghttpd nghttp2/" NGHTTP2_VERSION << std::endl;
84 }
85 } // namespace
86 
87 namespace {
print_usage(std::ostream & out)88 void print_usage(std::ostream &out) {
89   out << "Usage: nghttpd [OPTION]... <PORT> [<PRIVATE_KEY> <CERT>]\n"
90       << "HTTP/2 server" << std::endl;
91 }
92 } // namespace
93 
94 namespace {
print_help(std::ostream & out)95 void print_help(std::ostream &out) {
96   Config config;
97   print_usage(out);
98   out << R"(
99   <PORT>      Specify listening port number.
100   <PRIVATE_KEY>
101               Set  path  to  server's private  key.   Required  unless
102               --no-tls is specified.
103   <CERT>      Set  path  to  server's  certificate.   Required  unless
104               --no-tls is specified.
105 Options:
106   -a, --address=<ADDR>
107               The address to bind to.  If not specified the default IP
108               address determined by getaddrinfo is used.
109   -D, --daemon
110               Run in a background.  If -D is used, the current working
111               directory is  changed to '/'.  Therefore  if this option
112               is used, -d option must be specified.
113   -V, --verify-client
114               The server  sends a client certificate  request.  If the
115               client did  not return  a certificate, the  handshake is
116               terminated.   Currently,  this  option just  requests  a
117               client certificate and does not verify it.
118   -d, --htdocs=<PATH>
119               Specify document root.  If this option is not specified,
120               the document root is the current working directory.
121   -v, --verbose
122               Print debug information  such as reception/ transmission
123               of frames and name/value pairs.
124   --no-tls    Disable SSL/TLS.
125   -c, --header-table-size=<SIZE>
126               Specify decoder header table size.
127   --encoder-header-table-size=<SIZE>
128               Specify encoder header table size.  The decoder (client)
129               specifies  the maximum  dynamic table  size it  accepts.
130               Then the negotiated dynamic table size is the minimum of
131               this option value and the value which client specified.
132   --color     Force colored log output.
133   -p, --push=<PATH>=<PUSH_PATH,...>
134               Push  resources <PUSH_PATH>s  when <PATH>  is requested.
135               This option  can be used repeatedly  to specify multiple
136               push  configurations.    <PATH>  and   <PUSH_PATH>s  are
137               relative  to   document  root.   See   --htdocs  option.
138               Example: -p/=/foo.png -p/doc=/bar.css
139   -b, --padding=<N>
140               Add at  most <N>  bytes to a  frame payload  as padding.
141               Specify 0 to disable padding.
142   -m, --max-concurrent-streams=<N>
143               Set the maximum number of  the concurrent streams in one
144               HTTP/2 session.
145               Default: )"
146       << config.max_concurrent_streams << R"(
147   -n, --workers=<N>
148               Set the number of worker threads.
149               Default: 1
150   -e, --error-gzip
151               Make error response gzipped.
152   -w, --window-bits=<N>
153               Sets the stream level initial window size to 2**<N>-1.
154   -W, --connection-window-bits=<N>
155               Sets  the  connection  level   initial  window  size  to
156               2**<N>-1.
157   --dh-param-file=<PATH>
158               Path to file that contains  DH parameters in PEM format.
159               Without  this   option,  DHE   cipher  suites   are  not
160               available.
161   --early-response
162               Start sending response when request HEADERS is received,
163               rather than complete request is received.
164   --trailer=<HEADER>
165               Add a trailer  header to a response.   <HEADER> must not
166               include pseudo header field  (header field name starting
167               with ':').  The  trailer is sent only if  a response has
168               body part.  Example: --trailer 'foo: bar'.
169   --hexdump   Display the  incoming traffic in  hexadecimal (Canonical
170               hex+ASCII display).  If SSL/TLS  is used, decrypted data
171               are used.
172   --echo-upload
173               Send back uploaded content if method is POST or PUT.
174   --mime-types-file=<PATH>
175               Path  to file  that contains  MIME media  types and  the
176               extensions that represent them.
177               Default: )"
178       << config.mime_types_file << R"(
179   --no-content-length
180               Don't send content-length header field.
181   --version   Display version information and exit.
182   -h, --help  Display this help and exit.
183 
184 --
185 
186   The <SIZE> argument is an integer and an optional unit (e.g., 10K is
187   10 * 1024).  Units are K, M and G (powers of 1024).)"
188       << std::endl;
189 }
190 } // namespace
191 
main(int argc,char ** argv)192 int main(int argc, char **argv) {
193   tls::libssl_init();
194 
195 #ifndef NOTHREADS
196   tls::LibsslGlobalLock lock;
197 #endif // NOTHREADS
198 
199   Config config;
200   bool color = false;
201   auto mime_types_file_set_manually = false;
202 
203   while (1) {
204     static int flag = 0;
205     constexpr static option long_options[] = {
206         {"address", required_argument, nullptr, 'a'},
207         {"daemon", no_argument, nullptr, 'D'},
208         {"htdocs", required_argument, nullptr, 'd'},
209         {"help", no_argument, nullptr, 'h'},
210         {"verbose", no_argument, nullptr, 'v'},
211         {"verify-client", no_argument, nullptr, 'V'},
212         {"header-table-size", required_argument, nullptr, 'c'},
213         {"push", required_argument, nullptr, 'p'},
214         {"padding", required_argument, nullptr, 'b'},
215         {"max-concurrent-streams", required_argument, nullptr, 'm'},
216         {"workers", required_argument, nullptr, 'n'},
217         {"error-gzip", no_argument, nullptr, 'e'},
218         {"window-bits", required_argument, nullptr, 'w'},
219         {"connection-window-bits", required_argument, nullptr, 'W'},
220         {"no-tls", no_argument, &flag, 1},
221         {"color", no_argument, &flag, 2},
222         {"version", no_argument, &flag, 3},
223         {"dh-param-file", required_argument, &flag, 4},
224         {"early-response", no_argument, &flag, 5},
225         {"trailer", required_argument, &flag, 6},
226         {"hexdump", no_argument, &flag, 7},
227         {"echo-upload", no_argument, &flag, 8},
228         {"mime-types-file", required_argument, &flag, 9},
229         {"no-content-length", no_argument, &flag, 10},
230         {"encoder-header-table-size", required_argument, &flag, 11},
231         {nullptr, 0, nullptr, 0}};
232     int option_index = 0;
233     int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:w:W:", long_options,
234                         &option_index);
235     if (c == -1) {
236       break;
237     }
238     switch (c) {
239     case 'a':
240       config.address = optarg;
241       break;
242     case 'D':
243       config.daemon = true;
244       break;
245     case 'V':
246       config.verify_client = true;
247       break;
248     case 'b':
249       config.padding = strtol(optarg, nullptr, 10);
250       break;
251     case 'd':
252       config.htdocs = optarg;
253       break;
254     case 'e':
255       config.error_gzip = true;
256       break;
257     case 'm': {
258       // max-concurrent-streams option
259       auto n = util::parse_uint(optarg);
260       if (n == -1) {
261         std::cerr << "-m: invalid argument: " << optarg << std::endl;
262         exit(EXIT_FAILURE);
263       }
264       config.max_concurrent_streams = n;
265       break;
266     }
267     case 'n': {
268 #ifdef NOTHREADS
269       std::cerr << "-n: WARNING: Threading disabled at build time, "
270                 << "no threads created." << std::endl;
271 #else
272       char *end;
273       errno = 0;
274       config.num_worker = strtoul(optarg, &end, 10);
275       if (errno == ERANGE || *end != '\0' || config.num_worker == 0) {
276         std::cerr << "-n: Bad option value: " << optarg << std::endl;
277         exit(EXIT_FAILURE);
278       }
279 #endif // NOTHREADS
280       break;
281     }
282     case 'h':
283       print_help(std::cout);
284       exit(EXIT_SUCCESS);
285     case 'v':
286       config.verbose = true;
287       break;
288     case 'c': {
289       auto n = util::parse_uint_with_unit(optarg);
290       if (n == -1) {
291         std::cerr << "-c: Bad option value: " << optarg << std::endl;
292         exit(EXIT_FAILURE);
293       }
294       if (n > std::numeric_limits<uint32_t>::max()) {
295         std::cerr << "-c: Value too large.  It should be less than or equal to "
296                   << std::numeric_limits<uint32_t>::max() << std::endl;
297         exit(EXIT_FAILURE);
298       }
299       config.header_table_size = n;
300       break;
301     }
302     case 'p':
303       if (parse_push_config(config, optarg) != 0) {
304         std::cerr << "-p: Bad option value: " << optarg << std::endl;
305       }
306       break;
307     case 'w':
308     case 'W': {
309       char *endptr;
310       errno = 0;
311       auto n = strtoul(optarg, &endptr, 10);
312       if (errno != 0 || *endptr != '\0' || n >= 31) {
313         std::cerr << "-" << static_cast<char>(c)
314                   << ": specify the integer in the range [0, 30], inclusive"
315                   << std::endl;
316         exit(EXIT_FAILURE);
317       }
318 
319       if (c == 'w') {
320         config.window_bits = n;
321       } else {
322         config.connection_window_bits = n;
323       }
324 
325       break;
326     }
327     case '?':
328       util::show_candidates(argv[optind - 1], long_options);
329       exit(EXIT_FAILURE);
330     case 0:
331       switch (flag) {
332       case 1:
333         // no-tls option
334         config.no_tls = true;
335         break;
336       case 2:
337         // color option
338         color = true;
339         break;
340       case 3:
341         // version
342         print_version(std::cout);
343         exit(EXIT_SUCCESS);
344       case 4:
345         // dh-param-file
346         config.dh_param_file = optarg;
347         break;
348       case 5:
349         // early-response
350         config.early_response = true;
351         break;
352       case 6: {
353         // trailer option
354         auto header = optarg;
355         auto value = strchr(optarg, ':');
356         if (!value) {
357           std::cerr << "--trailer: invalid header: " << optarg << std::endl;
358           exit(EXIT_FAILURE);
359         }
360         *value = 0;
361         value++;
362         while (isspace(*value)) {
363           value++;
364         }
365         if (*value == 0) {
366           // This could also be a valid case for suppressing a header
367           // similar to curl
368           std::cerr << "--trailer: invalid header - value missing: " << optarg
369                     << std::endl;
370           exit(EXIT_FAILURE);
371         }
372         config.trailer.emplace_back(header, value, false);
373         util::inp_strlower(config.trailer.back().name);
374         break;
375       }
376       case 7:
377         // hexdump option
378         config.hexdump = true;
379         break;
380       case 8:
381         // echo-upload option
382         config.echo_upload = true;
383         break;
384       case 9:
385         // mime-types-file option
386         mime_types_file_set_manually = true;
387         config.mime_types_file = optarg;
388         break;
389       case 10:
390         // no-content-length option
391         config.no_content_length = true;
392         break;
393       case 11: {
394         // encoder-header-table-size option
395         auto n = util::parse_uint_with_unit(optarg);
396         if (n == -1) {
397           std::cerr << "--encoder-header-table-size: Bad option value: "
398                     << optarg << std::endl;
399           exit(EXIT_FAILURE);
400         }
401         if (n > std::numeric_limits<uint32_t>::max()) {
402           std::cerr << "--encoder-header-table-size: Value too large.  It "
403                        "should be less than or equal to "
404                     << std::numeric_limits<uint32_t>::max() << std::endl;
405           exit(EXIT_FAILURE);
406         }
407         config.encoder_header_table_size = n;
408         break;
409       }
410       }
411       break;
412     default:
413       break;
414     }
415   }
416   if (argc - optind < (config.no_tls ? 1 : 3)) {
417     print_usage(std::cerr);
418     std::cerr << "Too few arguments" << std::endl;
419     exit(EXIT_FAILURE);
420   }
421 
422   config.port = strtol(argv[optind++], nullptr, 10);
423 
424   if (!config.no_tls) {
425     config.private_key_file = argv[optind++];
426     config.cert_file = argv[optind++];
427   }
428 
429   if (config.daemon) {
430     if (config.htdocs.empty()) {
431       print_usage(std::cerr);
432       std::cerr << "-d option must be specified when -D is used." << std::endl;
433       exit(EXIT_FAILURE);
434     }
435 #ifdef __sgi
436     if (daemon(0, 0, 0, 0) == -1) {
437 #else
438     if (util::daemonize(0, 0) == -1) {
439 #endif
440       perror("daemon");
441       exit(EXIT_FAILURE);
442     }
443   }
444   if (config.htdocs.empty()) {
445     config.htdocs = "./";
446   }
447 
448   if (util::read_mime_types(config.mime_types,
449                             config.mime_types_file.c_str()) != 0) {
450     if (mime_types_file_set_manually) {
451       std::cerr << "--mime-types-file: Could not open mime types file: "
452                 << config.mime_types_file << std::endl;
453     }
454   }
455 
456   auto &trailer_names = config.trailer_names;
457   for (auto &h : config.trailer) {
458     trailer_names += h.name;
459     trailer_names += ", ";
460   }
461   if (trailer_names.size() >= 2) {
462     trailer_names.resize(trailer_names.size() - 2);
463   }
464 
465   set_color_output(color || isatty(fileno(stdout)));
466 
467   struct sigaction act {};
468   act.sa_handler = SIG_IGN;
469   sigaction(SIGPIPE, &act, nullptr);
470 
471   reset_timer();
472 
473   HttpServer server(&config);
474   if (server.run() != 0) {
475     exit(EXIT_FAILURE);
476   }
477   return 0;
478 }
479 
480 } // namespace nghttp2
481 
main(int argc,char ** argv)482 int main(int argc, char **argv) {
483   return nghttp2::run_app(nghttp2::main, argc, argv);
484 }
485