]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
auth zone work on http feature.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Wed, 7 Feb 2018 16:10:31 +0000 (16:10 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Wed, 7 Feb 2018 16:10:31 +0000 (16:10 +0000)
git-svn-id: file:///svn/unbound/trunk@4517 be551aaa-1e26-0410-a405-d3ace91eadb9

services/authzone.c
services/outside_network.c
services/outside_network.h
testcode/fake_event.c
util/fptr_wlist.c
util/netevent.c
util/netevent.h

index 3dbcb4f09dfc94d20a84861e23388241cab990f5..8f1c2ea9054ab2eb525c2fba6fbf5f21bbbdca73 100644 (file)
@@ -4479,18 +4479,16 @@ xfr_transfer_init_fetch(struct auth_xfer* xfr, struct module_env* env)
                 * unless someone used unbound's host@port notation */
                if(strchr(master->host, '@') == NULL)
                        sockaddr_store_port(&addr, addrlen, master->port);
-               /* TODO http comm point, with NETEVENT_DONE */
-               /*
-               xfr->task_transfer->cp = outnet_comm_point_for_http(env->outnet,
-                       auth_xfer_transfer_http_callback, xfr, &addr, addrlen,
-                       env->scratch_buffer, AUTH_TRANSFER_TIMEOUT,
-                       master->ssl, master->host, master->file);
-               */
+               xfr->task_transfer->cp = outnet_comm_point_for_http(
+                       env->outnet, auth_xfer_transfer_http_callback, xfr,
+                       &addr, addrlen, AUTH_TRANSFER_TIMEOUT, master->ssl,
+                       master->host, master->file);
                if(!xfr->task_transfer->cp) {
                        char zname[255+1];
                        dname_str(xfr->name, zname);
-                       verbose(VERB_ALGO, "cannot create http cp connection for "
-                               "%s to %s", zname, master->host);
+                       verbose(VERB_ALGO, "cannot create http cp "
+                               "connection for %s to %s", zname,
+                               master->host);
                        return 0;
                }
                return 1;
@@ -5062,7 +5060,7 @@ auth_xfer_transfer_tcp_callback(struct comm_point* c, void* arg, int err,
 /** callback for task_transfer http connections */
 int
 auth_xfer_transfer_http_callback(struct comm_point* c, void* arg, int err,
-        struct comm_reply* ATTR_UNUSED(repinfo))
+        struct comm_reply* repinfo)
 {
        struct auth_xfer* xfr = (struct auth_xfer*)arg;
        struct module_env* env;
@@ -5079,6 +5077,8 @@ auth_xfer_transfer_http_callback(struct comm_point* c, void* arg, int err,
        failed:
                /* delete transferred data from list */
                auth_chunks_delete(xfr->task_transfer);
+               if(repinfo) repinfo->c = NULL; /* signal cp deleted to
+                               the routine calling this callback */
                comm_point_delete(xfr->task_transfer->cp);
                xfr->task_transfer->cp = NULL;
                xfr_transfer_nextmaster(xfr);
@@ -5097,6 +5097,8 @@ auth_xfer_transfer_http_callback(struct comm_point* c, void* arg, int err,
        }
        /* if the transfer is done now, disconnect and process the list */
        if(err == NETEVENT_DONE) {
+               if(repinfo) repinfo->c = NULL; /* signal cp deleted to
+                               the routine calling this callback */
                comm_point_delete(xfr->task_transfer->cp);
                xfr->task_transfer->cp = NULL;
                process_list_end_transfer(xfr, env);
index bdb48ad60842e3d08c03823fadbcffb148384697..0507d5a49c741b23d537a856c71c3e0e9fc5e9e9 100644 (file)
@@ -2257,6 +2257,79 @@ outnet_comm_point_for_tcp(struct outside_network* outnet,
        return cp;
 }
 
+/** setup http request headers in buffer for sending query to destination */
+static int
+setup_http_request(sldns_buffer* buf, char* host, char* path)
+{
+       sldns_buffer_clear(buf);
+       sldns_buffer_printf(buf, "GET /%s HTTP/1.1\r\n", path);
+       sldns_buffer_printf(buf, "Host: %s\r\n", host);
+       sldns_buffer_printf(buf, "User-Agent: unbound/%s\r\n",
+               PACKAGE_VERSION);
+       /* We do not really do multiple queries per connection,
+        * but this header setting is also not needed.
+        * sldns_buffer_printf(buf, "Connection: close\r\n") */
+       sldns_buffer_printf(buf, "\r\n");
+       if(sldns_buffer_position(buf)+10 > sldns_buffer_capacity(buf))
+               return 0; /* somehow buffer too short, but it is about 60K
+               and the request is only a couple bytes long. */
+       sldns_buffer_flip(buf);
+       return 1;
+}
+
+struct comm_point*
+outnet_comm_point_for_http(struct outside_network* outnet,
+       comm_point_callback_type* cb, void* cb_arg,
+       struct sockaddr_storage* to_addr, socklen_t to_addrlen, int timeout,
+       int ssl, char* host, char* path)
+{
+       /* cp calls cb with err=NETEVENT_DONE when transfer is done */
+       struct comm_point* cp;
+       int fd = outnet_get_tcp_fd(to_addr, to_addrlen, outnet->tcp_mss);
+       if(fd == -1) {
+               return 0;
+       }
+       fd_set_nonblock(fd);
+       if(!outnet_tcp_connect(fd, to_addr, to_addrlen)) {
+               /* outnet_tcp_connect has closed fd on error for us */
+               return 0;
+       }
+       cp = comm_point_create_http_out(outnet->base, 65552, cb, cb_arg,
+               outnet->udp_buff);
+       if(!cp) {
+               log_err("malloc failure");
+               close(fd);
+               return 0;
+       }
+       cp->repinfo.addrlen = to_addrlen;
+       memcpy(&cp->repinfo.addr, to_addr, to_addrlen);
+
+       /* setup for SSL (if needed) */
+       if(ssl) {
+               cp->ssl = outgoing_ssl_fd(outnet->sslctx, fd);
+               if(!cp->ssl) {
+                       log_err("cannot setup https");
+                       comm_point_delete(cp);
+                       return NULL;
+               }
+#ifdef USE_WINSOCK
+               comm_point_tcp_win_bio_cb(c, c->ssl);
+#endif
+               cp->ssl_shake_state = comm_ssl_shake_write;
+       }
+
+       /* set timeout on TCP connection */
+       comm_point_start_listening(cp, fd, timeout);
+
+       /* setup http request in cp->buffer */
+       if(!setup_http_request(cp->buffer, host, path)) {
+               log_err("error setting up http request");
+               comm_point_delete(cp);
+               return NULL;
+       }
+       return NULL;
+}
+
 /** get memory used by waiting tcp entry (in use or not) */
 static size_t
 waiting_tcp_get_mem(struct waiting_tcp* w)
index fc3cc44f7230e4c1716163753de205b87cef859b..09b2e6cedff62d4642207046a4eb42fc7b81f0d8 100644 (file)
@@ -572,6 +572,28 @@ struct comm_point* outnet_comm_point_for_tcp(struct outside_network* outnet,
        struct sockaddr_storage* to_addr, socklen_t to_addrlen,
        struct sldns_buffer* query, int timeout);
 
+/**
+ * Create http commpoint suitable for communication to the destination.
+ * Creates the http request buffer. It also performs connect() to the to_addr.
+ * @param outnet: outside_network with the comm_base it is attached to,
+ *     and the tcp_mss.
+ * @param cb: callback function for the commpoint.
+ * @param cb_arg: callback argument for cb.
+ * @param to_addr: intended destination.
+ * @param to_addrlen: length of to_addr.
+ * @param timeout: timeout for the TCP connection.
+ *     timeout in milliseconds, or -1 for no (change to the) timeout.
+ *     So seconds*1000.
+ * @param ssl: set to true for https.
+ * @param host: hostname to use for the destination. part of http request.
+ * @param path: pathname to lookup, eg. name of the file on the destination.
+ * @return http_out commpoint, or NULL.
+ */
+struct comm_point* outnet_comm_point_for_http(struct outside_network* outnet,
+       comm_point_callback_type* cb, void* cb_arg,
+       struct sockaddr_storage* to_addr, socklen_t to_addrlen, int timeout,
+       int ssl, char* host, char* path);
+
 /** connect tcp connection to addr, 0 on failure */
 int outnet_tcp_connect(int s, struct sockaddr_storage* addr, socklen_t addrlen);
 
index 376f9b3f734031f83eb9d2ace2ba939ea9bea5fa..300a42c154ab304ac19aa6d4b4d6099cc6911484 100644 (file)
@@ -75,8 +75,10 @@ struct fake_commpoint {
        int typecode;
        /** if this is a udp outgoing type of commpoint */
        int type_udp_out;
-       /** if this is a tcp outgoing tcp of commpoint */
+       /** if this is a tcp outgoing type of commpoint */
        int type_tcp_out;
+       /** if this is a http outgoing type of commpoint. */
+       int type_http_out;
 
        /** the callback, stored for usage */
        comm_point_callback_type* cb;
@@ -1420,6 +1422,12 @@ void comm_signal_callback(int ATTR_UNUSED(fd),
        log_assert(0);
 }
 
+void comm_point_http_handle_callback(int ATTR_UNUSED(fd), 
+       short ATTR_UNUSED(event), void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
 void comm_point_local_handle_callback(int ATTR_UNUSED(fd), 
        short ATTR_UNUSED(event), void* ATTR_UNUSED(arg))
 {
@@ -1676,6 +1684,36 @@ struct comm_point* outnet_comm_point_for_tcp(struct outside_network* outnet,
        return (struct comm_point*)fc;
 }
 
+struct comm_point* outnet_comm_point_for_http(struct outside_network* outnet,
+       comm_point_callback_type* cb, void* cb_arg,
+       struct sockaddr_storage* to_addr, socklen_t to_addrlen, int timeout,
+       int ssl, char* host, char* path)
+{
+       struct replay_runtime* runtime = (struct replay_runtime*)
+               outnet->base;
+       struct fake_commpoint* fc = (struct fake_commpoint*)calloc(1,
+               sizeof(*fc));
+       if(!fc) {
+               return NULL;
+       }
+       fc->typecode = FAKE_COMMPOINT_TYPECODE;
+       fc->type_http_out = 1;
+       fc->cb = cb;
+       fc->cb_arg = cb_arg;
+       fc->runtime = runtime;
+
+       (void)to_addr;
+       (void)to_addrlen;
+       (void)timeout;
+
+       (void)ssl;
+       (void)host;
+       (void)path;
+
+       /* handle http comm point and return contents from test script */
+       return (struct comm_point*)fc;
+}
+
 int comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet,
        struct sockaddr* addr, socklen_t addrlen) 
 {
index 32a6ba02e28dc0d2396a5951cc47fbba2d5d92bf..400a15de2ebb790f7477468d8d47d10eeb8112fd 100644 (file)
@@ -100,6 +100,7 @@ fptr_whitelist_comm_point(comm_point_callback_type *fptr)
        else if(fptr == &tube_handle_listen) return 1;
        else if(fptr == &auth_xfer_probe_udp_callback) return 1;
        else if(fptr == &auth_xfer_transfer_tcp_callback) return 1;
+       else if(fptr == &auth_xfer_transfer_http_callback) return 1;
        return 0;
 }
 
@@ -161,6 +162,7 @@ fptr_whitelist_event(void (*fptr)(int, short, void *))
        else if(fptr == &comm_point_raw_handle_callback) return 1;
        else if(fptr == &tube_handle_signal) return 1;
        else if(fptr == &comm_base_handle_slow_accept) return 1;
+       else if(fptr == &comm_point_http_handle_callback) return 1;
 #ifdef UB_ON_WINDOWS
        else if(fptr == &worker_win_stop_cb) return 1;
 #endif
index 7c0ced83b74c30ef4d3e446992482219cb129d9a..6c4d670266c7dc6cf4696929ef0593b221297b49 100644 (file)
@@ -1600,6 +1600,636 @@ comm_point_tcp_handle_callback(int fd, short event, void* arg)
        log_err("Ignored event %d for tcphdl.", event);
 }
 
+/** Make http handler free for next assignment */
+static void
+reclaim_http_handler(struct comm_point* c)
+{
+       log_assert(c->type == comm_http);
+       if(c->ssl) {
+#ifdef HAVE_SSL
+               SSL_shutdown(c->ssl);
+               SSL_free(c->ssl);
+               c->ssl = NULL;
+#endif
+       }
+       comm_point_close(c);
+       if(c->tcp_parent) {
+               c->tcp_parent->cur_tcp_count--;
+               c->tcp_free = c->tcp_parent->tcp_free;
+               c->tcp_parent->tcp_free = c;
+               if(!c->tcp_free) {
+                       /* re-enable listening on accept socket */
+                       comm_point_start_listening(c->tcp_parent, -1, -1);
+               }
+       }
+}
+
+/** read more data for http (with ssl) */
+static int
+ssl_http_read_more(struct comm_point* c)
+{
+#ifdef HAVE_SSL
+       int r;
+       log_assert(sldns_buffer_remaining(c->buffer) > 0);
+       ERR_clear_error();
+       r = SSL_read(c->ssl, (void*)sldns_buffer_current(c->buffer),
+               (int)sldns_buffer_remaining(c->buffer));
+       if(r <= 0) {
+               int want = SSL_get_error(c->ssl, r);
+               if(want == SSL_ERROR_ZERO_RETURN) {
+                       return 0; /* shutdown, closed */
+               } else if(want == SSL_ERROR_WANT_READ) {
+                       return 1; /* read more later */
+               } else if(want == SSL_ERROR_WANT_WRITE) {
+                       c->ssl_shake_state = comm_ssl_shake_hs_write;
+                       comm_point_listen_for_rw(c, 0, 1);
+                       return 1;
+               } else if(want == SSL_ERROR_SYSCALL) {
+                       if(errno != 0)
+                               log_err("SSL_read syscall: %s",
+                                       strerror(errno));
+                       return 0;
+               }
+               log_crypto_err("could not SSL_read");
+               return 0;
+       }
+       sldns_buffer_skip(c->buffer, (ssize_t)r);
+       return 1;
+#else
+       (void)c;
+       return 0;
+#endif /* HAVE_SSL */
+}
+
+/** read more data for http */
+static int
+http_read_more(int fd, struct comm_point* c)
+{
+       ssize_t r;
+       log_assert(sldns_buffer_remaining(c->buffer) > 0);
+       r = recv(fd, (void*)sldns_buffer_current(c->buffer), 
+               sldns_buffer_remaining(c->buffer), 0);
+       if(r == 0) {
+               return 0;
+       } else if(r == -1) {
+#ifndef USE_WINSOCK
+               if(errno == EINTR || errno == EAGAIN)
+                       return 1;
+               log_err_addr("read (in http r)", strerror(errno),
+                       &c->repinfo.addr, c->repinfo.addrlen);
+#else /* USE_WINSOCK */
+               if(WSAGetLastError() == WSAECONNRESET)
+                       return 0;
+               if(WSAGetLastError() == WSAEINPROGRESS)
+                       return 1;
+               if(WSAGetLastError() == WSAEWOULDBLOCK) {
+                       ub_winsock_tcp_wouldblock(c->ev->ev, UB_EV_READ);
+                       return 1;
+               }
+               log_err_addr("read (in http r)",
+                       wsa_strerror(WSAGetLastError()),
+                       &c->repinfo.addr, c->repinfo.addrlen);
+#endif
+               return 0;
+       }
+       sldns_buffer_skip(c->buffer, r);
+       return 1;
+}
+
+/** return true if http header has been read (one line complete) */
+static int
+http_header_done(sldns_buffer* buf)
+{
+       size_t i;
+       for(i=sldns_buffer_position(buf); i<sldns_buffer_limit(buf); i++) {
+               /* there was a \r before the \n, but we ignore that */
+               if((char)sldns_buffer_read_u8_at(buf, i) == '\n')
+                       return 1;
+       }
+       return 0;
+}
+
+/** return character string into buffer for header line, moves buffer
+ * past that line and puts zero terminator (into \r\n) */
+static char*
+http_header_line(sldns_buffer* buf)
+{
+       char* result = (char*)sldns_buffer_current(buf);
+       size_t i;
+       for(i=sldns_buffer_position(buf); i<sldns_buffer_limit(buf); i++) {
+               /* terminate the string on the \r */
+               if((char)sldns_buffer_read_u8_at(buf, i) == '\r')
+                       sldns_buffer_write_u8_at(buf, i, 0);
+               /* terminate on the \n and skip past the it and done */
+               if((char)sldns_buffer_read_u8_at(buf, i) == '\n') {
+                       sldns_buffer_write_u8_at(buf, i, 0);
+                       sldns_buffer_set_position(buf, i);
+                       return result;
+               }
+       }
+       return NULL;
+}
+
+/** move unread buffer to start and clear rest for putting the rest into it */
+static void
+http_moveover_buffer(sldns_buffer* buf)
+{
+       size_t pos = sldns_buffer_position(buf);
+       size_t len = sldns_buffer_remaining(buf);
+       sldns_buffer_clear(buf);
+       memmove(sldns_buffer_begin(buf), sldns_buffer_at(buf, pos), len);
+       sldns_buffer_set_position(buf, len);
+}
+
+/** a http header is complete, process it */
+static int
+http_process_initial_header(struct comm_point* c)
+{
+       char* line = http_header_line(c->buffer);
+       if(!line) return 1;
+       verbose(VERB_ALGO, "http header: %s", line);
+       if(strncasecmp(line, "HTTP/1.1 ", 9) == 0) {
+               /* check returncode */
+               if(line[9] != '2') {
+                       verbose(VERB_ALGO, "http bad status %s", line+9);
+                       return 0;
+               }
+       } else if(strncasecmp(line, "Content-Length: ", 16) == 0) {
+               if(!c->http_is_chunked)
+                       c->tcp_byte_count = (size_t)atoi(line+16);
+       } else if(strncasecmp(line, "Transfer-Encoding: chunked", 19+7) == 0) {
+               c->tcp_byte_count = 0;
+               c->http_is_chunked = 1;
+       } else if(line[0] == 0) {
+               /* end of initial headers */
+               c->http_in_headers = 0;
+               if(c->http_is_chunked)
+                       c->http_in_chunk_headers = 1;
+               /* remove header text from front of buffer */
+               http_moveover_buffer(c->buffer);
+               sldns_buffer_flip(c->buffer);
+               return 1;
+       }
+       /* ignore other headers */
+       return 1;
+}
+
+/** a chunk header is complete, process it, return 0=fail, 1=continue next
+ * header line, 2=done with chunked transfer*/
+static int
+http_process_chunk_header(struct comm_point* c)
+{
+       char* line = http_header_line(c->buffer);
+       if(!line) return 1;
+       if(c->http_in_chunk_headers == 3) {
+               verbose(VERB_ALGO, "http chunk trailer: %s", line);
+               /* are we done ? */
+               if(line[0] == 0 && c->tcp_byte_count == 0) {
+                       /* callback of http reader when NETEVENT_DONE,
+                        * end of data, with no data in buffer */
+                       sldns_buffer_set_position(c->buffer, 0);
+                       sldns_buffer_set_limit(c->buffer, 0);
+                       fptr_ok(fptr_whitelist_comm_point(c->callback));
+                       (void)(*c->callback)(c, c->cb_arg, NETEVENT_DONE, NULL);
+                       return 2;
+               }
+               if(line[0] == 0) {
+                       /* continue with header of the next chunk */
+                       c->http_in_chunk_headers = 1;
+                       /* remove header text from front of buffer */
+                       http_moveover_buffer(c->buffer);
+                       sldns_buffer_flip(c->buffer);
+                       return 1;
+               }
+               /* ignore further trail headers */
+               return 1;
+       }
+       verbose(VERB_ALGO, "http chunk header: %s", line);
+       if(c->http_in_chunk_headers == 1) {
+               /* read chunked start line */
+               char* end = NULL;
+               c->tcp_byte_count = (size_t)strtol(line, &end, 16);
+               if(end == line)
+                       return 0;
+               c->http_in_chunk_headers = 2;
+               return 1;
+       }
+       if(line[0] == 0) {
+               /* end of chunk headers */
+               c->http_in_chunk_headers = 0;
+               /* remove header text from front of buffer */
+               http_moveover_buffer(c->buffer);
+               sldns_buffer_flip(c->buffer);
+               if(c->tcp_byte_count == 0) {
+                       /* done with chunks, process chunk_trailer lines */
+                       c->http_in_chunk_headers = 3;
+               }
+       }
+       /* ignore other headers */
+       return 1;
+}
+
+/** handle nonchunked data segment */
+static int
+http_nonchunk_segment(struct comm_point* c)
+{
+       /* c->buffer at position..limit has new data we read in.
+        * the buffer itself is full of nonchunked data.
+        * we are looking to read tcp_byte_count more data
+        * and then the transfer is done. */
+       size_t remainbufferlen;
+       size_t got_now = sldns_buffer_remaining(c->buffer);
+       if(c->tcp_byte_count <= got_now) {
+               /* done, this is the last data fragment */
+               sldns_buffer_set_position(c->buffer, 0);
+               fptr_ok(fptr_whitelist_comm_point(c->callback));
+               (void)(*c->callback)(c, c->cb_arg, NETEVENT_DONE, NULL);
+               return 1;
+       }
+       c->tcp_byte_count -= got_now;
+       /* if we have lots of buffer space,
+        * read more data collected into the buffer */
+       remainbufferlen = sldns_buffer_capacity(c->buffer) -
+               sldns_buffer_limit(c->buffer);
+       if(remainbufferlen >= c->tcp_byte_count ||
+               remainbufferlen >= 1024) {
+               size_t total = sldns_buffer_limit(c->buffer);
+               sldns_buffer_clear(c->buffer);
+               sldns_buffer_set_position(c->buffer, total);
+               /* return and wait to read more */
+               return 1;
+       }
+       /* call callback with this data amount, then
+        * wait for more */
+       sldns_buffer_set_position(c->buffer, 0);
+       fptr_ok(fptr_whitelist_comm_point(c->callback));
+       (void)(*c->callback)(c, c->cb_arg, NETEVENT_NOERROR, NULL);
+       /* c->callback has to buffer_clear(c->buffer). */
+       /* return and wait to read more */
+       return 1;
+}
+
+/** handle nonchunked data segment, return 0=fail, 1=wait, 2=process more */
+static int
+http_chunked_segment(struct comm_point* c)
+{
+       /* the c->buffer has from position..limit new data we read. */
+       /* the current chunk has length tcp_byte_count.
+        * once we read that read more chunk headers.
+        */
+       size_t remainbufferlen;
+       size_t got_now = sldns_buffer_remaining(c->buffer);
+       if(c->tcp_byte_count <= got_now) {
+               /* the chunk has completed (with perhaps some extra data
+                * from next chunk header and next chunk) */
+               /* save too much info into temp buffer */
+               size_t fraglen;
+               struct comm_reply repinfo;
+               sldns_buffer_skip(c->buffer, c->tcp_byte_count);
+               c->tcp_byte_count = 0;
+               sldns_buffer_clear(c->http_temp);
+               sldns_buffer_write(c->http_temp,
+                       sldns_buffer_current(c->buffer),
+                       sldns_buffer_remaining(c->buffer));
+               sldns_buffer_flip(c->http_temp);
+
+               /* callback with this fragment */
+               fraglen = sldns_buffer_position(c->buffer);
+               sldns_buffer_set_position(c->buffer, 0);
+               sldns_buffer_set_limit(c->buffer, fraglen);
+               repinfo = c->repinfo;
+               fptr_ok(fptr_whitelist_comm_point(c->callback));
+               (void)(*c->callback)(c, c->cb_arg, NETEVENT_NOERROR, &repinfo);
+               /* c->callback has to buffer_clear(). */
+
+               /* is commpoint deleted? */
+               if(!repinfo.c) {
+                       return 1;
+               }
+               /* copy waiting info */
+               sldns_buffer_clear(c->buffer);
+               sldns_buffer_write(c->buffer,
+                       sldns_buffer_begin(c->http_temp),
+                       sldns_buffer_remaining(c->http_temp));
+               /* process end of chunk trailer header lines, until
+                * an empty line */
+               c->http_in_chunk_headers = 3;
+               /* process more data in buffer (if any) */
+               return 2;
+       }
+       c->tcp_byte_count -= got_now;
+
+       /* if we have lots of buffer space,
+        * read more data collected into the buffer */
+       remainbufferlen = sldns_buffer_capacity(c->buffer) -
+               sldns_buffer_limit(c->buffer);
+       if(remainbufferlen >= c->tcp_byte_count ||
+               remainbufferlen >= 1024) {
+               size_t total = sldns_buffer_limit(c->buffer);
+               sldns_buffer_clear(c->buffer);
+               sldns_buffer_set_position(c->buffer, total);
+               /* return and wait to read more */
+               return 1;
+       }
+       
+       /* callback of http reader for a new part of the data */
+       sldns_buffer_set_position(c->buffer, 0);
+       fptr_ok(fptr_whitelist_comm_point(c->callback));
+       (void)(*c->callback)(c, c->cb_arg, NETEVENT_NOERROR, NULL);
+       /* c->callback has to buffer_clear(c->buffer). */
+       /* return and wait to read more */
+       return 1;
+}
+
+/**
+ * Handle http reading callback. 
+ * @param fd: file descriptor of socket.
+ * @param c: comm point to read from into buffer.
+ * @return: 0 on error 
+ */
+static int
+comm_point_http_handle_read(int fd, struct comm_point* c)
+{
+       log_assert(c->type == comm_http);
+       if(!c->tcp_is_reading)
+               return 0;
+
+       log_assert(fd != -1);
+
+       /* if we are in ssl handshake, handle SSL handshake */
+       if(c->ssl && c->ssl_shake_state != comm_ssl_shake_none) {
+               if(!ssl_handshake(c))
+                       return 0;
+               if(c->ssl_shake_state != comm_ssl_shake_none)
+                       return 1;
+       }
+
+       /* read more data */
+       if(c->ssl) {
+               if(!ssl_http_read_more(c))
+                       return 0;
+       } else {
+               if(!http_read_more(fd, c))
+                       return 0;
+       }
+
+       sldns_buffer_flip(c->buffer);
+       while(sldns_buffer_remaining(c->buffer) > 0) {
+               /* if we are reading headers, read more headers */
+               if(c->http_in_headers || c->http_in_chunk_headers) {
+                       /* if header is done, process the header */
+                       if(!http_header_done(c->buffer)) {
+                               /* copy remaining data to front of buffer
+                                * and set rest for writing into it */
+                               http_moveover_buffer(c->buffer);
+                               /* return and wait to read more */
+                               return 1;
+                       }
+                       if(!c->http_in_chunk_headers) {
+                               /* process initial headers */
+                               if(!http_process_initial_header(c))
+                                       return 0;
+                       } else {
+                               /* process chunk headers */
+                               int r = http_process_chunk_header(c);
+                               if(r == 0) return 0;
+                               if(r == 2) return 1; /* done */
+                               /* r == 1, continue */
+                       }
+                       /* see if we have more to process */
+                       continue;
+               }
+
+               if(!c->http_is_chunked) {
+                       /* if we are reading nonchunks, process that*/
+                       return http_nonchunk_segment(c);
+               } else {
+                       /* if we are reading chunks, read the chunk */
+                       int r = http_chunked_segment(c);
+                       if(r == 0) return 0;
+                       if(r == 1) return 1;
+                       continue;
+               }
+       }
+       /* broke out of the loop; could not process header instead need
+        * to read more */
+       /* moveover any remaining data and read more data */
+       http_moveover_buffer(c->buffer);
+       /* return and wait to read more */
+       return 1;
+}
+
+/** check pending connect for http */
+static int
+http_check_connect(int fd, struct comm_point* c)
+{
+       /* check for pending error from nonblocking connect */
+       /* from Stevens, unix network programming, vol1, 3rd ed, p450*/
+       int error = 0;
+       socklen_t len = (socklen_t)sizeof(error);
+       if(getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&error, 
+               &len) < 0){
+#ifndef USE_WINSOCK
+               error = errno; /* on solaris errno is error */
+#else /* USE_WINSOCK */
+               error = WSAGetLastError();
+#endif
+       }
+#ifndef USE_WINSOCK
+#if defined(EINPROGRESS) && defined(EWOULDBLOCK)
+       if(error == EINPROGRESS || error == EWOULDBLOCK)
+               return 1; /* try again later */
+       else
+#endif
+       if(error != 0 && verbosity < 2)
+               return 0; /* silence lots of chatter in the logs */
+       else if(error != 0) {
+               log_err_addr("http connect", strerror(error),
+                       &c->repinfo.addr, c->repinfo.addrlen);
+#else /* USE_WINSOCK */
+       /* examine error */
+       if(error == WSAEINPROGRESS)
+               return 1;
+       else if(error == WSAEWOULDBLOCK) {
+               ub_winsock_tcp_wouldblock(c->ev->ev, UB_EV_WRITE);
+               return 1;
+       } else if(error != 0 && verbosity < 2)
+               return 0;
+       else if(error != 0) {
+               log_err_addr("http connect", wsa_strerror(error),
+                       &c->repinfo.addr, c->repinfo.addrlen);
+#endif /* USE_WINSOCK */
+               return 0;
+       }
+       /* keep on processing this socket */
+       return 2;
+}
+
+/** write more data for http (with ssl) */
+static int
+ssl_http_write_more(struct comm_point* c)
+{
+#ifdef HAVE_SSL
+       int r;
+       log_assert(sldns_buffer_remaining(c->buffer) > 0);
+       ERR_clear_error();
+       r = SSL_write(c->ssl, (void*)sldns_buffer_current(c->buffer),
+               (int)sldns_buffer_remaining(c->buffer));
+       if(r <= 0) {
+               int want = SSL_get_error(c->ssl, r);
+               if(want == SSL_ERROR_ZERO_RETURN) {
+                       return 0; /* closed */
+               } else if(want == SSL_ERROR_WANT_READ) {
+                       c->ssl_shake_state = comm_ssl_shake_read;
+                       comm_point_listen_for_rw(c, 1, 0);
+                       return 1; /* wait for read condition */
+               } else if(want == SSL_ERROR_WANT_WRITE) {
+                       return 1; /* write more later */
+               } else if(want == SSL_ERROR_SYSCALL) {
+                       if(errno != 0)
+                               log_err("SSL_write syscall: %s",
+                                       strerror(errno));
+                       return 0;
+               }
+               log_crypto_err("could not SSL_write");
+               return 0;
+       }
+       sldns_buffer_skip(c->buffer, (ssize_t)r);
+       return 1;
+#else
+       (void)c;
+       return 0;
+#endif /* HAVE_SSL */
+}
+
+/** write more data for http */
+static int
+http_write_more(int fd, struct comm_point* c)
+{
+       ssize_t r;
+       log_assert(sldns_buffer_remaining(c->buffer) > 0);
+       r = send(fd, (void*)sldns_buffer_current(c->buffer), 
+               sldns_buffer_remaining(c->buffer), 0);
+       if(r == -1) {
+#ifndef USE_WINSOCK
+               if(errno == EINTR || errno == EAGAIN)
+                       return 1;
+               log_err_addr("http send r", strerror(errno),
+                       &c->repinfo.addr, c->repinfo.addrlen);
+#else
+               if(WSAGetLastError() == WSAEINPROGRESS)
+                       return 1;
+               if(WSAGetLastError() == WSAEWOULDBLOCK) {
+                       ub_winsock_tcp_wouldblock(c->ev->ev, UB_EV_WRITE);
+                       return 1; 
+               }
+               log_err_addr("http send r", wsa_strerror(WSAGetLastError()),
+                       &c->repinfo.addr, c->repinfo.addrlen);
+#endif
+               return 0;
+       }
+       sldns_buffer_skip(c->buffer, r);
+       return 1;
+}
+
+/** 
+ * Handle http writing callback. 
+ * @param fd: file descriptor of socket.
+ * @param c: comm point to write buffer out of.
+ * @return: 0 on error
+ */
+static int
+comm_point_http_handle_write(int fd, struct comm_point* c)
+{
+       log_assert(c->type == comm_http);
+       if(c->tcp_is_reading)
+               return 0;
+
+       log_assert(fd != -1);
+
+       /* check pending connect errors, if that fails, we wait for more,
+        * or we can continue to write contents */
+       if(c->tcp_check_nb_connect) {
+               int r = http_check_connect(fd, c);
+               if(r == 0) return 0;
+               if(r == 1) return 1;
+               c->tcp_check_nb_connect = 0;
+       }
+       /* if we are in ssl handshake, handle SSL handshake */
+       if(c->ssl && c->ssl_shake_state != comm_ssl_shake_none) {
+               if(!ssl_handshake(c))
+                       return 0;
+               if(c->ssl_shake_state != comm_ssl_shake_none)
+                       return 1;
+       }
+       /* if we are writing, write more */
+       if(c->ssl) {
+               if(!ssl_http_write_more(c))
+                       return 0;
+       } else {
+               if(!http_write_more(fd, c))
+                       return 0;
+       }
+
+       /* we write a single buffer contents, that can contain
+        * the http request, and then flip to read the results */
+       /* see if write is done */
+       if(sldns_buffer_remaining(c->buffer) == 0) {
+               sldns_buffer_clear(c->buffer);
+               if(c->tcp_do_toggle_rw)
+                       c->tcp_is_reading = 1;
+               c->tcp_byte_count = 0;
+               /* switch from listening(write) to listening(read) */
+               comm_point_stop_listening(c);
+               comm_point_start_listening(c, -1, -1);
+       }
+       return 1;
+}
+
+void 
+comm_point_http_handle_callback(int fd, short event, void* arg)
+{
+       struct comm_point* c = (struct comm_point*)arg;
+       log_assert(c->type == comm_http);
+       ub_comm_base_now(c->ev->base);
+
+       if(event&UB_EV_READ) {
+               if(!comm_point_http_handle_read(fd, c)) {
+                       reclaim_http_handler(c);
+                       if(!c->tcp_do_close) {
+                               fptr_ok(fptr_whitelist_comm_point(
+                                       c->callback));
+                               (void)(*c->callback)(c, c->cb_arg, 
+                                       NETEVENT_CLOSED, NULL);
+                       }
+               }
+               return;
+       }
+       if(event&UB_EV_WRITE) {
+               if(!comm_point_http_handle_write(fd, c)) {
+                       reclaim_http_handler(c);
+                       if(!c->tcp_do_close) {
+                               fptr_ok(fptr_whitelist_comm_point(
+                                       c->callback));
+                               (void)(*c->callback)(c, c->cb_arg, 
+                                       NETEVENT_CLOSED, NULL);
+                       }
+               }
+               return;
+       }
+       if(event&UB_EV_TIMEOUT) {
+               verbose(VERB_QUERY, "http took too long, dropped");
+               reclaim_http_handler(c);
+               if(!c->tcp_do_close) {
+                       fptr_ok(fptr_whitelist_comm_point(c->callback));
+                       (void)(*c->callback)(c, c->cb_arg,
+                               NETEVENT_TIMEOUT, NULL);
+               }
+               return;
+       }
+       log_err("Ignored event %d for httphdl.", event);
+}
+
 void comm_point_local_handle_callback(int fd, short event, void* arg)
 {
        struct comm_point* c = (struct comm_point*)arg;
@@ -1957,6 +2587,73 @@ comm_point_create_tcp_out(struct comm_base *base, size_t bufsize,
        return c;
 }
 
+struct comm_point* 
+comm_point_create_http_out(struct comm_base *base, size_t bufsize,
+        comm_point_callback_type* callback, void* callback_arg,
+       sldns_buffer* temp)
+{
+       struct comm_point* c = (struct comm_point*)calloc(1,
+               sizeof(struct comm_point));
+       short evbits;
+       if(!c)
+               return NULL;
+       c->ev = (struct internal_event*)calloc(1,
+               sizeof(struct internal_event));
+       if(!c->ev) {
+               free(c);
+               return NULL;
+       }
+       c->ev->base = base;
+       c->fd = -1;
+       c->buffer = sldns_buffer_new(bufsize);
+       if(!c->buffer) {
+               free(c->ev);
+               free(c);
+               return NULL;
+       }
+       c->timeout = NULL;
+       c->tcp_is_reading = 0;
+       c->tcp_byte_count = 0;
+       c->tcp_parent = NULL;
+       c->max_tcp_count = 0;
+       c->cur_tcp_count = 0;
+       c->tcp_handlers = NULL;
+       c->tcp_free = NULL;
+       c->type = comm_http;
+       c->tcp_do_close = 0;
+       c->do_not_close = 0;
+       c->tcp_do_toggle_rw = 1;
+       c->tcp_check_nb_connect = 1;
+       c->http_in_headers = 1;
+       c->http_in_chunk_headers = 0;
+       c->http_is_chunked = 0;
+       c->http_temp = temp;
+#ifdef USE_MSG_FASTOPEN
+       c->tcp_do_fastopen = 1;
+#endif
+#ifdef USE_DNSCRYPT
+       c->dnscrypt = 0;
+       c->dnscrypt_buffer = c->buffer;
+#endif
+       c->repinfo.c = c;
+       c->callback = callback;
+       c->cb_arg = callback_arg;
+       evbits = UB_EV_PERSIST | UB_EV_WRITE;
+       c->ev->ev = ub_event_new(base->eb->base, c->fd, evbits,
+               comm_point_http_handle_callback, c);
+       if(c->ev->ev == NULL)
+       {
+               log_err("could not baseset tcpout event");
+               SSL_free(c->ssl);
+               sldns_buffer_free(c->buffer);
+               free(c->ev);
+               free(c);
+               return NULL;
+       }
+
+       return c;
+}
+
 struct comm_point* 
 comm_point_create_local(struct comm_base *base, int fd, size_t bufsize,
         comm_point_callback_type* callback, void* callback_arg)
index be221e0deba37c57fa2e3bac9cb9a969d7c8da2a..4f73ab7f5c4f05cac50e338b3351e3e30d1b8341 100644 (file)
@@ -203,6 +203,17 @@ struct comm_point {
                comm_ssl_shake_hs_write
        } ssl_shake_state;
 
+       /* -------- HTTP ------- */
+       /** Currently reading in http headers */
+       int http_in_headers;
+       /** Currently reading in chunk headers, 0=not, 1=firstline, 2=rest
+        * of chunk header, 3=trailer headers after chunk */
+       int http_in_chunk_headers;
+       /** chunked transfer */
+       int http_is_chunked;
+       /** http temp buffer (shared buffer for temporary work) */
+       struct sldns_buffer* http_temp;
+
        /* -------- dnstap ------- */
        /** the dnstap environment */
        struct dt_env* dtenv;
@@ -215,6 +226,8 @@ struct comm_point {
                comm_tcp_accept, 
                /** TCP handler socket - handle byteperbyte readwrite. */
                comm_tcp,
+               /** HTTP handler socket */
+               comm_http,
                /** AF_UNIX socket - for internal commands. */
                comm_local,
                /** raw - not DNS format - for pipe readers and writers */
@@ -451,6 +464,20 @@ struct comm_point* comm_point_create_tcp(struct comm_base* base,
 struct comm_point* comm_point_create_tcp_out(struct comm_base* base,
        size_t bufsize, comm_point_callback_type* callback, void* callback_arg);
 
+/**
+ * Create an outgoing HTTP commpoint. No file descriptor is opened, left at -1.
+ * @param base: in which base to alloc the commpoint.
+ * @param bufsize: size of buffer to create for handlers.
+ * @param callback: callback function pointer for the handler.
+ * @param callback_arg: will be passed to your callback function.
+ * @param temp: sldns buffer, shared between other http_out commpoints, for
+ *     temporary data when performing callbacks.
+ * @return: the commpoint or NULL on error.
+ */
+struct comm_point* comm_point_create_http_out(struct comm_base* base,
+       size_t bufsize, comm_point_callback_type* callback,
+       void* callback_arg, struct sldns_buffer* temp);
+
 /**
  * Create commpoint to listen to a local domain file descriptor.
  * @param base: in which base to alloc the commpoint.
@@ -667,6 +694,16 @@ void comm_point_tcp_accept_callback(int fd, short event, void* arg);
  */
 void comm_point_tcp_handle_callback(int fd, short event, void* arg);
 
+/**
+ * This routine is published for checks and tests, and is only used internally.
+ * handle libevent callback for tcp data comm point
+ * @param fd: file descriptor.
+ * @param event: event bits from libevent: 
+ *     EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+void comm_point_http_handle_callback(int fd, short event, void* arg);
+
 /**
  * This routine is published for checks and tests, and is only used internally.
  * handle libevent callback for timer comm.