diff --git a/hostapd/Makefile b/hostapd/Makefile index 169177ebb..2933d0dd6 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -310,6 +310,7 @@ OBJS += ../src/wps/wps_upnp_ssdp.o OBJS += ../src/wps/wps_upnp_web.o OBJS += ../src/wps/wps_upnp_event.o OBJS += ../src/wps/httpread.o +OBJS += ../src/wps/http_client.o endif endif diff --git a/src/wps/http_client.c b/src/wps/http_client.c new file mode 100644 index 000000000..bdf63c041 --- /dev/null +++ b/src/wps/http_client.c @@ -0,0 +1,299 @@ +/** + * http_client - HTTP client + * Copyright (c) 2009, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#include "includes.h" +#include + +#include "common.h" +#include "eloop.h" +#include "httpread.h" +#include "http_client.h" + + +#define HTTP_CLIENT_TIMEOUT 30 + + +struct http_client { + struct sockaddr_in dst; + int sd; + struct wpabuf *req; + size_t req_pos; + size_t max_response; + + void (*cb)(void *ctx, struct http_client *c, + enum http_client_event event); + void *cb_ctx; + struct httpread *hread; + struct wpabuf body; +}; + + +static void http_client_timeout(void *eloop_data, void *user_ctx) +{ + struct http_client *c = eloop_data; + wpa_printf(MSG_DEBUG, "HTTP: Timeout"); + c->cb(c->cb_ctx, c, HTTP_CLIENT_TIMEOUT); +} + + +static void http_client_got_response(struct httpread *handle, void *cookie, + enum httpread_event e) +{ + struct http_client *c = cookie; + + eloop_cancel_timeout(http_client_timeout, c, NULL); + switch (e) { + case HTTPREAD_EVENT_FILE_READY: + if (httpread_hdr_type_get(c->hread) == HTTPREAD_HDR_TYPE_REPLY) + { + int reply_code = httpread_reply_code_get(c->hread); + if (reply_code == 200 /* OK */) { + wpa_printf(MSG_DEBUG, "HTTP: Response OK from " + "%s:%d", + inet_ntoa(c->dst.sin_addr), + ntohs(c->dst.sin_port)); + c->cb(c->cb_ctx, c, HTTP_CLIENT_OK); + } else { + wpa_printf(MSG_DEBUG, "HTTP: Error %d from " + "%s:%d", reply_code, + inet_ntoa(c->dst.sin_addr), + ntohs(c->dst.sin_port)); + c->cb(c->cb_ctx, c, HTTP_CLIENT_INVALID_REPLY); + } + } else + c->cb(c->cb_ctx, c, HTTP_CLIENT_INVALID_REPLY); + break; + case HTTPREAD_EVENT_TIMEOUT: + c->cb(c->cb_ctx, c, HTTP_CLIENT_TIMEOUT); + break; + case HTTPREAD_EVENT_ERROR: + c->cb(c->cb_ctx, c, HTTP_CLIENT_FAILED); + break; + } +} + + +static void http_client_tx_ready(int sock, void *eloop_ctx, void *sock_ctx) +{ + struct http_client *c = eloop_ctx; + int res; + + wpa_printf(MSG_DEBUG, "HTTP: Send client request to %s:%d (%lu of %lu " + "bytes remaining)", + inet_ntoa(c->dst.sin_addr), ntohs(c->dst.sin_port), + (unsigned long) wpabuf_len(c->req), + (unsigned long) wpabuf_len(c->req) - c->req_pos); + + res = send(c->sd, wpabuf_head(c->req) + c->req_pos, + wpabuf_len(c->req) - c->req_pos, 0); + if (res < 0) { + wpa_printf(MSG_DEBUG, "HTTP: Failed to send buffer: %s", + strerror(errno)); + eloop_unregister_sock(c->sd, EVENT_TYPE_WRITE); + c->cb(c->cb_ctx, c, HTTP_CLIENT_FAILED); + return; + } + + if ((size_t) res < wpabuf_len(c->req) - c->req_pos) { + wpa_printf(MSG_DEBUG, "HTTP: Sent %d of %lu bytes; %lu bytes " + "remaining", + res, (unsigned long) wpabuf_len(c->req), + (unsigned long) wpabuf_len(c->req) - c->req_pos - + res); + c->req_pos += res; + return; + } + + wpa_printf(MSG_DEBUG, "HTTP: Full client request sent to %s:%d", + inet_ntoa(c->dst.sin_addr), ntohs(c->dst.sin_port)); + eloop_unregister_sock(c->sd, EVENT_TYPE_WRITE); + wpabuf_free(c->req); + c->req = NULL; + + c->hread = httpread_create(c->sd, http_client_got_response, c, + c->max_response, HTTP_CLIENT_TIMEOUT); + if (c->hread == NULL) { + c->cb(c->cb_ctx, c, HTTP_CLIENT_FAILED); + return; + } +} + + +struct http_client * http_client_addr(struct sockaddr_in *dst, + struct wpabuf *req, size_t max_response, + void (*cb)(void *ctx, + struct http_client *c, + enum http_client_event event), + void *cb_ctx) +{ + struct http_client *c; + + c = os_zalloc(sizeof(*c)); + if (c == NULL) + return NULL; + c->sd = -1; + c->dst = *dst; + c->max_response = max_response; + c->cb = cb; + c->cb_ctx = cb_ctx; + + c->sd = socket(AF_INET, SOCK_STREAM, 0); + if (c->sd < 0) { + http_client_free(c); + return NULL; + } + + if (fcntl(c->sd, F_SETFL, O_NONBLOCK) != 0) { + wpa_printf(MSG_DEBUG, "HTTP: fnctl(O_NONBLOCK) failed: %s", + strerror(errno)); + http_client_free(c); + return NULL; + } + + if (connect(c->sd, (struct sockaddr *) dst, sizeof(*dst))) { + if (errno != EINPROGRESS) { + wpa_printf(MSG_DEBUG, "HTTP: Failed to connect: %s", + strerror(errno)); + http_client_free(c); + return NULL; + } + + /* + * Continue connecting in the background; eloop will call us + * once the connection is ready (or failed). + */ + } + + if (eloop_register_sock(c->sd, EVENT_TYPE_WRITE, http_client_tx_ready, + c, NULL)) { + http_client_free(c); + return NULL; + } + + if (eloop_register_timeout(HTTP_CLIENT_TIMEOUT, 0, http_client_timeout, + c, NULL)) { + http_client_free(c); + return NULL; + } + + c->req = req; + + return c; +} + + +struct http_client * http_client_url(const char *url, + struct wpabuf *req, size_t max_response, + void (*cb)(void *ctx, + struct http_client *c, + enum http_client_event event), + void *cb_ctx) +{ + struct sockaddr_in dst; + struct http_client *c; + char *u, *addr, *port, *path; + struct wpabuf *req_buf = NULL; + + if (os_strncmp(url, "http://", 7) != 0) + return NULL; + u = os_strdup(url); + if (u == NULL) + return NULL; + os_memset(&dst, 0, sizeof(dst)); + dst.sin_family = AF_INET; + addr = u + 7; + path = os_strchr(addr, '/'); + port = os_strchr(addr, ':'); + if (path == NULL) { + path = "/"; + } else { + *path = '\0'; /* temporary nul termination for address */ + if (port > path) + port = NULL; + } + if (port) + *port++ = '\0'; + + if (inet_aton(addr, &dst.sin_addr) == 0) { + /* TODO: name lookup */ + wpa_printf(MSG_DEBUG, "HTTP: Unsupported address in URL '%s' " + "(addr='%s' port='%s')", + url, addr, port); + os_free(u); + return NULL; + } + + if (port) + dst.sin_port = htons(atoi(port)); + else + dst.sin_port = htons(80); + + if (*path == '\0') { + /* remove temporary nul termination for address */ + *path = '/'; + } + + if (req == NULL) { + req_buf = wpabuf_alloc(os_strlen(url) + 1000); + if (req_buf == NULL) { + os_free(u); + return NULL; + } + req = req_buf; + wpabuf_printf(req, + "GET %s HTTP/1.1\r\n" + "Cache-Control: no-cache\r\n" + "Pragma: no-cache\r\n" + "Accept: text/xml, application/xml\r\n" + "User-Agent: wpa_supplicant\r\n" + "Host: %s:%d\r\n" + "\r\n", + path, inet_ntoa(dst.sin_addr), + ntohs(dst.sin_port)); + } + os_free(u); + + c = http_client_addr(&dst, req, max_response, cb, cb_ctx); + if (c == NULL) { + wpabuf_free(req_buf); + return NULL; + } + + return c; +} + + +void http_client_free(struct http_client *c) +{ + if (c == NULL) + return; + httpread_destroy(c->hread); + wpabuf_free(c->req); + if (c->sd >= 0) { + eloop_unregister_sock(c->sd, EVENT_TYPE_WRITE); + close(c->sd); + } + eloop_cancel_timeout(http_client_timeout, c, NULL); + os_free(c); +} + + +struct wpabuf * http_client_get_body(struct http_client *c) +{ + if (c->hread == NULL) + return NULL; + wpabuf_set(&c->body, httpread_data_get(c->hread), + httpread_length_get(c->hread)); + return &c->body; +} diff --git a/src/wps/http_client.h b/src/wps/http_client.h new file mode 100644 index 000000000..84c1ac462 --- /dev/null +++ b/src/wps/http_client.h @@ -0,0 +1,42 @@ +/** + * http_client - HTTP client + * Copyright (c) 2009, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#ifndef HTTP_CLIENT_H +#define HTTP_CLIENT_H + +struct http_client; + +enum http_client_event { + HTTP_CLIENT_FAILED, + HTTP_CLIENT_TIMEOUT, + HTTP_CLIENT_OK, + HTTP_CLIENT_INVALID_REPLY, +}; + +struct http_client * http_client_addr(struct sockaddr_in *dst, + struct wpabuf *req, size_t max_response, + void (*cb)(void *ctx, + struct http_client *c, + enum http_client_event event), + void *cb_ctx); +struct http_client * http_client_url(const char *url, + struct wpabuf *req, size_t max_response, + void (*cb)(void *ctx, + struct http_client *c, + enum http_client_event event), + void *cb_ctx); +void http_client_free(struct http_client *c); +struct wpabuf * http_client_get_body(struct http_client *c); + +#endif /* HTTP_CLIENT_H */ diff --git a/src/wps/wps_upnp_event.c b/src/wps/wps_upnp_event.c index 4122a87de..2827ca27a 100644 --- a/src/wps/wps_upnp_event.c +++ b/src/wps/wps_upnp_event.c @@ -10,12 +10,11 @@ #include "includes.h" #include -#include #include "common.h" #include "eloop.h" #include "uuid.h" -#include "httpread.h" +#include "http_client.h" #include "wps_defs.h" #include "wps_upnp.h" #include "wps_upnp_i.h" @@ -54,35 +53,19 @@ struct wps_event_ { int retry; /* which retry */ struct subscr_addr *addr; /* address to connect to */ struct wpabuf *data; /* event data to send */ - /* The following apply while we are sending an event message. */ - int sd; /* -1 or socket descriptor for open connection */ - int sd_registered; /* nonzero if we must cancel registration */ - struct httpread *hread; /* NULL or open connection for event msg */ + struct http_client *http_event; }; -static void event_timeout_handler(void *eloop_data, void *user_ctx); - /* event_clean -- clean sockets etc. of event * Leaves data, retry count etc. alone. */ static void event_clean(struct wps_event_ *e) { - if (e->s->current_event == e) { - eloop_cancel_timeout(event_timeout_handler, NULL, e); + if (e->s->current_event == e) e->s->current_event = NULL; - } - if (e->sd_registered) { - eloop_unregister_sock(e->sd, EVENT_TYPE_WRITE); - e->sd_registered = 0; - } - if (e->sd != -1) { - close(e->sd); - e->sd = -1; - } - if (e->hread) - httpread_destroy(e->hread); - e->hread = NULL; + http_client_free(e->http_event); + e->http_event = NULL; } @@ -197,104 +180,14 @@ static void event_retry(struct wps_event_ *e, int do_next_address) } -/* called if the overall event-sending process takes too long */ -static void event_timeout_handler(void *eloop_data, void *user_ctx) +static struct wpabuf * event_build_message(struct wps_event_ *e) { - struct wps_event_ *e = user_ctx; - struct subscription *s = e->s; - - assert(e == s->current_event); - - wpa_printf(MSG_DEBUG, "WPS UPnP: Event send timeout"); - event_retry(e, 1); -} - - -/* event_got_response_handler -- called back when http response is received. */ -static void event_got_response_handler(struct httpread *handle, void *cookie, - enum httpread_event en) -{ - struct wps_event_ *e = cookie; - struct subscription *s = e->s; - struct upnp_wps_device_sm *sm = s->sm; - struct httpread *hread = e->hread; - int reply_code = 0; - - assert(e == s->current_event); - eloop_cancel_timeout(event_timeout_handler, NULL, e); - - if (en == HTTPREAD_EVENT_FILE_READY) { - if (httpread_hdr_type_get(hread) == HTTPREAD_HDR_TYPE_REPLY) { - reply_code = httpread_reply_code_get(hread); - if (reply_code == HTTP_OK) { - wpa_printf(MSG_DEBUG, - "WPS UPnP: Got event reply OK from " - "%s", e->addr->domain_and_port); - event_delete(e); - goto send_more; - } else { - wpa_printf(MSG_DEBUG, "WPS UPnP: Got event " - "error reply code %d from %s", - reply_code, - e->addr->domain_and_port); - goto bad; - } - } else { - wpa_printf(MSG_DEBUG, "WPS UPnP: Got bogus event " - "response %d from %s", en, - e->addr->domain_and_port); - } - } else { - wpa_printf(MSG_DEBUG, "WPS UPnP: Event response timeout/fail " - "for %s", e->addr->domain_and_port); - goto bad; - } - event_retry(e, 1); - goto send_more; - -send_more: - /* Schedule sending more if there is more to send */ - if (s->event_queue) - event_send_all_later(sm); - return; - -bad: - /* - * If other side doesn't like what we say, forget about them. - * (There is no way to tell other side that we are dropping - * them...). - * Alternately, we could just do event_delete(e) - */ - wpa_printf(MSG_DEBUG, "WPS UPnP: Deleting subscription due to errors"); - subscription_unlink(s); - subscription_destroy(s); -} - - -/* event_send_tx_ready -- actually write event message - * - * Prequisite: subscription socket descriptor has become ready to - * write (because connection to subscriber has been made). - * - * It is also possible that we are called because the connect has failed; - * it is possible to test for this, or we can just go ahead and then - * the write will fail. - */ -static void event_send_tx_ready(int sock, void *eloop_ctx, void *sock_ctx) -{ - struct wps_event_ *e = sock_ctx; - struct subscription *s = e->s; struct wpabuf *buf; char *b; - assert(e == s->current_event); - assert(e->sd == sock); - buf = wpabuf_alloc(1000 + wpabuf_len(e->data)); - if (buf == NULL) { - event_retry(e, 0); - goto bad; - } + if (buf == NULL) + return NULL; wpabuf_printf(buf, "NOTIFY %s HTTP/1.1\r\n", e->addr->path); wpabuf_put_str(buf, "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"); wpabuf_printf(buf, "HOST: %s\r\n", e->addr->domain_and_port); @@ -303,7 +196,7 @@ static void event_send_tx_ready(int sock, void *eloop_ctx, void *sock_ctx) "NTS: upnp:propchange\r\n"); wpabuf_put_str(buf, "SID: uuid:"); b = wpabuf_put(buf, 0); - uuid_bin2str(s->uuid, b, 80); + uuid_bin2str(e->s->uuid, b, 80); wpabuf_put(buf, os_strlen(b)); wpabuf_put_str(buf, "\r\n"); wpabuf_printf(buf, "SEQ: %u\r\n", e->subscriber_sequence); @@ -311,45 +204,47 @@ static void event_send_tx_ready(int sock, void *eloop_ctx, void *sock_ctx) (int) wpabuf_len(e->data)); wpabuf_put_str(buf, "\r\n"); /* terminating empty line */ wpabuf_put_buf(buf, e->data); + return buf; +} - /* Since the message size is pretty small, we should be - * able to get the operating system to buffer what we give it - * and not have to come back again later to write more... - */ -#if 0 - /* we could: Turn blocking back on? */ - fcntl(e->sd, F_SETFL, 0); -#endif - wpa_printf(MSG_DEBUG, "WPS UPnP: Sending event to %s", - e->addr->domain_and_port); - if (send_wpabuf(e->sd, buf) < 0) { + +static void event_http_cb(void *ctx, struct http_client *c, + enum http_client_event event) +{ + struct wps_event_ *e = ctx; + struct subscription *s = e->s; + + switch (event) { + case HTTP_CLIENT_OK: + wpa_printf(MSG_DEBUG, + "WPS UPnP: Got event reply OK from " + "%s", e->addr->domain_and_port); + event_delete(e); + + /* Schedule sending more if there is more to send */ + if (s->event_queue) + event_send_all_later(s->sm); + break; + case HTTP_CLIENT_FAILED: + case HTTP_CLIENT_INVALID_REPLY: + wpa_printf(MSG_DEBUG, "WPS UPnP: Failed to send event to %s", + e->addr->domain_and_port); + + /* + * If other side doesn't like what we say, forget about them. + * (There is no way to tell other side that we are dropping + * them...). + * Alternately, we could just do event_delete(e) + */ + wpa_printf(MSG_DEBUG, "WPS UPnP: Deleting subscription due to " + "errors"); + subscription_unlink(s); + subscription_destroy(s); + break; + case HTTP_CLIENT_TIMEOUT: + wpa_printf(MSG_DEBUG, "WPS UPnP: Event send timeout"); event_retry(e, 1); - goto bad; } - wpabuf_free(buf); - buf = NULL; - - if (e->sd_registered) { - e->sd_registered = 0; - eloop_unregister_sock(e->sd, EVENT_TYPE_WRITE); - } - /* Set up to read the reply */ - e->hread = httpread_create(e->sd, event_got_response_handler, - e /* cookie */, - 0 /* no data expected */, - EVENT_TIMEOUT_SEC); - if (e->hread == NULL) { - wpa_printf(MSG_ERROR, "WPS UPnP: httpread_create failed"); - event_retry(e, 0); - goto bad; - } - return; - -bad: - /* Schedule sending more if there is more to send */ - if (s->event_queue) - event_send_all_later(s->sm); - wpabuf_free(buf); } @@ -377,6 +272,7 @@ static int event_send_start(struct subscription *s) { struct wps_event_ *e; int itry; + struct wpabuf *buf; /* * Assume we are called ONLY with no current event and ONLY with @@ -393,40 +289,20 @@ static int event_send_start(struct subscription *s) for (itry = 0; itry < e->retry; itry++) e->addr = e->addr->next; - e->sd = socket(AF_INET, SOCK_STREAM, 0); - if (e->sd < 0) { + buf = event_build_message(e); + if (buf == NULL) { event_retry(e, 0); return -1; } - /* set non-blocking so we don't sleep waiting for connection */ - if (fcntl(e->sd, F_SETFL, O_NONBLOCK) != 0) { - event_retry(e, 0); - return -1; - } - /* - * Start the connect. It might succeed immediately but more likely will - * return errno EINPROGRESS. - */ - if (connect(e->sd, (struct sockaddr *) &e->addr->saddr, - sizeof(e->addr->saddr))) { - if (errno != EINPROGRESS) { - event_retry(e, 1); - return -1; - } - } - /* Call back when ready for writing (or on failure...). */ - if (eloop_register_sock(e->sd, EVENT_TYPE_WRITE, event_send_tx_ready, - NULL, e)) { - event_retry(e, 0); - return -1; - } - e->sd_registered = 1; - /* Don't wait forever! */ - if (eloop_register_timeout(EVENT_TIMEOUT_SEC, 0, event_timeout_handler, - NULL, e)) { + + e->http_event = http_client_addr(&e->addr->saddr, buf, 0, + event_http_cb, e); + if (e->http_event == NULL) { + wpabuf_free(buf); event_retry(e, 0); return -1; } + return 0; } @@ -519,7 +395,6 @@ int event_add(struct subscription *s, const struct wpabuf *data) if (e == NULL) return 1; e->s = s; - e->sd = -1; e->data = wpabuf_dup(data); if (e->data == NULL) { os_free(e); diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index e234bc8e5..13936822f 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -510,6 +510,7 @@ OBJS += ../src/wps/wps_upnp_ssdp.o OBJS += ../src/wps/wps_upnp_web.o OBJS += ../src/wps/wps_upnp_event.o OBJS += ../src/wps/httpread.o +OBJS += ../src/wps/http_client.o endif endif