improve checking of sent and received http messages
[strongswan.git] / src / libcharon / plugins / tnc_ifmap / tnc_ifmap_soap_msg.c
1 /*
2 * Copyright (C) 2013 Andreas Steffen
3 * HSR Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #define _GNU_SOURCE /* for asprintf() */
17
18 #include "tnc_ifmap_soap_msg.h"
19
20 #include <utils/debug.h>
21 #include <utils/lexparser.h>
22
23 #include <stdio.h>
24
25 #define SOAP_NS "http://www.w3.org/2003/05/soap-envelope"
26
27 typedef struct private_tnc_ifmap_soap_msg_t private_tnc_ifmap_soap_msg_t;
28
29 /**
30 * Private data of an tnc_ifmap_soap_msg_t object.
31 */
32 struct private_tnc_ifmap_soap_msg_t {
33
34 /**
35 * Public tnc_ifmap_soap_msg_t interface.
36 */
37 tnc_ifmap_soap_msg_t public;
38
39 /**
40 * HTTPS Server URI with https:// prefix removed
41 */
42 char *uri;
43
44 /**
45 * Optional base64-encoded username:password for HTTP Basic Authentication
46 */
47 chunk_t user_pass;
48
49 /**
50 * TLS Socket
51 */
52 tls_socket_t *tls;
53
54 /**
55 * XML Document
56 */
57 xmlDocPtr doc;
58
59 };
60
61 /**
62 * Send HTTP POST request and receive HTTP response
63 */
64 static bool http_post(private_tnc_ifmap_soap_msg_t *this, chunk_t out,
65 chunk_t *in)
66 {
67 char *host, *path, *request, buf[2048];
68 chunk_t line, http, parameter;
69 int len, written, code, content_len = 0;
70
71 /* Duplicate host[/path] string since we are going to manipulate it */
72 len = strlen(this->uri) + 2;
73 host = malloc(len);
74 memset(host, '\0', len);
75 strcpy(host, this->uri);
76
77 /* Extract appended path or set to root */
78 path = strchr(host, '/');
79 if (!path)
80 {
81 path = host + len - 2;
82 *path = '/';
83 }
84
85 /* Use Basic Authentication? */
86 if (this->user_pass.len)
87 {
88 snprintf(buf, sizeof(buf), "Authorization: Basic %.*s\r\n",
89 this->user_pass.len, this->user_pass.ptr);
90 }
91 else
92 {
93 *buf = '\0';
94 }
95
96 /* Write HTTP POST request */
97 len = asprintf(&request,
98 "POST %s HTTP/1.1\r\n"
99 "Host: %.*s\r\n"
100 "%s"
101 "Content-Type: application/soap+xml;charset=utf-8\r\n"
102 "Content-Length: %d\r\n"
103 "\r\n"
104 "%.*s", path, (path-host), host, buf, out.len, out.len, out.ptr);
105 free(host);
106
107 if (len == -1)
108 {
109 return FALSE;
110 }
111 http = chunk_create(request, len);
112 DBG3(DBG_TLS, "%B", &http);
113
114 written = this->tls->write(this->tls, request, len);
115 free(request);
116 if (written != len)
117 {
118 return FALSE;
119 }
120
121 /* Read HTTP response */
122 len = this->tls->read(this->tls, buf, sizeof(buf), TRUE);
123 if (len <= 0)
124 {
125 return FALSE;
126 }
127 *in = chunk_create(buf, len);
128
129 /* Process HTTP protocol version */
130 if (!fetchline(in, &line) || !extract_token(&http, ' ', &line) ||
131 !match("HTTP/1.1", &http) || sscanf(line.ptr, "%d", &code) != 1)
132 {
133 DBG1(DBG_TNC, "malformed http response header");
134 return FALSE;
135 }
136 if (code != 200)
137 {
138 DBG1(DBG_TNC, "http response returns error code %d", code);
139 return FALSE;
140 }
141
142 /* Process HTTP header line by line until the HTTP body is reached */
143 while (fetchline(in, &line))
144 {
145 if (line.len == 0)
146 {
147 break;
148 }
149
150 if (extract_token(&parameter, ':', &line) &&
151 match("Content-Length", &parameter) &&
152 sscanf(line.ptr, "%d", &len) == 1)
153 {
154 content_len = len;
155 }
156 }
157
158 /* Found Content-Length parameter and check size of HTTP body */
159 if (content_len)
160 {
161 if (content_len > in->len)
162 {
163 DBG1(DBG_TNC, "http body is smaller than content length");
164 return FALSE;
165 }
166 in->len = content_len;
167 }
168 *in = chunk_clone(*in);
169
170 return TRUE;
171 }
172
173 /**
174 * Find a child node with a given name
175 */
176 static xmlNodePtr find_child(xmlNodePtr parent, const xmlChar* name)
177 {
178 xmlNodePtr child;
179
180 child = parent->xmlChildrenNode;
181 while (child)
182 {
183 if (xmlStrcmp(child->name, name) == 0)
184 {
185 return child;
186 }
187 child = child->next;
188 }
189
190 DBG1(DBG_TNC, "child node \"%s\" not found", name);
191 return NULL;
192 }
193
194 METHOD(tnc_ifmap_soap_msg_t, post, bool,
195 private_tnc_ifmap_soap_msg_t *this, xmlNodePtr request, char *result_name,
196 xmlNodePtr *result)
197 {
198 xmlDocPtr doc;
199 xmlNodePtr env, body, cur, response;
200 xmlNsPtr ns;
201 xmlChar *xml, *errorCode, *errorString;
202 int len;
203 chunk_t in, out;
204
205 DBG2(DBG_TNC, "sending ifmap %s", request->name);
206
207 /* Generate XML Document containing SOAP Envelope */
208 doc = xmlNewDoc("1.0");
209 env =xmlNewNode(NULL, "Envelope");
210 ns = xmlNewNs(env, SOAP_NS, "env");
211 xmlSetNs(env, ns);
212 xmlDocSetRootElement(doc, env);
213
214 /* Add SOAP Body containing IF-MAP request */
215 body = xmlNewNode(ns, "Body");
216 xmlAddChild(body, request);
217 xmlAddChild(env, body);
218
219 /* Convert XML Document into a character string */
220 xmlDocDumpFormatMemory(doc, &xml, &len, 1);
221 xmlFreeDoc(doc);
222 DBG3(DBG_TNC, "%.*s", len, xml);
223 out = chunk_create(xml, len);
224
225 /* Send SOAP-XML request via HTTP POST */
226 if (!http_post(this, out, &in))
227 {
228 xmlFree(xml);
229 return FALSE;
230 }
231 xmlFree(xml);
232
233 DBG3(DBG_TNC, "%B", &in);
234 this->doc = xmlParseMemory(in.ptr, in.len);
235 free(in.ptr);
236
237 if (!this->doc)
238 {
239 DBG1(DBG_TNC, "failed to parse XML message");
240 return FALSE;
241 }
242
243 /* check out XML document */
244 cur = xmlDocGetRootElement(this->doc);
245 if (!cur)
246 {
247 DBG1(DBG_TNC, "empty XML message");
248 return FALSE;
249 }
250
251 /* get XML Document type is a SOAP Envelope */
252 if (xmlStrcmp(cur->name, "Envelope"))
253 {
254 DBG1(DBG_TNC, "XML message does not contain a SOAP Envelope");
255 return FALSE;
256 }
257
258 /* get SOAP Body */
259 cur = find_child(cur, "Body");
260 if (!cur)
261 {
262 return FALSE;
263 }
264
265 /* get IF-MAP response */
266 response = find_child(cur, "response");
267 if (!response)
268 {
269 return FALSE;
270 }
271
272 /* get IF-MAP result */
273 cur = find_child(response, result_name);
274 if (!cur)
275 {
276 cur = find_child(response, "errorResult");
277 if (cur)
278 {
279 DBG1(DBG_TNC, "received errorResult");
280
281 errorCode = xmlGetProp(cur, "errorCode");
282 if (errorCode)
283 {
284 DBG1(DBG_TNC, " %s", errorCode);
285 xmlFree(errorCode);
286 }
287
288 cur = find_child(cur, "errorString");
289 if (cur)
290 {
291 errorString = xmlNodeGetContent(cur);
292 if (errorString)
293 {
294 DBG1(DBG_TNC, " %s", errorString);
295 xmlFree(errorString);
296 }
297 }
298 }
299 return FALSE;
300 }
301
302 if (result)
303 {
304 *result = cur;
305 }
306 return TRUE;
307 }
308
309 METHOD(tnc_ifmap_soap_msg_t, destroy, void,
310 private_tnc_ifmap_soap_msg_t *this)
311 {
312 if (this->doc)
313 {
314 xmlFreeDoc(this->doc);
315 }
316 free(this);
317 }
318
319 /**
320 * See header
321 */
322 tnc_ifmap_soap_msg_t *tnc_ifmap_soap_msg_create(char *uri, chunk_t user_pass,
323 tls_socket_t *tls)
324 {
325 private_tnc_ifmap_soap_msg_t *this;
326
327 INIT(this,
328 .public = {
329 .post = _post,
330 .destroy = _destroy,
331 },
332 .uri = uri,
333 .user_pass = user_pass,
334 .tls = tls,
335 );
336
337 return &this->public;
338 }
339