• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * nghttp2 - HTTP/2 C Library
3  *
4  * Copyright (c) 2013 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 #ifdef __sgi
26 #  define errx(exitcode, format, args...)                                      \
27     {                                                                          \
28       warnx(format, ##args);                                                   \
29       exit(exitcode);                                                          \
30     }
31 #  define warn(format, args...) warnx(format ": %s", ##args, strerror(errno))
32 #  define warnx(format, args...) fprintf(stderr, format "\n", ##args)
33 #endif
34 
35 #ifdef HAVE_CONFIG_H
36 #  include <config.h>
37 #endif /* HAVE_CONFIG_H */
38 
39 #include <sys/types.h>
40 #ifdef HAVE_SYS_SOCKET_H
41 #  include <sys/socket.h>
42 #endif /* HAVE_SYS_SOCKET_H */
43 #ifdef HAVE_NETDB_H
44 #  include <netdb.h>
45 #endif /* HAVE_NETDB_H */
46 #include <signal.h>
47 #ifdef HAVE_UNISTD_H
48 #  include <unistd.h>
49 #endif /* HAVE_UNISTD_H */
50 #include <sys/stat.h>
51 #ifdef HAVE_FCNTL_H
52 #  include <fcntl.h>
53 #endif /* HAVE_FCNTL_H */
54 #include <ctype.h>
55 #ifdef HAVE_NETINET_IN_H
56 #  include <netinet/in.h>
57 #endif /* HAVE_NETINET_IN_H */
58 #include <netinet/tcp.h>
59 #ifndef __sgi
60 #  include <err.h>
61 #endif
62 #include <string.h>
63 #include <errno.h>
64 
65 #include <openssl/ssl.h>
66 #include <openssl/err.h>
67 #include <openssl/conf.h>
68 
69 #include <event.h>
70 #include <event2/event.h>
71 #include <event2/bufferevent_ssl.h>
72 #include <event2/listener.h>
73 
74 #define NGHTTP2_NO_SSIZE_T
75 #include <nghttp2/nghttp2.h>
76 
77 #define OUTPUT_WOULDBLOCK_THRESHOLD (1 << 16)
78 
79 #define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
80 
81 #define MAKE_NV(NAME, VALUE)                                                   \
82   {                                                                            \
83     (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1,    \
84         NGHTTP2_NV_FLAG_NONE                                                   \
85   }
86 
87 struct app_context;
88 typedef struct app_context app_context;
89 
90 typedef struct http2_stream_data {
91   struct http2_stream_data *prev, *next;
92   char *request_path;
93   int32_t stream_id;
94   int fd;
95 } http2_stream_data;
96 
97 typedef struct http2_session_data {
98   struct http2_stream_data root;
99   struct bufferevent *bev;
100   app_context *app_ctx;
101   nghttp2_session *session;
102   char *client_addr;
103 } http2_session_data;
104 
105 struct app_context {
106   SSL_CTX *ssl_ctx;
107   struct event_base *evbase;
108 };
109 
alpn_select_proto_cb(SSL * ssl,const unsigned char ** out,unsigned char * outlen,const unsigned char * in,unsigned int inlen,void * arg)110 static int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
111                                 unsigned char *outlen, const unsigned char *in,
112                                 unsigned int inlen, void *arg) {
113   int rv;
114   (void)ssl;
115   (void)arg;
116 
117   rv = nghttp2_select_alpn(out, outlen, in, inlen);
118 
119   if (rv != 1) {
120     return SSL_TLSEXT_ERR_NOACK;
121   }
122 
123   return SSL_TLSEXT_ERR_OK;
124 }
125 
126 /* Create SSL_CTX. */
create_ssl_ctx(const char * key_file,const char * cert_file)127 static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) {
128   SSL_CTX *ssl_ctx;
129 
130   ssl_ctx = SSL_CTX_new(TLS_server_method());
131   if (!ssl_ctx) {
132     errx(1, "Could not create SSL/TLS context: %s",
133          ERR_error_string(ERR_get_error(), NULL));
134   }
135   SSL_CTX_set_options(ssl_ctx,
136                       SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
137                           SSL_OP_NO_COMPRESSION |
138                           SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
139 #if OPENSSL_VERSION_NUMBER >= 0x30000000L
140   if (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) {
141     errx(1, "SSL_CTX_set1_curves_list failed: %s",
142          ERR_error_string(ERR_get_error(), NULL));
143   }
144 #else  /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
145   {
146     EC_KEY *ecdh;
147     ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
148     if (!ecdh) {
149       errx(1, "EC_KEY_new_by_curv_name failed: %s",
150            ERR_error_string(ERR_get_error(), NULL));
151     }
152     SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh);
153     EC_KEY_free(ecdh);
154   }
155 #endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
156 
157   if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM) != 1) {
158     errx(1, "Could not read private key file %s", key_file);
159   }
160   if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) {
161     errx(1, "Could not read certificate file %s", cert_file);
162   }
163 
164   SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL);
165 
166   return ssl_ctx;
167 }
168 
169 /* Create SSL object */
create_ssl(SSL_CTX * ssl_ctx)170 static SSL *create_ssl(SSL_CTX *ssl_ctx) {
171   SSL *ssl;
172   ssl = SSL_new(ssl_ctx);
173   if (!ssl) {
174     errx(1, "Could not create SSL/TLS session object: %s",
175          ERR_error_string(ERR_get_error(), NULL));
176   }
177   return ssl;
178 }
179 
add_stream(http2_session_data * session_data,http2_stream_data * stream_data)180 static void add_stream(http2_session_data *session_data,
181                        http2_stream_data *stream_data) {
182   stream_data->next = session_data->root.next;
183   session_data->root.next = stream_data;
184   stream_data->prev = &session_data->root;
185   if (stream_data->next) {
186     stream_data->next->prev = stream_data;
187   }
188 }
189 
remove_stream(http2_session_data * session_data,http2_stream_data * stream_data)190 static void remove_stream(http2_session_data *session_data,
191                           http2_stream_data *stream_data) {
192   (void)session_data;
193 
194   stream_data->prev->next = stream_data->next;
195   if (stream_data->next) {
196     stream_data->next->prev = stream_data->prev;
197   }
198 }
199 
200 static http2_stream_data *
create_http2_stream_data(http2_session_data * session_data,int32_t stream_id)201 create_http2_stream_data(http2_session_data *session_data, int32_t stream_id) {
202   http2_stream_data *stream_data;
203   stream_data = malloc(sizeof(http2_stream_data));
204   memset(stream_data, 0, sizeof(http2_stream_data));
205   stream_data->stream_id = stream_id;
206   stream_data->fd = -1;
207 
208   add_stream(session_data, stream_data);
209   return stream_data;
210 }
211 
delete_http2_stream_data(http2_stream_data * stream_data)212 static void delete_http2_stream_data(http2_stream_data *stream_data) {
213   if (stream_data->fd != -1) {
214     close(stream_data->fd);
215   }
216   free(stream_data->request_path);
217   free(stream_data);
218 }
219 
create_http2_session_data(app_context * app_ctx,int fd,struct sockaddr * addr,int addrlen)220 static http2_session_data *create_http2_session_data(app_context *app_ctx,
221                                                      int fd,
222                                                      struct sockaddr *addr,
223                                                      int addrlen) {
224   int rv;
225   http2_session_data *session_data;
226   SSL *ssl;
227   char host[NI_MAXHOST];
228   int val = 1;
229 
230   ssl = create_ssl(app_ctx->ssl_ctx);
231   session_data = malloc(sizeof(http2_session_data));
232   memset(session_data, 0, sizeof(http2_session_data));
233   session_data->app_ctx = app_ctx;
234   setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
235   session_data->bev = bufferevent_openssl_socket_new(
236       app_ctx->evbase, fd, ssl, BUFFEREVENT_SSL_ACCEPTING,
237       BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
238   bufferevent_enable(session_data->bev, EV_READ | EV_WRITE);
239   rv = getnameinfo(addr, (socklen_t)addrlen, host, sizeof(host), NULL, 0,
240                    NI_NUMERICHOST);
241   if (rv != 0) {
242     session_data->client_addr = strdup("(unknown)");
243   } else {
244     session_data->client_addr = strdup(host);
245   }
246 
247   return session_data;
248 }
249 
delete_http2_session_data(http2_session_data * session_data)250 static void delete_http2_session_data(http2_session_data *session_data) {
251   http2_stream_data *stream_data;
252   SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev);
253   fprintf(stderr, "%s disconnected\n", session_data->client_addr);
254   if (ssl) {
255     SSL_shutdown(ssl);
256   }
257   bufferevent_free(session_data->bev);
258   nghttp2_session_del(session_data->session);
259   for (stream_data = session_data->root.next; stream_data;) {
260     http2_stream_data *next = stream_data->next;
261     delete_http2_stream_data(stream_data);
262     stream_data = next;
263   }
264   free(session_data->client_addr);
265   free(session_data);
266 }
267 
268 /* Serialize the frame and send (or buffer) the data to
269    bufferevent. */
session_send(http2_session_data * session_data)270 static int session_send(http2_session_data *session_data) {
271   int rv;
272   rv = nghttp2_session_send(session_data->session);
273   if (rv != 0) {
274     warnx("Fatal error: %s", nghttp2_strerror(rv));
275     return -1;
276   }
277   return 0;
278 }
279 
280 /* Read the data in the bufferevent and feed them into nghttp2 library
281    function. Invocation of nghttp2_session_mem_recv2() may make
282    additional pending frames, so call session_send() at the end of the
283    function. */
session_recv(http2_session_data * session_data)284 static int session_recv(http2_session_data *session_data) {
285   nghttp2_ssize readlen;
286   struct evbuffer *input = bufferevent_get_input(session_data->bev);
287   size_t datalen = evbuffer_get_length(input);
288   unsigned char *data = evbuffer_pullup(input, -1);
289 
290   readlen = nghttp2_session_mem_recv2(session_data->session, data, datalen);
291   if (readlen < 0) {
292     warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
293     return -1;
294   }
295   if (evbuffer_drain(input, (size_t)readlen) != 0) {
296     warnx("Fatal error: evbuffer_drain failed");
297     return -1;
298   }
299   if (session_send(session_data) != 0) {
300     return -1;
301   }
302   return 0;
303 }
304 
send_callback(nghttp2_session * session,const uint8_t * data,size_t length,int flags,void * user_data)305 static nghttp2_ssize send_callback(nghttp2_session *session,
306                                    const uint8_t *data, size_t length,
307                                    int flags, void *user_data) {
308   http2_session_data *session_data = (http2_session_data *)user_data;
309   struct bufferevent *bev = session_data->bev;
310   (void)session;
311   (void)flags;
312 
313   /* Avoid excessive buffering in server side. */
314   if (evbuffer_get_length(bufferevent_get_output(session_data->bev)) >=
315       OUTPUT_WOULDBLOCK_THRESHOLD) {
316     return NGHTTP2_ERR_WOULDBLOCK;
317   }
318   bufferevent_write(bev, data, length);
319   return (nghttp2_ssize)length;
320 }
321 
322 /* Returns nonzero if the string |s| ends with the substring |sub| */
ends_with(const char * s,const char * sub)323 static int ends_with(const char *s, const char *sub) {
324   size_t slen = strlen(s);
325   size_t sublen = strlen(sub);
326   if (slen < sublen) {
327     return 0;
328   }
329   return memcmp(s + slen - sublen, sub, sublen) == 0;
330 }
331 
332 /* Returns int value of hex string character |c| */
hex_to_uint(uint8_t c)333 static uint8_t hex_to_uint(uint8_t c) {
334   if ('0' <= c && c <= '9') {
335     return (uint8_t)(c - '0');
336   }
337   if ('A' <= c && c <= 'F') {
338     return (uint8_t)(c - 'A' + 10);
339   }
340   if ('a' <= c && c <= 'f') {
341     return (uint8_t)(c - 'a' + 10);
342   }
343   return 0;
344 }
345 
346 /* Decodes percent-encoded byte string |value| with length |valuelen|
347    and returns the decoded byte string in allocated buffer. The return
348    value is NULL terminated. The caller must free the returned
349    string. */
percent_decode(const uint8_t * value,size_t valuelen)350 static char *percent_decode(const uint8_t *value, size_t valuelen) {
351   char *res;
352 
353   res = malloc(valuelen + 1);
354   if (valuelen > 3) {
355     size_t i, j;
356     for (i = 0, j = 0; i < valuelen - 2;) {
357       if (value[i] != '%' || !isxdigit(value[i + 1]) ||
358           !isxdigit(value[i + 2])) {
359         res[j++] = (char)value[i++];
360         continue;
361       }
362       res[j++] =
363           (char)((hex_to_uint(value[i + 1]) << 4) + hex_to_uint(value[i + 2]));
364       i += 3;
365     }
366     memcpy(&res[j], &value[i], 2);
367     res[j + 2] = '\0';
368   } else {
369     memcpy(res, value, valuelen);
370     res[valuelen] = '\0';
371   }
372   return res;
373 }
374 
file_read_callback(nghttp2_session * session,int32_t stream_id,uint8_t * buf,size_t length,uint32_t * data_flags,nghttp2_data_source * source,void * user_data)375 static nghttp2_ssize file_read_callback(nghttp2_session *session,
376                                         int32_t stream_id, uint8_t *buf,
377                                         size_t length, uint32_t *data_flags,
378                                         nghttp2_data_source *source,
379                                         void *user_data) {
380   int fd = source->fd;
381   ssize_t r;
382   (void)session;
383   (void)stream_id;
384   (void)user_data;
385 
386   while ((r = read(fd, buf, length)) == -1 && errno == EINTR)
387     ;
388   if (r == -1) {
389     return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
390   }
391   if (r == 0) {
392     *data_flags |= NGHTTP2_DATA_FLAG_EOF;
393   }
394   return (nghttp2_ssize)r;
395 }
396 
send_response(nghttp2_session * session,int32_t stream_id,nghttp2_nv * nva,size_t nvlen,int fd)397 static int send_response(nghttp2_session *session, int32_t stream_id,
398                          nghttp2_nv *nva, size_t nvlen, int fd) {
399   int rv;
400   nghttp2_data_provider2 data_prd;
401   data_prd.source.fd = fd;
402   data_prd.read_callback = file_read_callback;
403 
404   rv = nghttp2_submit_response2(session, stream_id, nva, nvlen, &data_prd);
405   if (rv != 0) {
406     warnx("Fatal error: %s", nghttp2_strerror(rv));
407     return -1;
408   }
409   return 0;
410 }
411 
412 static const char ERROR_HTML[] = "<html><head><title>404</title></head>"
413                                  "<body><h1>404 Not Found</h1></body></html>";
414 
error_reply(nghttp2_session * session,http2_stream_data * stream_data)415 static int error_reply(nghttp2_session *session,
416                        http2_stream_data *stream_data) {
417   int rv;
418   ssize_t writelen;
419   int pipefd[2];
420   nghttp2_nv hdrs[] = {MAKE_NV(":status", "404")};
421 
422   rv = pipe(pipefd);
423   if (rv != 0) {
424     warn("Could not create pipe");
425     rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
426                                    stream_data->stream_id,
427                                    NGHTTP2_INTERNAL_ERROR);
428     if (rv != 0) {
429       warnx("Fatal error: %s", nghttp2_strerror(rv));
430       return -1;
431     }
432     return 0;
433   }
434 
435   writelen = write(pipefd[1], ERROR_HTML, sizeof(ERROR_HTML) - 1);
436   close(pipefd[1]);
437 
438   if (writelen != sizeof(ERROR_HTML) - 1) {
439     close(pipefd[0]);
440     return -1;
441   }
442 
443   stream_data->fd = pipefd[0];
444 
445   if (send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs),
446                     pipefd[0]) != 0) {
447     close(pipefd[0]);
448     return -1;
449   }
450   return 0;
451 }
452 
453 /* nghttp2_on_header_callback: Called when nghttp2 library emits
454    single header name/value pair. */
on_header_callback(nghttp2_session * session,const nghttp2_frame * frame,const uint8_t * name,size_t namelen,const uint8_t * value,size_t valuelen,uint8_t flags,void * user_data)455 static int on_header_callback(nghttp2_session *session,
456                               const nghttp2_frame *frame, const uint8_t *name,
457                               size_t namelen, const uint8_t *value,
458                               size_t valuelen, uint8_t flags, void *user_data) {
459   http2_stream_data *stream_data;
460   const char PATH[] = ":path";
461   (void)flags;
462   (void)user_data;
463 
464   switch (frame->hd.type) {
465   case NGHTTP2_HEADERS:
466     if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
467       break;
468     }
469     stream_data =
470         nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
471     if (!stream_data || stream_data->request_path) {
472       break;
473     }
474     if (namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) {
475       size_t j;
476       for (j = 0; j < valuelen && value[j] != '?'; ++j)
477         ;
478       stream_data->request_path = percent_decode(value, j);
479     }
480     break;
481   }
482   return 0;
483 }
484 
on_begin_headers_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)485 static int on_begin_headers_callback(nghttp2_session *session,
486                                      const nghttp2_frame *frame,
487                                      void *user_data) {
488   http2_session_data *session_data = (http2_session_data *)user_data;
489   http2_stream_data *stream_data;
490 
491   if (frame->hd.type != NGHTTP2_HEADERS ||
492       frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
493     return 0;
494   }
495   stream_data = create_http2_stream_data(session_data, frame->hd.stream_id);
496   nghttp2_session_set_stream_user_data(session, frame->hd.stream_id,
497                                        stream_data);
498   return 0;
499 }
500 
501 /* Minimum check for directory traversal. Returns nonzero if it is
502    safe. */
check_path(const char * path)503 static int check_path(const char *path) {
504   /* We don't like '\' in url. */
505   return path[0] && path[0] == '/' && strchr(path, '\\') == NULL &&
506          strstr(path, "/../") == NULL && strstr(path, "/./") == NULL &&
507          !ends_with(path, "/..") && !ends_with(path, "/.");
508 }
509 
on_request_recv(nghttp2_session * session,http2_session_data * session_data,http2_stream_data * stream_data)510 static int on_request_recv(nghttp2_session *session,
511                            http2_session_data *session_data,
512                            http2_stream_data *stream_data) {
513   int fd;
514   nghttp2_nv hdrs[] = {MAKE_NV(":status", "200")};
515   char *rel_path;
516 
517   if (!stream_data->request_path) {
518     if (error_reply(session, stream_data) != 0) {
519       return NGHTTP2_ERR_CALLBACK_FAILURE;
520     }
521     return 0;
522   }
523   fprintf(stderr, "%s GET %s\n", session_data->client_addr,
524           stream_data->request_path);
525   if (!check_path(stream_data->request_path)) {
526     if (error_reply(session, stream_data) != 0) {
527       return NGHTTP2_ERR_CALLBACK_FAILURE;
528     }
529     return 0;
530   }
531   for (rel_path = stream_data->request_path; *rel_path == '/'; ++rel_path)
532     ;
533   fd = open(rel_path, O_RDONLY);
534   if (fd == -1) {
535     if (error_reply(session, stream_data) != 0) {
536       return NGHTTP2_ERR_CALLBACK_FAILURE;
537     }
538     return 0;
539   }
540   stream_data->fd = fd;
541 
542   if (send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs), fd) !=
543       0) {
544     close(fd);
545     return NGHTTP2_ERR_CALLBACK_FAILURE;
546   }
547   return 0;
548 }
549 
on_frame_recv_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)550 static int on_frame_recv_callback(nghttp2_session *session,
551                                   const nghttp2_frame *frame, void *user_data) {
552   http2_session_data *session_data = (http2_session_data *)user_data;
553   http2_stream_data *stream_data;
554   switch (frame->hd.type) {
555   case NGHTTP2_DATA:
556   case NGHTTP2_HEADERS:
557     /* Check that the client request has finished */
558     if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
559       stream_data =
560           nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
561       /* For DATA and HEADERS frame, this callback may be called after
562          on_stream_close_callback. Check that stream still alive. */
563       if (!stream_data) {
564         return 0;
565       }
566       return on_request_recv(session, session_data, stream_data);
567     }
568     break;
569   default:
570     break;
571   }
572   return 0;
573 }
574 
on_stream_close_callback(nghttp2_session * session,int32_t stream_id,uint32_t error_code,void * user_data)575 static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
576                                     uint32_t error_code, void *user_data) {
577   http2_session_data *session_data = (http2_session_data *)user_data;
578   http2_stream_data *stream_data;
579   (void)error_code;
580 
581   stream_data = nghttp2_session_get_stream_user_data(session, stream_id);
582   if (!stream_data) {
583     return 0;
584   }
585   remove_stream(session_data, stream_data);
586   delete_http2_stream_data(stream_data);
587   return 0;
588 }
589 
initialize_nghttp2_session(http2_session_data * session_data)590 static void initialize_nghttp2_session(http2_session_data *session_data) {
591   nghttp2_session_callbacks *callbacks;
592 
593   nghttp2_session_callbacks_new(&callbacks);
594 
595   nghttp2_session_callbacks_set_send_callback2(callbacks, send_callback);
596 
597   nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
598                                                        on_frame_recv_callback);
599 
600   nghttp2_session_callbacks_set_on_stream_close_callback(
601       callbacks, on_stream_close_callback);
602 
603   nghttp2_session_callbacks_set_on_header_callback(callbacks,
604                                                    on_header_callback);
605 
606   nghttp2_session_callbacks_set_on_begin_headers_callback(
607       callbacks, on_begin_headers_callback);
608 
609   nghttp2_session_server_new(&session_data->session, callbacks, session_data);
610 
611   nghttp2_session_callbacks_del(callbacks);
612 }
613 
614 /* Send HTTP/2 client connection header, which includes 24 bytes
615    magic octets and SETTINGS frame */
send_server_connection_header(http2_session_data * session_data)616 static int send_server_connection_header(http2_session_data *session_data) {
617   nghttp2_settings_entry iv[1] = {
618       {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
619   int rv;
620 
621   rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
622                                ARRLEN(iv));
623   if (rv != 0) {
624     warnx("Fatal error: %s", nghttp2_strerror(rv));
625     return -1;
626   }
627   return 0;
628 }
629 
630 /* readcb for bufferevent after client connection header was
631    checked. */
readcb(struct bufferevent * bev,void * ptr)632 static void readcb(struct bufferevent *bev, void *ptr) {
633   http2_session_data *session_data = (http2_session_data *)ptr;
634   (void)bev;
635 
636   if (session_recv(session_data) != 0) {
637     delete_http2_session_data(session_data);
638     return;
639   }
640 }
641 
642 /* writecb for bufferevent. To greaceful shutdown after sending or
643    receiving GOAWAY, we check the some conditions on the nghttp2
644    library and output buffer of bufferevent. If it indicates we have
645    no business to this session, tear down the connection. If the
646    connection is not going to shutdown, we call session_send() to
647    process pending data in the output buffer. This is necessary
648    because we have a threshold on the buffer size to avoid too much
649    buffering. See send_callback(). */
writecb(struct bufferevent * bev,void * ptr)650 static void writecb(struct bufferevent *bev, void *ptr) {
651   http2_session_data *session_data = (http2_session_data *)ptr;
652   if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
653     return;
654   }
655   if (nghttp2_session_want_read(session_data->session) == 0 &&
656       nghttp2_session_want_write(session_data->session) == 0) {
657     delete_http2_session_data(session_data);
658     return;
659   }
660   if (session_send(session_data) != 0) {
661     delete_http2_session_data(session_data);
662     return;
663   }
664 }
665 
666 /* eventcb for bufferevent */
eventcb(struct bufferevent * bev,short events,void * ptr)667 static void eventcb(struct bufferevent *bev, short events, void *ptr) {
668   http2_session_data *session_data = (http2_session_data *)ptr;
669   if (events & BEV_EVENT_CONNECTED) {
670     const unsigned char *alpn = NULL;
671     unsigned int alpnlen = 0;
672     SSL *ssl;
673     (void)bev;
674 
675     fprintf(stderr, "%s connected\n", session_data->client_addr);
676 
677     ssl = bufferevent_openssl_get_ssl(session_data->bev);
678 
679     SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
680 
681     if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) {
682       fprintf(stderr, "%s h2 is not negotiated\n", session_data->client_addr);
683       delete_http2_session_data(session_data);
684       return;
685     }
686 
687     initialize_nghttp2_session(session_data);
688 
689     if (send_server_connection_header(session_data) != 0 ||
690         session_send(session_data) != 0) {
691       delete_http2_session_data(session_data);
692       return;
693     }
694 
695     return;
696   }
697   if (events & BEV_EVENT_EOF) {
698     fprintf(stderr, "%s EOF\n", session_data->client_addr);
699   } else if (events & BEV_EVENT_ERROR) {
700     fprintf(stderr, "%s network error\n", session_data->client_addr);
701   } else if (events & BEV_EVENT_TIMEOUT) {
702     fprintf(stderr, "%s timeout\n", session_data->client_addr);
703   }
704   delete_http2_session_data(session_data);
705 }
706 
707 /* callback for evconnlistener */
acceptcb(struct evconnlistener * listener,int fd,struct sockaddr * addr,int addrlen,void * arg)708 static void acceptcb(struct evconnlistener *listener, int fd,
709                      struct sockaddr *addr, int addrlen, void *arg) {
710   app_context *app_ctx = (app_context *)arg;
711   http2_session_data *session_data;
712   (void)listener;
713 
714   session_data = create_http2_session_data(app_ctx, fd, addr, addrlen);
715 
716   bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, session_data);
717 }
718 
start_listen(struct event_base * evbase,const char * service,app_context * app_ctx)719 static void start_listen(struct event_base *evbase, const char *service,
720                          app_context *app_ctx) {
721   int rv;
722   struct addrinfo hints;
723   struct addrinfo *res, *rp;
724 
725   memset(&hints, 0, sizeof(hints));
726   hints.ai_family = AF_UNSPEC;
727   hints.ai_socktype = SOCK_STREAM;
728   hints.ai_flags = AI_PASSIVE;
729 #ifdef AI_ADDRCONFIG
730   hints.ai_flags |= AI_ADDRCONFIG;
731 #endif /* AI_ADDRCONFIG */
732 
733   rv = getaddrinfo(NULL, service, &hints, &res);
734   if (rv != 0) {
735     errx(1, "Could not resolve server address");
736   }
737   for (rp = res; rp; rp = rp->ai_next) {
738     struct evconnlistener *listener;
739     listener = evconnlistener_new_bind(
740         evbase, acceptcb, app_ctx, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
741         16, rp->ai_addr, (int)rp->ai_addrlen);
742     if (listener) {
743       freeaddrinfo(res);
744 
745       return;
746     }
747   }
748   errx(1, "Could not start listener");
749 }
750 
initialize_app_context(app_context * app_ctx,SSL_CTX * ssl_ctx,struct event_base * evbase)751 static void initialize_app_context(app_context *app_ctx, SSL_CTX *ssl_ctx,
752                                    struct event_base *evbase) {
753   memset(app_ctx, 0, sizeof(app_context));
754   app_ctx->ssl_ctx = ssl_ctx;
755   app_ctx->evbase = evbase;
756 }
757 
run(const char * service,const char * key_file,const char * cert_file)758 static void run(const char *service, const char *key_file,
759                 const char *cert_file) {
760   SSL_CTX *ssl_ctx;
761   app_context app_ctx;
762   struct event_base *evbase;
763 
764   ssl_ctx = create_ssl_ctx(key_file, cert_file);
765   evbase = event_base_new();
766   initialize_app_context(&app_ctx, ssl_ctx, evbase);
767   start_listen(evbase, service, &app_ctx);
768 
769   event_base_loop(evbase, 0);
770 
771   event_base_free(evbase);
772   SSL_CTX_free(ssl_ctx);
773 }
774 
main(int argc,char ** argv)775 int main(int argc, char **argv) {
776   struct sigaction act;
777 
778   if (argc < 4) {
779     fprintf(stderr, "Usage: libevent-server PORT KEY_FILE CERT_FILE\n");
780     exit(EXIT_FAILURE);
781   }
782 
783   memset(&act, 0, sizeof(struct sigaction));
784   act.sa_handler = SIG_IGN;
785   sigaction(SIGPIPE, &act, NULL);
786 
787   run(argv[1], argv[2], argv[3]);
788   return 0;
789 }
790