dates element are mandatory at DataBoxCreditInfo
[libisds.git] / test / simline / service.c
blob92c2b4dd7e997053a76eb0d6ef02a60890af18ab
1 #define _XOPEN_SOURCE 500 /* For strdup(3) */
2 #include "../test-tools.h"
3 #include "http.h"
4 #include "services.h"
5 #include "system.h"
6 #include <string.h>
7 #include <libxml/parser.h>
8 #include <libxml/xpath.h>
9 #include <libxml/xpathInternals.h>
10 #include <libxml/xmlsave.h>
12 static const char *soap_mime_type = "text/xml"; /* SOAP/1.1 requires text/xml */
14 /* Used to choose proper name space for message elements.
15 * See _isds_register_namespaces(). */
16 typedef enum {
17 MESSAGE_NS_1,
18 MESSAGE_NS_UNSIGNED,
19 MESSAGE_NS_SIGNED_INCOMING,
20 MESSAGE_NS_SIGNED_OUTGOING,
21 MESSAGE_NS_SIGNED_DELIVERY,
22 MESSAGE_NS_OTP
23 } message_ns_type;
25 #define SOAP_NS "http://schemas.xmlsoap.org/soap/envelope/"
26 #define SOAP2_NS "http://www.w3.org/2003/05/soap-envelope"
27 #define ISDS1_NS "http://isds.czechpoint.cz"
28 #define ISDS_NS "http://isds.czechpoint.cz/v20"
29 #define OISDS_NS "http://isds.czechpoint.cz/v20/asws"
30 #define SISDS_INCOMING_NS "http://isds.czechpoint.cz/v20/message"
31 #define SISDS_OUTGOING_NS "http://isds.czechpoint.cz/v20/SentMessage"
32 #define SISDS_DELIVERY_NS "http://isds.czechpoint.cz/v20/delivery"
33 #define SCHEMA_NS "http://www.w3.org/2001/XMLSchema"
34 #define DEPOSIT_NS "urn:uschovnaWSDL"
37 struct service {
38 service_id id;
39 const char *end_point;
40 const xmlChar *name_space;
41 const xmlChar *name;
42 http_error (*function) (const struct http_connection *, xmlDocPtr,
43 xmlXPathContextPtr, xmlNodePtr, xmlDocPtr, xmlNodePtr,
44 const void *arguments);
47 /* Following EXTRACT_* macros expect @xpath_ctx, @error, @message,
48 * and leave label. */
49 #define ELEMENT_EXISTS(element, allow_multiple) { \
50 xmlXPathObjectPtr result = NULL; \
51 result = xmlXPathEvalExpression(BAD_CAST element, xpath_ctx); \
52 if (NULL == result) { \
53 error = HTTP_ERROR_SERVER; \
54 goto leave; \
55 } \
56 if (xmlXPathNodeSetIsEmpty(result->nodesetval)) { \
57 xmlXPathFreeObject(result); \
58 test_asprintf(&message, "Element %s does not exist", element); \
59 error = HTTP_ERROR_CLIENT; \
60 goto leave; \
61 } else { \
62 if (!allow_multiple && result->nodesetval->nodeNr > 1) { \
63 xmlXPathFreeObject(result); \
64 test_asprintf(&message, "Multiple %s element", element); \
65 error = HTTP_ERROR_CLIENT; \
66 goto leave; \
67 } \
68 } \
69 xmlXPathFreeObject(result); \
72 #define EXTRACT_STRING(element, string) { \
73 xmlXPathObjectPtr result = NULL; \
74 result = xmlXPathEvalExpression(BAD_CAST element "/text()", xpath_ctx); \
75 if (NULL == result) { \
76 error = HTTP_ERROR_SERVER; \
77 goto leave; \
78 } \
79 if (!xmlXPathNodeSetIsEmpty(result->nodesetval)) { \
80 if (result->nodesetval->nodeNr > 1) { \
81 xmlXPathFreeObject(result); \
82 test_asprintf(&message, "Multiple %s element", element); \
83 error = HTTP_ERROR_CLIENT; \
84 goto leave; \
85 } \
86 (string) = (char *) \
87 xmlXPathCastNodeSetToString(result->nodesetval); \
88 if (!(string)) { \
89 xmlXPathFreeObject(result); \
90 error = HTTP_ERROR_SERVER; \
91 goto leave; \
92 } \
93 } \
94 xmlXPathFreeObject(result); \
97 #define EXTRACT_BOOLEAN(element, booleanPtr) { \
98 char *string = NULL; \
99 EXTRACT_STRING(element, string); \
101 if (NULL != string) { \
102 (booleanPtr) = calloc(1, sizeof(*(booleanPtr))); \
103 if (NULL == (booleanPtr)) { \
104 free(string); \
105 error = HTTP_ERROR_SERVER; \
106 goto leave; \
109 if (!xmlStrcmp((xmlChar *)string, BAD_CAST "true") || \
110 !xmlStrcmp((xmlChar *)string, BAD_CAST "1")) \
111 *(booleanPtr) = 1; \
112 else if (!xmlStrcmp((xmlChar *)string, BAD_CAST "false") || \
113 !xmlStrcmp((xmlChar *)string, BAD_CAST "0")) \
114 *(booleanPtr) = 0; \
115 else { \
116 test_asprintf(&message, \
117 "%s value is not valid boolean: %s", \
118 element, string); \
119 free(string); \
120 error = HTTP_ERROR_CLIENT; \
121 goto leave; \
124 free(string); \
129 #define EXTRACT_DATE(element, tmPtr) { \
130 char *string = NULL; \
131 EXTRACT_STRING(element, string); \
132 if (NULL != string) { \
133 (tmPtr) = calloc(1, sizeof(*(tmPtr))); \
134 if (NULL == (tmPtr)) { \
135 free(string); \
136 error = HTTP_ERROR_SERVER; \
137 goto leave; \
139 error = _server_datestring2tm(string, (tmPtr)); \
140 if (error) { \
141 if (error == HTTP_ERROR_CLIENT) { \
142 test_asprintf(&message, "%s value is not a valid date: %s", \
143 element, string); \
145 free(string); \
146 goto leave; \
148 free(string); \
152 /* Following INSERT_* macros expect @error and leave label */
153 #define INSERT_STRING_WITH_NS(parent, ns, element, string) \
155 xmlNodePtr node = xmlNewTextChild(parent, ns, BAD_CAST (element), \
156 (xmlChar *) (string)); \
157 if (NULL == node) { \
158 error = HTTP_ERROR_SERVER; \
159 goto leave; \
163 #define INSERT_STRING(parent, element, string) \
164 { INSERT_STRING_WITH_NS(parent, NULL, element, string) }
166 #define INSERT_LONGINTPTR(parent, element, longintPtr) { \
167 if ((longintPtr)) { \
168 char *buffer = NULL; \
169 /* FIXME: locale sensitive */ \
170 if (-1 == test_asprintf(&buffer, "%ld", *(longintPtr))) { \
171 error = HTTP_ERROR_SERVER; \
172 goto leave; \
174 INSERT_STRING(parent, element, buffer) \
175 free(buffer); \
176 } else { INSERT_STRING(parent, element, NULL) } \
179 #define INSERT_TIMEVALPTR(parent, element, timevalPtr) { \
180 if (NULL != (timevalPtr)) { \
181 char *buffer = NULL; \
182 error = timeval2timestring(timevalPtr, &buffer); \
183 if (error) { \
184 free(buffer); \
185 goto leave; \
187 INSERT_STRING(parent, element, buffer); \
188 free(buffer); \
189 } else { \
190 INSERT_STRING(parent, element, NULL); \
194 #define INSERT_TMPTR(parent, element, tmPtr) { \
195 if (NULL != (tmPtr)) { \
196 char *buffer = NULL; \
197 error = tm2datestring(tmPtr, &buffer); \
198 if (error) { \
199 free(buffer); \
200 goto leave; \
202 INSERT_STRING(parent, element, buffer); \
203 free(buffer); \
204 } else { \
205 INSERT_STRING(parent, element, NULL); \
209 #define INSERT_ELEMENT(child, parent, element) \
211 (child) = xmlNewChild((parent), NULL, BAD_CAST (element), NULL); \
212 if (NULL == (child)) { \
213 error = HTTP_ERROR_SERVER; \
214 goto leave; \
219 /* Insert dmStatus or similar subtree
220 * @parent is element to insert to
221 * @dm is true for dmStatus, otherwise dbStatus
222 * @code is status code as string
223 * @message is UTF-8 encoded message
224 * @db_ref_number is optinal reference number propagated if not @dm
225 * @return 0 on success, otherwise non-0. */
226 static http_error insert_isds_status(xmlNodePtr parent, _Bool dm,
227 const xmlChar *code, const xmlChar *message,
228 const xmlChar *db_ref_number) {
229 http_error error = HTTP_ERROR_SUCCESS;
230 xmlNodePtr status;
232 if (NULL == code || NULL == message) {
233 error = HTTP_ERROR_SERVER;
234 goto leave;
237 INSERT_ELEMENT(status, parent, (dm) ? "dmStatus" : "dbStatus");
238 INSERT_STRING(status, (dm) ? "dmStatusCode" : "dbStatusCode", code);
239 INSERT_STRING(status, (dm) ? "dmStatusMessage" : "dbStatusMessage", message);
240 if (!dm && NULL != db_ref_number) {
241 INSERT_STRING(status, "dbStatusRefNumber", db_ref_number);
244 leave:
245 return error;
249 /* Convert struct tm *@time to UTF-8 ISO 8601 date @string. */
250 static http_error tm2datestring(const struct tm *time, char **string) {
251 if (NULL == time || NULL == string) return HTTP_ERROR_SERVER;
253 if (-1 == test_asprintf(string, "%d-%02d-%02d",
254 time->tm_year + 1900, time->tm_mon + 1, time->tm_mday))
255 return HTTP_ERROR_SERVER;
257 return HTTP_ERROR_SUCCESS;
261 /* Convert struct timeval *@time to UTF-8 ISO 8601 date-time @string. It
262 * respects the @time microseconds too. */
263 static http_error timeval2timestring(const struct timeval *time,
264 char **string) {
265 struct tm broken;
267 if (!time || !string) return HTTP_ERROR_SERVER;
269 if (!gmtime_r(&time->tv_sec, &broken)) return HTTP_ERROR_SERVER;
270 if (time->tv_usec < 0 || time->tv_usec > 999999) return HTTP_ERROR_SERVER;
272 /* TODO: small negative year should be formatted as "-0012". This is not
273 * true for glibc "%04d". We should implement it.
274 * TODO: What's type of time->tv_usec exactly? Unsigned? Absolute?
275 * See <http://www.w3.org/TR/2001/REC-xmlschema-2-20010502/#dateTime> */
276 if (-1 == test_asprintf(string,
277 "%04d-%02d-%02dT%02d:%02d:%02d.%06ld",
278 broken.tm_year + 1900, broken.tm_mon + 1, broken.tm_mday,
279 broken.tm_hour, broken.tm_min, broken.tm_sec,
280 time->tv_usec))
281 return HTTP_ERROR_SERVER;
283 return HTTP_ERROR_SUCCESS;
287 /* Implement DummyOperation */
288 static http_error service_DummyOperation(
289 const struct http_connection *connection, const xmlDocPtr soap_request,
290 xmlXPathContextPtr xpath_ctx, xmlNodePtr isds_request,
291 xmlDocPtr soap_response, xmlNodePtr isds_response,
292 const void *arguments) {
293 return insert_isds_status(isds_response, 1, BAD_CAST "0000",
294 BAD_CAST "Success", NULL);
298 /* Implement Re-signISDSDocument.
299 * It sends document from request back.
300 * @arguments is pointer to struct arguments_DS_Dz_ResignISDSDocument */
301 static http_error service_ResignISDSDocument(
302 const struct http_connection *connection,
303 const xmlDocPtr soap_request, xmlXPathContextPtr xpath_ctx,
304 const xmlNodePtr isds_request,
305 xmlDocPtr soap_response, xmlNodePtr isds_response,
306 const void *arguments) {
307 http_error error = HTTP_ERROR_SUCCESS;
308 const char *code = "9999";
309 char *message = NULL;
310 const struct arguments_DS_Dz_ResignISDSDocument *configuration =
311 (const struct arguments_DS_Dz_ResignISDSDocument *)arguments;
312 char *data = NULL;
314 if (NULL == configuration || NULL == configuration->status_code ||
315 NULL == configuration->status_message) {
316 error = HTTP_ERROR_SERVER;
317 goto leave;
320 EXTRACT_STRING("isds:dmDoc", data);
321 if (NULL == data) {
322 message = strdup("Missing isds:dmDoc");
323 error = HTTP_ERROR_CLIENT;
324 goto leave;
328 /* dmResultDoc is mandatory in response */
329 if (xmlStrcmp(BAD_CAST configuration->status_code, BAD_CAST "0000")) {
330 free(data);
331 data = NULL;
333 INSERT_STRING(isds_response, "dmResultDoc", data);
335 if (configuration->valid_to != NULL) {
336 error = tm2datestring(configuration->valid_to, &data);
337 if (error) {
338 message = strdup("Could not format date");
339 goto leave;
341 INSERT_STRING(isds_response, "dmValidTo", data);
344 code = configuration->status_code;
345 message = strdup(configuration->status_message);
347 leave:
348 if (HTTP_ERROR_SERVER != error) {
349 http_error next_error = insert_isds_status(isds_response, 1,
350 BAD_CAST code, BAD_CAST message, NULL);
351 if (HTTP_ERROR_SUCCESS != next_error) error = next_error;
353 free(data);
354 free(message);
355 return error;
359 /* Implement EraseMessage.
360 * @arguments is pointer to struct arguments_DS_DsManage_ChangeISDSPassword */
361 static http_error service_EraseMessage(const struct http_connection *connection,
362 const xmlDocPtr soap_request, xmlXPathContextPtr xpath_ctx,
363 const xmlNodePtr isds_request,
364 xmlDocPtr soap_response, xmlNodePtr isds_response,
365 const void *arguments) {
366 http_error error = HTTP_ERROR_SUCCESS;
367 char *code = "9999", *message = NULL;
368 const struct arguments_DS_Dx_EraseMessage *configuration =
369 (const struct arguments_DS_Dx_EraseMessage *)arguments;
370 char *message_id = NULL;
371 _Bool *incoming = NULL;
373 if (NULL == configuration || NULL == configuration->message_id) {
374 error = HTTP_ERROR_SERVER;
375 goto leave;
378 EXTRACT_STRING("isds:dmID", message_id);
379 if (NULL == message_id) {
380 message = strdup("Missing isds:dmID");
381 error = HTTP_ERROR_CLIENT;
382 goto leave;
384 EXTRACT_BOOLEAN("isds:dmIncoming", incoming);
385 if (NULL == incoming) {
386 message = strdup("Missing isds:dmIncoming");
387 error = HTTP_ERROR_CLIENT;
388 goto leave;
391 if (xmlStrcmp((const xmlChar *) configuration->message_id,
392 (const xmlChar *) message_id)) {
393 code = "1219";
394 message = strdup("Message is not in the long term storage");
395 error = HTTP_ERROR_CLIENT;
396 goto leave;
398 if (configuration->incoming != *incoming) {
399 code = "1219";
400 message = strdup("Message direction mismatches");
401 error = HTTP_ERROR_CLIENT;
402 goto leave;
405 code = "0000";
406 message = strdup("Success");
407 leave:
408 if (HTTP_ERROR_SERVER != error) {
409 http_error next_error = insert_isds_status(isds_response, 1,
410 BAD_CAST code, BAD_CAST message, NULL);
411 if (HTTP_ERROR_SUCCESS != next_error) error = next_error;
413 free(incoming);
414 free(message_id);
415 free(message);
416 return error;
420 /* Insert list of credit info as XSD:tCiRecord XML tree.
421 * @isds_response is XML node with the response
422 * @history is list of struct server_credit_event. If NULL, no ciRecord XML
423 * subtree will be created. */
424 static http_error insert_ciRecords(xmlNodePtr isds_response,
425 const struct server_list *history) {
426 http_error error = HTTP_ERROR_SUCCESS;
427 xmlNodePtr records, record;
429 if (NULL == isds_response) return HTTP_ERROR_SERVER;
430 if (NULL == history) return HTTP_ERROR_SUCCESS;
432 INSERT_ELEMENT(records, isds_response, "ciRecords");
433 for (const struct server_list *item = history; NULL != item;
434 item = item->next) {
435 const struct server_credit_event *event =
436 (struct server_credit_event*)item->data;
438 INSERT_ELEMENT(record, records, "ciRecord");
439 if (NULL == event) continue;
441 INSERT_TIMEVALPTR(record, "ciEventTime", event->time);
442 switch(event->type) {
443 case SERVER_CREDIT_CHARGED:
444 INSERT_STRING(record, "ciEventType", "1");
445 INSERT_STRING(record, "ciTransID",
446 event->details.charged.transaction);
447 break;
448 case SERVER_CREDIT_DISCHARGED:
449 INSERT_STRING(record, "ciEventType", "2");
450 INSERT_STRING(record, "ciTransID",
451 event->details.discharged.transaction);
452 break;
453 case SERVER_CREDIT_MESSAGE_SENT:
454 INSERT_STRING(record, "ciEventType", "3");
455 INSERT_STRING(record, "ciRecipientID",
456 event->details.message_sent.recipient);
457 INSERT_STRING(record, "ciPDZID",
458 event->details.message_sent.message_id);
459 break;
460 case SERVER_CREDIT_STORAGE_SET:
461 INSERT_STRING(record, "ciEventType", "4");
462 INSERT_LONGINTPTR(record, "ciNewCapacity",
463 &event->details.storage_set.new_capacity);
464 INSERT_TMPTR(record, "ciNewFrom",
465 event->details.storage_set.new_valid_from);
466 INSERT_TMPTR(record, "ciNewTo",
467 event->details.storage_set.new_valid_to);
468 INSERT_LONGINTPTR(record, "ciOldCapacity",
469 event->details.storage_set.old_capacity);
470 INSERT_TMPTR(record, "ciOldFrom",
471 event->details.storage_set.old_valid_from);
472 INSERT_TMPTR(record, "ciOldTo",
473 event->details.storage_set.old_valid_to);
474 INSERT_STRING(record, "ciDoneBy",
475 event->details.storage_set.initiator);
476 break;
477 case SERVER_CREDIT_EXPIRED:
478 INSERT_STRING(record, "ciEventType", "5");
479 break;
480 default:
481 error = HTTP_ERROR_SERVER;
482 goto leave;
484 INSERT_LONGINTPTR(record, "ciCreditChange", &event->credit_change);
485 INSERT_LONGINTPTR(record, "ciCreditAfter", &event->new_credit);
489 leave:
490 return error;
494 /* Implement DataBoxCreditInfo.
495 * @arguments is pointer to struct arguments_DS_df_DataBoxCreditInfo */
496 static http_error service_DataBoxCreditInfo(
497 const struct http_connection *connection,
498 const xmlDocPtr soap_request, xmlXPathContextPtr xpath_ctx,
499 const xmlNodePtr isds_request,
500 xmlDocPtr soap_response, xmlNodePtr isds_response,
501 const void *arguments) {
502 http_error error = HTTP_ERROR_SUCCESS;
503 const char *code = "9999";
504 char *message = NULL;
505 const struct arguments_DS_df_DataBoxCreditInfo *configuration =
506 (const struct arguments_DS_df_DataBoxCreditInfo *)arguments;
507 char *box_id = NULL;
508 struct tm *from_date = NULL, *to_date = NULL;
510 if (NULL == configuration || NULL == configuration->status_code ||
511 NULL == configuration->status_message) {
512 error = HTTP_ERROR_SERVER;
513 goto leave;
516 EXTRACT_STRING("isds:dbID", box_id);
517 if (NULL == box_id) {
518 message = strdup("Missing isds:dbID");
519 error = HTTP_ERROR_CLIENT;
520 goto leave;
522 if (NULL != configuration->box_id &&
523 xmlStrcmp(BAD_CAST configuration->box_id,
524 BAD_CAST box_id)) {
525 code = "9999";
526 message = strdup("Unexpected isds:dbID value");
527 error = HTTP_ERROR_CLIENT;
528 goto leave;
531 ELEMENT_EXISTS("isds:ciFromDate", 0);
532 EXTRACT_DATE("isds:ciFromDate", from_date);
533 ELEMENT_EXISTS("isds:ciTodate", 0);
534 EXTRACT_DATE("isds:ciTodate", to_date);
535 /* FIXME: Check for date values */
537 INSERT_LONGINTPTR(isds_response, "currentCredit",
538 &configuration->current_credit);
539 INSERT_STRING(isds_response, "notifEmail", configuration->email);
540 if ((error = insert_ciRecords(isds_response, configuration->history))) {
541 goto leave;
544 code = configuration->status_code;
545 message = strdup(configuration->status_message);
546 leave:
547 if (HTTP_ERROR_SERVER != error) {
548 http_error next_error = insert_isds_status(isds_response, 0,
549 BAD_CAST code, BAD_CAST message, NULL);
550 if (HTTP_ERROR_SUCCESS != next_error) error = next_error;
552 free(box_id);
553 free(from_date);
554 free(to_date);
555 free(message);
556 return error;
560 /* Common part for ChangeISDSPassword and ChangePasswordOTP.
561 * @code is output pointer to static string
562 * @pass_message is output pointer to auto-allocated string
563 * @arguments is pointer to struct arguments_DS_DsManage_ChangeISDSPassword */
564 static http_error check_passwd(
565 const char *username, const char *current_password,
566 xmlXPathContextPtr xpath_ctx,
567 char **code, char **pass_message) {
568 http_error error = HTTP_ERROR_SUCCESS;
569 char *message = NULL;
570 char *old_password = NULL, *new_password = NULL;
571 size_t length;
573 if (NULL == username || NULL == current_password ||
574 NULL == code || NULL == pass_message) {
575 error = HTTP_ERROR_SERVER;
576 goto leave;
579 *code = "9999";
582 /* Parse request */
583 EXTRACT_STRING("isds:dbOldPassword", old_password);
584 if (NULL == old_password) {
585 message = strdup("Empty isds:dbOldPassword");
586 error = HTTP_ERROR_CLIENT;
587 goto leave;
589 EXTRACT_STRING("isds:dbNewPassword", new_password);
590 if (NULL == new_password) {
591 message = strdup("Empty isds:dbOldPassword");
592 error = HTTP_ERROR_CLIENT;
593 goto leave;
596 /* Check defined cases */
597 if (strcmp(current_password, old_password)) {
598 *code = "1090";
599 message = strdup("Bad current password");
600 error = HTTP_ERROR_CLIENT;
601 goto leave;
604 length = strlen(new_password);
606 if (length < 8 || length > 32) {
607 *code = "1066";
608 message = strdup("Too short or too long");
609 error = HTTP_ERROR_CLIENT;
610 goto leave;
614 const char lower[] = "abcdefghijklmnopqrstuvwxyz";
615 const char upper[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
616 const char digit[] = "0123456789";
617 const char special[] = "!#$%&()*+,-.:=?@[]_{}|~";
618 _Bool has_lower = 0, has_upper = 0, has_digit=0;
620 for (int i = 0; i < length; i++) {
621 if (NULL != strchr(lower, new_password[i]))
622 has_lower = 1;
623 else if (NULL != strchr(upper, new_password[i]))
624 has_upper = 1;
625 else if (NULL != strchr(digit, new_password[i]))
626 has_digit = 1;
627 else if (NULL == strchr(special, new_password[i])) {
628 *code = "1079";
629 message = strdup("Password contains forbidden character");
630 error = HTTP_ERROR_CLIENT;
631 goto leave;
635 if (!has_lower || !has_upper || !has_digit) {
636 *code = "1080";
637 message = strdup("Password does not contain lower cased letter, "
638 "upper cased letter and a digit");
639 error = HTTP_ERROR_CLIENT;
640 goto leave;
644 if (!strcmp(old_password, new_password)) {
645 *code = "1067";
646 message = strdup("New password same as current one");
647 error = HTTP_ERROR_CLIENT;
648 goto leave;
651 if (NULL != strstr(new_password, username)) {
652 *code = "1082";
653 message = strdup("New password contains user ID");
654 error = HTTP_ERROR_CLIENT;
655 goto leave;
658 for (int i = 0; i < length - 2; i++) {
659 if (new_password[i] == new_password[i+1] &&
660 new_password[i] == new_password[i+2]) {
661 *code = "1083";
662 message = strdup("Password contains sequence "
663 "of three identical characters");
664 error = HTTP_ERROR_CLIENT;
665 goto leave;
670 const char *forbidden_prefix[] = { "qwert", "asdgf", "12345" };
671 for (int i = 0; i < sizeof(forbidden_prefix)/sizeof(*forbidden_prefix);
672 i++) {
673 if (!strncmp(new_password, forbidden_prefix[i],
674 strlen(forbidden_prefix[i]))) {
675 *code = "1083";
676 message = strdup("Password has forbidden prefix");
677 error = HTTP_ERROR_CLIENT;
678 goto leave;
683 *code = "0000";
684 message = strdup("Success");
685 leave:
686 free(old_password);
687 free(new_password);
688 *pass_message = message;
689 return error;
693 /* Implement ChangeISDSPassword.
694 * @arguments is pointer to struct arguments_DS_DsManage_ChangeISDSPassword */
695 static http_error service_ChangeISDSPassword(
696 const struct http_connection *connection,
697 const xmlDocPtr soap_request, xmlXPathContextPtr xpath_ctx,
698 const xmlNodePtr isds_request,
699 xmlDocPtr soap_response, xmlNodePtr isds_response,
700 const void *arguments) {
701 http_error error = HTTP_ERROR_SUCCESS;
702 char *code = "9999", *message = NULL;
703 const struct arguments_DS_DsManage_ChangeISDSPassword *configuration =
704 (const struct arguments_DS_DsManage_ChangeISDSPassword *)arguments;
706 if (NULL == configuration || NULL == configuration->username ||
707 NULL == configuration->current_password) {
708 error = HTTP_ERROR_SERVER;
709 goto leave;
712 /* Check for common password rules */
713 error = check_passwd(
714 configuration->username, configuration->current_password,
715 xpath_ctx, &code, &message);
717 leave:
718 if (HTTP_ERROR_SERVER != error) {
719 http_error next_error = insert_isds_status(isds_response, 0,
720 BAD_CAST code, BAD_CAST message, NULL);
721 if (HTTP_ERROR_SUCCESS != next_error) error = next_error;
723 free(message);
724 return error;
728 /* Implement ChangePasswordOTP.
729 * @arguments is pointer to struct
730 * arguments_asws_changePassword_ChangePasswordOTP */
731 static http_error service_ChangePasswordOTP(
732 const struct http_connection *connection,
733 const xmlDocPtr soap_request, xmlXPathContextPtr xpath_ctx,
734 const xmlNodePtr isds_request,
735 xmlDocPtr soap_response, xmlNodePtr isds_response,
736 const void *arguments) {
737 http_error error = HTTP_ERROR_SUCCESS;
738 char *code = "9999", *message = NULL;
739 const struct arguments_asws_changePassword_ChangePasswordOTP *configuration
740 = (const struct arguments_asws_changePassword_ChangePasswordOTP *)
741 arguments;
742 char *method = NULL;
744 if (NULL == configuration || NULL == configuration->username ||
745 NULL == configuration->current_password) {
746 error = HTTP_ERROR_SERVER;
747 goto leave;
750 /* Chek for OTP method */
751 EXTRACT_STRING("isds:dbOTPType", method);
752 if (NULL == method) {
753 message = strdup("Empty isds:dbOTPType");
754 error = HTTP_ERROR_CLIENT;
755 goto leave;
757 if ((configuration->method == AUTH_OTP_HMAC && strcmp(method, "HOTP")) ||
758 (configuration->method == AUTH_OTP_TIME && strcmp(method, "TOTP"))) {
759 message = strdup("isds:dbOTPType does not match OTP method");
760 error = HTTP_ERROR_CLIENT;
761 goto leave;
764 /* Check for common password rules */
765 error = check_passwd(
766 configuration->username, configuration->current_password,
767 xpath_ctx, &code, &message);
769 leave:
770 if (HTTP_ERROR_SERVER != error) {
771 http_error next_error = insert_isds_status(isds_response, 0,
772 BAD_CAST code, BAD_CAST message,
773 BAD_CAST configuration->reference_number);
774 if (HTTP_ERROR_SUCCESS != next_error) error = next_error;
776 free(message);
777 free(method);
778 return error;
782 /* Implement SendSMSCode.
783 * @arguments is pointer to struct arguments_asws_changePassword_SendSMSCode */
784 static http_error service_SendSMSCode(
785 const struct http_connection *connection,
786 const xmlDocPtr soap_request, xmlXPathContextPtr xpath_ctx,
787 const xmlNodePtr isds_request,
788 xmlDocPtr soap_response, xmlNodePtr isds_response,
789 const void *arguments) {
790 const struct arguments_asws_changePassword_SendSMSCode *configuration
791 = (const struct arguments_asws_changePassword_SendSMSCode *)
792 arguments;
794 if (NULL == configuration || NULL == configuration->status_code ||
795 NULL == configuration->status_message) {
796 return HTTP_ERROR_SERVER;
799 return insert_isds_status(isds_response, 0,
800 BAD_CAST configuration->status_code,
801 BAD_CAST configuration->status_message,
802 BAD_CAST configuration->reference_number);
806 /* List of implemented services */
807 static struct service services[] = {
808 { SERVICE_DS_Dz_DummyOperation,
809 "DS/dz", BAD_CAST ISDS_NS, BAD_CAST "DummyOperation",
810 service_DummyOperation },
811 { SERVICE_DS_Dz_ResignISDSDocument,
812 "DS/dz", BAD_CAST ISDS_NS, BAD_CAST "Re-signISDSDocument",
813 service_ResignISDSDocument },
814 { SERVICE_DS_df_DataBoxCreditInfo,
815 "DS/df", BAD_CAST ISDS_NS, BAD_CAST "DataBoxCreditInfo",
816 service_DataBoxCreditInfo },
817 { SERVICE_DS_DsManage_ChangeISDSPassword,
818 "DS/DsManage", BAD_CAST ISDS_NS, BAD_CAST "ChangeISDSPassword",
819 service_ChangeISDSPassword },
820 { SERVICE_DS_Dx_EraseMessage,
821 "DS/dx", BAD_CAST ISDS_NS, BAD_CAST "EraseMessage",
822 service_EraseMessage },
823 { SERVICE_asws_changePassword_ChangePasswordOTP,
824 "/asws/changePassword", BAD_CAST OISDS_NS, BAD_CAST "ChangePasswordOTP",
825 service_ChangePasswordOTP },
826 { SERVICE_asws_changePassword_SendSMSCode,
827 "/asws/changePassword", BAD_CAST OISDS_NS, BAD_CAST "SendSMSCode",
828 service_SendSMSCode },
832 /* Makes known all relevant namespaces to given XPath context
833 * @xpath_ctx is XPath context
834 * @otp_ns selects name space for the request and response know as "isds".
835 * Use true for OTP-authenticated password change services, otherwise false.
836 * @message_ns selects proper message name space. Unsigned and signed
837 * messages and delivery info's differ in prefix and URI.
838 * @return 0 in success, otherwise not 0. */
839 static int register_namespaces(xmlXPathContextPtr xpath_ctx,
840 const _Bool otp_ns, const message_ns_type message_ns) {
841 const xmlChar *service_namespace = NULL;
842 const xmlChar *message_namespace = NULL;
844 if (!xpath_ctx) return -1;
846 if (otp_ns) {
847 service_namespace = BAD_CAST OISDS_NS;
848 } else {
849 service_namespace = BAD_CAST ISDS_NS;
852 switch(message_ns) {
853 case MESSAGE_NS_1:
854 message_namespace = BAD_CAST ISDS1_NS; break;
855 case MESSAGE_NS_UNSIGNED:
856 message_namespace = BAD_CAST ISDS_NS; break;
857 case MESSAGE_NS_SIGNED_INCOMING:
858 message_namespace = BAD_CAST SISDS_INCOMING_NS; break;
859 case MESSAGE_NS_SIGNED_OUTGOING:
860 message_namespace = BAD_CAST SISDS_OUTGOING_NS; break;
861 case MESSAGE_NS_SIGNED_DELIVERY:
862 message_namespace = BAD_CAST SISDS_DELIVERY_NS; break;
863 default:
864 return -1;
867 if (xmlXPathRegisterNs(xpath_ctx, BAD_CAST "soap", BAD_CAST SOAP_NS))
868 return -1;
869 if (xmlXPathRegisterNs(xpath_ctx, BAD_CAST "isds", service_namespace))
870 return -1;
871 if (xmlXPathRegisterNs(xpath_ctx, BAD_CAST "sisds", message_namespace))
872 return -1;
873 if (xmlXPathRegisterNs(xpath_ctx, BAD_CAST "xs", BAD_CAST SCHEMA_NS))
874 return -1;
875 if (xmlXPathRegisterNs(xpath_ctx, BAD_CAST "deposit", BAD_CAST DEPOSIT_NS))
876 return -1;
877 return 0;
881 /* Parse soap request, pass it to service endpoint and respond to it.
882 * It sends final HTTP response. */
883 void soap(const struct http_connection *connection,
884 const struct service_configuration *configuration,
885 const void *request, size_t request_length, const char *end_point) {
886 xmlDocPtr request_doc = NULL;
887 xmlXPathContextPtr xpath_ctx = NULL;
888 xmlXPathObjectPtr request_soap_body = NULL;
889 xmlNodePtr isds_request = NULL; /* pointer only */
890 _Bool service_handled = 0, service_passed = 0;
891 xmlDocPtr response_doc = NULL;
892 xmlNodePtr response_soap_envelope = NULL, response_soap_body = NULL,
893 isds_response = NULL;
894 xmlNsPtr soap_ns = NULL, isds_ns = NULL;
895 char *response_name = NULL;
896 xmlBufferPtr http_response_body = NULL;
897 xmlSaveCtxtPtr save_ctx = NULL;
900 if (NULL == configuration) {
901 http_send_response_500(connection,
902 "Second argument of soap() is NULL");
903 return;
906 if (NULL == request || request_length == 0) {
907 http_send_response_400(connection, "Client sent empty body");
908 return;
911 request_doc = xmlParseMemory(request, request_length);
912 if (NULL == request_doc) {
913 http_send_response_400(connection, "Client sent invalid XML document");
914 return;
917 xpath_ctx = xmlXPathNewContext(request_doc);
918 if (NULL == xpath_ctx) {
919 xmlFreeDoc(request_doc);
920 http_send_response_500(connection, "Could not create XPath context");
921 return;
924 if (register_namespaces(xpath_ctx, 0, MESSAGE_NS_UNSIGNED)) {
925 xmlXPathFreeContext(xpath_ctx);
926 xmlFreeDoc(request_doc);
927 http_send_response_500(connection,
928 "Could not register name spaces to the XPath context");
929 return;
932 /* Get SOAP Body */
933 request_soap_body = xmlXPathEvalExpression(
934 BAD_CAST "/soap:Envelope/soap:Body", xpath_ctx);
935 if (NULL == request_soap_body) {
936 xmlXPathFreeContext(xpath_ctx);
937 xmlFreeDoc(request_doc);
938 http_send_response_400(connection, "Client sent invalid SOAP request");
939 return;
941 if (xmlXPathNodeSetIsEmpty(request_soap_body->nodesetval)) {
942 xmlXPathFreeObject(request_soap_body);
943 xmlXPathFreeContext(xpath_ctx);
944 xmlFreeDoc(request_doc);
945 http_send_response_400(connection,
946 "SOAP request does not contain SOAP Body element");
947 return;
949 if (request_soap_body->nodesetval->nodeNr > 1) {
950 xmlXPathFreeObject(request_soap_body);
951 xmlXPathFreeContext(xpath_ctx);
952 xmlFreeDoc(request_doc);
953 http_send_response_400(connection,
954 "SOAP response has more than one Body element");
955 return;
957 isds_request = request_soap_body->nodesetval->nodeTab[0]->children;
958 if (isds_request->next != NULL) {
959 xmlXPathFreeObject(request_soap_body);
960 xmlXPathFreeContext(xpath_ctx);
961 xmlFreeDoc(request_doc);
962 http_send_response_400(connection, "SOAP body has more than one child");
963 return;
965 if (isds_request->type != XML_ELEMENT_NODE || isds_request->ns == NULL ||
966 NULL == isds_request->ns->href) {
967 xmlXPathFreeObject(request_soap_body);
968 xmlXPathFreeContext(xpath_ctx);
969 xmlFreeDoc(request_doc);
970 http_send_response_400(connection,
971 "SOAP body does not contain a name-space-qualified element");
972 return;
975 /* Build SOAP response envelope */
976 response_doc = xmlNewDoc(BAD_CAST "1.0");
977 if (!response_doc) {
978 http_send_response_500(connection,
979 "Could not build SOAP response document");
980 goto leave;
982 response_soap_envelope = xmlNewNode(NULL, BAD_CAST "Envelope");
983 if (!response_soap_envelope) {
984 http_send_response_500(connection,
985 "Could not build SOAP response envelope");
986 goto leave;
988 xmlDocSetRootElement(response_doc, response_soap_envelope);
989 /* Only this way we get namespace definition as @xmlns:soap,
990 * otherwise we get namespace prefix without definition */
991 soap_ns = xmlNewNs(response_soap_envelope, BAD_CAST SOAP_NS, NULL);
992 if(NULL == soap_ns) {
993 http_send_response_500(connection, "Could not create SOAP name space");
994 goto leave;
996 xmlSetNs(response_soap_envelope, soap_ns);
997 response_soap_body = xmlNewChild(response_soap_envelope, NULL,
998 BAD_CAST "Body", NULL);
999 if (!response_soap_body) {
1000 http_send_response_500(connection,
1001 "Could not add Body to SOAP response envelope");
1002 goto leave;
1004 /* Append ISDS response element */
1005 if (-1 == test_asprintf(&response_name, "%s%s", isds_request->name,
1006 "Response")) {
1007 http_send_response_500(connection,
1008 "Could not buld ISDS resposne element name");
1009 goto leave;
1011 isds_response = xmlNewChild(response_soap_body, NULL,
1012 BAD_CAST response_name, NULL);
1013 free(response_name);
1014 if (NULL == isds_response) {
1015 http_send_response_500(connection,
1016 "Could not add ISDS response element to SOAP response body");
1017 goto leave;
1019 isds_ns = xmlNewNs(isds_response, isds_request->ns->href, NULL);
1020 if(NULL == isds_ns) {
1021 http_send_response_500(connection,
1022 "Could not create a name space for the response body");
1023 goto leave;
1025 xmlSetNs(isds_response, isds_ns);
1027 /* Dispatch request to service */
1028 for (int i = 0; i < sizeof(services)/sizeof(services[0]); i++) {
1029 if (!strcmp(services[i].end_point, end_point) &&
1030 !xmlStrcmp(services[i].name_space, isds_request->ns->href) &&
1031 !xmlStrcmp(services[i].name, isds_request->name)) {
1032 /* Check if the configuration is enabled and find configuration */
1033 for (const struct service_configuration *service = configuration;
1034 service->name != SERVICE_END; service++) {
1035 if (service->name == services[i].id) {
1036 service_handled = 1;
1037 if (!xmlStrcmp(services[i].name_space, BAD_CAST OISDS_NS)) {
1038 /* Alias "isds" XPath identifier to OISDS_NS */
1039 if (register_namespaces(xpath_ctx, 1,
1040 MESSAGE_NS_UNSIGNED)) {
1041 http_send_response_500(connection,
1042 "Could not register name spaces to the "
1043 "XPath context");
1044 break;
1047 xpath_ctx->node = isds_request;
1048 if (HTTP_ERROR_SERVER != services[i].function(connection,
1049 request_doc, xpath_ctx, isds_request,
1050 response_doc, isds_response,
1051 service->arguments)) {
1052 service_passed = 1;
1053 } else {
1054 http_send_response_500(connection,
1055 "Internal server error while processing "
1056 "ISDS request");
1060 break;
1064 /* Send response */
1065 if (service_passed) {
1066 /* Serialize the SOAP response */
1067 http_response_body = xmlBufferCreate();
1068 if (NULL == http_response_body) {
1069 http_send_response_500(connection,
1070 "Could not create xmlBuffer for response serialization");
1071 goto leave;
1073 /* Last argument 1 means format the XML tree. This is pretty but it breaks
1074 * XML document transport as it adds text nodes (indentiation) between
1075 * elements. */
1076 save_ctx = xmlSaveToBuffer(http_response_body, "UTF-8", 0);
1077 if (NULL == save_ctx) {
1078 http_send_response_500(connection, "Could not create XML serializer");
1079 goto leave;
1081 /* XXX: According LibXML documentation, this function does not return
1082 * meaningful value yet */
1083 xmlSaveDoc(save_ctx, response_doc);
1084 if (-1 == xmlSaveFlush(save_ctx)) {
1085 http_send_response_500(connection,
1086 "Could not serialize SOAP response");
1087 goto leave;
1090 http_send_response_200(connection, http_response_body->content,
1091 http_response_body->use, soap_mime_type);
1094 leave:
1095 xmlSaveClose(save_ctx);
1096 xmlBufferFree(http_response_body);
1098 xmlFreeDoc(response_doc);
1100 xmlXPathFreeObject(request_soap_body);
1101 xmlXPathFreeContext(xpath_ctx);
1102 xmlFreeDoc(request_doc);
1104 if (!service_handled) {
1105 http_send_response_500(connection,
1106 "Requested ISDS service not implemented");