diff --git a/build/ac/Makefile.am b/build/ac/Makefile.am index 90f8d52..43b9eb9 100755 --- a/build/ac/Makefile.am +++ b/build/ac/Makefile.am @@ -27,6 +27,8 @@ AM_CFLAGS = \ -D_REENTRANT \ -D_GNU_SOURCE +AM_CFLAGS += $(LIBXML2_CFLAGS) + if DTLS_ENABLED AM_CFLAGS += $(SSL_CFLAGS) endif @@ -42,6 +44,7 @@ ac_SOURCES = \ $(capwap_SOURCES) \ $(top_srcdir)/src/common/capwap_event.c \ $(top_srcdir)/src/common/capwap_lock.c \ + $(top_srcdir)/src/common/capwap_socket.c \ $(top_srcdir)/src/ac/ac.c \ $(top_srcdir)/src/ac/ac_execute.c \ $(top_srcdir)/src/ac/ac_session.c \ @@ -53,13 +56,14 @@ ac_SOURCES = \ $(top_srcdir)/src/ac/ac_dfa_dtls.c \ $(top_srcdir)/src/ac/ac_dfa_run.c \ $(top_srcdir)/src/ac/ac_dfa_reset.c \ - $(top_srcdir)/src/ac/ac_dfa_teardown.c + $(top_srcdir)/src/ac/ac_dfa_teardown.c \ + $(top_srcdir)/src/ac/ac_soap.c ac_LDADD = \ $(CONFIG_LIBS) \ - $(PTHREAD_LIBS) + $(PTHREAD_LIBS) \ + $(LIBXML2_LIBS) if DTLS_ENABLED ac_LDADD += $(SSL_LIBS) endif - diff --git a/configure.ac b/configure.ac index c051420..d6edc37 100644 --- a/configure.ac +++ b/configure.ac @@ -134,6 +134,12 @@ AC_CHECK_LIB([config], [config_init], [CONFIG_LIBS="-lconfig"], [AC_MSG_ERROR(Yo AC_CHECK_HEADER([pthread.h], [], [AC_MSG_ERROR(You need the pthread headers)]) AC_CHECK_LIB([pthread], [pthread_create], [PTHREAD_LIBS="-lpthread"], [AC_MSG_ERROR(You need the pthread library)]) +# Check XML2 library +PKG_CHECK_MODULES( + [LIBXML2], + [libxml-2.0 >= 2.6] +) + # Check nl80211 has_libnl_ver=0 PKG_CHECK_MODULES( diff --git a/src/ac/ac_soap.c b/src/ac/ac_soap.c new file mode 100644 index 0000000..25c54b3 --- /dev/null +++ b/src/ac/ac_soap.c @@ -0,0 +1,682 @@ +#include "ac.h" +#include "ac_soap.h" +#include "capwap_socket.h" + +/* */ +#define SOAP_PROTOCOL_CONNECT_TIMEOUT 10000 +#define SOAP_PROTOCOL_REQUEST_TIMEOUT 10000 +#define SOAP_PROTOCOL_RESPONSE_TIMEOUT 10000 + +/* */ +#define HTTP_RESPONSE_STATUS_CODE 0 +#define HTTP_RESPONSE_HEADER 1 +#define HTTP_RESPONSE_BODY 2 +#define HTTP_RESPONSE_ERROR 3 + +/* */ +xmlNodePtr ac_xml_get_children(xmlNodePtr parent) { + xmlNodePtr children; + + ASSERT(parent != NULL); + + /* */ + children = parent->xmlChildrenNode; + while ((children != NULL) && (children->type != XML_ELEMENT_NODE)) { + children = children->next; + } + + return children; +} + +/* */ +xmlNodePtr ac_xml_get_next(xmlNodePtr element) { + xmlNodePtr node; + + ASSERT(element != NULL); + + node = element->next; + while ((node != NULL) && (node->type != XML_ELEMENT_NODE)) { + node = node->next; + } + + return node; +} + +xmlNodePtr ac_xml_search_child(xmlNodePtr parent, char* prefix, char* name) { + xmlNodePtr children; + + ASSERT(parent != NULL); + ASSERT(name != NULL); + + children = ac_xml_get_children(parent); + while (children != NULL) { + if (!xmlStrcmp(children->name, BAD_CAST name) && (!prefix || !xmlStrcmp(children->ns->prefix, BAD_CAST prefix))) { + break; + } + + /* */ + children = ac_xml_get_next(children); + } + + return children; +} + +/* */ +static int ac_soapclient_parsing_url(struct ac_http_soap_server* server, char* url) { + int length; + int protocol; + int host; + int port; + int hostlength; + int pathlength; + + ASSERT(server != NULL); + ASSERT(url != NULL); + + /* */ + length = strlen(url); + if (length < 8) { + /* Invalid URL */ + return 0; + } + + /* Search protocol */ + protocol = 0; + while (url[protocol] && url[protocol] != ':') { + protocol++; + } + + if (!protocol || (protocol + 3 >= length)) { + /* Invalid URL */ + return 0; + } else if ((url[protocol] != ':') || (url[protocol + 1] != '/') || (url[protocol + 2] != '/')) { + /* Invalid URL */ + return 0; + } + + /* Parsing protocol */ + if (!strncasecmp(url, "http", protocol)) { + server->protocol = SOAP_HTTP_PROTOCOL; + /* TODO: write code for SSL connection + } else if (!strncasecmp(url, "https", protocol)) { + server->protocol = SOAP_HTTPS_PROTOCOL;*/ + } else { + /* Unknown protocol */ + return 0; + } + + protocol += 3; + + /* Search hostname */ + host = protocol; + while (url[host] && (url[host] != ':') && (url[host] != '/')) { + host++; + } + + if (host == protocol) { + /* Invalid hostname */ + return 0; + } + + /* Search port */ + port = host; + if (url[port] == ':') { + while (url[port] && (url[port] != '/')) { + port++; + } + } + + /* Retrieve hostname */ + hostlength = port - protocol; + server->host = capwap_alloc(hostlength + 1); + if (!server->host) { + capwap_outofmemory(); + } + + strncpy(server->host, &url[protocol], hostlength); + server->host[hostlength] = 0; + + /* Parsing hostname */ + if (!capwap_address_from_string(server->host, &server->address)) { + /* Invalid hostname */ + return 0; + } + + if (port == host) { + /* Get default port */ + if (server->protocol == SOAP_HTTP_PROTOCOL) { + CAPWAP_SET_NETWORK_PORT(&server->address, SOAP_HTTP_PORT); + } else if (server->protocol == SOAP_HTTPS_PROTOCOL) { + CAPWAP_SET_NETWORK_PORT(&server->address, SOAP_HTTPS_PORT); + } + } + + /* Retrieve path */ + pathlength = length - port; + if (!pathlength) { + pathlength = 1; + } + + server->path = capwap_alloc(pathlength + 1); + if (!server->path) { + capwap_outofmemory(); + } + + if (length == port) { + strcpy(server->path, "/"); + } else { + strncpy(server->path, &url[port], pathlength); + server->path[pathlength] = 0; + } + + return 1; +} + +/* */ +static int ac_soapclient_send_http(struct ac_http_soap_request* httprequest, char* soapaction, char* body, int length) { + time_t ts; + struct tm stm; + char datetime[32]; + int headerlength; + int result; + char* buffer; + + /* Retrieve datetime */ + ts = time(NULL); + localtime_r(&ts, &stm); + strftime(datetime, 32, "%a, %d %b %Y %T %z", &stm); + + /* Calculate header length */ + headerlength = 128 + length + strlen(httprequest->server->path) + strlen(httprequest->server->host) + strlen(datetime) + strlen((soapaction ? soapaction : "")); + buffer = capwap_alloc(headerlength); + if (!buffer) { + capwap_outofmemory(); + } + + /* HTTP headers */ + result = snprintf(buffer, headerlength, + "POST %s HTTP/1.1\r\n" + "Host: %s\r\n" + "Date: %s\r\n" + "Content-Length: %d\r\n" + "Content-Type: text/xml\r\n" + "Connection: Close\r\n" + "SoapAction: %s\r\n" + "\r\n" + "%s", + httprequest->server->path, + httprequest->server->host, + datetime, + length, + (soapaction ? soapaction : ""), + body + ); + + /* Send headers */ + if (result < 0) { + result = 0; + } else { + if (capwap_socket_send_timeout(httprequest->sock, buffer, result, SOAP_PROTOCOL_REQUEST_TIMEOUT) == result) { + result = 1; + } else { + result = 0; + } + } + + /* */ + capwap_free(buffer); + return result; +} + +/* */ +static int ac_soapclient_xml_io_read(void* ctx, char* buffer, int len) { + int result = -1; + struct ac_http_soap_request* httprequest = (struct ac_http_soap_request*)ctx; + + if ((httprequest->httpstate == HTTP_RESPONSE_STATUS_CODE) || (httprequest->httpstate == HTTP_RESPONSE_HEADER)) { + char respbuffer[8192]; + int respbufferlength = 0; + + for (;;) { + /* Receive packet into temporaly buffer */ + if (capwap_socket_recv_timeout(httprequest->sock, &respbuffer[respbufferlength], 1, SOAP_PROTOCOL_RESPONSE_TIMEOUT) != 1) { + httprequest->httpstate = HTTP_RESPONSE_ERROR; + break; + } + + /* Update buffer size */ + respbufferlength += 1; + if (respbufferlength >= sizeof(respbuffer)) { + /* Buffer overflow */ + httprequest->httpstate = HTTP_RESPONSE_ERROR; + break; + } + + /* Search line */ + if ((respbufferlength > 1) && (respbuffer[respbufferlength - 2] == '\r') && (respbuffer[respbufferlength - 1] == '\n')) { + if (httprequest->httpstate == HTTP_RESPONSE_STATUS_CODE) { + int temp; + int descpos; + + /* Parse response code */ + respbuffer[respbufferlength - 2] = 0; + temp = sscanf(respbuffer, "HTTP/1.1 %d %n", &httprequest->responsecode, &descpos); + if ((temp != 1) || (httprequest->responsecode != 200)) { + httprequest->httpstate = HTTP_RESPONSE_ERROR; + break; + } + + /* Parsing headers */ + respbufferlength = 0; + httprequest->httpstate = HTTP_RESPONSE_HEADER; + } else if (httprequest->httpstate == HTTP_RESPONSE_HEADER) { + char* value; + + if (respbufferlength == 2) { + if (httprequest->contentlength > 0) { + /* Retrieve body */ + httprequest->httpstate = HTTP_RESPONSE_BODY; + } else { + httprequest->httpstate = HTTP_RESPONSE_ERROR; + } + + break; + } + + /* Separate key from value */ + respbuffer[respbufferlength - 2] = 0; + value = strchr(respbuffer, ':'); + if (!value) { + httprequest->httpstate = HTTP_RESPONSE_ERROR; + break; + } + + /* */ + *value = 0; + value++; + while (*value == ' ') { + value++; + } + + /* */ + if (!strcmp(respbuffer, "Content-Length")) { + httprequest->contentlength = atoi(value); + if (!httprequest->contentlength) { + httprequest->httpstate = HTTP_RESPONSE_ERROR; + break; + } + } else if (!strcmp(respbuffer, "Content-Type")) { + char* param; + + /* Remove param from value */ + param = strchr(value, ';'); + if (param) { + *param = 0; + } + + if (strcmp(value, "text/xml")) { + httprequest->httpstate = HTTP_RESPONSE_ERROR; + break; + } + } + + /* Next header */ + respbufferlength = 0; + } + } + } + } + + if (httprequest->httpstate == HTTP_RESPONSE_BODY) { + if (!httprequest->contentlength) { + return 0; + } + + /* Receive body directly into XML buffer */ + result = capwap_socket_recv_timeout(httprequest->sock, buffer, len, SOAP_PROTOCOL_RESPONSE_TIMEOUT); + if (result > 0) { + httprequest->contentlength -= result; + } + } + + return result; +} + +/* */ +static int ac_soapclient_xml_io_close(void *ctx) { + struct ac_http_soap_request* httprequest = (struct ac_http_soap_request*)ctx; + + if ((httprequest->httpstate == HTTP_RESPONSE_BODY) && httprequest->contentlength) { + return -1; + } + + return 0; +} + +/* */ +static void ac_soapclient_parse_error(void* ctxt, const char* msg, ...) { +} + +/* */ +void ac_soapclient_init(void) { + xmlInitParser(); + xmlSetGenericErrorFunc(NULL, ac_soapclient_parse_error); +} + +/* */ +void ac_soapclient_free(void) { + xmlCleanupParser(); +} + +/* */ +struct ac_http_soap_server* ac_soapclient_create_server(char* url) { + struct ac_http_soap_server* server; + + ASSERT(url != NULL); + + /* */ + server = (struct ac_http_soap_server*)capwap_alloc(sizeof(struct ac_http_soap_server)); + if (!server) { + capwap_outofmemory(); + } + + memset(server, 0, sizeof(struct ac_http_soap_server)); + + /* */ + if (!ac_soapclient_parsing_url(server, url)) { + ac_soapclient_free_server(server); + return NULL; + } + + return server; +} + +/* */ +void ac_soapclient_free_server(struct ac_http_soap_server* server) { + ASSERT(server != NULL); + + if (server->host) { + capwap_free(server->host); + } + + if (server->path) { + capwap_free(server->path); + } + + capwap_free(server); +} + +/* */ +struct ac_soap_request* ac_soapclient_create_request(char* method, char* urinamespace) { + struct ac_soap_request* request; + + ASSERT(method != NULL); + + /* */ + request = (struct ac_soap_request*)capwap_alloc(sizeof(struct ac_soap_request)); + if (!request) { + capwap_outofmemory(); + } + + memset(request, 0, sizeof(struct ac_soap_request)); + + /* Build XML SOAP Request */ + request->xmlDocument = xmlNewDoc(BAD_CAST "1.0"); + request->xmlRoot = xmlNewNode(NULL, BAD_CAST "SOAP-ENV:Envelope"); + xmlNewProp(request->xmlRoot, BAD_CAST "xmlns:xsd", BAD_CAST "http://www.w3.org/2001/XMLSchema"); + xmlNewProp(request->xmlRoot, BAD_CAST "xmlns:xsi", BAD_CAST "http://www.w3.org/2001/XMLSchema-instance"); + xmlNewProp(request->xmlRoot, BAD_CAST "xmlns:SOAP-ENC", BAD_CAST "http://schemas.xmlsoap.org/soap/encoding/"); + xmlNewProp(request->xmlRoot, BAD_CAST "SOAP-ENV:encodingStyle", BAD_CAST "http://schemas.xmlsoap.org/soap/encoding/"); + xmlNewProp(request->xmlRoot, BAD_CAST "xmlns:SOAP-ENV", BAD_CAST "http://schemas.xmlsoap.org/soap/envelope/"); + xmlDocSetRootElement(request->xmlDocument, request->xmlRoot); + + xmlNewChild(request->xmlRoot, NULL, BAD_CAST "SOAP-ENV:Header", NULL); + request->xmlBody = xmlNewChild(request->xmlRoot, NULL, BAD_CAST "SOAP-ENV:Body", NULL); + + /* */ + request->method = capwap_duplicate_string(method); + if (urinamespace && *urinamespace) { + char* tagMethod; + + /* Create request with prefix namespace */ + tagMethod = capwap_alloc(strlen(method) + 5); + if (!tagMethod) { + capwap_outofmemory(); + } + + sprintf(tagMethod, "ns1:%s", method); + request->xmlRequest = xmlNewChild(request->xmlBody, NULL, BAD_CAST tagMethod, NULL); + + xmlNewProp(request->xmlRequest, BAD_CAST "xmlns:ns1", BAD_CAST urinamespace); + + capwap_free(tagMethod); + } else { + request->xmlRequest = xmlNewChild(request->xmlBody, NULL, BAD_CAST method, NULL); + } + + return request; +} + +/* */ +void ac_soapclient_free_request(struct ac_soap_request* request) { + ASSERT(request != NULL); + + if (request->xmlDocument) { + xmlFreeDoc(request->xmlDocument); + } + + if (request->method) { + capwap_free(request->method); + } + + capwap_free(request); +} + +/* */ +int ac_soapclient_add_param(struct ac_soap_request* request, char* type, char* name, char* value) { + xmlNodePtr xmlParam; + + ASSERT(request != NULL); + ASSERT(name != NULL); + ASSERT(value != NULL); + + /* */ + xmlParam = xmlNewTextChild(request->xmlRequest, NULL, BAD_CAST name, BAD_CAST value); + if (!xmlParam) { + return 0; + } + + if (type) { + if (!xmlNewProp(xmlParam, BAD_CAST "xsi:type", BAD_CAST type)) { + return 0; + } + } + + return 1; +} + +/* */ +char* ac_soapclient_get_request(struct ac_soap_request* request) { + char* result; + size_t length; + xmlBufferPtr buffer; + + ASSERT(request != NULL); + ASSERT(request->xmlDocument != NULL); + + /* */ + buffer = xmlBufferCreate(); + length = xmlNodeDump(buffer, request->xmlDocument, request->xmlRoot, 1, 0); + + /* Clone XML document string */ + result = capwap_alloc(length + 1); + memcpy(result, (char*)xmlBufferContent(buffer), length); + result[length] = 0; + + /* */ + xmlBufferFree(buffer); + return result; +} + +/* */ +struct ac_http_soap_request* ac_soapclient_prepare_request(struct ac_soap_request* request, struct ac_http_soap_server* server) { + struct ac_http_soap_request* httprequest; + + ASSERT(request != NULL); + ASSERT(request->xmlDocument != NULL); + ASSERT(request->xmlRoot != NULL); + ASSERT(server != NULL); + + /* */ + httprequest = (struct ac_http_soap_request*)capwap_alloc(sizeof(struct ac_http_soap_request)); + if (!httprequest) { + capwap_outofmemory(); + } + + /* */ + memset(httprequest, 0, sizeof(struct ac_http_soap_request)); + httprequest->request = request; + httprequest->server = server; + + /* Create socket */ + httprequest->sock = socket(httprequest->server->address.ss_family, SOCK_STREAM, 0); + if (httprequest->sock < 0) { + ac_soapclient_close_request(httprequest, 0); + return NULL; + } + + /* Non blocking socket */ + if (!capwap_socket_nonblocking(httprequest->sock, 1)) { + ac_soapclient_close_request(httprequest, 0); + return NULL; + } + + return httprequest; +} + +/* */ +int ac_soapclient_send_request(struct ac_http_soap_request* httprequest, char* soapaction) { + char* buffer; + size_t length; + xmlBufferPtr xmlBuffer; + + ASSERT(httprequest != NULL); + + /* Retrieve XML SOAP Request */ + xmlBuffer = xmlBufferCreate(); + length = xmlNodeDump(xmlBuffer, httprequest->request->xmlDocument, httprequest->request->xmlRoot, 1, 0); + if (!length) { + return 0; + } + + buffer = (char*)xmlBufferContent(xmlBuffer); + + /* Connect to remote host */ + if (!capwap_socket_connect_timeout(httprequest->sock, &httprequest->server->address, SOAP_PROTOCOL_CONNECT_TIMEOUT)) { + xmlBufferFree(xmlBuffer); + return 0; + } + + /* Send HTTP Header */ + if (!ac_soapclient_send_http(httprequest, soapaction, buffer, (int)length)) { + xmlBufferFree(xmlBuffer); + return 0; + } + + /* Sent SOAP Request */ + xmlBufferFree(xmlBuffer); + return 1; +} + +/* */ +void ac_soapclient_close_request(struct ac_http_soap_request* httprequest, int closerequest) { + ASSERT(httprequest != NULL); + + /* */ + if (closerequest && httprequest->request) { + ac_soapclient_free_request(httprequest->request); + } + + /* Close socket */ + if (httprequest->sock >= 0) { + capwap_socket_nonblocking(httprequest->sock, 0); + shutdown(httprequest->sock, SHUT_RDWR); + close(httprequest->sock); + } + + capwap_free(httprequest); +} + +/* */ +struct ac_soap_response* ac_soapclient_recv_response(struct ac_http_soap_request* httprequest) { + char* tagMethod; + struct ac_soap_response* response; + + ASSERT(httprequest != NULL); + ASSERT(httprequest->sock >= 0); + + /* */ + response = (struct ac_soap_response*)capwap_alloc(sizeof(struct ac_soap_response)); + if (!response) { + capwap_outofmemory(); + } + + memset(response, 0, sizeof(struct ac_soap_response)); + + /* Receive HTTP response into XML callback */ + httprequest->httpstate = HTTP_RESPONSE_STATUS_CODE; + response->xmlDocument = xmlReadIO(ac_soapclient_xml_io_read, ac_soapclient_xml_io_close, (void*)httprequest, "", NULL, 0); + if (!response->xmlDocument) { + ac_soapclient_free_response(response); + return NULL; + } + + /* Parsing response */ + response->xmlRoot = xmlDocGetRootElement(response->xmlDocument); + if (!response->xmlRoot) { + ac_soapclient_free_response(response); + return NULL; + } + + /* Retrieve Body */ + response->xmlBody = ac_xml_search_child(response->xmlRoot, "SOAP-ENV", "Body"); + if (!response->xmlBody) { + ac_soapclient_free_response(response); + return NULL; + } + + /* Retrieve response */ + tagMethod = capwap_alloc(strlen(httprequest->request->method) + 9); + if (!tagMethod) { + capwap_outofmemory(); + } + + sprintf(tagMethod, "%sResponse", httprequest->request->method); + response->xmlResponse = ac_xml_search_child(response->xmlBody, NULL, tagMethod); + capwap_free(tagMethod); + + if (!response->xmlResponse) { + ac_soapclient_free_response(response); + return NULL; + } + + /* Retrieve Return response */ + response->xmlResponseReturn = ac_xml_search_child(response->xmlResponse, NULL, "return"); + if (!response->xmlResponseReturn) { + ac_soapclient_free_response(response); + return NULL; + } + + return response; +} + +/* */ +void ac_soapclient_free_response(struct ac_soap_response* response) { + ASSERT(response != NULL); + + if (response->xmlDocument) { + xmlFreeDoc(response->xmlDocument); + } + + capwap_free(response); +} diff --git a/src/ac/ac_soap.h b/src/ac/ac_soap.h new file mode 100644 index 0000000..064f3fa --- /dev/null +++ b/src/ac/ac_soap.h @@ -0,0 +1,79 @@ +#ifndef __AC_SOAP_HEADER__ +#define __AC_SOAP_HEADER__ + +#include +#include + +/* */ +#define SOAP_HTTP_PROTOCOL 1 +#define SOAP_HTTPS_PROTOCOL 2 + +#define SOAP_HTTP_PORT 80 +#define SOAP_HTTPS_PORT 443 + +/* */ +struct ac_http_soap_server { + int protocol; + struct sockaddr_storage address; + + char* host; + char* path; +}; + +/* */ +struct ac_soap_request { + xmlDocPtr xmlDocument; + xmlNodePtr xmlRoot; + xmlNodePtr xmlBody; + xmlNodePtr xmlRequest; + + char* method; +}; + +/* */ +struct ac_http_soap_request { + struct ac_http_soap_server* server; + struct ac_soap_request* request; + + int sock; + + /* Information for SOAP Response */ + int httpstate; + int responsecode; + int contentlength; +}; + +/* */ +struct ac_soap_response { + xmlDocPtr xmlDocument; + xmlNodePtr xmlRoot; + xmlNodePtr xmlBody; + xmlNodePtr xmlResponse; + xmlNodePtr xmlResponseReturn; +}; + +/* */ +void ac_soapclient_init(void); +void ac_soapclient_free(void); + +/* */ +struct ac_http_soap_server* ac_soapclient_create_server(char* url); +void ac_soapclient_free_server(struct ac_http_soap_server* server); + +/* Request */ +struct ac_soap_request* ac_soapclient_create_request(char* method, char* urinamespace); +int ac_soapclient_add_param(struct ac_soap_request* request, char* type, char* name, char* value); +char* ac_soapclient_get_request(struct ac_soap_request* request); +void ac_soapclient_free_request(struct ac_soap_request* request); + +/* Transport Request */ +struct ac_http_soap_request* ac_soapclient_prepare_request(struct ac_soap_request* request, struct ac_http_soap_server* server); +int ac_soapclient_send_request(struct ac_http_soap_request* httprequest, char* soapaction); +struct ac_soap_response* ac_soapclient_recv_response(struct ac_http_soap_request* httprequest); + +void ac_soapclient_close_request(struct ac_http_soap_request* httprequest, int closerequest); + +/* Response */ +void ac_soapclient_free_response(struct ac_soap_response* response); + +#endif /* __AC_SOAP_HEADER__ */