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