test: Introduce isds_change_password
[libisds.git] / test / simline / service.c
blobf8c90510ad5e50ea352861d6f8e6745ad8a7a18d
1 #define _XOPEN_SOURCE 500 /* For strdup(3) */
2 #include "../test-tools.h"
3 #include "http.h"
4 #include "services.h"
5 #include <string.h>
6 #include <libxml/parser.h>
7 #include <libxml/xpath.h>
8 #include <libxml/xpathInternals.h>
9 #include <libxml/xmlsave.h>
11 static const char *soap_mime_type = "text/xml"; /* SOAP/1.1 requires text/xml */
13 /* Used to choose proper name space for message elements.
14 * See _isds_register_namespaces(). */
15 typedef enum {
16 MESSAGE_NS_1,
17 MESSAGE_NS_UNSIGNED,
18 MESSAGE_NS_SIGNED_INCOMING,
19 MESSAGE_NS_SIGNED_OUTGOING,
20 MESSAGE_NS_SIGNED_DELIVERY
21 } message_ns_type;
23 #define SOAP_NS "http://schemas.xmlsoap.org/soap/envelope/"
24 #define SOAP2_NS "http://www.w3.org/2003/05/soap-envelope"
25 #define ISDS1_NS "http://isds.czechpoint.cz"
26 #define ISDS_NS "http://isds.czechpoint.cz/v20"
27 #define SISDS_INCOMING_NS "http://isds.czechpoint.cz/v20/message"
28 #define SISDS_OUTGOING_NS "http://isds.czechpoint.cz/v20/SentMessage"
29 #define SISDS_DELIVERY_NS "http://isds.czechpoint.cz/v20/delivery"
30 #define SCHEMA_NS "http://www.w3.org/2001/XMLSchema"
31 #define DEPOSIT_NS "urn:uschovnaWSDL"
34 struct service {
35 service_id id;
36 const char *end_point;
37 const xmlChar *name;
38 http_error (*function) (int, xmlDocPtr, xmlXPathContextPtr, xmlNodePtr,
39 xmlDocPtr, xmlNodePtr, const void *arguments);
42 /* Following EXTRACT_* macros expect @xpath_ctx, @message, and leave label */
43 #define EXTRACT_STRING(element, string) { \
44 xmlXPathObjectPtr result = NULL; \
45 result = xmlXPathEvalExpression(BAD_CAST element "/text()", xpath_ctx); \
46 if (NULL == result) { \
47 error = HTTP_ERROR_SERVER; \
48 goto leave; \
49 } \
50 if (!xmlXPathNodeSetIsEmpty(result->nodesetval)) { \
51 if (result->nodesetval->nodeNr > 1) { \
52 xmlXPathFreeObject(result); \
53 test_asprintf(&message, "Multiple %s element", element); \
54 error = HTTP_ERROR_CLIENT; \
55 goto leave; \
56 } \
57 (string) = (char *) \
58 xmlXPathCastNodeSetToString(result->nodesetval); \
59 if (!(string)) { \
60 xmlXPathFreeObject(result); \
61 error = HTTP_ERROR_SERVER; \
62 goto leave; \
63 } \
64 } \
65 xmlXPathFreeObject(result); \
68 /* Following INSERT_* macros expect @error and leave label */
69 #define INSERT_STRING_WITH_NS(parent, ns, element, string) \
70 { \
71 xmlNodePtr node = xmlNewTextChild(parent, ns, BAD_CAST (element), \
72 (xmlChar *) (string)); \
73 if (NULL == node) { \
74 error = HTTP_ERROR_SERVER; \
75 goto leave; \
76 } \
79 #define INSERT_STRING(parent, element, string) \
80 { INSERT_STRING_WITH_NS(parent, NULL, element, string) }
82 #define INSERT_ELEMENT(child, parent, element) \
83 { \
84 (child) = xmlNewChild((parent), NULL, BAD_CAST (element), NULL); \
85 if (NULL == (child)) { \
86 error = HTTP_ERROR_SERVER; \
87 goto leave; \
88 } \
91 /* Insert dmStatus or similar subtree
92 * @parent is element to insert to
93 * @dm is true for dmStatus, otherwise dbStatus
94 * @code is stautus code as string
95 * @message is UTF-8 encoded message
96 * @db_ref_number is optinal reference number propagated if not @dm
97 * @return 0 on success, otherwise non-0. */
98 static http_error insert_isds_status(xmlNodePtr parent, _Bool dm,
99 const xmlChar *code, const xmlChar *message,
100 const xmlChar *db_ref_number) {
101 http_error error = HTTP_ERROR_SUCCESS;
102 xmlNodePtr status;
104 if (NULL == code || NULL == message) {
105 error = HTTP_ERROR_SERVER;
106 goto leave;
109 INSERT_ELEMENT(status, parent, (dm) ? "dmStatus" : "dbStatus");
110 INSERT_STRING(status, (dm) ? "dmStatusCode" : "dbStatusCode", code);
111 INSERT_STRING(status, (dm) ? "dmStatusMessage" : "dbStatusMessage", message);
112 if (!dm && NULL != db_ref_number) {
113 INSERT_STRING(status, "dbStatusRefNumber", db_ref_number);
116 leave:
117 return error;
121 /* Implement DummyOperation */
122 static http_error service_DummyOperation(int socket, const xmlDocPtr soap_request,
123 xmlXPathContextPtr xpath_ctx, xmlNodePtr isds_request,
124 xmlDocPtr soap_response, xmlNodePtr isds_response,
125 const void *arguments) {
126 return insert_isds_status(isds_response, 1, BAD_CAST "0000",
127 BAD_CAST "Success", NULL);
131 /* Implement ChangeISDSPassword.
132 * @arguments is current password as const char * */
133 static http_error service_ChangeISDSPassword(int socket,
134 const xmlDocPtr soap_request, xmlXPathContextPtr xpath_ctx,
135 const xmlNodePtr isds_request,
136 xmlDocPtr soap_response, xmlNodePtr isds_response,
137 const void *arguments) {
138 http_error error = HTTP_ERROR_SUCCESS;
139 char *message = NULL;
140 const char *current_password = (const char *)arguments;
141 char *old_password = NULL, *new_password = NULL;
143 if (NULL == current_password) {
144 error = HTTP_ERROR_SERVER;
145 goto leave;
148 /* Parse request */
149 EXTRACT_STRING("isds:dbOldPassword", old_password);
150 if (NULL == old_password) {
151 message = strdup("Empty isds:dbOldPassword");
152 error = HTTP_ERROR_CLIENT;
153 goto leave;
155 EXTRACT_STRING("isds:dbNewPassword", new_password);
156 if (NULL == new_password) {
157 message = strdup("Empty isds:dbOldPassword");
158 error = HTTP_ERROR_CLIENT;
159 goto leave;
162 /* Check defined cases */
163 if (strcmp(current_password, old_password)) {
164 error = insert_isds_status(isds_response, 0, BAD_CAST "1090",
165 BAD_CAST "Bad current password", NULL);
166 if (error == HTTP_ERROR_SUCCESS) error = HTTP_ERROR_CLIENT;
167 } else {
168 error = insert_isds_status(isds_response, 0, BAD_CAST "0000",
169 BAD_CAST "Success", NULL);
171 goto end;
172 leave:
173 if (HTTP_ERROR_CLIENT == error) {
174 http_error next_error = insert_isds_status(isds_response, 0,
175 BAD_CAST "9999", BAD_CAST message, NULL);
176 if (HTTP_ERROR_SUCCESS != next_error) error = next_error;
178 end:
179 free(old_password);
180 free(new_password);
181 free(message);
182 return error;
186 /* List of implemented services */
187 static struct service services[] = {
188 { SERVICE_DS_Dz_DummyOperation,
189 "DS/dz", BAD_CAST "DummyOperation",
190 service_DummyOperation },
191 { SERVICE_DS_DsManage_ChangeISDSPassword,
192 "DS/DsManage", BAD_CAST "ChangeISDSPassword",
193 service_ChangeISDSPassword },
197 /* Makes known all relevant namespaces to given XPath context
198 * @xpath_ctx is XPath context
199 * @message_ns selects proper message name space. Unsigned and signed
200 * messages and delivery info's differ in prefix and URI.
201 * @return 0 in success, otherwise not 0. */
202 static int register_namespaces(xmlXPathContextPtr xpath_ctx,
203 const message_ns_type message_ns) {
204 const xmlChar *message_namespace = NULL;
206 if (!xpath_ctx) return -1;
208 switch(message_ns) {
209 case MESSAGE_NS_1:
210 message_namespace = BAD_CAST ISDS1_NS; break;
211 case MESSAGE_NS_UNSIGNED:
212 message_namespace = BAD_CAST ISDS_NS; break;
213 case MESSAGE_NS_SIGNED_INCOMING:
214 message_namespace = BAD_CAST SISDS_INCOMING_NS; break;
215 case MESSAGE_NS_SIGNED_OUTGOING:
216 message_namespace = BAD_CAST SISDS_OUTGOING_NS; break;
217 case MESSAGE_NS_SIGNED_DELIVERY:
218 message_namespace = BAD_CAST SISDS_DELIVERY_NS; break;
219 default:
220 return -1;
223 if (xmlXPathRegisterNs(xpath_ctx, BAD_CAST "soap", BAD_CAST SOAP_NS))
224 return -1;
225 if (xmlXPathRegisterNs(xpath_ctx, BAD_CAST "isds", BAD_CAST ISDS_NS))
226 return -1;
227 if (xmlXPathRegisterNs(xpath_ctx, BAD_CAST "sisds", message_namespace))
228 return -1;
229 if (xmlXPathRegisterNs(xpath_ctx, BAD_CAST "xs", BAD_CAST SCHEMA_NS))
230 return -1;
231 if (xmlXPathRegisterNs(xpath_ctx, BAD_CAST "deposit", BAD_CAST DEPOSIT_NS))
232 return -1;
233 return 0;
237 /* Parse soap request, pass it to service endpoint and respond to it.
238 * It sends final HTTP response. */
239 void soap(int socket, const struct service_configuration *configuration,
240 const void *request, size_t request_length, const char *end_point) {
241 xmlDocPtr request_doc = NULL;
242 xmlXPathContextPtr xpath_ctx = NULL;
243 xmlXPathObjectPtr request_soap_body = NULL;
244 xmlNodePtr isds_request = NULL; /* pointer only */
245 _Bool service_handled = 0, service_passed = 0;
246 xmlDocPtr response_doc = NULL;
247 xmlNodePtr response_soap_envelope = NULL, response_soap_body = NULL,
248 isds_response = NULL;
249 xmlNsPtr soap_ns = NULL, isds_ns = NULL;
250 char *response_name = NULL;
251 xmlBufferPtr http_response_body = NULL;
252 xmlSaveCtxtPtr save_ctx = NULL;
255 if (NULL == configuration) {
256 http_send_response_500(socket, "Second argument of soap() is NULL");
257 return;
260 if (NULL == request || request_length == 0) {
261 http_send_response_400(socket, "Client sent empty body");
262 return;
265 request_doc = xmlParseMemory(request, request_length);
266 if (NULL == request_doc) {
267 http_send_response_400(socket, "Client sent invalid XML document");
268 return;
271 xpath_ctx = xmlXPathNewContext(request_doc);
272 if (NULL == xpath_ctx) {
273 xmlFreeDoc(request_doc);
274 http_send_response_500(socket, "Could not create XPath context");
275 return;
278 if (register_namespaces(xpath_ctx, MESSAGE_NS_UNSIGNED)) {
279 xmlXPathFreeContext(xpath_ctx);
280 xmlFreeDoc(request_doc);
281 http_send_response_500(socket,
282 "Could not register name spaces to the XPath context");
283 return;
286 /* Get SOAP Body */
287 request_soap_body = xmlXPathEvalExpression(
288 BAD_CAST "/soap:Envelope/soap:Body", xpath_ctx);
289 if (NULL == request_soap_body) {
290 xmlXPathFreeContext(xpath_ctx);
291 xmlFreeDoc(request_doc);
292 http_send_response_400(socket, "Client sent invalid SOAP request");
293 return;
295 if (xmlXPathNodeSetIsEmpty(request_soap_body->nodesetval)) {
296 xmlXPathFreeObject(request_soap_body);
297 xmlXPathFreeContext(xpath_ctx);
298 xmlFreeDoc(request_doc);
299 http_send_response_400(socket,
300 "SOAP request does not contain SOAP Body element");
301 return;
303 if (request_soap_body->nodesetval->nodeNr > 1) {
304 xmlXPathFreeObject(request_soap_body);
305 xmlXPathFreeContext(xpath_ctx);
306 xmlFreeDoc(request_doc);
307 http_send_response_400(socket,
308 "SOAP response has more than one Body element");
309 return;
311 isds_request = request_soap_body->nodesetval->nodeTab[0]->children;
312 if (isds_request->next != NULL) {
313 xmlXPathFreeObject(request_soap_body);
314 xmlXPathFreeContext(xpath_ctx);
315 xmlFreeDoc(request_doc);
316 http_send_response_400(socket, "SOAP body has more than one child");
317 return;
319 if (isds_request->type != XML_ELEMENT_NODE || isds_request->ns == NULL ||
320 xmlStrcmp(isds_request->ns->href, BAD_CAST ISDS_NS)) {
321 xmlXPathFreeObject(request_soap_body);
322 xmlXPathFreeContext(xpath_ctx);
323 xmlFreeDoc(request_doc);
324 http_send_response_400(socket,
325 "SOAP body does not contain an ISDS elment");
326 return;
329 /* Build SOAP response envelope */
330 response_doc = xmlNewDoc(BAD_CAST "1.0");
331 if (!response_doc) {
332 http_send_response_500(socket, "Could not build SOAP response document");
333 goto leave;
335 response_soap_envelope = xmlNewNode(NULL, BAD_CAST "Envelope");
336 if (!response_soap_envelope) {
337 http_send_response_500(socket, "Could not build SOAP response envelope");
338 goto leave;
340 xmlDocSetRootElement(response_doc, response_soap_envelope);
341 /* Only this way we get namespace definition as @xmlns:soap,
342 * otherwise we get namespace prefix without definition */
343 soap_ns = xmlNewNs(response_soap_envelope, BAD_CAST SOAP_NS, NULL);
344 if(NULL == soap_ns) {
345 http_send_response_500(socket, "Could not create SOAP name space");
346 goto leave;
348 xmlSetNs(response_soap_envelope, soap_ns);
349 response_soap_body = xmlNewChild(response_soap_envelope, NULL,
350 BAD_CAST "Body", NULL);
351 if (!response_soap_body) {
352 http_send_response_500(socket,
353 "Could not add Body to SOAP response envelope");
354 goto leave;
356 /* Append ISDS response element */
357 if (-1 == test_asprintf(&response_name, "%s%s", isds_request->name,
358 "Response")) {
359 http_send_response_500(socket,
360 "Could not buld ISDS resposne element name");
361 goto leave;
363 isds_response = xmlNewChild(response_soap_body, NULL,
364 BAD_CAST response_name, NULL);
365 free(response_name);
366 if (NULL == isds_response) {
367 http_send_response_500(socket,
368 "Could not add ISDS response element to SOAP response body");
369 goto leave;
371 isds_ns = xmlNewNs(isds_response, BAD_CAST ISDS_NS, NULL);
372 if(NULL == isds_ns) {
373 http_send_response_500(socket, "Could not create ISDS name space");
374 goto leave;
376 xmlSetNs(isds_response, isds_ns);
378 /* Dispatch request to service */
379 for (int i = 0; i < sizeof(services)/sizeof(services[0]); i++) {
380 if (!strcmp(services[i].end_point, end_point) &&
381 !xmlStrcmp(services[i].name, isds_request->name)) {
382 /* Check if the configuration is enabled and find configuration */
383 for (const struct service_configuration *service = configuration;
384 service->name != SERVICE_END; service++) {
385 if (service->name == services[i].id) {
386 service_handled = 1;
387 xpath_ctx->node = isds_request;
388 if (HTTP_ERROR_SERVER != services[i].function(socket,
389 request_doc, xpath_ctx, isds_request,
390 response_doc, isds_response,
391 service->arguments)) {
392 service_passed = 1;
393 } else {
394 http_send_response_500(socket,
395 "Internal server error while processing "
396 "ISDS request");
400 break;
404 /* Send response */
405 if (service_passed) {
406 /* Serialize the SOAP response */
407 http_response_body = xmlBufferCreate();
408 if (NULL == http_response_body) {
409 http_send_response_500(socket,
410 "Could not create xmlBuffer for response serialization");
411 goto leave;
413 /* Last argument 1 means format the XML tree. This is pretty but it breaks
414 * XML document transport as it adds text nodes (indentiation) between
415 * elements. */
416 save_ctx = xmlSaveToBuffer(http_response_body, "UTF-8", 0);
417 if (NULL == save_ctx) {
418 http_send_response_500(socket, "Could not create XML serializer");
419 goto leave;
421 /* XXX: According LibXML documentation, this function does not return
422 * meaningful value yet */
423 xmlSaveDoc(save_ctx, response_doc);
424 if (-1 == xmlSaveFlush(save_ctx)) {
425 http_send_response_500(socket,
426 "Could not serialize SOAP response");
427 goto leave;
430 http_send_response_200(socket, http_response_body->content,
431 http_response_body->use, soap_mime_type);
434 leave:
435 xmlSaveClose(save_ctx);
436 xmlBufferFree(http_response_body);
438 xmlFreeDoc(response_doc);
440 xmlXPathFreeObject(request_soap_body);
441 xmlXPathFreeContext(xpath_ctx);
442 xmlFreeDoc(request_doc);
444 if (!service_handled) {
445 http_send_response_500(socket,
446 "Requested ISDS service not implemented");