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