support chunked HTTP responses
This commit is contained in:
@@ -22,6 +22,7 @@ libstrongswan_tnc_ifmap_la_SOURCES = \
|
||||
tnc_ifmap_listener.h tnc_ifmap_listener.c \
|
||||
tnc_ifmap_soap.h tnc_ifmap_soap.c \
|
||||
tnc_ifmap_soap_msg.h tnc_ifmap_soap_msg.c \
|
||||
tnc_ifmap_http.h tnc_ifmap_http.c \
|
||||
tnc_ifmap_renew_session_job.h tnc_ifmap_renew_session_job.c
|
||||
|
||||
libstrongswan_tnc_ifmap_la_LDFLAGS = -module -avoid-version
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Andreas Steffen
|
||||
* HSR Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE /* for asprintf() */
|
||||
|
||||
#include "tnc_ifmap_http.h"
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <utils/lexparser.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
typedef struct private_tnc_ifmap_http_t private_tnc_ifmap_http_t;
|
||||
|
||||
/**
|
||||
* Private data of an tnc_ifmap_http_t object.
|
||||
*/
|
||||
struct private_tnc_ifmap_http_t {
|
||||
|
||||
/**
|
||||
* Public tnc_ifmap_http_t interface.
|
||||
*/
|
||||
tnc_ifmap_http_t public;
|
||||
|
||||
/**
|
||||
* HTTPS Server URI with https:// prefix removed
|
||||
*/
|
||||
char *uri;
|
||||
|
||||
/**
|
||||
* Optional base64-encoded username:password for HTTP Basic Authentication
|
||||
*/
|
||||
chunk_t user_pass;
|
||||
|
||||
/**
|
||||
* HTTP chunked mode
|
||||
*/
|
||||
bool chunked;
|
||||
|
||||
};
|
||||
|
||||
METHOD(tnc_ifmap_http_t, build, status_t,
|
||||
private_tnc_ifmap_http_t *this, chunk_t *in, chunk_t *out)
|
||||
{
|
||||
char *host, *path, *request, auth[128];
|
||||
int len;
|
||||
|
||||
/* Duplicate host[/path] string since we are going to manipulate it */
|
||||
len = strlen(this->uri) + 2;
|
||||
host = malloc(len);
|
||||
memset(host, '\0', len);
|
||||
strcpy(host, this->uri);
|
||||
|
||||
/* Extract appended path or set to root */
|
||||
path = strchr(host, '/');
|
||||
if (!path)
|
||||
{
|
||||
path = host + len - 2;
|
||||
*path = '/';
|
||||
}
|
||||
|
||||
/* Use Basic Authentication? */
|
||||
if (this->user_pass.len)
|
||||
{
|
||||
snprintf(auth, sizeof(auth), "Authorization: Basic %.*s\r\n",
|
||||
this->user_pass.len, this->user_pass.ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
*auth = '\0';
|
||||
}
|
||||
|
||||
/* Write HTTP POST request, TODO break up into chunks */
|
||||
len = asprintf(&request,
|
||||
"POST %s HTTP/1.1\r\n"
|
||||
"Host: %.*s\r\n"
|
||||
"%s"
|
||||
"Content-Type: application/soap+xml;charset=utf-8\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"\r\n"
|
||||
"%.*s", path, (path-host), host, auth, in->len, in->len, in->ptr);
|
||||
free(host);
|
||||
|
||||
if (len == -1)
|
||||
{
|
||||
return FAILED;
|
||||
}
|
||||
*out = chunk_create(request, len);
|
||||
DBG3(DBG_TLS, "sending HTML POST request %B", out);
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static bool process_header(chunk_t *in, bool *chunked, u_int *content_len)
|
||||
{
|
||||
chunk_t line, version, parameter;
|
||||
int code;
|
||||
u_int len;
|
||||
|
||||
/* Process HTTP protocol version */
|
||||
if (!fetchline(in, &line) || !extract_token(&version, ' ', &line) ||
|
||||
!match("HTTP/1.1", &version) || sscanf(line.ptr, "%d", &code) != 1)
|
||||
{
|
||||
DBG1(DBG_TNC, "malformed http response header");
|
||||
return FALSE;
|
||||
}
|
||||
if (code != 200)
|
||||
{
|
||||
DBG1(DBG_TNC, "http response returns error code %d", code);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
*content_len = 0;
|
||||
*chunked = FALSE;
|
||||
|
||||
/* Process HTTP header line by line until the HTTP body is reached */
|
||||
while (fetchline(in, &line))
|
||||
{
|
||||
if (line.len == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (extract_token(¶meter, ':', &line) && eat_whitespace(&line))
|
||||
{
|
||||
if (match("Content-Length", ¶meter))
|
||||
{
|
||||
if (sscanf(line.ptr, "%u", &len) == 1)
|
||||
{
|
||||
*content_len = len;
|
||||
}
|
||||
}
|
||||
else if (match("Transfer-Encoding", ¶meter) &&
|
||||
match("chunked", &line))
|
||||
{
|
||||
*chunked = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(tnc_ifmap_http_t, process, status_t,
|
||||
private_tnc_ifmap_http_t *this, chunk_t *in, chunk_t *out)
|
||||
{
|
||||
u_int len = 0;
|
||||
chunk_t line, out_chunk;
|
||||
|
||||
DBG3(DBG_TLS, "receiving HTTP response %B", in);
|
||||
|
||||
if (!this->chunked)
|
||||
{
|
||||
if (!process_header(in, &this->chunked, &len))
|
||||
{
|
||||
return FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
while (in->len)
|
||||
{
|
||||
if (this->chunked)
|
||||
{
|
||||
if (!fetchline(in, &line) || sscanf(line.ptr, "%x", &len) != 1)
|
||||
{
|
||||
return FAILED;
|
||||
}
|
||||
DBG3(DBG_TLS, "received HTTP response is chunked (%u bytes)", len);
|
||||
|
||||
/* Received last chunk? */
|
||||
if (len == 0)
|
||||
{
|
||||
return SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check size of of remaining HTTP body */
|
||||
if (len > in->len)
|
||||
{
|
||||
DBG1(DBG_TNC, "insufficient data in HTTP body");
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
if (this->chunked)
|
||||
{
|
||||
out_chunk = *in;
|
||||
out_chunk.len = len;
|
||||
*out = chunk_cat("mc", *out, out_chunk);
|
||||
*in = chunk_skip(*in, len);
|
||||
if (!fetchline(in, &line) || line.len > 0)
|
||||
{
|
||||
return FAILED;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (len)
|
||||
{
|
||||
in->len = len;
|
||||
}
|
||||
*out = chunk_clone(*in);
|
||||
return SUCCESS;
|
||||
}
|
||||
}
|
||||
return NEED_MORE;
|
||||
}
|
||||
|
||||
METHOD(tnc_ifmap_http_t, destroy, void,
|
||||
private_tnc_ifmap_http_t *this)
|
||||
{
|
||||
free(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* See header
|
||||
*/
|
||||
tnc_ifmap_http_t *tnc_ifmap_http_create(char *uri, chunk_t user_pass)
|
||||
{
|
||||
private_tnc_ifmap_http_t *this;
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.build = _build,
|
||||
.process = _process,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
.uri = uri,
|
||||
.user_pass = user_pass,
|
||||
);
|
||||
|
||||
return &this->public;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Andreas Steffen
|
||||
* HSR Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup tnc_ifmap_http tnc_ifmap_http
|
||||
* @{ @ingroup tnc_ifmap
|
||||
*/
|
||||
|
||||
#ifndef TNC_IFMAP_HTTP_H_
|
||||
#define TNC_IFMAP_HTTP_H_
|
||||
|
||||
#include <library.h>
|
||||
#include <tls_socket.h>
|
||||
|
||||
#include <libxml/parser.h>
|
||||
|
||||
typedef struct tnc_ifmap_http_t tnc_ifmap_http_t;
|
||||
|
||||
/**
|
||||
* Interface for building and processing HTTP messages
|
||||
*/
|
||||
struct tnc_ifmap_http_t {
|
||||
|
||||
/**
|
||||
* Build a HTTP POST message
|
||||
*
|
||||
* @param in input data
|
||||
* @param out HTTP POST request
|
||||
* @result status return code
|
||||
*/
|
||||
status_t (*build)(tnc_ifmap_http_t *this, chunk_t *in, chunk_t *out);
|
||||
|
||||
/**
|
||||
* Receive a HTTP [chunked] response
|
||||
*
|
||||
* @param in [chunked] HTTP response
|
||||
* @param out output data
|
||||
* @result status return code
|
||||
*/
|
||||
status_t (*process)(tnc_ifmap_http_t *this, chunk_t *in, chunk_t *out);
|
||||
|
||||
/**
|
||||
* Destroy a tnc_ifmap_http_t object.
|
||||
*/
|
||||
void (*destroy)(tnc_ifmap_http_t *this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a tnc_ifmap_http instance.
|
||||
*
|
||||
* @param uri HTTPS URI with https:// prefix removed
|
||||
* @param user_pass Optional username:password for HTTP Basic Authentication
|
||||
*/
|
||||
tnc_ifmap_http_t *tnc_ifmap_http_create(char *uri, chunk_t user_pass);
|
||||
|
||||
#endif /** TNC_IFMAP_HTTP_H_ @}*/
|
||||
@@ -13,14 +13,10 @@
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE /* for asprintf() */
|
||||
|
||||
#include "tnc_ifmap_soap_msg.h"
|
||||
#include "tnc_ifmap_http.h"
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <utils/lexparser.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define SOAP_NS "http://www.w3.org/2003/05/soap-envelope"
|
||||
|
||||
@@ -37,17 +33,12 @@ struct private_tnc_ifmap_soap_msg_t {
|
||||
tnc_ifmap_soap_msg_t public;
|
||||
|
||||
/**
|
||||
* HTTPS Server URI with https:// prefix removed
|
||||
* HTTP POST request builder and response processing
|
||||
*/
|
||||
char *uri;
|
||||
tnc_ifmap_http_t *http;
|
||||
|
||||
/**
|
||||
* Optional base64-encoded username:password for HTTP Basic Authentication
|
||||
*/
|
||||
chunk_t user_pass;
|
||||
|
||||
/**
|
||||
* TLS Socket
|
||||
* TLS socket
|
||||
*/
|
||||
tls_socket_t *tls;
|
||||
|
||||
@@ -58,118 +49,6 @@ struct private_tnc_ifmap_soap_msg_t {
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Send HTTP POST request and receive HTTP response
|
||||
*/
|
||||
static bool http_post(private_tnc_ifmap_soap_msg_t *this, chunk_t out,
|
||||
chunk_t *in)
|
||||
{
|
||||
char *host, *path, *request, buf[2048];
|
||||
chunk_t line, http, parameter;
|
||||
int len, written, code, content_len = 0;
|
||||
|
||||
/* Duplicate host[/path] string since we are going to manipulate it */
|
||||
len = strlen(this->uri) + 2;
|
||||
host = malloc(len);
|
||||
memset(host, '\0', len);
|
||||
strcpy(host, this->uri);
|
||||
|
||||
/* Extract appended path or set to root */
|
||||
path = strchr(host, '/');
|
||||
if (!path)
|
||||
{
|
||||
path = host + len - 2;
|
||||
*path = '/';
|
||||
}
|
||||
|
||||
/* Use Basic Authentication? */
|
||||
if (this->user_pass.len)
|
||||
{
|
||||
snprintf(buf, sizeof(buf), "Authorization: Basic %.*s\r\n",
|
||||
this->user_pass.len, this->user_pass.ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
*buf = '\0';
|
||||
}
|
||||
|
||||
/* Write HTTP POST request */
|
||||
len = asprintf(&request,
|
||||
"POST %s HTTP/1.1\r\n"
|
||||
"Host: %.*s\r\n"
|
||||
"%s"
|
||||
"Content-Type: application/soap+xml;charset=utf-8\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"\r\n"
|
||||
"%.*s", path, (path-host), host, buf, out.len, out.len, out.ptr);
|
||||
free(host);
|
||||
|
||||
if (len == -1)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
http = chunk_create(request, len);
|
||||
DBG3(DBG_TLS, "%B", &http);
|
||||
|
||||
written = this->tls->write(this->tls, request, len);
|
||||
free(request);
|
||||
if (written != len)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Read HTTP response */
|
||||
len = this->tls->read(this->tls, buf, sizeof(buf), TRUE);
|
||||
if (len <= 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
*in = chunk_create(buf, len);
|
||||
|
||||
/* Process HTTP protocol version */
|
||||
if (!fetchline(in, &line) || !extract_token(&http, ' ', &line) ||
|
||||
!match("HTTP/1.1", &http) || sscanf(line.ptr, "%d", &code) != 1)
|
||||
{
|
||||
DBG1(DBG_TNC, "malformed http response header");
|
||||
return FALSE;
|
||||
}
|
||||
if (code != 200)
|
||||
{
|
||||
DBG1(DBG_TNC, "http response returns error code %d", code);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Process HTTP header line by line until the HTTP body is reached */
|
||||
while (fetchline(in, &line))
|
||||
{
|
||||
if (line.len == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (extract_token(¶meter, ':', &line) &&
|
||||
match("Content-Length", ¶meter) &&
|
||||
sscanf(line.ptr, "%d", &len) == 1)
|
||||
{
|
||||
content_len = len;
|
||||
}
|
||||
}
|
||||
|
||||
/* Found Content-Length parameter and check size of HTTP body */
|
||||
if (content_len)
|
||||
{
|
||||
if (content_len > in->len)
|
||||
{
|
||||
DBG1(DBG_TNC, "http body is smaller than content length");
|
||||
return FALSE;
|
||||
}
|
||||
in->len = content_len;
|
||||
}
|
||||
*in = chunk_clone(*in);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a child node with a given name
|
||||
*/
|
||||
@@ -198,9 +77,11 @@ METHOD(tnc_ifmap_soap_msg_t, post, bool,
|
||||
xmlDocPtr doc;
|
||||
xmlNodePtr env, body, cur, response;
|
||||
xmlNsPtr ns;
|
||||
xmlChar *xml, *errorCode, *errorString;
|
||||
int len;
|
||||
chunk_t in, out;
|
||||
xmlChar *xml_str, *errorCode, *errorString;
|
||||
int xml_len, len, written;
|
||||
chunk_t xml, http;
|
||||
char buf[4096];
|
||||
status_t status;
|
||||
|
||||
DBG2(DBG_TNC, "sending ifmap %s", request->name);
|
||||
|
||||
@@ -217,22 +98,58 @@ METHOD(tnc_ifmap_soap_msg_t, post, bool,
|
||||
xmlAddChild(env, body);
|
||||
|
||||
/* Convert XML Document into a character string */
|
||||
xmlDocDumpFormatMemory(doc, &xml, &len, 1);
|
||||
xmlDocDumpFormatMemory(doc, &xml_str, &xml_len, 1);
|
||||
xmlFreeDoc(doc);
|
||||
DBG3(DBG_TNC, "%.*s", len, xml);
|
||||
out = chunk_create(xml, len);
|
||||
DBG3(DBG_TNC, "%.*s", xml_len, xml_str);
|
||||
xml = chunk_create(xml_str, xml_len);
|
||||
|
||||
/* Send SOAP-XML request via HTTP POST */
|
||||
if (!http_post(this, out, &in))
|
||||
/* Send SOAP-XML request via HTTPS POST */
|
||||
do
|
||||
{
|
||||
status = this->http->build(this->http, &xml, &http);
|
||||
if (status == FAILED)
|
||||
{
|
||||
break;
|
||||
}
|
||||
written = this->tls->write(this->tls, http.ptr, http.len);
|
||||
free(http.ptr);
|
||||
if (written != http.len)
|
||||
{
|
||||
status = FAILED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (status == NEED_MORE);
|
||||
|
||||
xmlFree(xml_str);
|
||||
if (status != SUCCESS)
|
||||
{
|
||||
xmlFree(xml);
|
||||
return FALSE;
|
||||
}
|
||||
xmlFree(xml);
|
||||
|
||||
DBG3(DBG_TNC, "%B", &in);
|
||||
this->doc = xmlParseMemory(in.ptr, in.len);
|
||||
free(in.ptr);
|
||||
/* Receive SOAP-XML response via [chunked] HTTPS */
|
||||
xml = chunk_empty;
|
||||
do
|
||||
{
|
||||
len = this->tls->read(this->tls, buf, sizeof(buf), TRUE);
|
||||
if (len <= 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
http = chunk_create(buf, len);
|
||||
|
||||
status = this->http->process(this->http, &http, &xml);
|
||||
if (status == FAILED)
|
||||
{
|
||||
free(xml.ptr);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
while (status == NEED_MORE);
|
||||
|
||||
DBG3(DBG_TNC, "parsing XML message %B", &xml);
|
||||
this->doc = xmlParseMemory(xml.ptr, xml.len);
|
||||
free(xml.ptr);
|
||||
|
||||
if (!this->doc)
|
||||
{
|
||||
@@ -309,6 +226,7 @@ METHOD(tnc_ifmap_soap_msg_t, post, bool,
|
||||
METHOD(tnc_ifmap_soap_msg_t, destroy, void,
|
||||
private_tnc_ifmap_soap_msg_t *this)
|
||||
{
|
||||
this->http->destroy(this->http);
|
||||
if (this->doc)
|
||||
{
|
||||
xmlFreeDoc(this->doc);
|
||||
@@ -329,8 +247,7 @@ tnc_ifmap_soap_msg_t *tnc_ifmap_soap_msg_create(char *uri, chunk_t user_pass,
|
||||
.post = _post,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
.uri = uri,
|
||||
.user_pass = user_pass,
|
||||
.http = tnc_ifmap_http_create(uri, user_pass),
|
||||
.tls = tls,
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user