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 == nullptr) {
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 == nullptr) {
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 --ktls Enable ktls.
182 --no-rfc7540-pri
183 Disable RFC7540 priorities.
184 --version Display version information and exit.
185 -h, --help Display this help and exit.
186
187 --
188
189 The <SIZE> argument is an integer and an optional unit (e.g., 10K is
190 10 * 1024). Units are K, M and G (powers of 1024).)"
191 << std::endl;
192 }
193 } // namespace
194
main(int argc,char ** argv)195 int main(int argc, char **argv) {
196 tls::libssl_init();
197
198 #ifndef NOTHREADS
199 tls::LibsslGlobalLock lock;
200 #endif // NOTHREADS
201
202 Config config;
203 bool color = false;
204 auto mime_types_file_set_manually = false;
205
206 while (1) {
207 static int flag = 0;
208 constexpr static option long_options[] = {
209 {"address", required_argument, nullptr, 'a'},
210 {"daemon", no_argument, nullptr, 'D'},
211 {"htdocs", required_argument, nullptr, 'd'},
212 {"help", no_argument, nullptr, 'h'},
213 {"verbose", no_argument, nullptr, 'v'},
214 {"verify-client", no_argument, nullptr, 'V'},
215 {"header-table-size", required_argument, nullptr, 'c'},
216 {"push", required_argument, nullptr, 'p'},
217 {"padding", required_argument, nullptr, 'b'},
218 {"max-concurrent-streams", required_argument, nullptr, 'm'},
219 {"workers", required_argument, nullptr, 'n'},
220 {"error-gzip", no_argument, nullptr, 'e'},
221 {"window-bits", required_argument, nullptr, 'w'},
222 {"connection-window-bits", required_argument, nullptr, 'W'},
223 {"no-tls", no_argument, &flag, 1},
224 {"color", no_argument, &flag, 2},
225 {"version", no_argument, &flag, 3},
226 {"dh-param-file", required_argument, &flag, 4},
227 {"early-response", no_argument, &flag, 5},
228 {"trailer", required_argument, &flag, 6},
229 {"hexdump", no_argument, &flag, 7},
230 {"echo-upload", no_argument, &flag, 8},
231 {"mime-types-file", required_argument, &flag, 9},
232 {"no-content-length", no_argument, &flag, 10},
233 {"encoder-header-table-size", required_argument, &flag, 11},
234 {"ktls", no_argument, &flag, 12},
235 {"no-rfc7540-pri", no_argument, &flag, 13},
236 {nullptr, 0, nullptr, 0}};
237 int option_index = 0;
238 int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:w:W:", long_options,
239 &option_index);
240 if (c == -1) {
241 break;
242 }
243 switch (c) {
244 case 'a':
245 config.address = optarg;
246 break;
247 case 'D':
248 config.daemon = true;
249 break;
250 case 'V':
251 config.verify_client = true;
252 break;
253 case 'b': {
254 auto n = util::parse_uint(optarg);
255 if (n == -1) {
256 std::cerr << "-b: Bad option value: " << optarg << std::endl;
257 exit(EXIT_FAILURE);
258 }
259 config.padding = n;
260 break;
261 }
262 case 'd':
263 config.htdocs = optarg;
264 break;
265 case 'e':
266 config.error_gzip = true;
267 break;
268 case 'm': {
269 // max-concurrent-streams option
270 auto n = util::parse_uint(optarg);
271 if (n == -1) {
272 std::cerr << "-m: invalid argument: " << optarg << std::endl;
273 exit(EXIT_FAILURE);
274 }
275 config.max_concurrent_streams = n;
276 break;
277 }
278 case 'n': {
279 #ifdef NOTHREADS
280 std::cerr << "-n: WARNING: Threading disabled at build time, "
281 << "no threads created." << std::endl;
282 #else
283 auto n = util::parse_uint(optarg);
284 if (n == -1) {
285 std::cerr << "-n: Bad option value: " << optarg << std::endl;
286 exit(EXIT_FAILURE);
287 }
288 config.num_worker = n;
289 #endif // NOTHREADS
290 break;
291 }
292 case 'h':
293 print_help(std::cout);
294 exit(EXIT_SUCCESS);
295 case 'v':
296 config.verbose = true;
297 break;
298 case 'c': {
299 auto n = util::parse_uint_with_unit(optarg);
300 if (n == -1) {
301 std::cerr << "-c: Bad option value: " << optarg << std::endl;
302 exit(EXIT_FAILURE);
303 }
304 if (n > std::numeric_limits<uint32_t>::max()) {
305 std::cerr << "-c: Value too large. It should be less than or equal to "
306 << std::numeric_limits<uint32_t>::max() << std::endl;
307 exit(EXIT_FAILURE);
308 }
309 config.header_table_size = n;
310 break;
311 }
312 case 'p':
313 if (parse_push_config(config, optarg) != 0) {
314 std::cerr << "-p: Bad option value: " << optarg << std::endl;
315 }
316 break;
317 case 'w':
318 case 'W': {
319 auto n = util::parse_uint(optarg);
320 if (n == -1 || n > 30) {
321 std::cerr << "-" << static_cast<char>(c)
322 << ": specify the integer in the range [0, 30], inclusive"
323 << std::endl;
324 exit(EXIT_FAILURE);
325 }
326
327 if (c == 'w') {
328 config.window_bits = n;
329 } else {
330 config.connection_window_bits = n;
331 }
332
333 break;
334 }
335 case '?':
336 util::show_candidates(argv[optind - 1], long_options);
337 exit(EXIT_FAILURE);
338 case 0:
339 switch (flag) {
340 case 1:
341 // no-tls option
342 config.no_tls = true;
343 break;
344 case 2:
345 // color option
346 color = true;
347 break;
348 case 3:
349 // version
350 print_version(std::cout);
351 exit(EXIT_SUCCESS);
352 case 4:
353 // dh-param-file
354 config.dh_param_file = optarg;
355 break;
356 case 5:
357 // early-response
358 config.early_response = true;
359 break;
360 case 6: {
361 // trailer option
362 auto header = optarg;
363 auto value = strchr(optarg, ':');
364 if (!value) {
365 std::cerr << "--trailer: invalid header: " << optarg << std::endl;
366 exit(EXIT_FAILURE);
367 }
368 *value = 0;
369 value++;
370 while (isspace(*value)) {
371 value++;
372 }
373 if (*value == 0) {
374 // This could also be a valid case for suppressing a header
375 // similar to curl
376 std::cerr << "--trailer: invalid header - value missing: " << optarg
377 << std::endl;
378 exit(EXIT_FAILURE);
379 }
380 config.trailer.emplace_back(header, value, false);
381 util::inp_strlower(config.trailer.back().name);
382 break;
383 }
384 case 7:
385 // hexdump option
386 config.hexdump = true;
387 break;
388 case 8:
389 // echo-upload option
390 config.echo_upload = true;
391 break;
392 case 9:
393 // mime-types-file option
394 mime_types_file_set_manually = true;
395 config.mime_types_file = optarg;
396 break;
397 case 10:
398 // no-content-length option
399 config.no_content_length = true;
400 break;
401 case 11: {
402 // encoder-header-table-size option
403 auto n = util::parse_uint_with_unit(optarg);
404 if (n == -1) {
405 std::cerr << "--encoder-header-table-size: Bad option value: "
406 << optarg << std::endl;
407 exit(EXIT_FAILURE);
408 }
409 if (n > std::numeric_limits<uint32_t>::max()) {
410 std::cerr << "--encoder-header-table-size: Value too large. It "
411 "should be less than or equal to "
412 << std::numeric_limits<uint32_t>::max() << std::endl;
413 exit(EXIT_FAILURE);
414 }
415 config.encoder_header_table_size = n;
416 break;
417 }
418 case 12:
419 // tls option
420 config.ktls = true;
421 break;
422 case 13:
423 // no-rfc7540-pri option
424 config.no_rfc7540_pri = true;
425 break;
426 }
427 break;
428 default:
429 break;
430 }
431 }
432 if (argc - optind < (config.no_tls ? 1 : 3)) {
433 print_usage(std::cerr);
434 std::cerr << "Too few arguments" << std::endl;
435 exit(EXIT_FAILURE);
436 }
437
438 {
439 auto portStr = argv[optind++];
440 auto n = util::parse_uint(portStr);
441 if (n == -1 || n > std::numeric_limits<uint16_t>::max()) {
442 std::cerr << "<PORT>: Bad value: " << portStr << std::endl;
443 exit(EXIT_FAILURE);
444 }
445 config.port = n;
446 }
447
448 if (!config.no_tls) {
449 config.private_key_file = argv[optind++];
450 config.cert_file = argv[optind++];
451 }
452
453 if (config.daemon) {
454 if (config.htdocs.empty()) {
455 print_usage(std::cerr);
456 std::cerr << "-d option must be specified when -D is used." << std::endl;
457 exit(EXIT_FAILURE);
458 }
459 #ifdef __sgi
460 if (daemon(0, 0, 0, 0) == -1) {
461 #else
462 if (util::daemonize(0, 0) == -1) {
463 #endif
464 perror("daemon");
465 exit(EXIT_FAILURE);
466 }
467 }
468 if (config.htdocs.empty()) {
469 config.htdocs = "./";
470 }
471
472 if (util::read_mime_types(config.mime_types,
473 config.mime_types_file.c_str()) != 0) {
474 if (mime_types_file_set_manually) {
475 std::cerr << "--mime-types-file: Could not open mime types file: "
476 << config.mime_types_file << std::endl;
477 }
478 }
479
480 auto &trailer_names = config.trailer_names;
481 for (auto &h : config.trailer) {
482 trailer_names += h.name;
483 trailer_names += ", ";
484 }
485 if (trailer_names.size() >= 2) {
486 trailer_names.resize(trailer_names.size() - 2);
487 }
488
489 set_color_output(color || isatty(fileno(stdout)));
490
491 struct sigaction act {};
492 act.sa_handler = SIG_IGN;
493 sigaction(SIGPIPE, &act, nullptr);
494
495 reset_timer();
496
497 HttpServer server(&config);
498 if (server.run() != 0) {
499 exit(EXIT_FAILURE);
500 }
501 return 0;
502 }
503
504 } // namespace nghttp2
505
main(int argc,char ** argv)506 int main(int argc, char **argv) {
507 return nghttp2::run_app(nghttp2::main, argc, argv);
508 }
509