test: unify SOAP response path in server
[libisds.git] / test / simline / server.c
blob8f65000daaab74be42affb0fec0d5b2607d97169
1 #ifndef _POSIX_SOURCE
2 #define _POSIX_SOURCE /* For getaddrinfo(3) */
3 #endif
5 #ifndef _BSD_SOURCE
6 #define _BSD_SOURCE /* For NI_MAXHOST */
7 #endif
9 #include "../test-tools.h"
10 #include "http.h"
11 #include "server.h"
13 #include <stdlib.h>
14 #include <stdio.h>
15 #include <sys/types.h>
16 #include <sys/socket.h>
17 #include <netdb.h>
18 #include <string.h>
19 #include <signal.h>
20 #include <unistd.h>
21 #include <wait.h>
23 const char *server_error = NULL;
25 static const char *soap_mime_type = "text/xml"; /* SOAP/1.1 requires text/xml */
26 /* DummyOperation respons */
27 static const char *pong = "<?xml version='1.0' encoding='utf-8'?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><SOAP-ENV:Body><q:DummyOperationResponse xmlns:q=\"http://isds.czechpoint.cz/v20\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><q:dmStatus><q:dmStatusCode>0000</q:dmStatusCode><q:dmStatusMessage>Provedeno úspěšně.</q:dmStatusMessage></q:dmStatus></q:DummyOperationResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>";
29 static const char *as_path_hotp = "/as/processLogin?type=hotp&uri=";
30 static const char *as_path_sendsms = "/as/processLogin?type=totp&sendSms=true&uri=";
31 static const char *as_path_dontsendsms = "/as/processLogin?type=totp&uri=";
32 static const char *as_path_logout = "/as/processLogout?uri=";
33 static const char *ws_path = "/apps/DS/dz";
35 static const char *authorization_cookie_name = "IPCZ-X-COOKIE";
36 static char *authorization_cookie_value = NULL;
38 /* Save pointer to static error message if not yet set */
39 void set_server_error(const char *message) {
40 if (server_error == NULL) {
41 server_error = message;
46 /* Creates listening TCP socket on localhost.
47 * Returns the socket descriptor or -1. */
48 int listen_on_socket(void) {
49 int retval;
50 struct addrinfo hints;
51 struct addrinfo *addresses, *address;
52 int fd;
54 memset(&hints, 0, sizeof(hints));
55 hints.ai_family = AF_UNSPEC;
56 hints.ai_socktype = SOCK_STREAM;
57 retval = getaddrinfo("localhost", NULL, &hints, &addresses);
58 if (retval) {
59 set_server_error("Could not resolve `localhost'");
60 return -1;
63 for (address = addresses; address != NULL; address = address->ai_next) {
64 fd = socket(address->ai_family, address->ai_socktype,
65 address->ai_protocol);
66 if (fd == -1) continue;
68 retval = bind(fd, address->ai_addr, address->ai_addrlen);
69 if (retval != 0) continue;
71 retval = listen(fd, 0);
72 if (retval == 0) {
73 freeaddrinfo(addresses);
74 return fd;
78 freeaddrinfo(addresses);
79 set_server_error("Could not start listening on TCP/localhost");
80 return -1;
84 /* Format socket address as printable string.
85 * @return allocated string or NULL in case of error. */
86 char *socket2address(int socket) {
87 struct sockaddr_storage storage;
88 socklen_t length = (socklen_t) sizeof(storage);
89 char host[NI_MAXHOST];
90 char service[NI_MAXSERV];
91 char *address = NULL;
93 if (-1 == getsockname(socket, (struct sockaddr *)&storage, &length)) {
94 set_server_error("Could not get address of server socket");
95 return NULL;
98 if (0 != getnameinfo((struct sockaddr *)&storage, length,
99 host, sizeof(host), service, sizeof(service),
100 NI_NUMERICHOST|NI_NUMERICSERV)) {
101 set_server_error("Could not resolve address of server socket");
102 return NULL;
105 if (-1 == test_asprintf(&address,
106 (strchr(host, ':') == NULL) ? "%s:%s" : "[%s]:%s",
107 host, service)) {
108 set_server_error("Could not format server address");
109 return NULL;
112 return address;
116 /* Process ISDS WS ping */
117 static void do_ws(int client_socket, const struct http_request *request) {
118 if (request->method != HTTP_METHOD_POST) {
119 http_send_response_400(client_socket,
120 "Regular ISDS web service request must be POST");
121 return;
124 http_send_response_200(client_socket, pong, strlen(pong), soap_mime_type);
128 /* Do the server protocol.
129 * @server_socket is listening TCP socket of the server
130 * @server_arguments is pointer to structure:
131 * Never returns. Terminates by exit(). */
132 void server_basic_authentication(int server_socket,
133 const void *server_arguments) {
134 int client_socket;
135 const struct arguments_basic_authentication *arguments =
136 (const struct arguments_basic_authentication *) server_arguments;
137 struct http_request *request = NULL;
138 http_error error;
140 if (arguments == NULL) {
141 close(server_socket);
142 exit(EXIT_FAILURE);
145 while (0 <= (client_socket = accept(server_socket, NULL, NULL))) {
146 fprintf(stderr, "Connection accepted\n");
147 error = http_read_request(client_socket, &request);
148 if (error) {
149 fprintf(stderr, "Error while reading request\n");
150 if (error == HTTP_ERROR_CLIENT)
151 http_send_response_400(client_socket, "Error in request");
152 else
153 http_send_response_500(client_socket);
154 close(client_socket);
155 continue;
158 if (request->method == HTTP_METHOD_POST) {
159 /* Only POST requests are used in Basic authentication mode */
160 if (arguments->username != NULL) {
161 if (http_client_authenticates(request)) {
162 switch(http_authenticate_basic(request,
163 arguments->username, arguments->password)) {
164 case HTTP_ERROR_SUCCESS:
165 do_ws(client_socket, request);
166 break;
167 case HTTP_ERROR_CLIENT:
168 if (arguments->isds_deviations)
169 http_send_response_401_basic(client_socket);
170 else
171 http_send_response_403(client_socket);
172 break;
173 default:
174 http_send_response_500(client_socket);
176 } else {
177 http_send_response_401_basic(client_socket);
179 } else {
180 do_ws(client_socket, request);
182 } else {
183 /* HTTP method unsupported per ISDS specification */
184 http_send_response_400(client_socket,
185 "Only POST method is allowed");
187 http_request_free(&request);
190 close(client_socket);
193 close(server_socket);
194 exit(EXIT_SUCCESS);
198 /* Process first phase of TOTP request */
199 static void do_as_sendsms(int client_socket, const struct http_request *request,
200 const struct arguments_otp_authentication *arguments) {
201 if (arguments == NULL) {
202 http_send_response_500(client_socket);
203 return;
206 if (request->method != HTTP_METHOD_POST) {
207 http_send_response_400(client_socket,
208 "First phase TOTP request must be POST");
209 return;
212 if (!http_client_authenticates(request)) {
213 http_send_response_401_otp(client_socket,
214 "totpsendsms",
215 "authentication.error.userIsNotAuthenticated",
216 "Client did not send any authentication header");
217 return;
220 switch(http_authenticate_basic(request,
221 arguments->username, arguments->password)) {
222 case HTTP_ERROR_SUCCESS: {
223 /* Find final URI */
224 char *uri = strstr(request->uri, "&uri=");
225 if (uri == NULL) {
226 http_send_response_400(client_socket,
227 "Missing uri parameter in Request URI");
228 return;
230 uri += 5;
231 /* Build new location for second OTP phase */
232 char *location = NULL;
233 if (-1 == test_asprintf(&location, "%s%s", as_path_dontsendsms, uri)) {
234 http_send_response_500(client_socket);
235 return;
237 char *terminator = strchr(uri, '&');
238 if (NULL != terminator)
239 location[strlen(as_path_dontsendsms) + (uri - terminator)] = '\0';
240 http_send_response_302_totp(client_socket,
241 "authentication.info.totpSended",
242 "=?UTF-8?B?SmVkbm9yw6F6b3bDvSBrw7NkIG9kZXNsw6FuLg==?=",
243 location);
244 free(location);
246 break;
247 case HTTP_ERROR_CLIENT:
248 if (arguments->isds_deviations)
249 http_send_response_401_otp(client_socket,
250 "totpsendsms",
251 "authentication.error.userIsNotAuthenticated",
252 " Retry: Bad user name or password in first OTP phase.\r\n"
253 " This is very long header\r\n"
254 " which should span to more lines.\r\n"
255 " Surrounding LWS are meaning-less. ");
256 else
257 http_send_response_403(client_socket);
258 break;
259 default:
260 http_send_response_500(client_socket);
265 /* Return static string representation of HTTP OTP authentication method.
266 * Or NULL in case of error. */
267 static const char *auth_otp_method2string(enum auth_otp_method method) {
268 switch (method) {
269 case AUTH_OTP_HMAC: return "hotp";
270 case AUTH_OTP_TIME: return "totp";
271 default: return NULL;
276 /* Process second phase of OTP request */
277 static void do_as_phase_two(int client_socket, const struct http_request *request,
278 const struct arguments_otp_authentication *arguments) {
279 if (arguments == NULL) {
280 http_send_response_500(client_socket);
281 return;
284 if (request->method != HTTP_METHOD_POST) {
285 http_send_response_400(client_socket,
286 "Second phase OTP request must be POST");
287 return;
290 if (!http_client_authenticates(request)) {
291 http_send_response_401_otp(client_socket,
292 auth_otp_method2string(arguments->method),
293 "authentication.error.userIsNotAuthenticated",
294 "Client did not send any authentication header");
295 return;
298 switch(http_authenticate_otp(request,
299 arguments->username, arguments->password, arguments->otp)) {
300 case HTTP_ERROR_SUCCESS: {
301 /* Find final URI */
302 char *uri = strstr(request->uri, "&uri=");
303 if (uri == NULL) {
304 http_send_response_400(client_socket,
305 "Missing uri parameter in Request URI");
306 return;
308 uri += 5;
309 /* Build new location for final request */
310 char *location = NULL;
311 if (NULL == (location = strdup(uri))) {
312 http_send_response_500(client_socket);
313 return;
315 char *terminator = strchr(location, '&');
316 if (NULL != terminator)
317 *terminator = '\0';
318 /* Generate pseudo-random cookie value. This is to prevent
319 * client from reusing the cookie accidentally. We use the
320 * same seed to get reproducible tests. */
321 if (-1 == test_asprintf(&authorization_cookie_value, "%d",
322 rand())) {
323 http_send_response_500(client_socket);
324 free(location);
325 return;
327 /* XXX: Add Path parameter to cookie, otherwise
328 * different paths will not match.
329 * FIXME: Domain argument does not work with cURL. Report a bug. */
330 http_send_response_302_cookie(client_socket,
331 authorization_cookie_name,
332 authorization_cookie_value,
333 /*http_find_host(request)*/NULL,
334 /*NULL*/"/",
335 location);
336 free(location);
338 break;
339 case HTTP_ERROR_CLIENT:
340 if (arguments->isds_deviations)
341 http_send_response_401_otp(client_socket,
342 auth_otp_method2string(arguments->method),
343 "authentication.error.userIsNotAuthenticated",
344 " Retry: Bad user name or password in second OTP phase.\r\n"
345 " This is very long header\r\n"
346 " which should span to more lines.\r\n"
347 " Surrounding LWS are meaning-less. ");
348 else
349 http_send_response_403(client_socket);
350 break;
351 default:
352 http_send_response_500(client_socket);
357 /* Process OTP session cookie invalidation request */
358 static void do_as_logout(int client_socket, const struct http_request *request,
359 const struct arguments_otp_authentication *arguments) {
360 if (arguments == NULL) {
361 http_send_response_500(client_socket);
362 return;
365 if (request->method != HTTP_METHOD_GET) {
366 http_send_response_400(client_socket,
367 "OTP cookie invalidation request must be GET");
368 return;
371 const char *received_cookie =
372 http_find_cookie(request, authorization_cookie_name);
374 if (authorization_cookie_value == NULL || received_cookie == NULL ||
375 strcmp(authorization_cookie_value, received_cookie)) {
376 http_send_response_403(client_socket);
377 return;
380 /* XXX: Add Path parameter to cookie, otherwise
381 * different paths will not match.
382 * FIXME: Domain argument does not work with cURL. Report a bug. */
383 http_send_response_200_cookie(client_socket,
384 authorization_cookie_name,
386 /*http_find_host(request)*/ NULL,
387 /*NULL*/"/",
388 NULL, 0, NULL);
392 /* Process ISDS WS ping authorized by cookie */
393 static void do_ws_with_cookie(int client_socket, const struct http_request *request,
394 const struct arguments_otp_authentication *arguments) {
395 const char *received_cookie =
396 http_find_cookie(request, authorization_cookie_name);
398 if (authorization_cookie_value != NULL && received_cookie != NULL &&
399 !strcmp(authorization_cookie_value, received_cookie))
400 do_ws(client_socket, request);
401 else
402 http_send_response_403(client_socket);
406 /* Do the server protocol with OTP authentication.
407 * @server_socket is listening TCP socket of the server
408 * @server_arguments is pointer to structure arguments_otp_authentication. It
409 * selects OTP method to enable.
410 * Never returns. Terminates by exit(). */
411 void server_otp_authentication(int server_socket,
412 const void *server_arguments) {
413 int client_socket;
414 const struct arguments_otp_authentication *arguments =
415 (const struct arguments_otp_authentication *) server_arguments;
416 struct http_request *request = NULL;
417 http_error error;
419 if (arguments == NULL) {
420 close(server_socket);
421 exit(EXIT_FAILURE);
424 while (0 <= (client_socket = accept(server_socket, NULL, NULL))) {
425 fprintf(stderr, "Connection accepted\n");
426 error = http_read_request(client_socket, &request);
427 if (error) {
428 fprintf(stderr, "Error while reading request\n");
429 if (error == HTTP_ERROR_CLIENT)
430 http_send_response_400(client_socket, "Error in request");
431 else
432 http_send_response_500(client_socket);
433 close(client_socket);
434 continue;
437 if (arguments->username != NULL) {
438 if (arguments->method == AUTH_OTP_HMAC &&
439 !strncmp(request->uri, as_path_hotp, strlen(as_path_hotp))) {
440 do_as_phase_two(client_socket, request, arguments);
441 } else if (arguments->method == AUTH_OTP_TIME &&
442 !strncmp(request->uri, as_path_sendsms,
443 strlen(as_path_sendsms))) {
444 do_as_sendsms(client_socket, request, arguments);
445 } else if (arguments->method == AUTH_OTP_TIME &&
446 !strncmp(request->uri, as_path_dontsendsms,
447 strlen(as_path_dontsendsms))) {
448 do_as_phase_two(client_socket, request, arguments);
449 } else if (!strncmp(request->uri, as_path_logout,
450 strlen(as_path_logout))) {
451 do_as_logout(client_socket, request, arguments);
452 } else if (!strcmp(request->uri, ws_path)) {
453 do_ws_with_cookie(client_socket, request, arguments);
454 } else {
455 http_send_response_400(client_socket,
456 "Unknown path for TOTP authenticating service");
458 } else {
459 if (!strcmp(request->uri, ws_path)) {
460 do_ws(client_socket, request);
461 } else {
462 http_send_response_400(client_socket,
463 "Unknown path for TOTP authenticating service");
466 http_request_free(&request);
469 close(client_socket);
472 close(server_socket);
473 free(authorization_cookie_value);
474 exit(EXIT_SUCCESS);
478 /* Implementation of server that is out of order.
479 * It always sends back SOAP Fault with HTTP error 503.
480 * @server_socket is listening TCP socket of the server
481 * @server_arguments is ununsed pointer
482 * Never returns. Terminates by exit(). */
483 void server_out_of_order(int server_socket,
484 const void *server_arguments) {
485 int client_socket;
486 struct http_request *request = NULL;
487 http_error error;
488 const char *soap_mime_type = "text/xml"; /* SOAP/1.1 requires text/xml */
489 const char *fault = "<?xml version='1.0' encoding='UTF-8'?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/1999/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/1999/XMLSchema\"><SOAP-ENV:Body><SOAP-ENV:Fault><faultcode xsi:type=\"xsd:string\">Probíhá plánovaná údržba</faultcode><faultstring xsi:type=\"xsd:string\">Omlouváme se všem uživatelům datových schránek za dočasné omezení přístupu do systému datových schránek z důvodu plánované údržby systému. Děkujeme za pochopení.</faultstring></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>";
491 while (0 <= (client_socket = accept(server_socket, NULL, NULL))) {
492 fprintf(stderr, "Connection accepted\n");
493 error = http_read_request(client_socket, &request);
494 if (error) {
495 fprintf(stderr, "Error while reading request\n");
496 if (error == HTTP_ERROR_CLIENT)
497 http_send_response_400(client_socket, "Error in request");
498 close(client_socket);
499 continue;
502 http_send_response_503(client_socket, fault, strlen(fault),
503 soap_mime_type);
504 http_request_free(&request);
506 close(client_socket);
509 close(server_socket);
510 exit(EXIT_SUCCESS);
514 /* Start sever in separate process.
515 * @server_process is PID of forked server
516 * @server_address is automatically allocated TCP address of listening server
517 * @username sets required user name server has to require. Set NULL to
518 * disable HTTP authentication.
519 * @password sets required password server has to require
520 * socket.
521 * @isds_deviations is flag to set conformance level. If false, server is
522 * compliant to standards (HTTP, SOAP) if not conflicts with ISDS
523 * specification. Otherwise server mimics real ISDS implementation as much
524 * as possible.
525 * @return -1 in case of error. */
526 int start_server(pid_t *server_process, char **server_address,
527 void (*server_implementation)(int, const void *),
528 const void *server_arguments) {
529 int server_socket;
531 if (server_address == NULL) {
532 set_server_error("start_server(): Got invalid server_address pointer");
533 return -1;
535 *server_address = NULL;
537 if (server_process == NULL) {
538 set_server_error("start_server(): Got invalid server_process pointer");
539 return -1;
542 server_socket = listen_on_socket();
543 if (server_socket == -1) {
544 set_server_error("Could not create listening socket");
545 return -1;
548 *server_address = socket2address(server_socket);
549 if (*server_address == NULL) {
550 close(server_socket);
551 set_server_error("Could not format address of listening address");
552 return -1;
555 *server_process = fork();
556 if (*server_process == -1) {
557 close(server_socket);
558 set_server_error("Server could not been forked");
559 return -1;
562 if (*server_process == 0) {
563 server_implementation(server_socket, server_arguments);
564 /* Does not return */
567 return 0;
571 /* Kill the server process.
572 * Return -1 in case of error. */
573 int stop_server(pid_t server_process) {
574 if (server_process <= 0) {
575 set_server_error("Invalid server PID to kill");
576 return -1;
578 if (-1 == kill(server_process, SIGTERM)) {
579 set_server_error("Could not terminate server");
580 return -1;
582 if (-1 == waitpid(server_process, NULL, 0)) {
583 set_server_error("Could not wait for server termination");
584 return -1;
586 return 0;