1 /**
2  * Copyright 2010 Christian Liesch
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /**
18  * @file
19  *
20  * @Author christian liesch <liesch@gmx.ch>
21  * @Author armin abfalterer <armin.abfalterer@united-security-providers.ch>
22  *
23  * Implementation of the HTTP Test Tool h2 module
24  */
25 
26 /************************************************************************
27  * Includes
28  ***********************************************************************/
29 
30 #include <apr.h>
31 #include <apr_poll.h>
32 #include <apr_rmm.h>
33 #include <apr_strings.h>
34 #include <apr_ring.h>
35 #include <openssl/ssl.h>
36 
37 #include "nghttp2/nghttp2.h"
38 #include "regex.h"
39 #include "module.h"
40 #include "ssl_module.h"
41 #include "body.h"
42 
43 /************************************************************************
44  * Globals
45  ***********************************************************************/
46 extern command_t local_commands[];
47 extern int success;
48 
49 /************************************************************************
50  * Definitions
51  ***********************************************************************/
52 const char * h2_module = "h2_module";
53 
54 const unsigned char *h2 = (const unsigned char *)"\x2h2";
55 const unsigned char *h214 = (const unsigned char *)"\x5h2-14";
56 const unsigned char *h216 = (const unsigned char *)"\x5h2-16";
57 
58 enum { IO_NONE, WANT_READ, WANT_WRITE };
59 
60 /* as defined in nghttp2_session.h */
61 #define NGHTTP2_INBOUND_BUFFER_LENGTH 16384
62 
63 #define MAKE_NV(NAME, NAMELEN, VALUE, VALUELEN)                       \
64   {                                                                   \
65     (uint8_t *)NAME, (uint8_t *)VALUE, NAMELEN, VALUELEN,             \
66         NGHTTP2_NV_FLAG_NONE                                          \
67   }
68 
69 #define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
70 #define DEFER_MARKER         "CTL_DEFER_MARKER"
71 #define DEFER_MARKER_CHECKED "CTL_DEFER_CHECKED"
72 
73 typedef struct _event_t {
74 #define EVENT_FLUSH 1
75 #define EVENT_DEFER 2
76   APR_RING_ENTRY(_event_t) link;
77 
78   apr_size_t at;
79   int type;
80   apr_size_t sleep;
81   int defer;
82 } event_t;
83 typedef struct _event_ring_t event_ring_t;
84 APR_RING_HEAD(_event_ring_t, _event_t);
85 
86 typedef struct h2_stream_s {
87   int id;
88   int closed;
89 
90   apr_pool_t *p;
91 
92   char *data;
93   apr_size_t data_len;
94   apr_size_t data_sent;
95 
96   char *data_in;
97   apr_size_t data_in_len;
98   apr_size_t data_in_read;
99   apr_size_t data_in_expect;
100 
101   int reset_expect;
102   int reset_received;
103 
104   apr_table_t *headers_in;
105   apr_table_t *headers_out;
106 
107   validation_t match;
108   validation_t expect;
109 
110   event_ring_t *events;
111 } h2_stream_t;
112 
113 typedef struct h2_sconf_s {
114   SSL *ssl;
115   int is_server;
116   nghttp2_session *session;
117   char *authority;
118 } h2_sconf_t;
119 
120 typedef struct h2_wconf_t {
121   h2_stream_t *current_stream;
122   apr_hash_t *streams;
123 #define H2_STATE_CLOSED       0x00
124 #define H2_STATE_INIT         0x01
125 #define H2_STATE_NEGOTIATE    0x02
126 #define H2_STATE_ESTABLISHED  0x04
127   int state;
128   int settings;
129   int pings;
130   int open_streams;
131   int buffer_size;
132   int goaway;
133   const char *goaway_expect;
134 } h2_wconf_t;
135 
136 static ssize_t h2_data_read_callback(nghttp2_session *session,
137                                      int32_t stream_id, uint8_t *buf,
138                                      size_t length, uint32_t *data_flags,
139                                      nghttp2_data_source *source,
140                                      void *user_data);
141 
142 /************************************************************************
143  * Local
144  ***********************************************************************/
145 
h2_get_socket_config(worker_t * worker)146 static h2_sconf_t *h2_get_socket_config(worker_t *worker) {
147   h2_sconf_t *config;
148 
149   if (!worker || !worker->socket) {
150     return NULL;
151   }
152 
153   config = module_get_config(worker->socket->config, h2_module);
154   if (config == NULL) {
155     worker_log(worker, LOG_DEBUG,
156                "create new sconf for socket %" APR_UINT64_T_HEX_FMT,
157                worker->socket);
158     config = apr_pcalloc(worker->pbody, sizeof(*config));
159     module_set_config(worker->socket->config,
160                       apr_pstrdup(worker->pbody, h2_module), config);
161   }
162   return config;
163 }
164 
h2_get_worker_config(worker_t * worker)165 static h2_wconf_t *h2_get_worker_config(worker_t *worker) {
166   h2_wconf_t *config;
167 
168   if (!worker) {
169     return NULL;
170   }
171 
172   config = module_get_config(worker->config, h2_module);
173   if (config == NULL) {
174     worker_log(worker, LOG_DEBUG, "create new wconf for worker %"APR_UINT64_T_HEX_FMT, worker);
175     config = apr_pcalloc(worker->pbody, sizeof(*config));
176     config->streams = apr_hash_make(worker->pbody);
177     config->buffer_size = NGHTTP2_INBOUND_BUFFER_LENGTH * 10;
178     module_set_config(worker->config, apr_pstrdup(worker->pbody, h2_module), config);
179   }
180   return config;
181 }
182 
copy_table_entry(void * rec,const char * key,const char * val)183 static int copy_table_entry(void *rec, const char *key, const char *val) {
184   apr_table_addn(rec, key, val);
185 
186   const int non_zero_meaning_continue_iteration = 1;
187   return non_zero_meaning_continue_iteration;
188 }
189 
h2_worker_body(worker_t ** body,worker_t * worker,char * end)190 static apr_status_t h2_worker_body(worker_t **body, worker_t *worker,
191                                    char *end) {
192   char *file_and_line;
193   char *line = "", *copy;
194   apr_table_entry_t *e;
195   apr_pool_t *p;
196   int end_len, headers_end = 0, ends = 0;
197 
198   HT_POOL_CREATE(&p);
199   end_len = strlen(end);
200   (*body) = apr_pcalloc(p, sizeof(worker_t));
201   memcpy(*body, worker, sizeof(worker_t));
202   (*body)->heartbeat = p;
203 
204   /* fill lines */
205   (*body)->lines = apr_table_make(p, 20);
206   e = (apr_table_entry_t *) apr_table_elts(worker->lines)->elts;
207   for (worker->cmd += 1; worker->cmd < apr_table_elts(worker->lines)->nelts;
208        worker->cmd++) {
209     file_and_line = e[worker->cmd].key;
210 
211     line = e[worker->cmd].val;
212     copy = apr_pstrdup(p, line);
213     copy = worker_replace_vars(worker, copy, NULL, p);
214 
215     if (!headers_end && copy[1] != '_') {
216       worker_log(worker, LOG_ERR, "command can not be used within headers definition", copy);
217       return APR_EGENERAL;
218     }
219     else if (strcmp("__", copy) == 0) {
220       headers_end = 1;
221     }
222 
223     if (strncmp(copy, end, end_len) == 0) {
224       worker_log(worker, LOG_INFO, end);
225       ends = 1;
226       break;
227     } else if (strncmp("_GREP", copy, 5) == 0) {
228       worker_log(worker, LOG_ERR, "not implemented for h2", line);
229       return APR_EINVAL;
230     } else if (strncmp("_SLEEP", copy, 6) == 0) {
231       copy = apr_psprintf(p, "_H2:SLEEP %s", &copy[6]);
232       apr_table_add((*body)->lines, file_and_line, copy);
233     } else if (strncmp("_FLUSH", copy, 6) == 0) {
234       apr_table_add((*body)->lines, file_and_line, "_H2:FLUSH");
235     } else if (strncmp("_H2:WAIT", copy, 8) == 0) {
236       apr_table_add((*body)->lines, file_and_line, "_H2:DEFER");
237     } else {
238       apr_table_addn((*body)->lines, file_and_line, line);
239     }
240     worker_log(worker, LOG_INFO, "%s", line);
241   }
242 
243   if (!ends) {
244     worker_log(worker, LOG_ERR, "Interpreter failed: no %s found", end);
245     return APR_EGENERAL;
246   }
247 
248   return APR_SUCCESS;
249 }
250 
251 typedef apr_status_t (*validate_func)(worker_t *, apr_table_t *, const char *,
252                                   apr_size_t);
253 
check(worker_t * worker,h2_stream_t * stream,apr_table_t * orig,char * type,validate_func func)254 static apr_table_t *check(worker_t *worker, h2_stream_t *stream,
255                           apr_table_t *orig, char *type, validate_func func) {
256   apr_table_t *copy =
257       apr_table_make(worker->pbody, apr_table_elts(orig)->nelts);
258   apr_table_entry_t *e;
259   int i;
260 
261   e = (apr_table_entry_t *)apr_table_elts(orig)->elts;
262   for (i = 0; i < apr_table_elts(orig)->nelts; i++) {
263     if (strcmp(e[i].key, DEFER_MARKER_CHECKED) == 0) {
264       continue;
265     } else if (strcmp(e[i].key, DEFER_MARKER) == 0) {
266       /* mark as checked */
267       e[i].key = DEFER_MARKER_CHECKED;
268       break;
269     }
270 
271     apr_table_addn(copy, e[i].key, e[i].val);
272     e[i].key = DEFER_MARKER_CHECKED;
273   }
274 
275   if (strcmp(type, "HEADERS") == 0) {
276     e = (apr_table_entry_t *)apr_table_elts(stream->headers_in)->elts;
277     for (i = 0; i < apr_table_elts(stream->headers_in)->nelts; i++) {
278       char *header = apr_pstrcat(worker->pbody, e[i].key, ": ", e[i].val, NULL);
279 
280       func(worker, copy, header, strlen(header));
281     }
282   } else { /* data */
283     func(worker, copy, stream->data_in, stream->data_in_len);
284   }
285 
286   return copy;
287 }
288 
check_headers(worker_t * worker,h2_stream_t * stream)289 static int check_headers(worker_t *worker, h2_stream_t *stream) {
290   apr_status_t status = APR_SUCCESS;
291   apr_table_t *checked;
292   char *errmsg;
293 
294   checked =
295       check(worker, stream, stream->expect.headers, "HEADERS", worker_expect);
296   errmsg =
297       apr_psprintf(worker->pbody, "EXPECT headers for stream %d", stream->id);
298   status = worker_assert_expect(worker, checked, errmsg, status);
299   if (status != APR_SUCCESS) {
300     goto exit;
301   }
302 
303   checked =
304       check(worker, stream, stream->match.headers, "HEADERS", worker_match);
305   errmsg =
306       apr_psprintf(worker->pbody, "MATCH headers for stream %d", stream->id);
307   status = worker_assert_match(worker, checked, errmsg, status);
308   if (status != APR_SUCCESS) {
309     goto exit;
310   }
311 
312   /* do assertion after receiving of body */
313   check(worker, stream, stream->expect.dot, "HEADERS", worker_expect);
314   check(worker, stream, stream->match.dot, "HEADERS", worker_match);
315 
316 exit:
317   apr_table_clear(stream->headers_in);
318 
319   if (status != APR_SUCCESS) {
320     return NGHTTP2_ERR_CALLBACK_FAILURE;
321   } else {
322     return 0;
323   }
324 }
325 
check_data(worker_t * worker,h2_stream_t * stream)326 static int check_data(worker_t *worker, h2_stream_t *stream) {
327   apr_status_t status = APR_SUCCESS;
328   apr_table_t *checked;
329   char *errmsg;
330 
331   if (stream->data_in_expect &&
332       stream->data_in_read != stream->data_in_expect) {
333     worker_log(worker, LOG_ERR, "EXPECT bodysize for stream %d: read %d bytes",
334                stream->id, stream->data_in_read);
335     return APR_EINVAL;
336   }
337 
338   checked = check(worker, stream, stream->expect.body, "DATA", worker_expect);
339   errmsg = apr_psprintf(worker->pbody, "EXPECT body for stream %d", stream->id);
340   status = worker_assert_expect(worker, checked, errmsg, status);
341   if (status != APR_SUCCESS) {
342     goto exit;
343   }
344 
345   checked = check(worker, stream, stream->expect.dot, "DATA", worker_expect);
346   errmsg = apr_psprintf(worker->pbody, "EXPECT . for stream %d", stream->id);
347   status = worker_assert_expect(worker, checked, errmsg, status);
348   if (status != APR_SUCCESS) {
349     goto exit;
350   }
351 
352   checked = check(worker, stream, stream->match.body, "DATA", worker_match);
353   errmsg = apr_psprintf(worker->pbody, "MATCH body for stream %d", stream->id);
354   status = worker_assert_match(worker, checked, errmsg, status);
355   if (status != APR_SUCCESS) {
356     goto exit;
357   }
358 
359   checked = check(worker, stream, stream->match.dot, "DATA", worker_match);
360   errmsg = apr_psprintf(worker->pbody, "MATCH . for stream %d", stream->id);
361   status = worker_assert_match(worker, checked, errmsg, status);
362   if (status != APR_SUCCESS) {
363     goto exit;
364   }
365 
366 exit:
367   stream->data_in = NULL;
368 
369   if (status != APR_SUCCESS) {
370     return NGHTTP2_ERR_CALLBACK_FAILURE;
371   } else {
372     return 0;
373   }
374 }
375 
376 /************************************************************************
377  * nghttp2 stuff
378  ***********************************************************************/
379 const char *h2_settings_array[] = {
380   "NONE",
381   "SETTINGS_HEADER_TABLE_SIZE",
382   "SETTINGS_ENABLE_PUSH",
383   "SETTINGS_MAX_CONCURRENT_STREAMS",
384   "SETTINGS_INITIAL_WINDOW_SIZE",
385   "SETTINGS_MAX_FRAME_SIZE",
386   "SETTINGS_MAX_HEADER_LIST_SIZE",
387   NULL
388 };
389 
390 const char *h2_error_code_array[] = {
391   "NO_ERROR",
392   "PROTOCOL_ERROR",
393   "INTERNAL_ERROR",
394   "FLOW_CONTROL_ERROR",
395   "SETTINGS_TIMEOUT",
396   "STREAM_CLOSED",
397   "FRAME_SIZE_ERROR",
398   "REFUSED_STREAM",
399   "CANCEL",
400   "COMPRESSION_ERROR",
401   "CONNECT_ERROR",
402   "ENHANCE_YOUR_CALM",
403   "INADEQUATE_SECURITY",
404   "HTTP_1_1_REQUIRED"
405 };
406 
h2_get_id_of(const char * array[],const char * key)407 static int32_t h2_get_id_of(const char *array[], const char *key) {
408   int i;
409   if (key != NULL) {
410     for (i = 0; i < 7; i++) {
411       if (strcmp(key, array[i]) == 0) {
412         return i;
413       }
414     }
415   }
416   return -1;
417 }
418 
h2_get_name_of(const char * array[],int32_t id)419 static const char *h2_get_name_of(const char *array[], int32_t id) {
420   if (array[id]) {
421     return array[id];
422   } else {
423     return "NOT_FOUND";
424   }
425 }
426 
h2_get_settings_as_text(apr_pool_t * pool,nghttp2_settings_entry * settings,int size)427 static const char *h2_get_settings_as_text(apr_pool_t *pool,
428                                            nghttp2_settings_entry *settings,
429                                            int size) {
430   int i;
431   char *result = "";
432   for (i = 0; i < size; i++) {
433     result =
434         apr_psprintf(pool, "%s%s=%d%s", result,
435                      h2_get_name_of(h2_settings_array, settings[i].settings_id),
436                      settings[i].value, i + 1 < size ? ", " : "");
437   }
438   return result;
439 }
440 
h2_open_session(worker_t * worker)441 static apr_status_t h2_open_session(worker_t *worker) {
442   h2_sconf_t *sconf = h2_get_socket_config(worker);
443 
444   if (!sconf || !sconf->session) {
445     worker_log(worker, LOG_ERR, "no open h2 session");
446     return APR_EGENERAL;
447   }
448 
449   return APR_SUCCESS;
450 }
451 
452 #define WANT_READ_WRITE(s) nghttp2_session_want_read(s) || nghttp2_session_want_write(s)
453 
mypoll(worker_t * worker)454 static apr_status_t mypoll(worker_t *worker) {
455   h2_wconf_t *wconf = h2_get_worker_config(worker);
456   h2_sconf_t *sconf = h2_get_socket_config(worker);
457   apr_pollset_t *pollset;
458   apr_pollfd_t pollfd;
459   apr_status_t status;
460   apr_pool_t *p;
461   int poll;
462   int rv;
463 
464   apr_pool_create(&p, NULL);
465 
466   if ((status = apr_pollset_create(&pollset, 1, p, 0)) != APR_SUCCESS) {
467     worker_log(worker, LOG_ERR, "Can not create pollset %s(%d)",
468                my_status_str(p, status), status);
469     return status;
470   }
471 
472   pollfd.p = p;
473   pollfd.desc_type = APR_POLL_SOCKET;
474   pollfd.reqevents = APR_POLLIN | APR_POLLERR | APR_POLLHUP;
475   pollfd.desc.s = worker->socket->socket;
476 
477   if ((status = apr_pollset_add(pollset, &pollfd)) != APR_SUCCESS) {
478     worker_log(worker, LOG_ERR, "Can not add pollfd to pollset: %s(%d)",
479                my_status_str(p, status), status);
480     return status;
481   }
482 
483   poll = WANT_READ_WRITE(sconf->session);
484 
485   if (!poll) {
486     return APR_EGENERAL;
487   }
488 
489   while (poll) {
490     const apr_pollfd_t *result;
491     apr_int32_t num;
492 
493     worker_log(worker, LOG_DEBUG, "start poll cycle");
494 
495     if ((rv = nghttp2_session_send(sconf->session)) != 0) {
496       worker_log(worker, LOG_DEBUG, "error on sending session data frame %d",
497                  rv);
498       return APR_EGENERAL;
499     }
500 
501     if ((status = apr_pollset_poll(pollset, worker->socktmo, &num, &result)) !=
502         APR_SUCCESS) {
503       if (APR_STATUS_IS_EINTR(status)) {
504         continue;
505       }
506       worker_log(worker, LOG_ERR, "can not poll on pollset: %s (%d)",
507                  my_status_str(p, status), status);
508       return status;
509     }
510 
511     if (result[0].rtnevents & APR_POLLIN) {
512       worker_log(worker, LOG_DEBUG, "ready to receive session data frames");
513       if ((rv = nghttp2_session_recv(sconf->session)) != 0) {
514         worker_log(worker, LOG_DEBUG, "error on recieving session data frame %d", rv);
515         return APR_EGENERAL;
516       }
517     }
518 
519     if (result[0].rtnevents & APR_POLLERR) {
520       worker_log(worker, LOG_ERR, "Error on connection");
521       return APR_EGENERAL;
522     }
523 
524     if (result[0].rtnevents & APR_POLLHUP) {
525       worker_log(worker, LOG_ERR, "Connection hangup");
526       return APR_EGENERAL;
527     }
528 
529     poll = (wconf->settings || wconf->open_streams || wconf->pings);
530 
531     worker_log(worker, LOG_DEBUG,
532                "next poll cycle: settings=%d open_streams=%d pings=%d",
533                wconf->settings, wconf->open_streams, wconf->pings);
534 
535     worker_log(worker, LOG_DEBUG, "end poll cycle");
536   }
537 
538   apr_pollset_destroy(pollset);
539   apr_pool_destroy(p);
540 
541   return APR_SUCCESS;
542 }
543 
h2_send_callback(nghttp2_session * session,const uint8_t * data,size_t length,int flags,void * user_data)544 static ssize_t h2_send_callback(nghttp2_session *session, const uint8_t *data,
545                                 size_t length, int flags, void *user_data) {
546   int rv;
547   worker_t *worker = user_data;
548   h2_sconf_t *sconf = h2_get_socket_config(worker);
549 
550   worker_log(worker, LOG_DEBUG,
551              "h2_send_callback length: %d, flags: %d", length, flags);
552 
553   rv = SSL_write(sconf->ssl, data, (int)length);
554   if (rv <= 0) {
555     int err = SSL_get_error(sconf->ssl, rv);
556     if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
557       rv = NGHTTP2_ERR_WOULDBLOCK;
558     } else {
559       worker_log(worker, LOG_ERR, "Could not send %d", rv);
560       rv = NGHTTP2_ERR_CALLBACK_FAILURE;
561     }
562   }
563 
564   worker_log(worker, LOG_DEBUG, "send callback rv: %d", rv);
565   return rv;
566 }
567 
h2_recv_callback(nghttp2_session * session,uint8_t * buf,size_t length,int flags,void * user_data)568 static ssize_t h2_recv_callback(nghttp2_session *session, uint8_t *buf,
569                                 size_t length, int flags, void *user_data) {
570   worker_t *worker = user_data;
571   h2_sconf_t *sconf = h2_get_socket_config(worker);
572   int rv;
573 
574   worker_log(worker, LOG_DEBUG,
575              "h2_recv_callback length: %d, flags: %d", length, flags);
576 
577   rv = SSL_read(sconf->ssl, buf, (int)length);
578   if (rv < 0) {
579     int err = SSL_get_error(sconf->ssl, rv);
580     if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
581       rv = NGHTTP2_ERR_WOULDBLOCK;
582     } else {
583       worker_log(worker, LOG_ERR, "Could not recv %d", rv);
584       rv = NGHTTP2_ERR_CALLBACK_FAILURE;
585     }
586   } else if (rv == 0) {
587     rv = NGHTTP2_ERR_EOF;
588   }
589 
590   worker_log(worker, LOG_DEBUG, "recv callback rv: %d", rv);
591   return rv;
592 }
593 
h2_on_frame_send_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)594 static int h2_on_frame_send_callback(nghttp2_session *session,
595                                      const nghttp2_frame *frame,
596                                      void *user_data) {
597   int rv = 0;
598   size_t i;
599   apr_pool_t *p;
600   worker_t *worker = user_data;
601 	h2_wconf_t *wconf = h2_get_worker_config(worker);
602   apr_pool_create(&p, NULL);
603 
604   worker_log(worker, LOG_DEBUG, "> frame header stream %d, type: %d, flag: %d",
605              frame->hd.stream_id, frame->hd.type, frame->hd.flags);
606 
607   switch (frame->hd.type) {
608     case NGHTTP2_WINDOW_UPDATE:
609       worker_log(worker, LOG_DEBUG, "> WINDOW_UPDATE");
610       break;
611     case NGHTTP2_HEADERS:
612       worker_log(worker, LOG_DEBUG, "> HEADERS");
613       h2_stream_t *stream =
614           apr_hash_get(wconf->streams, apr_itoa(worker->pbody, frame->hd.stream_id),
615                        APR_HASH_KEY_STRING);
616       nghttp2_data_provider data_prd;
617 
618       /* submit data */
619       data_prd.read_callback = h2_data_read_callback;
620       if (stream->data_len > 0 ) {
621         nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, stream->id,
622                             &data_prd);
623       }
624 
625       if (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) {
626         const nghttp2_nv *nva = frame->headers.nva;
627         for (i = 0; i < frame->headers.nvlen; ++i) {
628           worker_log(worker, LOG_INFO, ">%d %s: %s", frame->hd.stream_id,
629                      nva[i].name, nva[i].value);
630         }
631       }
632     case NGHTTP2_DATA:
633       worker_log(worker, LOG_DEBUG, "> DATA %s",
634                  frame->hd.flags & NGHTTP2_DATA_FLAG_EOF ? "[EOF]" : "");
635       break;
636     case NGHTTP2_RST_STREAM:
637       worker_log(worker, LOG_DEBUG, "> RST_STREAM");
638       break;
639     case NGHTTP2_GOAWAY: {
640       char *reason = NULL;
641 
642       if (frame->goaway.opaque_data_len) {
643         char *opaque = apr_pmemdup(p, frame->goaway.opaque_data,
644                                    frame->goaway.opaque_data_len);
645         opaque[frame->goaway.opaque_data_len] = 0;
646         reason = apr_psprintf(p, " reason=%s", opaque);
647       }
648       worker_log(worker, LOG_INFO, "> GOAWAY [error=%s%s%s",
649                  h2_get_name_of(h2_error_code_array, frame->goaway.error_code),
650                  reason ? reason : "", "]");
651     } break;
652     case NGHTTP2_SETTINGS:
653       if (frame->hd.flags == NGHTTP2_FLAG_ACK) {
654         worker_log(worker, LOG_INFO, "> SETTINGS ACK");
655       } else {
656         const char *settings = h2_get_settings_as_text(
657             worker->pbody, frame->settings.iv, frame->settings.niv);
658         worker_log(worker, LOG_INFO, "> SETTINGS [%s]", settings);
659       }
660       break;
661     case NGHTTP2_PING:
662       if (frame->hd.flags == NGHTTP2_FLAG_ACK) {
663         char *opaque_data = apr_pcalloc(p, 9);
664         memcpy(opaque_data, frame->ping.opaque_data, 8);
665         worker_log(worker, LOG_INFO, "> PING ACK: %s", opaque_data);
666       } else {
667         worker_log(worker, LOG_INFO, "> PING");
668       }
669       break;
670     default:
671       worker_log(worker, LOG_INFO, "> UNKNOWN");
672       break;
673   }
674 
675   apr_pool_destroy(p);
676 
677   return rv;
678 }
679 
h2_on_frame_not_send_callback(nghttp2_session * session,const nghttp2_frame * frame,int lib_error_code,void * user_data)680 static int h2_on_frame_not_send_callback(nghttp2_session *session,
681                                          const nghttp2_frame *frame,
682                                          int lib_error_code, void *user_data) {
683   worker_t *worker = user_data;
684 
685   worker_log(worker, LOG_ERR, "could not sent frame for stream %d",
686              frame->hd.stream_id);
687 
688   const int callback_succeeded = 0;
689   return callback_succeeded;
690 }
691 
h2_on_frame_recv_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)692 static int h2_on_frame_recv_callback(nghttp2_session *session,
693                                      const nghttp2_frame *frame,
694                                      void *user_data) {
695   worker_t *worker = user_data;
696   h2_wconf_t *wconf = h2_get_worker_config(worker);
697   h2_stream_t *stream = apr_hash_get(
698       wconf->streams, apr_itoa(worker->pbody, frame->hd.stream_id), APR_HASH_KEY_STRING);
699   apr_pool_t *p;
700   size_t rv = 0;
701 
702   apr_pool_create(&p, NULL);
703 
704   worker_log(worker, LOG_DEBUG, "< frame header type: %d, flag: %d",
705              frame->hd.type, frame->hd.flags);
706 
707   switch (frame->hd.type) {
708     case NGHTTP2_HEADERS:
709       if (frame->hd.flags == NGHTTP2_FLAG_END_HEADERS) {
710         worker_log(worker, LOG_DEBUG, "< END_HEADERS");
711         nghttp2_session_resume_data(session, frame->hd.stream_id);
712         rv = check_headers(worker, stream);
713       }
714       break;
715     case NGHTTP2_DATA:
716       if (frame->hd.flags == NGHTTP2_FLAG_END_STREAM) {
717         worker_log(worker, LOG_DEBUG, "< END_STREAM");
718         rv = check_data(worker, stream);
719 
720         stream->closed = 1;
721         wconf->open_streams--;
722       }
723       else {
724         worker_log(worker, LOG_DEBUG, "< DATA");
725       }
726       break;
727     case NGHTTP2_WINDOW_UPDATE:
728       worker_log(worker, LOG_INFO, "< WINDOW_UPDATE");
729       break;
730     case NGHTTP2_RST_STREAM:
731       worker_log(
732           worker, LOG_INFO, "< RST_STREAM [error=%s]",
733           h2_get_name_of(h2_error_code_array, frame->rst_stream.error_code));
734 
735       /* mark stream as closed if not already done due to
736        * END_STREAM */
737       if (stream->closed == 0) {
738         stream->closed = 1;
739         wconf->open_streams--;
740       }
741 
742       if (!stream->reset_expect) {
743         rv = NGHTTP2_ERR_CALLBACK_FAILURE;
744       } else {
745         stream->reset_received = 1;
746       }
747       break;
748     case NGHTTP2_GOAWAY: {
749       char *reason = NULL;
750 
751       if (frame->goaway.opaque_data_len) {
752         char *opaque = apr_pmemdup(p, frame->goaway.opaque_data,
753                                    frame->goaway.opaque_data_len);
754         opaque[frame->goaway.opaque_data_len] = 0;
755         reason = apr_psprintf(p, " reason=%s", opaque);
756       }
757 
758       wconf->goaway = 1;
759       worker_log(worker, LOG_INFO, "> GOAWAY [error=%s%s]",
760                  h2_get_name_of(h2_error_code_array, frame->goaway.error_code),
761                  reason ? reason : "");
762       if (wconf->goaway_expect && strcasecmp(reason, wconf->goaway_expect)) {
763         rv = NGHTTP2_ERR_CALLBACK_FAILURE;
764       }
765     } break;
766     case NGHTTP2_SETTINGS:
767       if (frame->hd.flags == NGHTTP2_FLAG_ACK) {
768         const char *settings = apr_pstrdup(worker->pbody, "SETTINGS ACK");
769         worker_log(worker, LOG_INFO, "< %s", settings);
770         wconf->settings--;
771       } else {
772         const char *settings = h2_get_settings_as_text(
773             worker->pbody, frame->settings.iv, frame->settings.niv);
774         worker_log(worker, LOG_INFO, "< SETTINGS [%s]", settings);
775       }
776       break;
777     case NGHTTP2_PING: {
778       char *text = apr_pcalloc(p, 9);
779       memcpy(text, frame->ping.opaque_data, 8);
780       if (frame->hd.flags == NGHTTP2_FLAG_ACK) {
781         const char *pingText = apr_pstrcat(p, "PING ACK: ", text, NULL);
782         worker_log(worker, LOG_INFO, "< %s", pingText);
783         wconf->pings--;
784       } else {
785         const char *pingText = apr_pstrcat(p, "PING: ", text, NULL);
786         worker_log(worker, LOG_INFO, "< %s", pingText);
787       }
788     } break;
789     default:
790       worker_log(worker, LOG_INFO, "< UNKNOWN");
791   }
792   apr_pool_destroy(p);
793 
794   return rv;
795 }
796 
h2_on_begin_headers_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)797 static int h2_on_begin_headers_callback(nghttp2_session *session,
798                                         const nghttp2_frame *frame,
799                                         void *user_data) {
800   worker_t *worker = user_data;
801   worker_log(worker, LOG_DEBUG, "< HEADERS");
802 
803   return 0;
804 }
805 
h2_on_stream_close_callback(nghttp2_session * session,int32_t stream_id,uint32_t error_code,void * user_data)806 static int h2_on_stream_close_callback(nghttp2_session *session,
807                                        int32_t stream_id, uint32_t error_code,
808                                        void *user_data) {
809   worker_t *worker = user_data;
810   h2_wconf_t *wconf = h2_get_worker_config(worker);
811   h2_stream_t *stream = apr_hash_get(
812       wconf->streams, apr_itoa(worker->pbody, stream_id), APR_HASH_KEY_STRING);
813 
814   worker_log(worker, LOG_DEBUG, "stream %d closed", stream->id);
815 
816   return 0;
817 }
818 
h2_check_content_length(h2_stream_t * stream,const char * name,const char * val)819 static void h2_check_content_length(h2_stream_t *stream, const char *name,
820                                     const char *val) {
821   if (strcmp(name, "content-length") == 0) {
822     stream->data_in_len = apr_atoi64(val);
823   }
824 }
825 
h2_check_response_header(worker_t * worker,const char * name)826 static int h2_check_response_header(worker_t *worker, const char *name) {
827 #define H2_RES_HEADER_DENY     0x1
828 #define H2_RES_HEADER_FILTER   0x2
829   if (worker->headers_filter) {
830     if (apr_table_get(worker->headers_filter, name)) {
831       return H2_RES_HEADER_FILTER;
832     }
833   }
834   if (worker->headers_allow) {
835     if (!apr_table_get(worker->headers_allow, name)) {
836       worker_log(worker, LOG_ERR, "%s header not allowed", name);
837       return H2_RES_HEADER_DENY;
838     }
839   }
840   return 0;
841 }
842 
h2_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)843 static int h2_on_header_callback(nghttp2_session *session,
844                                  const nghttp2_frame *frame,
845                                  const uint8_t *name, size_t namelen,
846                                  const uint8_t *value, size_t valuelen,
847                                  uint8_t flags, void *user_data) {
848   worker_t *worker = user_data;
849   h2_wconf_t *wconf = h2_get_worker_config(worker);
850   h2_stream_t *stream =
851       apr_hash_get(wconf->streams, apr_itoa(worker->pbody, frame->hd.stream_id),
852                    APR_HASH_KEY_STRING);
853 
854   switch (frame->hd.type) {
855     case NGHTTP2_HEADERS: {
856       const char *nameStr = (const char *)name;
857       const char *valueStr = (const char *)value;
858       int action = h2_check_response_header(worker, nameStr);
859       if (action & H2_RES_HEADER_DENY) {
860         return NGHTTP2_ERR_CALLBACK_FAILURE;
861       }
862       h2_check_content_length(stream, nameStr, valueStr);
863 
864       if (!(action & H2_RES_HEADER_FILTER)) {
865         apr_table_add(stream->headers_in, nameStr, valueStr);
866         worker_log(worker, LOG_INFO, "<%d %s: %s", stream->id, nameStr, valueStr);
867       }
868 
869     } break;
870   }
871 
872   return 0;
873 }
874 
h2_on_data_chunk_recv_callback(nghttp2_session * session,uint8_t flags,int32_t stream_id,const uint8_t * data,size_t len,void * user_data)875 static int h2_on_data_chunk_recv_callback(nghttp2_session *session,
876                                           uint8_t flags, int32_t stream_id,
877                                           const uint8_t *data, size_t len,
878                                           void *user_data) {
879   worker_t *worker = user_data;
880   h2_wconf_t *wconf = h2_get_worker_config(worker);
881   h2_stream_t *stream = apr_hash_get(
882       wconf->streams, apr_itoa(worker->pbody, stream_id), APR_HASH_KEY_STRING);
883   int cur = stream->data_in_read;
884 
885   if (!stream->data_in) {
886     stream->data_in = apr_palloc(stream->p, stream->data_in_len);
887     stream->data_in_read = 0;
888   }
889 
890   worker_log(worker, LOG_DEBUG, "read %d from stream %d", len, stream_id);
891 
892   if (stream->data_in_read + len > stream->data_in_len) {
893     worker_log(worker, LOG_ERR, "Buffer to small (%d), stopping receving",
894                stream->data_in_len);
895 
896     return NGHTTP2_ERR_CALLBACK_FAILURE;
897   }
898 
899   memcpy(&stream->data_in[stream->data_in_read], data, len);
900   stream->data_in_read += len;
901   stream->data_in[stream->data_in_read] = 0;
902 
903   worker_log(worker, LOG_INFO, "<%d %s", stream_id, &stream->data_in[cur]);
904   return 0;
905 }
906 
h2_setup_callbacks(nghttp2_session_callbacks * callbacks)907 static void h2_setup_callbacks(nghttp2_session_callbacks *callbacks) {
908   nghttp2_session_callbacks_set_send_callback(callbacks, h2_send_callback);
909 
910   nghttp2_session_callbacks_set_recv_callback(callbacks, h2_recv_callback);
911 
912   nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
913                                                        h2_on_frame_send_callback);
914 
915   nghttp2_session_callbacks_set_on_frame_not_send_callback(callbacks,
916                                                            h2_on_frame_not_send_callback);
917 
918   nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
919                                                        h2_on_frame_recv_callback);
920 
921   nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks,
922                                                           h2_on_begin_headers_callback);
923   nghttp2_session_callbacks_set_on_stream_close_callback(
924       callbacks, h2_on_stream_close_callback);
925 
926   nghttp2_session_callbacks_set_on_header_callback(
927       callbacks, h2_on_header_callback);
928 
929   nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
930       callbacks, h2_on_data_chunk_recv_callback);
931 }
932 
933 
934 /************************************************************************
935  * Optional Functions
936 ************************************************************************/
937 
938 /************************************************************************
939  * Commands
940  ***********************************************************************/
block_H2_STREAM_BUFFER(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)941 apr_status_t block_H2_STREAM_BUFFER(worker_t *worker, worker_t *parent,
942                                     apr_pool_t *ptmp) {
943   h2_wconf_t *wconf = h2_get_worker_config(parent);
944   const char *size = store_get(worker->params, "1");
945 
946   wconf->buffer_size = apr_atoi64(size);
947 
948   return APR_SUCCESS;
949 }
950 
h2_setup(worker_t * worker,worker_t * parent)951 static apr_status_t h2_setup(worker_t *worker, worker_t *parent) {
952   h2_wconf_t *wconf = h2_get_worker_config(parent);
953   nghttp2_session_callbacks *callbacks;
954   h2_sconf_t *sconf;
955   apr_status_t rv;
956 
957   sconf = h2_get_socket_config(parent);
958   sconf->ssl = ssl_get_session(parent);
959   sconf->authority = apr_pstrdup(parent->pbody, "sesdev.tarsec.com");
960 
961   wconf->state |= H2_STATE_ESTABLISHED;
962 
963   /* hacky: see worker.c:command_CALL() */
964   worker->socket = parent->socket;
965 
966   rv = nghttp2_session_callbacks_new(&callbacks);
967   if (rv != 0) {
968     worker_log(worker, LOG_ERR, "Can not create http2 callbacks");
969     return APR_EGENERAL;
970   }
971   h2_setup_callbacks(callbacks);
972 
973   if (!sconf->is_server) {
974     worker_log(worker, LOG_DEBUG,
975                "client nghttp2 install worker: %" APR_UINT64_T_HEX_FMT, parent);
976     rv = nghttp2_session_client_new(&sconf->session, callbacks, parent);
977   }
978   else {
979     worker_log(worker, LOG_DEBUG,
980                "server nghttp2 install worker: %" APR_UINT64_T_HEX_FMT, parent);
981     rv = nghttp2_session_server_new(&sconf->session, callbacks, parent);
982   }
983   nghttp2_session_callbacks_del(callbacks);
984   if (rv != 0) {
985     worker_log(worker, LOG_ERR, "Can not create http2 session");
986     return APR_EGENERAL;
987   }
988 
989   return rv;
990 }
991 
block_H2_UPGRADE(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)992 apr_status_t block_H2_UPGRADE(worker_t *worker, worker_t *parent,
993                               apr_pool_t *ptmp) {
994   return h2_setup(worker, parent);
995 }
996 
block_H2_SESSION(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)997 apr_status_t block_H2_SESSION(worker_t *worker, worker_t *parent,
998                               apr_pool_t *ptmp) {
999   h2_wconf_t *wconf = h2_get_worker_config(parent);
1000   const char *host = store_get(worker->params, "1");
1001   const char *port = store_get(worker->params, "2");
1002   const char *cert = store_get(worker->params, "3");
1003   const char *key = store_get(worker->params, "4");
1004   const char *cacert = store_get(worker->params, "5");
1005   const char *data;
1006   apr_status_t rv;
1007 
1008   data = apr_psprintf(ptmp, "%s %s%s", host,
1009                       strstr(port, "SSL") ? "" : "SSL:", port);
1010   if (cert && key) {
1011     data = apr_pstrcat(ptmp, data, " ", cert, " ", key, NULL);
1012   }
1013   if (cacert) {
1014     data = apr_pstrcat(ptmp, data, " ", cacert, NULL);
1015   }
1016 
1017   wconf->state |= H2_STATE_INIT;
1018   rv = command_REQ(NULL, parent, (char *)data, parent->pbody);
1019 
1020   if (rv != 0) {
1021     return APR_EGENERAL;
1022   }
1023 
1024   return h2_setup(worker, parent);
1025 }
1026 
block_H2_SETTINGS(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1027 apr_status_t block_H2_SETTINGS(worker_t *worker, worker_t *parent,
1028                                apr_pool_t *ptmp) {
1029   h2_sconf_t *sconf = h2_get_socket_config(parent);
1030   h2_wconf_t *wconf = h2_get_worker_config(parent);
1031   nghttp2_settings_entry settings[6];
1032   size_t cur = 0;
1033   const char *param;
1034   int i = 0, rv;
1035 
1036   param  = store_get(worker->params, apr_itoa(ptmp, ++i));
1037   memset(&settings, 0, sizeof(settings));
1038 
1039   while (param && cur < 6) {
1040     char *setting = apr_pstrdup(parent->pbody, param);
1041     char *value;
1042     char *key = apr_strtok(setting, "=", &value);
1043     int32_t settings_id = h2_get_id_of(h2_settings_array, key);
1044 
1045     if (settings_id > 0) {
1046       settings[cur].settings_id = settings_id;
1047       settings[cur].value = apr_atoi64(value);
1048       cur++;
1049     } else {
1050       worker_log(worker, LOG_ERR, "Unknown h2 setting '%s'", key);
1051       return APR_EINVAL;
1052     }
1053     param = store_get(worker->params, apr_itoa(ptmp, ++i));
1054   }
1055 
1056   if ((rv = nghttp2_submit_settings(sconf->session, NGHTTP2_FLAG_NONE, settings,
1057                                     cur)) != 0) {
1058     worker_log(worker, LOG_ERR, "%s", nghttp2_strerror(rv));
1059     return APR_EINVAL;
1060   }
1061   wconf->settings++;
1062 
1063   return APR_SUCCESS;
1064 }
1065 
h2_data_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)1066 static ssize_t h2_data_read_callback(nghttp2_session *session,
1067                                      int32_t stream_id, uint8_t *buf,
1068                                      size_t length, uint32_t *data_flags,
1069                                      nghttp2_data_source *source,
1070                                      void *user_data) {
1071   worker_t *worker = user_data;
1072   h2_wconf_t *wconf = h2_get_worker_config(worker);
1073   h2_stream_t *stream = apr_hash_get(
1074       wconf->streams, apr_itoa(worker->pbody, stream_id), APR_HASH_KEY_STRING);
1075   event_t *event = APR_RING_FIRST(stream->events);
1076   apr_size_t len = 0;
1077 
1078   if (event) {
1079     apr_size_t diff = -1;
1080 
1081     if (event->at >= stream->data_sent) {
1082       diff = event->at - stream->data_sent;
1083     }
1084 
1085     if (diff == 0) {
1086       APR_RING_UNSPLICE(event, event, link);
1087 
1088       if (event->type == EVENT_FLUSH) {
1089         apr_sleep(event->sleep * 1000);
1090       } else if (event->type == EVENT_DEFER) {
1091         return NGHTTP2_ERR_DEFERRED;
1092       }
1093 
1094       return h2_data_read_callback(session, stream_id, buf, length, data_flags,
1095                                    source, user_data);
1096     } else if (diff <= length) {
1097       length = diff;
1098     }
1099   }
1100 
1101   if (stream->data_len == 0) {
1102     len = 0;
1103     goto eofcheck;
1104   } else if (stream->data_len - stream->data_sent <= length) {
1105     len = stream->data_len - stream->data_sent;
1106     memcpy(buf, &stream->data[stream->data_sent], len);
1107   } else {
1108     len = length;
1109     memcpy(buf, &stream->data[stream->data_sent], length);
1110   }
1111 
1112   buf[len] = 0;
1113   stream->data_sent += len;
1114 
1115   worker_log(worker, LOG_INFO, ">%d %s", stream_id, buf);
1116   worker_log(worker, LOG_DEBUG, "send %u bytes (%u/%u)", len, stream->data_sent,
1117              stream->data_len);
1118 
1119 eofcheck:
1120   if (APR_RING_EMPTY(stream->events, _event_t, link) &&
1121       stream->data_len == stream->data_sent) {
1122     *data_flags |= NGHTTP2_DATA_FLAG_EOF;
1123   }
1124 
1125   return len;
1126 }
1127 
h2_get_new_stream(worker_t * worker,int stream_id)1128 h2_stream_t* h2_get_new_stream(worker_t *worker, int stream_id) {
1129   h2_wconf_t *wconf = h2_get_worker_config(worker);
1130   h2_stream_t *stream = apr_pcalloc(worker->pbody, sizeof(*stream));
1131 
1132   stream->id = stream_id;
1133   apr_pool_create(&stream->p, worker->pbody);
1134 
1135   stream->data_in_len = wconf->buffer_size;
1136   stream->headers_in = apr_table_make(stream->p, 20);
1137   stream->headers_out = apr_table_make(stream->p, 20);
1138   stream->events = apr_palloc(stream->p, sizeof(event_ring_t));
1139   APR_RING_INIT(stream->events, _event_t, link);
1140 
1141   return stream;
1142 }
1143 
copy_data(worker_t * worker,h2_stream_t * stream,int pos)1144 static apr_status_t copy_data(worker_t *worker, h2_stream_t *stream, int pos) {
1145   enum { CALC, COPY };
1146   apr_status_t status = APR_SUCCESS;
1147   apr_size_t data_len;
1148   apr_table_entry_t *e;
1149   int i, action = 0;
1150 
1151   /* first calculate size, then copy data */
1152   for (action = CALC; action <= COPY; action++) {
1153     i = pos;
1154     data_len = 0;
1155 
1156     e = (apr_table_entry_t *)apr_table_elts(worker->cache)->elts;
1157     for (; i < apr_table_elts(worker->cache)->nelts; i++) {
1158       line_t line;
1159       line.info = e[i].key;
1160       line.buf = e[i].val;
1161       line.len = 0;
1162 
1163       if (strcmp("DEFER", line.info) == 0) {
1164         if (action == CALC) {
1165           event_t *event = apr_palloc(stream->p, sizeof(event_t));
1166           event->type = EVENT_DEFER;
1167           event->at = data_len;
1168           event->defer = 1;
1169           APR_RING_INSERT_TAIL(stream->events, event, _event_t, link);
1170         }
1171       }
1172       else if (strcmp("FLUSH", line.info) == 0) {
1173         if (action == CALC) {
1174           event_t *event = apr_palloc(stream->p, sizeof(event_t));
1175           event->type = EVENT_FLUSH;
1176           event->at = data_len;
1177           event->sleep = apr_atoi64(line.buf);
1178           APR_RING_INSERT_TAIL(stream->events, event, _event_t, link);
1179         }
1180         continue;
1181       }
1182 
1183       if (action == CALC && strstr(line.info, "resolve")) {
1184         int unresolved;
1185         e[i].val = line.buf =
1186             worker_replace_vars(worker, line.buf, &unresolved, worker->pbody);
1187       }
1188 
1189       if ((status = htt_run_line_flush(worker, &line)) != APR_SUCCESS) {
1190         return status;
1191       }
1192 
1193       if (strncasecmp(line.info, "NOCRLF:", 7) == 0) {
1194         line.len = apr_atoi64(&line.info[7]);
1195       } else if (strcasecmp(line.info, "NOCRLF") == 0) {
1196         line.len = strlen(line.buf);
1197       } else if (strcasecmp(line.info, "PLAIN") == 0) {
1198         /* add CRLF */
1199         if (action == CALC) {
1200           e[i].val = line.buf =
1201               apr_pstrcat(worker->pbody, line.buf, "\r\n", NULL);
1202         }
1203         line.len = strlen(line.buf);
1204       } else {
1205         line.len = strlen(line.buf);
1206       }
1207 
1208       if (action == COPY) {
1209         memcpy(&stream->data[data_len], line.buf, line.len);
1210       }
1211       data_len += line.len;
1212     }
1213 
1214     if (action == CALC) {
1215       stream->data_len = data_len;
1216       stream->data = apr_palloc(worker->pbody, data_len);
1217     }
1218   }
1219 
1220   return status;
1221 }
1222 
block_H2_SLEEP(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1223 apr_status_t block_H2_SLEEP(worker_t *worker, worker_t *parent,
1224                             apr_pool_t *ptmp) {
1225   const char *ms = store_get(worker->params, "1");
1226 
1227   apr_table_add(worker->cache, "FLUSH", ms);
1228 
1229   return APR_SUCCESS;
1230 }
1231 
block_H2_FLUSH(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1232 apr_status_t block_H2_FLUSH(worker_t *worker, worker_t *parent,
1233                             apr_pool_t *ptmp) {
1234   apr_table_add(worker->cache, "FLUSH", "0");
1235 
1236   return APR_SUCCESS;
1237 }
1238 
block_H2_DEFER(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1239 apr_status_t block_H2_DEFER(worker_t *worker, worker_t *parent,
1240                             apr_pool_t *ptmp) {
1241   apr_table_add(worker->cache, "DEFER", "");
1242 
1243   /* add marker */
1244   apr_table_add(worker->expect.headers, DEFER_MARKER, "");
1245   apr_table_add(worker->expect.dot, DEFER_MARKER, "");
1246   apr_table_add(worker->match.headers, DEFER_MARKER, "");
1247   apr_table_add(worker->match.dot, DEFER_MARKER, "");
1248 
1249   return APR_SUCCESS;
1250 }
1251 
block_H2_REQ(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1252 apr_status_t block_H2_REQ(worker_t *worker, worker_t *parent,
1253                           apr_pool_t *ptmp) {
1254   h2_wconf_t *wconf = h2_get_worker_config(parent);
1255   h2_sconf_t *sconf = h2_get_socket_config(parent);
1256   const char *method = store_get(worker->params, "1");
1257   const char *path = store_get(worker->params, "2");
1258   int i = 0, hdrn = 0, with_data = 0;
1259   apr_table_entry_t *e;
1260   apr_status_t status;
1261   h2_stream_t *stream;
1262   int32_t stream_id;
1263   worker_t *body;
1264   nghttp2_nv *hdrs;
1265 
1266   nghttp2_data_provider data_prd;
1267   data_prd.read_callback = h2_data_read_callback;
1268 
1269   if ((status = h2_open_session(parent)) != APR_SUCCESS) {
1270     return status;
1271   }
1272 
1273   if ((status = h2_worker_body(&body, parent, "_H2:END")) != APR_SUCCESS) {
1274     return status;
1275   }
1276 
1277   stream_id = nghttp2_session_get_next_stream_id(sconf->session);
1278   worker_log(parent, LOG_DEBUG, "new stream %d", stream_id);
1279 
1280   stream = h2_get_new_stream(parent, stream_id);
1281   wconf->current_stream = stream;
1282   status = body->interpret(body, parent, NULL);
1283 
1284   /* copy expectations */
1285   stream->expect.headers = apr_table_make(parent->pbody, 10);
1286   stream->expect.body = apr_table_make(parent->pbody, 10);
1287   stream->expect.dot = apr_table_make(parent->pbody, 10);
1288   stream->match.headers = apr_table_make(parent->pbody, 10);
1289   stream->match.body = apr_table_make(parent->pbody, 10);
1290   stream->match.dot = apr_table_make(parent->pbody, 10);
1291   apr_table_do(copy_table_entry, stream->expect.headers,
1292                parent->expect.headers, NULL);
1293   apr_table_do(copy_table_entry, stream->expect.body,
1294                parent->expect.body, NULL);
1295   apr_table_do(copy_table_entry, stream->expect.dot,
1296                parent->expect.dot, NULL);
1297   apr_table_do(copy_table_entry, stream->match.headers,
1298                parent->match.headers, NULL);
1299   apr_table_do(copy_table_entry, stream->match.body,
1300                parent->match.body, NULL);
1301   apr_table_do(copy_table_entry, stream->match.dot,
1302                parent->match.dot, NULL);
1303   apr_table_clear(parent->expect.headers);
1304   apr_table_clear(parent->expect.body);
1305   apr_table_clear(parent->expect.dot);
1306   apr_table_clear(parent->match.headers);
1307   apr_table_clear(parent->match.body);
1308   apr_table_clear(parent->match.dot);
1309 
1310   /* copy headers */
1311   e = (apr_table_entry_t *)apr_table_elts(parent->cache)->elts;
1312 
1313   while (i < apr_table_elts(parent->cache)->nelts && *e[i].val) {
1314     char *name, *val;
1315 
1316     name = apr_strtok(e[i].val, ":", &val);
1317     if (*val && apr_isspace(*val)) {
1318       val++;
1319     }
1320 
1321     if (strcasecmp("host", name) == 0)  {
1322       sconf->authority = val;
1323     }
1324     apr_table_add(stream->headers_out, name, val);
1325     i++;
1326   }
1327 
1328   /* jump over empty line that separates headers from data */
1329   if (++i >= apr_table_elts(parent->cache)->nelts) {
1330     /* no data */
1331     goto submit;
1332   }
1333 
1334   with_data = (i < apr_table_elts(parent->cache)->nelts);
1335   if (with_data) {
1336     /* copy data */
1337     if ((status = copy_data(parent, stream, i)) != APR_SUCCESS) {
1338       goto on_error;
1339     }
1340   }
1341 
1342 submit:
1343   apr_table_clear(parent->cache);
1344 
1345   hdrs = apr_pcalloc(parent->pbody, sizeof(*hdrs) * (4 + apr_table_elts(stream->headers_out)->nelts));
1346   nghttp2_nv meth_nv = MAKE_NV(":method", 7, method, strlen(method));
1347   nghttp2_nv path_nv = MAKE_NV(":path", 5, path, strlen(path));
1348   nghttp2_nv scheme_nv = MAKE_NV(":scheme", 7, "https", 5);
1349   nghttp2_nv auth_nv = MAKE_NV(":authority", 10, sconf->authority, strlen(sconf->authority));
1350   hdrs[hdrn++] = meth_nv;
1351   hdrs[hdrn++] = path_nv;
1352   hdrs[hdrn++] = scheme_nv;
1353   hdrs[hdrn++] = auth_nv;
1354 
1355   e = (apr_table_entry_t *) apr_table_elts(stream->headers_out)->elts;
1356   for (i = 0; i < apr_table_elts(stream->headers_out)->nelts; i++) {
1357     char *name = e[i].key;
1358     char *val = e[i].val;
1359 
1360     /* calculate content length (AUTO) */
1361     if (strcasecmp(name, "Content-Length") == 0 && *val == 0) {
1362       val = with_data ? apr_psprintf(parent->pbody, "%" APR_SIZE_T_FMT , stream->data_len) : "0";
1363     }
1364     nghttp2_nv hdr_nv = MAKE_NV(name, strlen(name), val, strlen(val));
1365     hdrs[hdrn++] = hdr_nv;
1366   }
1367 
1368   if (stream->data_len > 0) {
1369     /* data is submitted later in order to support deferring */
1370     stream_id =
1371         nghttp2_submit_headers(sconf->session, 0, -1, NULL, hdrs, hdrn, parent);
1372   } else {
1373     stream_id =
1374         nghttp2_submit_request(sconf->session, NULL, hdrs, hdrn, &data_prd, parent);
1375   }
1376 
1377   if (stream_id < 0) {
1378     worker_log(parent, LOG_ERR, "Could not submit request: %s",
1379                nghttp2_strerror(stream_id));
1380     return APR_EGENERAL;
1381   }
1382   apr_hash_set(wconf->streams, apr_itoa(parent->pbody, stream_id),
1383                APR_HASH_KEY_STRING, stream);
1384   wconf->open_streams++;
1385   wconf->current_stream = NULL;
1386 
1387 on_error:
1388   worker_body_end(body, parent);
1389   return status;
1390 }
1391 
block_H2_EXPECT(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1392 apr_status_t block_H2_EXPECT(worker_t *worker, worker_t *parent,
1393                              apr_pool_t *ptmp) {
1394   h2_wconf_t *wconf = h2_get_worker_config(parent);
1395   const char *cat = store_get(worker->params, "1");
1396   const char *expect = store_get(worker->params, "2");
1397   h2_stream_t *stream = wconf->current_stream;
1398   apr_status_t status = APR_SUCCESS;
1399 
1400   /* session expectations */
1401   if (strcmp(cat, "goaway") == 0) {
1402     wconf->goaway_expect = expect;
1403     status = APR_SUCCESS;
1404     goto exit;
1405   }
1406 
1407   /* stream expectations */
1408   if (!stream) {
1409     worker_log(worker, LOG_ERR, "Invalid scope for command, no submitted streams");
1410     status = APR_EINVAL;
1411     goto exit;
1412   }
1413 
1414   if (strcmp(cat, "bodysize") == 0) {
1415     stream->data_in_expect = apr_atoi64(expect);
1416   } else if (strcmp(cat, "rst_stream") == 0) {
1417     stream->reset_expect = 1;
1418   } else {
1419     worker_log(worker, LOG_ERR, "Invalid expectation");
1420     status = APR_EINVAL;
1421   }
1422 
1423 exit:
1424   return status;
1425 }
1426 
block_H2_PING(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1427 apr_status_t block_H2_PING(worker_t *worker, worker_t *parent, apr_pool_t *ptmp) {
1428   h2_sconf_t *sconf = h2_get_socket_config(parent);
1429   h2_wconf_t *wconf = h2_get_worker_config(parent);
1430   const char *data = store_get(worker->params, "1");
1431   apr_status_t status;
1432 
1433   if ((status = h2_open_session(parent)) != APR_SUCCESS) {
1434     return status;
1435   }
1436 
1437   if (nghttp2_submit_ping(sconf->session, NGHTTP2_FLAG_NONE,
1438                           (const uint8_t *)data) != 0) {
1439     worker_log(worker, LOG_ERR, "Could not submit PING");
1440     return APR_EINVAL;
1441   }
1442   wconf->pings++;
1443 
1444   return APR_SUCCESS;
1445 }
1446 
block_H2_GOAWAY(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1447 apr_status_t block_H2_GOAWAY(worker_t *worker, worker_t *parent, apr_pool_t *ptmp) {
1448   h2_sconf_t *sconf = h2_get_socket_config(parent);
1449   const char *error = store_get(worker->params, "1");
1450   const char *data = store_get(worker->params, "2");
1451   int32_t err_code;
1452   apr_status_t status;
1453 
1454   if ((status = h2_open_session(parent)) != APR_SUCCESS) {
1455     return status;
1456   }
1457 
1458   if (!error) {
1459     error = h2_error_code_array[0];
1460   }
1461   if ((err_code = h2_get_id_of(h2_error_code_array, error)) == -1) {
1462     worker_log(worker, LOG_ERR, "Invalid error code");
1463     return APR_EINVAL;
1464   }
1465   if (nghttp2_submit_goaway(
1466           sconf->session, NGHTTP2_FLAG_NONE,
1467           nghttp2_session_get_last_proc_stream_id(sconf->session),
1468           err_code , (void *)data,
1469           data ? strlen(data) : 0) != 0) {
1470 
1471     worker_log(worker, LOG_ERR, "Could not submit GOAWAY");
1472     return APR_EINVAL;
1473   }
1474 
1475   return APR_SUCCESS;
1476 }
1477 
1478 
1479 
block_H2_WAIT(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1480 apr_status_t block_H2_WAIT(worker_t *worker, worker_t *parent,
1481                            apr_pool_t *ptmp) {
1482   h2_wconf_t *wconf = h2_get_worker_config(parent);
1483   apr_status_t status;
1484   apr_hash_index_t *hi;
1485 
1486   if ((status = h2_open_session(parent)) != APR_SUCCESS) {
1487     return status;
1488   }
1489 
1490   status = mypoll(parent);
1491 
1492   if (wconf->goaway_expect && !wconf->goaway) {
1493     worker_log(worker, LOG_ERR, "EXPECT GOAWAY [reason=%s]", wconf->goaway_expect);
1494     status = APR_EGENERAL;
1495   } else if (wconf->goaway_expect && wconf->goaway) {
1496     status = APR_SUCCESS;
1497   } else if (status != APR_SUCCESS) {
1498     worker_log(worker, LOG_ERR, "error at reading from remote");
1499   }
1500 
1501 cleanup:
1502   for (hi = apr_hash_first(worker->pbody, wconf->streams); hi; hi = apr_hash_next(hi)) {
1503     h2_stream_t *stream;
1504     apr_hash_this(hi, NULL, NULL, (void **)&stream);
1505 
1506     if (stream->closed) {
1507       if (stream->reset_expect && !stream->reset_received) {
1508         worker_log(worker, LOG_ERR, "EXPECT RST_STREAM for stream %d", stream->id);
1509         return APR_EGENERAL;
1510       }
1511       worker_log(worker, LOG_DEBUG, "clean-up stream %p", stream);
1512       apr_pool_destroy(stream->p);
1513       apr_hash_set(wconf->streams, apr_itoa(parent->pbody, stream->id),
1514                          APR_HASH_KEY_STRING, NULL);
1515     }
1516   }
1517   wconf->goaway_expect = NULL;
1518 
1519   return status;
1520 }
1521 
1522 /************************************************************************
1523  * Hooks
1524  ************************************************************************/
h2_hook_pre_close(worker_t * worker)1525 apr_status_t h2_hook_pre_close(worker_t *worker) {
1526   h2_wconf_t *wconf = h2_get_worker_config(worker);
1527   h2_sconf_t *sconf = h2_get_socket_config(worker);
1528   apr_status_t status = APR_SUCCESS;
1529 
1530   if (!sconf || !sconf->session) {
1531     goto exit;
1532   }
1533   if (!wconf) {
1534     goto exit;
1535   }
1536   wconf->state = H2_STATE_CLOSED;
1537   module_set_config(worker->config, apr_pstrdup(worker->pbody, h2_module),
1538                     NULL);
1539 
1540 exit:
1541   return status;
1542 }
1543 
select_proto(unsigned char ** const out,unsigned char * outlen,const unsigned char * const in,unsigned int inlen,const unsigned char * const key)1544 int select_proto(      unsigned char ** const out, unsigned char *outlen,
1545                  const unsigned char *  const in,  unsigned int   inlen,
1546                  const unsigned char *  const key) {
1547   const char *p = (const char * const)in;
1548   const char * const end = (const char * const)(in + inlen);
1549 
1550   const char * const keyStr = (const char * const)key;
1551 
1552   for (; p + strlen(keyStr) <= end; p += *p + 1) {
1553     if (strncmp(p, keyStr, strlen(keyStr)) == 0) {
1554       /* From the manual of SSL_CTX_set_next_proto_select_cb():
1555        * "For the callback itself, out must be set to point to the selected protocol (which may be within in)."
1556        * This is why this "const-removing" cast is acceptable.*/
1557       *out = (unsigned char *)(p + 1);
1558       *outlen = *p;
1559       return 1;
1560     }
1561   }
1562 
1563   return 0;
1564 }
1565 
select_next_proto_cb(SSL * ssl,unsigned char ** out,unsigned char * outlen,const unsigned char * in,unsigned int inlen,void * arg)1566 int select_next_proto_cb(SSL *ssl, unsigned char **out,
1567                          unsigned char *outlen, const unsigned char *in,
1568                          unsigned int inlen, void *arg) {
1569   worker_t *worker = (worker_t *)arg;
1570   const unsigned char * const protos[] = {h2, h216, h214, NULL};
1571   int i;
1572 
1573   for (i = 0; protos[i]; i++) {
1574     if (select_proto(out, outlen, in, inlen, protos[i])) {
1575       worker_log(worker, LOG_DEBUG, "select %s", protos[i] + 1);
1576       return SSL_TLSEXT_ERR_OK;
1577     }
1578   }
1579 
1580   worker_log(worker, LOG_INFO, "remote does not offer h2 protocol");
1581   return ~ SSL_TLSEXT_ERR_OK;
1582 }
1583 
h2_hook_pre_connect(worker_t * worker)1584 static apr_status_t h2_hook_pre_connect(worker_t *worker) {
1585   h2_wconf_t *wconf = h2_get_worker_config(worker);
1586   SSL_CTX *ssl_ctx = ssl_get_ctx(worker);
1587 
1588   if (ssl_ctx && wconf->state & H2_STATE_INIT) {
1589     SSL_CTX_set_alpn_protos(ssl_ctx, h2, 3);
1590     SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, worker);
1591     wconf->state |= H2_STATE_NEGOTIATE;
1592   } else {
1593     /* reset in case of mixed protocol usage */
1594     /* SSL_CTX_set_alpn_protos(ssl_ctx, 0, 0); */
1595   }
1596 
1597   return APR_SUCCESS;
1598 }
1599 
h2_hook_close(worker_t * worker,char * info,char ** new_info)1600 static apr_status_t h2_hook_close(worker_t *worker, char *info, char **new_info) {
1601   h2_sconf_t *sconf = h2_get_socket_config(worker);
1602 
1603   worker_log(worker, LOG_DEBUG, "h2_hook_close worker: %" APR_UINT64_T_HEX_FMT
1604                                 ", info: %s, sconf: %" APR_UINT64_T_HEX_FMT,
1605              worker, info, sconf);
1606   nghttp2_session_del(sconf->session);
1607   sconf->session = NULL;
1608   sconf->ssl = NULL;
1609 
1610   return APR_SUCCESS;
1611 }
1612 
h2_hook_accept(worker_t * worker,char * data)1613 static apr_status_t h2_hook_accept(worker_t *worker, char *data) {
1614   h2_sconf_t *sconfig = h2_get_socket_config(worker);
1615   sconfig->is_server = 1;
1616 
1617   return APR_SUCCESS;
1618 }
1619 
1620 /************************************************************************
1621  * Module
1622  ***********************************************************************/
h2_module_init(global_t * global)1623 apr_status_t h2_module_init(global_t *global) {
1624   apr_status_t status;
1625   if ((status = module_command_new(global, "H2", "_STREAM_BUFFER", "<size>",
1626           "Set receive buffer size.",
1627           block_H2_STREAM_BUFFER)) != APR_SUCCESS) {
1628     return status;
1629   }
1630 
1631   if ((status = module_command_new(global, "H2", "_SESSION", "<host> SSL:<port> [<cert-file> <key-file> [<ca-cert-file>]]",
1632           "Connect to the remote peer and setup h2 session.\n"
1633           "<host>: host name or IPv4/IPv6 address (IPv6 address must be surrounded in square brackets)\n"
1634           "<cert-file>, <key-file> and <ca-cert-file> are optional for client/server authentication",
1635           block_H2_SESSION)) != APR_SUCCESS) {
1636     return status;
1637   }
1638 
1639   if ((status = module_command_new(global, "H2", "_UPGRADE", "",
1640           "Upgrade connection.",
1641           block_H2_UPGRADE)) != APR_SUCCESS) {
1642     return status;
1643   }
1644 
1645   if ((status = module_command_new(global, "H2", "_SETTINGS", "<settings>",
1646           "Submit session settings.",
1647           block_H2_SETTINGS)) != APR_SUCCESS) {
1648     return status;
1649   }
1650 
1651   if ((status = module_command_new(global, "H2", "_FLUSH", "",
1652           "Flush submitted data.",
1653           block_H2_FLUSH)) != APR_SUCCESS) {
1654     return status;
1655   }
1656 
1657   if ((status = module_command_new(global, "H2", "_DEFER", "",
1658           "Defers subsequent data frames until reception of new frames from the remote peer.",
1659           block_H2_DEFER)) != APR_SUCCESS) {
1660     return status;
1661   }
1662 
1663   if ((status = module_command_new(global, "H2", "_SLEEP", "<ms>",
1664           "Sleep <ms> milliseconds during the transmission of two consecutive data frames.",
1665           block_H2_SLEEP)) != APR_SUCCESS) {
1666     return status;
1667   }
1668 
1669   if ((status = module_command_new(global, "H2", "_REQ", "<method> <url>",
1670           "Submit request. Close the request body with _H2:END.",
1671           block_H2_REQ)) != APR_SUCCESS) {
1672     return status;
1673   }
1674 
1675   if ((status = module_command_new(global, "H2", "_EXPECT", "<category> <expectation>",
1676           "Possible expectations are\n"
1677           "  goaway <reason>\n"
1678           "    can be expected for a session, e.g. \"H2:EXPECT goaway timeout\"\n"
1679           "  bodysize <size>\n"
1680           "    can be expected for a stream, e.g. \"H2:EXPECT bodysize 1024\"\n"
1681           "  rst_stream\n"
1682           "    can be expected for a stream, no specific expectation required\n",
1683           block_H2_EXPECT)) != APR_SUCCESS) {
1684     return status;
1685   }
1686 
1687   if ((status = module_command_new(global, "H2", "_PING", "<8 byte>",
1688           "Send ping to the remote peer.",
1689           block_H2_PING)) != APR_SUCCESS) {
1690     return status;
1691   }
1692 
1693   if ((status = module_command_new(global, "H2", "_GOAWAY", "<error-code> <string>",
1694           "Send goaway to the remote peer.",
1695           block_H2_GOAWAY)) != APR_SUCCESS) {
1696     return status;
1697   }
1698 
1699   if ((status = module_command_new(global, "H2", "_WAIT", "",
1700           "Send and receive pending frames to/from the remote peer.",
1701           block_H2_WAIT)) != APR_SUCCESS) {
1702     return status;
1703   }
1704 
1705   htt_hook_accept(h2_hook_accept, NULL, NULL, 0);
1706   htt_hook_pre_connect(h2_hook_pre_connect, NULL, NULL, 0);
1707   htt_hook_pre_close(h2_hook_pre_close, NULL, NULL, 0);
1708   htt_hook_close(h2_hook_close, NULL, NULL, 0);
1709   return APR_SUCCESS;
1710 }
1711 
1712 
1713