1 /**
2 * crowd_client.c
3 *
4 * Implementation for the Atlassian Crowd C Client
5 */
6
7 /* Standard includes */
8 #include <stdbool.h>
9 #include <stddef.h>
10 #include <string.h>
11
12 /* libcurl includes */
13 #include <curl/curl.h>
14
15 /* libxml includes */
16 #include <libxml/parser.h>
17 #include <libxml/xmlIO.h>
18 #include <libxml/xmlreader.h>
19
20 /* Apache Portable Runtime includes */
21 #include <apr_strings.h>
22
23 /* Apache httpd includes */
24 #include <httpd.h>
25 #include <http_log.h>
26
27 #include "util.h"
28
29 #include "crowd_client.h"
30
31 #define STATUS_CODE_UNKNOWN -1
32 #define XML_PROLOG "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
33
34 cache_t *auth_cache;
35 cache_t *groups_cache;
36 cache_t *cookie_config_cache;
37 cache_t *session_cache;
38
39 /*===========================
40 * Initialisation & clean up
41 *===========================*/
42
xml_string(const char * string)43 static xmlChar *xml_string(const char *string) {
44 xmlChar *result = xmlCharStrdup(string);
45 if (result == NULL) {
46 fprintf(stderr, "Could not create XML string.");
47 exit(1);
48 }
49 return result;
50 }
51
52 xmlChar *user_xml_name = NULL;
53 xmlChar *groups_xml_name = NULL;
54 xmlChar *group_xml_name = NULL;
55 xmlChar *name_xml_name = NULL;
56 xmlChar *token_xml_name = NULL;
57 xmlChar *session_xml_name = NULL;
58 xmlChar *cookie_config_xml_name = NULL;
59 xmlChar *secure_xml_name = NULL;
60 xmlChar *domain_xml_name = NULL;
61
62 /**
63 * Must be called before the first use of the Crowd Client.
64 */
crowd_init()65 void crowd_init() {
66 user_xml_name = xml_string("user");
67 groups_xml_name = xml_string("groups");
68 group_xml_name = xml_string("group");
69 name_xml_name = xml_string("name");
70 token_xml_name = xml_string("token");
71 session_xml_name = xml_string("session");
72 cookie_config_xml_name = xml_string("cookie-config");
73 secure_xml_name = xml_string("secure");
74 domain_xml_name = xml_string("domain");
75 if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
76 fprintf(stderr, PACKAGE_STRING " failed to initialise libcurl.");
77 exit(1);
78 }
79 xmlInitParser();
80 }
81
82 /**
83 * Should be called after the final use of the Crowd Client.
84 */
crowd_cleanup()85 void crowd_cleanup() {
86 // Don't clean up libxml2 or libcurl as their's no guarantee that we're
87 // the only people in our process using them.
88 free(user_xml_name);
89 free(groups_xml_name);
90 free(group_xml_name);
91 free(name_xml_name);
92 free(token_xml_name);
93 free(session_xml_name);
94 free(cookie_config_xml_name);
95 free(secure_xml_name);
96 free(domain_xml_name);
97 }
98
99 /**
100 * Creates a crowd_config, populated with default values.
101 *
102 * @param p The APR pool from which to allocate memory.
103 * @returns A pointer to the crowd_config, or NULL upon failure.
104 */
crowd_create_config(apr_pool_t * p)105 crowd_config *crowd_create_config(apr_pool_t *p) {
106 crowd_config *config = log_palloc(p, apr_pcalloc(p, sizeof(crowd_config)));
107 if (config == NULL) {
108 return NULL;
109 }
110 return config;
111 }
112
copy_string(void * data,apr_pool_t * p)113 static void *copy_string(void *data, apr_pool_t *p){
114 return log_palloc(p, apr_pstrdup(p, data));
115 }
116
117 typedef struct {
118 } cached_auth_t;
119
120 typedef struct {
121 int count;
122 char **groups;
123 } cached_groups_t;
124
copy_groups(void * data,apr_pool_t * p)125 static void *copy_groups(void *data, apr_pool_t *p){
126 cached_groups_t *original = data;
127 cached_groups_t *copy = log_palloc(p, apr_palloc(p, sizeof(cached_groups_t)));
128 if (copy == NULL) {
129 return NULL;
130 }
131 copy->groups = log_palloc(p, apr_palloc(p, original->count * sizeof(char *)));
132 if (copy->groups == NULL) {
133 return NULL;
134 }
135 int i;
136 for (i = 0; i < original->count; i++) {
137 copy->groups[i] = log_palloc(p, apr_pstrdup(p, original->groups[i]));
138 if (copy->groups[i] == NULL) {
139 return NULL;
140 }
141 }
142 copy->count = original->count;
143 return copy;
144 }
145
free_groups(void * value)146 static void free_groups(void *value) {
147 cached_groups_t *cached_groups = value;
148 int i;
149 for (i = 0; i < cached_groups->count; i++) {
150 free(cached_groups->groups[i]);
151 }
152 free(cached_groups->groups);
153 free(cached_groups);
154 }
155
copy_cookie_config(void * data,apr_pool_t * p)156 static void *copy_cookie_config(void *data, apr_pool_t *p) {
157 crowd_cookie_config_t *original = data;
158 crowd_cookie_config_t *copy = log_palloc(p, apr_palloc(p, sizeof(crowd_cookie_config_t)));
159 if (copy == NULL) {
160 return NULL;
161 }
162 if (original->domain == NULL) {
163 copy->domain = NULL;
164 } else {
165 copy->domain = log_palloc(p, apr_pstrdup(p, original->domain));
166 if (copy->domain == NULL) {
167 return NULL;
168 }
169 }
170 copy->cookie_name = log_palloc(p, apr_pstrdup(p, original->cookie_name));
171 if (copy->cookie_name == NULL) {
172 return NULL;
173 }
174 copy->secure = original->secure;
175 return copy;
176 }
177
free_cookie_config(void * value)178 static void free_cookie_config(void *value) {
179 crowd_cookie_config_t *cookie_config = value;
180 free(cookie_config->domain);
181 free(cookie_config->cookie_name);
182 free(cookie_config);
183 }
184
crowd_cache_create(apr_pool_t * pool,apr_time_t max_age,unsigned int max_entries)185 bool crowd_cache_create(apr_pool_t *pool, apr_time_t max_age, unsigned int max_entries) {
186 auth_cache = cache_create("auth", pool, max_age, max_entries, copy_string, free);
187 if (auth_cache == NULL) {
188 return false;
189 }
190 groups_cache = cache_create("groups", pool, max_age, max_entries, copy_groups, free_groups);
191 if (groups_cache == NULL) {
192 return false;
193 }
194 cookie_config_cache = cache_create("cookie config", pool, max_age, max_entries, copy_cookie_config, free_cookie_config);
195 if (cookie_config_cache == NULL) {
196 return false;
197 }
198 session_cache = cache_create("session", pool, max_age, max_entries, copy_string, free);
199 if (session_cache == NULL) {
200 return false;
201 }
202 return true;
203 }
204
205 /*===========================
206 * HTTP request transmission
207 *===========================*/
208
209 typedef struct
210 {
211 const char *read_ptr;
212 size_t remaining;
213 } read_data_t;
214
make_read_data(read_data_t * read_data,const char * payload)215 static void make_read_data(read_data_t *read_data, const char *payload) {
216 read_data->read_ptr = payload;
217 read_data->remaining = strlen(payload);
218 }
219
read_crowd_authentication_request(void * ptr,size_t size,size_t nmemb,void * stream)220 static size_t read_crowd_authentication_request(void *ptr, size_t size, size_t nmemb, void *stream)
221 {
222 read_data_t *read_data = (read_data_t *)stream;
223 if (read_data->remaining > 0) {
224 size_t chunk_size = size * nmemb;
225 if (chunk_size > read_data->remaining) {
226 chunk_size = read_data->remaining;
227 }
228 memcpy(ptr, read_data->read_ptr, chunk_size);
229 read_data->read_ptr += chunk_size;
230 read_data->remaining -= chunk_size;
231 return chunk_size;
232 } else {
233 return 0;
234 }
235 }
236
237 /**
238 * Encodes text so that it can appear within an XML CDATA section.
239 *
240 * This is done by replacing all occurrences of "]]>" with "]]]]><![CDATA[>"
241 *
242 * Note that the returned string does NOT include the initial opening or final closing CDATA sequences.
243 */
cdata_encode(const request_rec * r,const char * text)244 static const char *cdata_encode(const request_rec *r, const char *text)
245 {
246 const size_t length = strlen(text);
247 if (length < 3) {
248 return text;
249 }
250 size_t new_length = length;
251 size_t i;
252 for (i = 0; i < length - 2; i++) {
253 if (!bcmp(text + i, "]]>", 3)) {
254 new_length += 12;
255 i += 2;
256 }
257 }
258 if (new_length == length) {
259 return text;
260 }
261 char *new_text = apr_palloc(r->pool, new_length + 1);
262 char *dest = new_text;
263 for (i = 0; i <= length; i++) {
264 if (!bcmp(text + i, "]]>", 3)) {
265 memcpy(dest, "]]]]><![CDATA[>", 15);
266 dest += 15;
267 i += 2;
268 } else {
269 *dest = text[i];
270 dest++;
271 }
272 }
273 return new_text;
274 }
275
make_url(const request_rec * r,const crowd_config * config,CURL * curl_easy,const char * user,const char * format)276 static const char *make_url(const request_rec *r, const crowd_config *config, CURL *curl_easy, const char *user,
277 const char *format) {
278 char *url;
279 if (user == NULL) {
280 url = apr_psprintf(r->pool, format, config->crowd_url);
281 } else {
282 char *encoded_user = log_ralloc(r, curl_easy_escape(curl_easy, user, 0));
283 if (encoded_user == NULL) {
284 return NULL;
285 }
286 url = apr_psprintf(r->pool, format, config->crowd_url, encoded_user);
287 curl_free(encoded_user);
288 }
289 log_ralloc(r, url);
290 if (url == NULL) {
291 return NULL;
292 }
293 return url;
294 }
295
add_header(const request_rec * r,struct curl_slist ** headers,const char * header)296 static bool add_header(const request_rec *r, struct curl_slist **headers, const char *header) {
297 struct curl_slist *new_headers = log_ralloc(r, curl_slist_append(*headers, header));
298 if (new_headers == NULL) {
299 return false;
300 }
301 *headers = new_headers;
302 return true;
303 }
304
305
306 /*=======================
307 * HTTP response receipt
308 *=======================*/
309
310 typedef struct write_data_struct write_data_t;
311
312 struct write_data_struct
313 {
314 const request_rec *r;
315 int status_code;
316 bool headers_done;
317 apr_array_header_t *response_text;
318 xmlTextReaderPtr xml_reader;
319 bool body_done;
320 bool body_valid;
321 bool (**xml_node_handlers)(write_data_t *write_data, const xmlChar *text);
322 void *extra;
323 };
324
xml_reader_error(void * arg,const char * msg,xmlParserSeverities severity,xmlTextReaderLocatorPtr locator)325 static void xml_reader_error(void *arg, const char *msg, xmlParserSeverities severity __attribute__((unused)),
326 xmlTextReaderLocatorPtr locator __attribute__((unused))) {
327 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ((write_data_t *)arg)->r, "XML reader error: %s", msg);
328 }
329
create_xml_reader(write_data_t * write_data)330 static bool create_xml_reader(write_data_t *write_data) {
331 write_data->xml_reader = log_ralloc(write_data->r, xmlReaderForMemory(write_data->response_text->elts, write_data->response_text->nelts * write_data->response_text->elt_size, NULL, NULL, 0) );
332 if (write_data->xml_reader == NULL) {
333 return false;
334 }
335 xmlTextReaderSetErrorHandler(write_data->xml_reader, xml_reader_error, write_data);
336 return true;
337 }
338
write_crowd_response_header(void * ptr,size_t size,size_t nmemb,void * stream)339 static size_t write_crowd_response_header(void *ptr, size_t size, size_t nmemb, void *stream) {
340 write_data_t *write_data = (write_data_t *)stream;
341 if (write_data->headers_done) {
342 /* A new header is starting, e.g. after re-direct */
343 write_data->status_code = STATUS_CODE_UNKNOWN;
344 write_data->headers_done = false;
345 write_data->body_done = false;
346 write_data->body_valid = false;
347 }
348 if (write_data->status_code == STATUS_CODE_UNKNOWN) {
349 /* Parse the status code from the status line. */
350 char *status_line = log_ralloc(write_data->r, apr_pstrmemdup(write_data->r->pool, ptr, size * nmemb));
351 if (status_line == NULL) {
352 return -1;
353 }
354 if (sscanf(status_line, "HTTP/%*u.%*u %u ", &(write_data->status_code)) != 1) {
355 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, write_data->r, "Failed to parse status line: '%s'", status_line);
356 return -1;
357 }
358 } else if (size * nmemb == 2 && memcmp("\r\n", ptr, 2) == 0) {
359 /* End of headers for this request */
360 if (write_data->status_code == STATUS_CODE_UNKNOWN) {
361 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, write_data->r, "No headers in request.");
362 return -1;
363 }
364 write_data->headers_done = TRUE;
365 }
366 return size * nmemb;
367 }
368
369 #define XML_READER_TYPE_MAX XML_READER_TYPE_XML_DECLARATION
370
371 const xmlChar *(*xml_text_accessors[XML_READER_TYPE_MAX + 1])(xmlTextReaderPtr xml_reader) = {
372 [XML_READER_TYPE_ELEMENT] = xmlTextReaderConstLocalName,
373 [XML_READER_TYPE_TEXT] = xmlTextReaderConstValue
374 };
375
write_response(void * ptr,size_t size,size_t nmemb,void * stream)376 static size_t write_response(void *ptr, size_t size, size_t nmemb, void *stream) {
377 write_data_t *write_data = (write_data_t *)stream;
378 size_t length = size * nmemb;
379 if (write_data->status_code == HTTP_OK || write_data->status_code == HTTP_CREATED) {
380 void *end = ptr + length;
381 while (ptr < end)
382 APR_ARRAY_PUSH(write_data->response_text, char) = *(char *)ptr++;
383 }
384 return length;
385 }
386
parse_xml(write_data_t * write_data)387 void parse_xml(write_data_t *write_data){
388 bool done = false;
389 do {
390 switch (xmlTextReaderRead(write_data->xml_reader)) {
391 int node_type;
392 case 0:
393 done = true;
394 break;
395 case 1:
396 node_type = xmlTextReaderNodeType(write_data->xml_reader);
397 if (node_type < 0 || node_type > XML_READER_TYPE_MAX) {
398 node_type = XML_READER_TYPE_NONE;
399 }
400 bool (*node_handler)(write_data_t *write_data, const xmlChar *local_name)
401 = write_data->xml_node_handlers[node_type];
402 if (node_handler == NULL) {
403 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, write_data->r, "Unexpected node type: %d", node_type);
404 write_data->body_done = done = true;
405 } else {
406 const xmlChar *(*text_accessor)(xmlTextReaderPtr xml_reader) = xml_text_accessors[node_type];
407 write_data->body_done = done = node_handler(write_data, text_accessor == NULL ? NULL
408 : text_accessor(write_data->xml_reader));
409 }
410 break;
411 default:
412 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, write_data->r, "Failed to parse XML.");
413 write_data->body_done = done = true;
414 }
415 } while (!done);
416 }
417
expect_xml_element(write_data_t * write_data,const xmlChar * expected_local_name,const xmlChar * local_name)418 static bool expect_xml_element(write_data_t *write_data, const xmlChar *expected_local_name,
419 const xmlChar *local_name) {
420 if (!xmlStrEqual(expected_local_name, local_name)) {
421 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, write_data->r, "Unrecognised element: %s", local_name);
422 return false;
423 }
424 return true;
425 }
426
427 typedef bool (*xml_node_handler_t)(write_data_t *write_data, const xmlChar *text);
428
make_xml_node_handlers(const request_rec * r)429 static xml_node_handler_t *make_xml_node_handlers(const request_rec *r) {
430 apr_array_header_t *array
431 = log_ralloc(r, apr_array_make(r->pool, XML_READER_TYPE_XML_DECLARATION + 1, sizeof(xml_node_handler_t)));
432 if (array == NULL) {
433 return NULL;
434 }
435 return (xml_node_handler_t *) array->elts;
436 }
437
handle_end_of_data(write_data_t * write_data,const xmlChar * text)438 static bool handle_end_of_data(write_data_t *write_data, const xmlChar *text __attribute__((unused))) {
439 write_data->body_valid = true;
440 return true;
441 }
442
handle_ignored_elements(write_data_t * write_data,const xmlChar * text)443 static bool handle_ignored_elements(write_data_t *write_data, const xmlChar* text __attribute__((unused))) {
444 if (!xmlTextReaderIsEmptyElement(write_data->xml_reader)) {
445 // TODO: Not yet required
446 return true;
447 }
448 return false;
449 }
450
451
452 /*===========================================
453 * Overall HTTP request & response lifecycle
454 *===========================================*/
455
crowd_request(const request_rec * r,const crowd_config * config,bool expect_bad_request,const char * (* make_url)(const request_rec * r,const crowd_config * config,CURL * curl_easy,const void * extra),const char * payload,bool (** xml_node_handlers)(write_data_t * write_data,const xmlChar * text),void * extra)456 static int crowd_request(const request_rec *r, const crowd_config *config, bool expect_bad_request,
457 const char *(*make_url)(const request_rec *r, const crowd_config *config, CURL *curl_easy, const void *extra),
458 const char *payload, bool (**xml_node_handlers)(write_data_t *write_data, const xmlChar *text), void *extra) {
459
460 bool success = true;
461
462 bool post = payload != NULL;
463
464 read_data_t read_data;
465 if (post) {
466 make_read_data(&read_data, payload);
467 }
468
469 write_data_t write_data = {
470 .r = r,
471 .status_code = STATUS_CODE_UNKNOWN,
472 .xml_node_handlers = xml_node_handlers,
473 .response_text = apr_array_make(r->pool, 1, sizeof(char)),
474 .extra = extra};
475
476 success = write_data.response_text != NULL;
477
478 struct curl_slist *headers = NULL;
479 if (success) {
480 success = add_header(r, &headers, "Accept: application/xml");
481 }
482 if (success && post) {
483 success = add_header(r, &headers, "Content-Type: application/xml; charset=\"utf-8\"");
484 }
485
486 CURL *curl_easy = NULL;
487 if (success) {
488 curl_easy = curl_easy_init();
489 if (curl_easy == NULL) {
490 ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, "Failed to initialise libcurl.");
491 success = false;
492 }
493 }
494
495 const char *url;
496 if (success) {
497 url = make_url(r, config, curl_easy, extra);
498 if (url == NULL) {
499 success = false;
500 }
501 }
502
503 #ifndef CURLOPT_USERNAME
504 const char *userpwd;
505 if (success) {
506 userpwd = log_ralloc(r, apr_pstrcat(r->pool, config->crowd_app_name, ":", config->crowd_app_password, NULL));
507 }
508 #endif
509
510 if (success) {
511 if (curl_easy_setopt(curl_easy, CURLOPT_HEADERFUNCTION, write_crowd_response_header)
512 || curl_easy_setopt(curl_easy, CURLOPT_WRITEHEADER, &write_data)
513 || curl_easy_setopt(curl_easy, CURLOPT_WRITEFUNCTION, write_response)
514 || curl_easy_setopt(curl_easy, CURLOPT_WRITEDATA, &write_data)
515 || curl_easy_setopt(curl_easy, CURLOPT_URL, url)
516 #ifdef CURLOPT_USERNAME
517 || curl_easy_setopt(curl_easy, CURLOPT_USERNAME, config->crowd_app_name)
518 || curl_easy_setopt(curl_easy, CURLOPT_PASSWORD, config->crowd_app_password)
519 #else
520 || curl_easy_setopt(curl_easy, CURLOPT_USERPWD, userpwd)
521 #endif
522 || curl_easy_setopt(curl_easy, CURLOPT_HTTPHEADER, headers)
523 || curl_easy_setopt(curl_easy, CURLOPT_TIMEOUT, config->crowd_timeout)
524 || curl_easy_setopt(curl_easy, CURLOPT_SSL_VERIFYPEER, config->crowd_ssl_verify_peer ? 1 : 0)
525 || curl_easy_setopt(curl_easy, CURLOPT_CAINFO, config->crowd_cert_path)
526 || (post && (curl_easy_setopt(curl_easy, CURLOPT_POST, 1)
527 || curl_easy_setopt(curl_easy, CURLOPT_READFUNCTION, read_crowd_authentication_request)
528 || curl_easy_setopt(curl_easy, CURLOPT_READDATA, &read_data)
529 || curl_easy_setopt(curl_easy, CURLOPT_POSTFIELDSIZE, read_data.remaining)))) {
530 ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, "Failed to set curl options.");
531 success = false;
532 }
533 }
534
535 if (success) {
536 CURLcode curl_code = curl_easy_perform(curl_easy);
537 if (curl_code != CURLE_OK) {
538 ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
539 "Failed to send authentication request (CURLcode %d - %s)", curl_code, curl_easy_strerror(curl_code));
540 success = false;
541 }
542 }
543
544 if (success) {
545 if (!write_data.headers_done) {
546 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Headers incomplete.");
547 success = false;
548 }
549 }
550
551 if (success) {
552 switch (write_data.status_code) {
553 case HTTP_BAD_REQUEST:
554 if (!expect_bad_request) {
555 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Unexpected status code: %d",
556 write_data.status_code);
557 success = false;
558 }
559 break;
560 case HTTP_OK:
561 case HTTP_CREATED:
562 break;
563 case HTTP_UNAUTHORIZED:
564 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
565 "Application failed to authenticate as '%s' to Crowd at '%s'.",
566 config->crowd_app_name, url);
567 success = false;
568 break;
569 default:
570 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Unexpected status code: %d",
571 write_data.status_code);
572 success = false;
573 }
574 }
575
576 /* Clean up curl */
577 if (curl_easy != NULL) {
578 curl_easy_cleanup(curl_easy);
579 }
580 if (headers != NULL) {
581 curl_slist_free_all(headers);
582 }
583
584 if (success && (write_data.status_code == HTTP_OK || write_data.status_code == HTTP_CREATED)) {
585
586 success = create_xml_reader(&write_data);
587
588 if (success) {
589 parse_xml(&write_data);
590 }
591
592 /* Clean up xml reader */
593 if (write_data.xml_reader != NULL) {
594 xmlFreeTextReader(write_data.xml_reader);
595 }
596
597 if (success && !write_data.body_valid) {
598 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Unrecognised response from Crowd.");
599 success = false;
600 }
601
602 }
603
604 return success ? write_data.status_code : -1;
605 }
606
make_user_cache_key(const char * username,const request_rec * r,const crowd_config * config)607 static char *make_user_cache_key(const char *username, const request_rec *r, const crowd_config *config) {
608 return log_ralloc(r, apr_psprintf(r->pool, "%s\037%s\037%s", username, config->crowd_app_name, config->crowd_url));
609 }
610
make_app_cache_key(const request_rec * r,const crowd_config * config)611 static char *make_app_cache_key(const request_rec *r, const crowd_config *config) {
612 return log_ralloc(r, apr_psprintf(r->pool, "%s\037%s", config->crowd_app_name, config->crowd_url));
613 }
614
make_session_cache_key(const char * token,const char * forwarded_for,const request_rec * r,const crowd_config * config)615 static char *make_session_cache_key(const char *token, const char *forwarded_for, const request_rec *r, const crowd_config *config) {
616 #if AP_MODULE_MAGIC_AT_LEAST(20080403,1)
617 return log_ralloc(r, apr_psprintf(r->pool, "%s\037%s\037%s\037%s\037%s", token,
618 forwarded_for == NULL ? "" : forwarded_for, r->connection->client_ip, config->crowd_app_name,
619 config->crowd_url));
620 #else
621 return log_ralloc(r, apr_psprintf(r->pool, "%s\037%s\037%s\037%s\037%s", token,
622 forwarded_for == NULL ? "" : forwarded_for, r->connection->remote_ip, config->crowd_app_name,
623 config->crowd_url));
624 #endif
625 }
626
627 /*==========================
628 * Crowd user authentication
629 *==========================*/
630
631 typedef struct {
632 const char *user;
633 } authentication_data;
634
make_authenticate_url(const request_rec * r,const crowd_config * config,CURL * curl_easy,const void * extra)635 static const char *make_authenticate_url(const request_rec *r, const crowd_config *config, CURL *curl_easy,
636 const void *extra) {
637 const authentication_data *data = (const authentication_data *)extra;
638 return make_url(r, config, curl_easy, data->user, "%srest/usermanagement/1/authentication?username=%s");
639 }
640
handle_crowd_authentication_user_element(write_data_t * write_data,const xmlChar * text)641 static bool handle_crowd_authentication_user_element(write_data_t *write_data, const xmlChar *text) {
642 if (expect_xml_element(write_data, user_xml_name, text)) {
643 write_data->body_valid = true;
644 }
645 return true;
646 }
647
648 /**
649 * Authenticate a user with Crowd.
650 *
651 * @param r The current Apache httpd request.
652 * @param config The configuration details of the Crowd Client.
653 * @param user The user name to authenticate.
654 * @param password The password to authenticate.
655 * @returns a crowd_authenticate_result.
656 */
crowd_authenticate(const request_rec * r,const crowd_config * config,const char * user,const char * password)657 crowd_authenticate_result crowd_authenticate(const request_rec *r, const crowd_config *config, const char *user,
658 const char *password) {
659
660 /* Check the cache */
661 char *cache_key = NULL;
662 if (auth_cache != NULL) {
663 cache_key = make_user_cache_key(user, r, config);
664 if (cache_key != NULL) {
665 char *cached_password = cache_get(auth_cache, cache_key, r);
666 if (cached_password != NULL && strcmp(password, cached_password) == 0) {
667 return CROWD_AUTHENTICATE_SUCCESS;
668 }
669 }
670 }
671
672 const char *payload = log_ralloc(r, apr_pstrcat(r->pool,
673 XML_PROLOG "<password><value><![CDATA[", cdata_encode(r, password), "]]></value></password>", NULL));
674 if (payload == NULL) {
675 return CROWD_AUTHENTICATE_EXCEPTION;
676 }
677
678 xml_node_handler_t *xml_node_handlers = make_xml_node_handlers(r);
679 if (xml_node_handlers == NULL) {
680 return CROWD_AUTHENTICATE_EXCEPTION;
681 }
682 xml_node_handlers[XML_READER_TYPE_ELEMENT] = handle_crowd_authentication_user_element;
683 authentication_data data = {user};
684 switch (crowd_request(r, config, true, make_authenticate_url, payload, xml_node_handlers, &data)) {
685 case HTTP_OK:
686
687 /* Cache successful results */
688 if (auth_cache != NULL && cache_key != NULL) {
689 char *cached_password = log_ralloc(r, strdup(password));
690 if (cached_password != NULL) {
691 cache_put(auth_cache, cache_key, cached_password, r);
692 }
693 }
694
695 return CROWD_AUTHENTICATE_SUCCESS;
696
697 case HTTP_BAD_REQUEST:
698 return CROWD_AUTHENTICATE_FAILURE;
699
700 default:
701 return CROWD_AUTHENTICATE_EXCEPTION;
702
703 }
704
705 }
706
707 typedef struct {
708 const request_rec *r;
709 const char *forwarded_for;
710 } forwarded_for_data_t;
711
make_create_session_url(const request_rec * r,const crowd_config * config,CURL * curl_easy,const void * extra)712 static const char *make_create_session_url(const request_rec *r, const crowd_config *config, CURL *curl_easy,
713 const void *extra __attribute__((unused))) {
714 return make_url(r, config, curl_easy, NULL, "%srest/usermanagement/1/session");
715 }
716
check_header(void * rec,const char * key,const char * value)717 static int check_header(void *rec, const char *key, const char *value) {
718 if (strcasecmp("X-Forwarded-For", key) == 0) {
719 forwarded_for_data_t *data = rec;
720 data->forwarded_for = log_ralloc(data->r, apr_pstrdup(data->r->pool, value));
721 return 0;
722 }
723 return 1;
724 }
725
handle_crowd_create_session_token_text(write_data_t * write_data,const xmlChar * text)726 static bool handle_crowd_create_session_token_text(write_data_t *write_data, const xmlChar *text) {
727 char **token = write_data->extra;
728 if (*token != NULL) {
729 *token = log_ralloc(write_data->r, apr_pstrcat(write_data->r->pool, *token, text, NULL));
730 }
731 return false;
732 }
733
handle_crowd_create_session_token_element(write_data_t * write_data,const xmlChar * text)734 static bool handle_crowd_create_session_token_element(write_data_t *write_data, const xmlChar *text) {
735 if (expect_xml_element(write_data, token_xml_name, text)) {
736 write_data->xml_node_handlers[XML_READER_TYPE_ELEMENT] = NULL;
737 write_data->xml_node_handlers[XML_READER_TYPE_TEXT] = handle_crowd_create_session_token_text;
738 write_data->xml_node_handlers[XML_READER_TYPE_END_ELEMENT] = handle_end_of_data;
739 return false;
740 } else {
741 return true;
742 }
743 }
744
handle_crowd_create_session_session_element(write_data_t * write_data,const xmlChar * text)745 static bool handle_crowd_create_session_session_element(write_data_t *write_data, const xmlChar *text) {
746 if (expect_xml_element(write_data, session_xml_name, text)) {
747 write_data->xml_node_handlers[XML_READER_TYPE_ELEMENT] = handle_crowd_create_session_token_element;
748 return false;
749 } else {
750 return true;
751 }
752 }
753
get_validation_factors(const request_rec * r,const char * forwarded_for)754 static const char *get_validation_factors(const request_rec *r, const char *forwarded_for) {
755 #if AP_MODULE_MAGIC_AT_LEAST(20080403,1)
756 const char *payload_beginning = log_ralloc(r, apr_pstrcat(r->pool,
757 "<validation-factors><validation-factor><name>remote_address</name><value>", r->connection->client_ip,
758 "</value></validation-factor>", NULL));
759 #else
760 const char *payload_beginning = log_ralloc(r, apr_pstrcat(r->pool,
761 "<validation-factors><validation-factor><name>remote_address</name><value>", r->connection->remote_ip,
762 "</value></validation-factor>", NULL));
763 #endif
764 if (payload_beginning == NULL) {
765 return NULL;
766 }
767 const char *payload_end = "</validation-factors>";
768 char *payload;
769 if (forwarded_for == NULL) {
770 payload = apr_pstrcat(r->pool, payload_beginning, payload_end, NULL);
771 } else {
772 payload = apr_pstrcat(r->pool, payload_beginning,
773 "<validation-factor><name>X-Forwarded-For</name><value><![CDATA[",
774 cdata_encode(r, forwarded_for), "]]></value></validation-factor>", payload_end, NULL);
775
776 }
777 log_ralloc(r, payload);
778 return payload;
779 }
780
get_forwarded_for(const request_rec * r)781 const char *get_forwarded_for(const request_rec *r) {
782 forwarded_for_data_t forwarded_for_data = { .r = r };
783 apr_table_do(check_header, &forwarded_for_data, r->headers_in, NULL);
784 return forwarded_for_data.forwarded_for;
785 }
786
787 /**
788 * Authenticate a user with Crowd and create a new SSO session.
789 *
790 * @param r The current Apache httpd request.
791 * @param config The configuration details of the Crowd Client.
792 * @param user The user name to authenticate.
793 * @param password The password to authenticate.
794 * @param token Pointer to variable to receive the session token upon successful authentication.
795 * @returns a crowd_authenticate_result.
796 */
crowd_create_session(const request_rec * r,const crowd_config * config,const char * user,const char * password,const char ** token)797 crowd_authenticate_result crowd_create_session(const request_rec *r, const crowd_config *config, const char *user,
798 const char *password, const char **token) {
799 *token = "";
800 const char *forwarded_for = get_forwarded_for(r);
801 const char *validation_factors = get_validation_factors(r, forwarded_for);
802 if (validation_factors == NULL) {
803 return CROWD_AUTHENTICATE_EXCEPTION;
804 }
805 char *payload = log_ralloc(r, apr_pstrcat(r->pool, XML_PROLOG "<authentication-context><username><![CDATA[",
806 cdata_encode(r, user), "]]></username><password><![CDATA[", cdata_encode(r, password), "]]></password>",
807 validation_factors, "</authentication-context>", NULL));
808 if (payload == NULL) {
809 return CROWD_AUTHENTICATE_EXCEPTION;
810 }
811 xml_node_handler_t *xml_node_handlers = make_xml_node_handlers(r);
812 if (xml_node_handlers == NULL) {
813 return CROWD_AUTHENTICATE_EXCEPTION;
814 }
815 xml_node_handlers[XML_READER_TYPE_ELEMENT] = handle_crowd_create_session_session_element;
816 switch (crowd_request(r, config, true, make_create_session_url, payload, xml_node_handlers, token)) {
817 case HTTP_CREATED:
818
819 /* Cache successful results */
820 if (session_cache != NULL) {
821 const char *cache_key = make_session_cache_key(*token, forwarded_for, r, config);
822 if (cache_key != NULL) {
823 char *cached_user = log_ralloc(r, strdup(user));
824 if (cached_user != NULL) {
825 cache_put(session_cache, cache_key, cached_user, r);
826 }
827 }
828 }
829
830 return CROWD_AUTHENTICATE_SUCCESS;
831
832 case HTTP_BAD_REQUEST:
833 case HTTP_FORBIDDEN:
834 return CROWD_AUTHENTICATE_FAILURE;
835
836 default:
837 return CROWD_AUTHENTICATE_EXCEPTION;
838
839 }
840 }
841
842 typedef struct {
843 char *token;
844 char **user;
845 } crowd_validate_session_data;
846
make_validate_session_url(const request_rec * r,const crowd_config * config,CURL * curl_easy,const void * extra)847 static const char *make_validate_session_url(const request_rec *r, const crowd_config *config, CURL *curl_easy,
848 const void *extra) {
849 const crowd_validate_session_data *data = extra;
850
851 const char *urlWithoutToken = make_url(r, config, curl_easy, NULL, "%srest/usermanagement/1/session/");
852
853 const char* escapedToken = curl_easy_escape(curl_easy, data->token, 0);
854 if (escapedToken == NULL) {
855 return NULL;
856 }
857
858 char *url = log_ralloc(r, apr_pstrcat(r->pool, urlWithoutToken, escapedToken, NULL));
859
860 curl_free((void *)escapedToken);
861
862 return url;
863 }
864
handle_crowd_validate_session_user_element(write_data_t * write_data,const xmlChar * text)865 static bool handle_crowd_validate_session_user_element(write_data_t *write_data, const xmlChar* text) {
866 crowd_validate_session_data *data = write_data->extra;
867 if (!expect_xml_element(write_data, user_xml_name, text)) {
868 return true;
869 }
870 xmlChar *user = xmlTextReaderGetAttribute(write_data->xml_reader, name_xml_name);
871 if (user == NULL) {
872 return true;
873 }
874 *data->user = log_ralloc(write_data->r, apr_pstrdup(write_data->r->pool, (char const*)user));
875 if (*data->user != NULL) {
876 return handle_end_of_data(write_data, text);
877 }
878 return true;
879 }
880
handle_crowd_validate_session_token_end(write_data_t * write_data,const xmlChar * text)881 static bool handle_crowd_validate_session_token_end(write_data_t *write_data,
882 const xmlChar *text __attribute__((unused))) {
883 write_data->xml_node_handlers[XML_READER_TYPE_ELEMENT] = handle_crowd_validate_session_user_element;
884 write_data->xml_node_handlers[XML_READER_TYPE_TEXT] = NULL;
885 write_data->xml_node_handlers[XML_READER_TYPE_END_ELEMENT] = NULL;
886 return false;
887 }
888
handle_crowd_validate_session_token_text(write_data_t * write_data,const xmlChar * text)889 static bool handle_crowd_validate_session_token_text(write_data_t *write_data __attribute__((unused)),
890 const xmlChar *text __attribute__((unused))) {
891 return false;
892 }
893
handle_crowd_validate_session_token_element(write_data_t * write_data,const xmlChar * text)894 static bool handle_crowd_validate_session_token_element(write_data_t *write_data,
895 const xmlChar *text __attribute__((unused))) {
896 write_data->xml_node_handlers[XML_READER_TYPE_ELEMENT] = NULL;
897 write_data->xml_node_handlers[XML_READER_TYPE_TEXT] = handle_crowd_validate_session_token_text;
898 write_data->xml_node_handlers[XML_READER_TYPE_END_ELEMENT] = handle_crowd_validate_session_token_end;
899 return false;
900 }
901
handle_crowd_validate_session_session_element(write_data_t * write_data,const xmlChar * text)902 static bool handle_crowd_validate_session_session_element(write_data_t *write_data, const xmlChar *text) {
903 if (expect_xml_element(write_data, session_xml_name, text)) {
904 write_data->xml_node_handlers[XML_READER_TYPE_ELEMENT] = handle_crowd_validate_session_token_element;
905 return false;
906 }
907 return true;
908 }
909
910 /**
911 * Validate an existing SSO session.
912 *
913 * @param r The current Apache httpd request.
914 * @param config The configuration details of the Crowd Client.
915 * @param token The session token.
916 * @returns a crowd_authenticate_result.
917 */
crowd_validate_session(const request_rec * r,const crowd_config * config,char * token,char ** user)918 crowd_authenticate_result crowd_validate_session(const request_rec *r, const crowd_config *config, char *token,
919 char **user) {
920 *user = NULL;
921 const char *forwarded_for = get_forwarded_for(r);
922
923 /* Check cache */
924 char *cache_key = NULL;
925 if (session_cache != NULL) {
926 cache_key = make_session_cache_key(token, forwarded_for, r, config);
927 if (cache_key != NULL) {
928 *user = cache_get(session_cache, cache_key, r);
929 if (*user != NULL) {
930 return CROWD_AUTHENTICATE_SUCCESS;
931 }
932 }
933 }
934
935 const char *validation_factors = get_validation_factors(r, forwarded_for);
936 if (validation_factors == NULL) {
937 return CROWD_AUTHENTICATE_EXCEPTION;
938 }
939 char *payload = log_ralloc(r, apr_pstrcat(r->pool, XML_PROLOG, validation_factors, NULL));
940 if (payload == NULL) {
941 return CROWD_AUTHENTICATE_EXCEPTION;
942 }
943 xml_node_handler_t *xml_node_handlers = make_xml_node_handlers(r);
944 if (xml_node_handlers == NULL) {
945 return CROWD_AUTHENTICATE_EXCEPTION;
946 }
947 crowd_validate_session_data data = {token, user};
948 xml_node_handlers[XML_READER_TYPE_ELEMENT] = handle_crowd_validate_session_session_element;
949 switch (crowd_request(r, config, false, make_validate_session_url, payload, xml_node_handlers, &data)) {
950 case HTTP_OK:
951
952 /* Cache successful results */
953 if (cache_key != NULL) {
954 char *cached_user = log_ralloc(r, strdup(*user));
955 if (cached_user != NULL) {
956 cache_put(session_cache, cache_key, cached_user, r);
957 }
958 }
959
960 return CROWD_AUTHENTICATE_SUCCESS;
961
962 case HTTP_BAD_REQUEST:
963 case HTTP_NOT_FOUND:
964 return CROWD_AUTHENTICATE_FAILURE;
965
966 default:
967 return CROWD_AUTHENTICATE_EXCEPTION;
968
969 }
970 }
971
972 /*============================
973 * Crowd user group retrieval
974 *============================*/
975
976 typedef struct {
977 const char *user;
978 apr_array_header_t *user_groups;
979 unsigned start_index;
980 } groups_data;
981
982 #define BATCH_SIZE 1000U
983
make_groups_url(const request_rec * r,const crowd_config * config,CURL * curl_easy,const void * extra)984 static const char *make_groups_url(const request_rec *r, const crowd_config *config, CURL *curl_easy,
985 const void *extra) {
986 const groups_data *data = (const groups_data *)extra;
987 const char *url_template = log_ralloc(r, apr_psprintf(r->pool,
988 "%%srest/usermanagement/1/user/group/nested?username=%%s&start-index=%u&max-results=%u",
989 data->start_index, BATCH_SIZE));
990 if (url_template == NULL) {
991 return NULL;
992 }
993 return make_url(r, config, curl_easy, data->user, url_template);
994 }
995
996 static bool handle_crowd_groups_group_end(write_data_t *write_data, const xmlChar* text);
997
handle_crowd_groups_group_element(write_data_t * write_data,const xmlChar * text)998 static bool handle_crowd_groups_group_element(write_data_t *write_data, const xmlChar* text) {
999 if (!expect_xml_element(write_data, group_xml_name, text)) {
1000 return true;
1001 }
1002 xmlChar *groupName = xmlTextReaderGetAttribute(write_data->xml_reader, name_xml_name);
1003 if (groupName == NULL) {
1004 return true;
1005 }
1006 groupName = log_ralloc(write_data->r, apr_pstrdup(write_data->r->pool, (char const*)groupName));
1007 if (groupName == NULL) {
1008 return true;
1009 }
1010 APR_ARRAY_PUSH(((groups_data *) write_data->extra)->user_groups, const char *) = (char const*)groupName;
1011 write_data->xml_node_handlers[XML_READER_TYPE_ELEMENT] = handle_ignored_elements;
1012 write_data->xml_node_handlers[XML_READER_TYPE_END_ELEMENT] = handle_crowd_groups_group_end;
1013 return false;
1014 }
1015
handle_crowd_groups_group_end(write_data_t * write_data,const xmlChar * text)1016 static bool handle_crowd_groups_group_end(write_data_t *write_data, const xmlChar* text __attribute__((unused))) {
1017 write_data->xml_node_handlers[XML_READER_TYPE_ELEMENT] = handle_crowd_groups_group_element;
1018 write_data->xml_node_handlers[XML_READER_TYPE_END_ELEMENT] = handle_end_of_data;
1019 return false;
1020 }
1021
handle_crowd_groups_groups_element(write_data_t * write_data,const xmlChar * text)1022 static bool handle_crowd_groups_groups_element(write_data_t *write_data, const xmlChar *text) {
1023 if (!expect_xml_element(write_data, groups_xml_name, text)) {
1024 return true;
1025 }
1026 if (xmlTextReaderIsEmptyElement(write_data->xml_reader)) {
1027 return handle_end_of_data(write_data, NULL);
1028 }
1029 write_data->xml_node_handlers[XML_READER_TYPE_ELEMENT] = handle_crowd_groups_group_element;
1030 write_data->xml_node_handlers[XML_READER_TYPE_END_ELEMENT] = handle_end_of_data;
1031 return false;
1032 }
1033
1034 /**
1035 * Obtain the list of Crowd groups to which the specified user belongs.
1036 *
1037 * Nested groups are included in the result.
1038 *
1039 * @param username The name of the user.
1040 * @param r The current Apache httpd request.
1041 * @param config The configuration details of the Crowd Client.
1042 * @returns An APR array of (char *) group names, or NULL upon failure.
1043 */
crowd_user_groups(const char * username,const request_rec * r,const crowd_config * config)1044 apr_array_header_t *crowd_user_groups(const char *username, const request_rec *r, const crowd_config *config) {
1045 apr_array_header_t *user_groups;
1046
1047 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Memberships requested for '%s'", username);
1048
1049 /* Check cache */
1050 char *cache_key = NULL;
1051 if (groups_cache != NULL) {
1052 cache_key = make_user_cache_key(username, r, config);
1053 if (cache_key != NULL) {
1054 cached_groups_t *cached_groups = cache_get(groups_cache, cache_key, r);
1055 if (cached_groups != NULL) {
1056 user_groups = log_ralloc(r, apr_array_make(r->pool, cached_groups->count, sizeof(char *)));
1057 if (user_groups == NULL) {
1058 return NULL;
1059 }
1060 int i;
1061 for (i = 0; i < cached_groups->count; i++) {
1062 APR_ARRAY_PUSH(user_groups, const char *) = apr_pstrdup(r->pool, cached_groups->groups[i]);
1063 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Cached group membership for '%s': %s", username, cached_groups->groups[i]);
1064 }
1065 return user_groups;
1066 }
1067 }
1068 }
1069
1070 user_groups = log_ralloc(r, apr_array_make(r->pool, 0, sizeof(char *)));
1071 if (user_groups == NULL) {
1072 return NULL;
1073 }
1074 groups_data data = {username, user_groups, 0};
1075 do {
1076 xml_node_handler_t *xml_node_handlers = make_xml_node_handlers(r);
1077 if (xml_node_handlers == NULL) {
1078 return NULL;
1079 }
1080 xml_node_handlers[XML_READER_TYPE_ELEMENT] = handle_crowd_groups_groups_element;
1081 if (crowd_request(r, config, false, make_groups_url, NULL, xml_node_handlers, &data) != HTTP_OK) {
1082 return NULL;
1083 }
1084 data.start_index += BATCH_SIZE;
1085 } while ((unsigned)(user_groups->nelts) == data.start_index);
1086
1087 /* Cache result */
1088 if (cache_key != NULL) {
1089 cached_groups_t *cached_groups = log_ralloc(r, malloc(sizeof(cached_groups_t)));
1090 if (cached_groups != NULL) {
1091 cached_groups->groups = log_ralloc(r, malloc(user_groups->nelts * sizeof(char *)));
1092 if (cached_groups->groups == NULL) {
1093 free(cached_groups);
1094 } else {
1095 int i;
1096 for (i = 0; i < user_groups->nelts; i++) {
1097 cached_groups->groups[i] = log_ralloc(r, strdup(APR_ARRAY_IDX(user_groups, i, char *)));
1098 if (cached_groups->groups[i] == NULL) {
1099 for (i--; i >= 0; i--) {
1100 free(cached_groups->groups[i]);
1101 }
1102 free(cached_groups->groups);
1103 free(cached_groups);
1104 return user_groups;
1105 }
1106 }
1107 cached_groups->count = user_groups->nelts;
1108 cache_put(groups_cache, cache_key, cached_groups, r);
1109 }
1110 }
1111 }
1112
1113 return user_groups;
1114 }
1115
make_cookie_config_url(const request_rec * r,const crowd_config * config,CURL * curl_easy,const void * extra)1116 static const char *make_cookie_config_url(const request_rec *r, const crowd_config *config, CURL *curl_easy,
1117 const void *extra __attribute__((unused))) {
1118 return make_url(r, config, curl_easy, NULL, "%srest/usermanagement/1/config/cookie");
1119 }
1120
1121 typedef struct {
1122 crowd_cookie_config_t *result;
1123 char *secure;
1124 } crowd_cookie_config_extra;
1125
handle_crowd_cookie_config_name_text(write_data_t * write_data,const xmlChar * text)1126 static bool handle_crowd_cookie_config_name_text(write_data_t *write_data, const xmlChar *text) {
1127 crowd_cookie_config_extra *extra = write_data->extra;
1128 extra->result->cookie_name = log_ralloc(write_data->r, apr_pstrcat(write_data->r->pool, extra->result->cookie_name,
1129 text, NULL));
1130 if (extra->result->cookie_name == NULL) {
1131 return true;
1132 }
1133 return false;
1134 }
1135
handle_crowd_cookie_config_name_element(write_data_t * write_data,const xmlChar * text)1136 static bool handle_crowd_cookie_config_name_element(write_data_t *write_data, const xmlChar *text) {
1137 if (expect_xml_element(write_data, name_xml_name, text)) {
1138 write_data->xml_node_handlers[XML_READER_TYPE_ELEMENT] = NULL;
1139 write_data->xml_node_handlers[XML_READER_TYPE_TEXT] = handle_crowd_cookie_config_name_text;
1140 write_data->xml_node_handlers[XML_READER_TYPE_END_ELEMENT] = handle_end_of_data;
1141 return false;
1142 } else {
1143 return true;
1144 }
1145 }
1146
handle_crowd_cookie_config_secure_text(write_data_t * write_data,const xmlChar * text)1147 static bool handle_crowd_cookie_config_secure_text(write_data_t *write_data, const xmlChar *text) {
1148 crowd_cookie_config_extra *extra = write_data->extra;
1149 extra->secure = log_ralloc(write_data->r, apr_pstrcat(write_data->r->pool, extra->secure, text, NULL));
1150 if (extra->secure == NULL) {
1151 return true;
1152 }
1153 return false;
1154 }
1155
handle_crowd_cookie_config_secure_end(write_data_t * write_data,const xmlChar * text)1156 static bool handle_crowd_cookie_config_secure_end(write_data_t *write_data,
1157 const xmlChar *text __attribute__((unused))) {
1158 write_data->xml_node_handlers[XML_READER_TYPE_ELEMENT] = handle_crowd_cookie_config_name_element;
1159 write_data->xml_node_handlers[XML_READER_TYPE_TEXT] = NULL;
1160 write_data->xml_node_handlers[XML_READER_TYPE_END_ELEMENT] = NULL;
1161 return false;
1162 }
1163
handle_crowd_cookie_config_secure_element(write_data_t * write_data,const xmlChar * text)1164 static bool handle_crowd_cookie_config_secure_element(write_data_t *write_data, const xmlChar *text) {
1165 if (expect_xml_element(write_data, secure_xml_name, text)) {
1166 write_data->xml_node_handlers[XML_READER_TYPE_ELEMENT] = NULL;
1167 write_data->xml_node_handlers[XML_READER_TYPE_TEXT] = handle_crowd_cookie_config_secure_text;
1168 write_data->xml_node_handlers[XML_READER_TYPE_END_ELEMENT] = handle_crowd_cookie_config_secure_end;
1169 return false;
1170 } else {
1171 return true;
1172 }
1173 }
1174
handle_crowd_cookie_config_domain_text(write_data_t * write_data,const xmlChar * text)1175 static bool handle_crowd_cookie_config_domain_text(write_data_t *write_data, const xmlChar *text) {
1176 crowd_cookie_config_extra *extra = write_data->extra;
1177 extra->result->domain
1178 = log_ralloc(write_data->r, apr_pstrcat(write_data->r->pool, extra->result->domain, text, NULL));
1179 if (extra->result->domain == NULL) {
1180 return true;
1181 }
1182 return false;
1183 }
1184
handle_crowd_cookie_config_domain_end(write_data_t * write_data,const xmlChar * text)1185 static bool handle_crowd_cookie_config_domain_end(write_data_t *write_data,
1186 const xmlChar *text __attribute__((unused))) {
1187 write_data->xml_node_handlers[XML_READER_TYPE_ELEMENT] = handle_crowd_cookie_config_secure_element;
1188 write_data->xml_node_handlers[XML_READER_TYPE_TEXT] = NULL;
1189 write_data->xml_node_handlers[XML_READER_TYPE_END_ELEMENT] = NULL;
1190 return false;
1191 }
1192
handle_crowd_cookie_config_domain_or_secure_element(write_data_t * write_data,const xmlChar * text)1193 static bool handle_crowd_cookie_config_domain_or_secure_element(write_data_t *write_data, const xmlChar *text) {
1194 if (xmlStrEqual(domain_xml_name, text)) {
1195 crowd_cookie_config_extra *extra = write_data->extra;
1196 extra->result->domain = "";
1197 write_data->xml_node_handlers[XML_READER_TYPE_ELEMENT] = NULL;
1198 write_data->xml_node_handlers[XML_READER_TYPE_TEXT] = handle_crowd_cookie_config_domain_text;
1199 write_data->xml_node_handlers[XML_READER_TYPE_END_ELEMENT] = handle_crowd_cookie_config_domain_end;
1200 return false;
1201 }
1202 return handle_crowd_cookie_config_secure_element(write_data, text);
1203 }
1204
handle_crowd_cookie_config_cookie_config_element(write_data_t * write_data,const xmlChar * text)1205 static bool handle_crowd_cookie_config_cookie_config_element(write_data_t *write_data, const xmlChar *text) {
1206 if (expect_xml_element(write_data, cookie_config_xml_name, text)) {
1207 write_data->xml_node_handlers[XML_READER_TYPE_ELEMENT] = handle_crowd_cookie_config_domain_or_secure_element;
1208 return false;
1209 } else {
1210 return true;
1211 }
1212 }
1213
crowd_get_cookie_config(const request_rec * r,const crowd_config * config)1214 crowd_cookie_config_t *crowd_get_cookie_config(const request_rec *r, const crowd_config *config) {
1215
1216 /* Check cache */
1217 char *cache_key = NULL;
1218 if (cookie_config_cache != NULL) {
1219 cache_key = make_app_cache_key(r, config);
1220 if (cache_key != NULL) {
1221 crowd_cookie_config_t *cookie_config = cache_get(cookie_config_cache, cache_key, r);
1222 if (cookie_config != NULL) {
1223 return cookie_config;
1224 }
1225 }
1226 }
1227
1228 crowd_cookie_config_extra extra = {
1229 log_ralloc(r, apr_pcalloc(r->pool, sizeof(crowd_cookie_config_t))),
1230 ""
1231 };
1232 if (extra.result == NULL) {
1233 return NULL;
1234 }
1235 extra.result->domain = NULL;
1236 extra.result->cookie_name = "";
1237 xml_node_handler_t *xml_node_handlers = make_xml_node_handlers(r);
1238 if (xml_node_handlers == NULL) {
1239 return NULL;
1240 }
1241 xml_node_handlers[XML_READER_TYPE_ELEMENT] = handle_crowd_cookie_config_cookie_config_element;
1242 if (crowd_request(r, config, false, make_cookie_config_url, NULL, xml_node_handlers, &extra) != HTTP_OK) {
1243 return NULL;
1244 }
1245 if (strcmp("true", extra.secure) == 0) {
1246 extra.result->secure = true;
1247 } else if (strcmp("false", extra.secure) != 0) {
1248 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Unrecognised 'secure' value from Crowd.");
1249 return NULL;
1250 }
1251
1252 /* Cache result */
1253 if (cache_key != NULL) {
1254 crowd_cookie_config_t *cached = log_ralloc(r, malloc(sizeof(crowd_cookie_config_t)));
1255 if (cached != NULL) {
1256 if (extra.result->domain != NULL) {
1257 cached->domain = log_ralloc(r, strdup(extra.result->domain));
1258 if (cached->domain == NULL) {
1259 free(cached);
1260 return NULL;
1261 }
1262 } else {
1263 cached->domain = NULL;
1264 }
1265 cached->cookie_name = log_ralloc(r, strdup(extra.result->cookie_name));
1266 if (cached->cookie_name == NULL) {
1267 free(cached->domain);
1268 free(cached);
1269 } else {
1270 cached->secure = extra.result->secure;
1271 cache_put(cookie_config_cache, cache_key, cached, r);
1272 }
1273 }
1274 }
1275
1276 return extra.result;
1277 }
1278