+Missing <time.h> include
[middleman.git] / src / redirect.c
1 /*
2     MiddleMan filtering proxy server
3     Copyright (C) 2002  Jason McLaughlin
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19
20 #include <stdio.h>
21 #include <string.h>
22 #include "proto.h"
23
24 REDIRECT_LIST *redirect_load(REDIRECT_LIST * redirect_list, XML_LIST * xml_list)
25 {
26         REDIRECT_LIST *tmp_list = redirect_list;
27         struct REDIRECT_LIST_LIST *redirect = NULL;
28
29         if (tmp_list == NULL) {
30                 tmp_list = xmalloc(sizeof(REDIRECT_LIST));
31                 tmp_list->redirect_list = NULL;
32                 tmp_list->id = 0;
33                 tmp_list->enabled = TRUE;
34
35                 redirect_list = tmp_list;
36
37                 pthread_rwlock_init(&tmp_list->lock, NULL);
38         } else
39                 redirect = tmp_list->redirect_list;
40
41         while ((xml_list = xml_section(xml_list, "<redirect>"))) {
42                 XML_LIST_LOOP(xml_list, "<redirect>") {
43                         XML_LIST_CMP(xml_list, "<item>") {
44                                 redirect = redirect_list_new(redirect);
45                                 redirect->id = redirect_list->id++;
46
47                                 if (tmp_list->redirect_list == NULL)
48                                         tmp_list->redirect_list = redirect;
49                                 XML_LIST_LOOP(xml_list, "<item>") {
50                                         XML_LIST_CMP(xml_list, "<enabled>") {
51                                                 xml_list = xml_list->next;
52                                                 if (xml_list->type == XML_VALUE) {
53                                                         if (!strcasecmp(xml_list->item, "false"))
54                                                                 redirect->enabled = FALSE;
55                                                         else
56                                                                 redirect->enabled = TRUE;
57                                                 }
58                                         }
59                                         XML_LIST_CMP(xml_list, "<comment>") {
60                                                 xml_list = xml_list->next;
61                                                 if (xml_list->type == XML_VALUE)
62                                                         redirect_list_insert(redirect, xml_list->item, NULL, NULL, NULL, NULL, NULL, NULL);
63                                         }
64                                         XML_LIST_CMP(xml_list, "<url>") {
65                                                 xml_list = xml_list->next;
66                                                 if (xml_list->type == XML_VALUE)
67                                                         redirect_list_insert(redirect, NULL, xml_list->item, NULL, NULL, NULL, NULL, NULL);
68                                         }
69                                         XML_LIST_CMP(xml_list, "<redirect>") {
70                                                 xml_list = xml_list->next;
71                                                 if (xml_list->type == XML_VALUE)
72                                                         redirect_list_insert(redirect, NULL, NULL, xml_list->item, NULL, NULL, NULL, NULL);
73                                         }
74                                         XML_LIST_CMP(xml_list, "<port>") {
75                                                 xml_list = xml_list->next;
76                                                 if (xml_list->type == XML_VALUE)
77                                                         redirect_list_insert(redirect, NULL, NULL, NULL, xml_list->item, NULL, NULL, NULL);
78                                         }
79                                         XML_LIST_CMP(xml_list, "<which>") {
80                                                 xml_list = xml_list->next;
81                                                 if (xml_list->type == XML_VALUE)
82                                                         redirect_list_insert(redirect, NULL, NULL, NULL, NULL, xml_list->item, NULL, NULL);
83                                         }
84                                         XML_LIST_CMP(xml_list, "<send302>") {
85                                                 xml_list = xml_list->next;
86                                                 if (xml_list->type == XML_VALUE)
87                                                         redirect_list_insert(redirect, NULL, NULL, NULL, NULL, NULL, xml_list->item, NULL);
88                                         }
89                                         XML_LIST_CMP(xml_list, "<options>") {
90                                                 xml_list = xml_list->next;
91                                                 if (xml_list->type == XML_VALUE)
92                                                         redirect_list_insert(redirect, NULL, NULL, NULL, NULL, NULL, NULL, xml_list->item);
93                                         }
94                                 }
95                         }
96                         XML_LIST_CMP(xml_list, "<enabled>") {
97                                 xml_list = xml_list->next;
98                                 if (xml_list->type == XML_VALUE) {
99                                         if (!strcasecmp(xml_list->item, "false"))
100                                                 tmp_list->enabled = FALSE;
101                                         else
102                                                 tmp_list->enabled = TRUE;
103                                 }
104                         }
105                 }
106         }
107
108         return tmp_list;
109 }
110
111 XML_LIST *redirect_xml(REDIRECT_LIST * redirect_list, XML_LIST * xml_list)
112 {
113         char *ptr, buf[128];
114         struct REDIRECT_LIST_LIST *rl;
115
116         pthread_rwlock_rdlock(&redirect_list->lock);
117
118         xml_list = xml_list_add(xml_list, "<redirect>", XML_TAG);
119
120         xml_list = xml_list_add(xml_list, "<enabled>", XML_TAG);
121         xml_list = xml_list_add(xml_list, (redirect_list->enabled == TRUE) ? "true" : "false", XML_VALUE);
122         xml_list = xml_list_add(xml_list, "</enabled>", XML_TAG);
123
124         for (rl = redirect_list->redirect_list; rl; rl = rl->next) {
125                 xml_list = xml_list_add(xml_list, "<item>", XML_TAG);
126
127                 xml_list = xml_list_add(xml_list, "<enabled>", XML_TAG);
128                 xml_list = xml_list_add(xml_list, (rl->enabled == TRUE) ? "true" : "false", XML_VALUE);
129                 xml_list = xml_list_add(xml_list, "</enabled>", XML_TAG);
130
131                 if (rl->comment != NULL) {
132                         xml_list = xml_list_add(xml_list, "<comment>", XML_TAG);
133                         ptr = string_to_xml(rl->comment);
134                         xml_list = xml_list_add(xml_list, ptr, XML_VALUE);
135                         xfree(ptr);
136                         xml_list = xml_list_add(xml_list, "</comment>", XML_TAG);
137                 }
138
139                 if (rl->url != NULL) {
140                         xml_list = xml_list_add(xml_list, "<url>", XML_TAG);
141                         ptr = string_to_xml(rl->url);
142                         xml_list = xml_list_add(xml_list, ptr, XML_VALUE);
143                         xfree(ptr);
144                         xml_list = xml_list_add(xml_list, "</url>", XML_TAG);
145                 }
146
147                 if (rl->redirect != NULL) {
148                         xml_list = xml_list_add(xml_list, "<redirect>", XML_TAG);
149                         ptr = string_to_xml(rl->redirect);
150                         xml_list = xml_list_add(xml_list, ptr, XML_VALUE);
151                         xfree(ptr);
152                         xml_list = xml_list_add(xml_list, "</redirect>", XML_TAG);
153                 }
154
155                 if (rl->port != -1) {
156                         xml_list = xml_list_add(xml_list, "<port>", XML_TAG);
157                         snprintf(buf, sizeof(buf), "%d", rl->port);
158                         xml_list = xml_list_add(xml_list, buf, XML_VALUE);
159                         xml_list = xml_list_add(xml_list, "</port>", XML_TAG);
160                 }
161
162                 xml_list = xml_list_add(xml_list, "<which>", XML_TAG);
163                 xml_list = xml_list_add(xml_list, (rl->which == REDIRECT_BOTH) ? "both" : (rl->which == REDIRECT_URL) ? "url" : "location", XML_VALUE);
164                 xml_list = xml_list_add(xml_list, "</which>", XML_TAG);
165
166                 xml_list = xml_list_add(xml_list, "<send302>", XML_TAG);
167                 xml_list = xml_list_add(xml_list, (rl->send302 == TRUE) ? "yes" : "no", XML_VALUE);
168                 xml_list = xml_list_add(xml_list, "</send302>", XML_TAG);
169
170                 xml_list = xml_list_add(xml_list, "<options>", XML_TAG);
171                 snprintf(buf, sizeof(buf), "%s,%s,%s", (rl->options & URL_ENCODE) ? "encode" : "", (rl->options & URL_DECODEBEFORE) ? "decodebefore" : "", (rl->options & URL_DECODEAFTER) ? "decodeafter" : "");
172                 xml_list = xml_list_add(xml_list, buf, XML_VALUE);
173                 xml_list = xml_list_add(xml_list, "</options>", XML_TAG);
174
175                 xml_list = xml_list_add(xml_list, "</item>", XML_TAG);
176         }
177
178         xml_list = xml_list_add(xml_list, "</redirect>", XML_TAG);
179
180         pthread_rwlock_unlock(&redirect_list->lock);
181
182         return xml_list;
183 }
184
185 struct REDIRECT_LIST_LIST *redirect_list_new(struct REDIRECT_LIST_LIST *x)
186 {
187         if (x == NULL) {
188                 x = xmalloc(sizeof(struct REDIRECT_LIST_LIST));
189                 x->prev = NULL;
190         } else {
191                 while (x->next != NULL)
192                         x = x->next;
193                 x->next = xmalloc(sizeof(struct REDIRECT_LIST_LIST));
194                 x->next->prev = x;
195                 x = x->next;
196         }
197
198         x->enabled = TRUE;
199         x->comment = NULL;
200         x->up = NULL;
201         x->url = NULL;
202         x->port = -1;
203         x->redirect = NULL;
204         x->which = REDIRECT_BOTH;
205         x->send302 = FALSE;
206         x->options = 0;
207         x->next = NULL;
208
209         return x;
210 }
211
212 void redirect_list_insert(struct REDIRECT_LIST_LIST *x, char *a, char *b, char *c, char *d, char *e, char *f, char *g)
213 {
214         int i;
215         char **args;
216
217         if (a != NULL) {
218                 FREE_AND_NULL(x->comment);
219
220                 if (strcmp(a, ""))
221                         x->comment = xstrdup(a);
222         }
223         if (b != NULL) {
224                 if (x->up != NULL)
225                         reg_sub_free(x->up);
226                 FREE_AND_NULL(x->url);
227
228                 if (strcmp(b, "")) {
229                         x->up = reg_sub_compile(b, PCREFLAGS);
230                         x->url = xstrdup(b);
231                 } else
232                         x->url = NULL;
233         }
234         if (c != NULL) {
235                 FREE_AND_NULL(x->redirect);
236
237                 if (strcmp(c, ""))
238                         x->redirect = xstrdup(c);
239         }
240         if (d != NULL) {
241                 x->port = -1;
242
243                 if (strcmp(d, ""))
244                         x->port = atoi(d);
245         }
246         if (e != NULL) {
247                 x->which = REDIRECT_BOTH;
248
249                 if (strcmp(e, "")) {
250                         if (!strcasecmp(e, "location"))
251                                 x->which = REDIRECT_HEADER;
252                         else if (!strcasecmp(e, "url"))
253                                 x->which = REDIRECT_URL;
254                 }
255         }
256         if (f != NULL) {
257                 x->send302 = FALSE;
258
259                 if (strcmp(f, "")) {
260                         if (!strcasecmp(f, "yes"))
261                                 x->send302 = TRUE;
262                         else
263                                 x->send302 = FALSE;
264                 }
265         }
266         if (g != NULL) {
267                 x->options = FALSE;
268
269                 if (strcmp(g, "")) {
270                         args = string_break(g, ',');
271                         for (i = 0; args[i]; i++) {
272                                 if (!strcasecmp(args[i], "encode"))
273                                         x->options |= URL_ENCODE;
274                                 else if (!strcasecmp(args[i], "decodeafter"))
275                                         x->options |= URL_DECODEAFTER;
276                                 else if (!strcasecmp(args[i], "decodebefore"))
277                                         x->options |= URL_DECODEBEFORE;
278
279                                 xfree(args[i]);
280                         }
281                         xfree(args);
282                 }
283         }
284 }
285
286 struct REDIRECT_LIST_LIST *redirect_list_delete(struct REDIRECT_LIST_LIST *x)
287 {
288         struct REDIRECT_LIST_LIST *start = x;
289
290         while (start->prev != NULL)
291                 start = start->prev;
292
293         if (x->next != NULL)
294                 x->next->prev = x->prev;
295         if (x->prev != NULL)
296                 x->prev->next = x->next;
297         else
298                 start = start->next;
299
300         if (x->up != NULL)
301                 reg_sub_free(x->up);
302         FREE_AND_NULL(x->comment);
303         FREE_AND_NULL(x->url);
304         FREE_AND_NULL(x->redirect);
305
306         xfree(x);
307
308         return start;
309 }
310
311 void redirect_free(REDIRECT_LIST * redirect_list)
312 {
313         if (redirect_list == NULL)
314                 return;
315
316         redirect_list_free(redirect_list->redirect_list);
317
318         xfree(redirect_list);
319 }
320
321 void redirect_list_free(struct REDIRECT_LIST_LIST *rl)
322 {
323         struct REDIRECT_LIST_LIST *tmp;
324
325         while (rl != NULL) {
326                 tmp = rl->next;
327
328                 if (rl->up != NULL)
329                         reg_sub_free(rl->up);
330                 FREE_AND_NULL(rl->comment);
331                 FREE_AND_NULL(rl->url);
332                 FREE_AND_NULL(rl->redirect);
333
334                 xfree(rl);
335                 rl = tmp;
336         }
337 }
338
339 int redirect_do(REDIRECT_LIST * redirect_list, CONNECTION * connection, int type)
340 {
341         int x = FALSE;
342         char url[1024], *ret, *ptr, *ptr2;
343         struct REDIRECT_LIST_LIST *rl;
344         regmatch_sub_t *rmatch;
345         HEADER *header;
346
347         if (redirect_list == NULL)
348                 return FALSE;
349         if (connection->bypass & FEATURE_REDIRECT)
350                 return FALSE;
351
352         pthread_rwlock_rdlock(&redirect_list->lock);
353
354         if (redirect_list->enabled == FALSE) {
355                 pthread_rwlock_unlock(&redirect_list->lock);
356
357                 return FALSE;
358         }
359
360         if (type == REDIRECT_REQUEST)
361                 snprintf(url, sizeof(url), "%s%s", (connection->header->host != NULL) ? connection->header->host : "", connection->header->file);
362         else {
363                 ptr = (!strncasecmp(connection->rheader->location, "http://", 7)) ? connection->rheader->location + 7 : connection->rheader->location;
364
365                 if (*ptr == '/')
366                         snprintf(url, sizeof(url), "%s%s", connection->rheader->host, connection->rheader->location);
367                 else
368                         snprintf(url, sizeof(url), "%s", ptr);
369         }
370
371         for (rl = redirect_list->redirect_list; rl; rl = rl->next) {
372                 if (rl->up != NULL && rl->enabled == TRUE && (rl->which == REDIRECT_BOTH || (rl->which == REDIRECT_URL && type == REDIRECT_REQUEST) || (rl->which == REDIRECT_LOCATION && type == REDIRECT_HEADER))) {
373                         if (rl->options & URL_DECODEBEFORE) {
374                                 /* decode url before matching */
375                                 ret = url_decode(url, strlen(url));
376                                 rmatch = reg_sub_exec(rl->up, ret);
377                                 xfree(ret);
378                         } else
379                                 rmatch = reg_sub_exec(rl->up, url);
380
381                         if (rmatch != NULL) {
382                                 /* make redirect rules with empty redirect url still match, but do nothing */
383                                 if (rl->redirect == NULL)
384                                         break;
385
386                                 ret = reg_sub_parse(rmatch, rl->redirect);
387
388                                 ptr = strchr(ret, '/');
389                                 if (ptr != NULL && (rl->options & URL_ENCODE || rl->options & URL_DECODEAFTER)) {
390                                         /* encode or decode file portion of url */
391                                         ptr2 = (rl->options & URL_ENCODE) ? url_encode(&ptr[1], strlen(&ptr[1])) : url_decode(&ptr[1], strlen(&ptr[1]));
392                                         ret = xrealloc(ret, ptr - ret + strlen(ptr2) + 2);
393
394                                         ptr = strchr(ret, '/');
395
396                                         strcpy(&ptr[1], ptr2);
397
398                                         xfree(ptr2);
399                                 }
400
401                                 if (type == REDIRECT_REQUEST) {
402                                         if (rl->send302 == TRUE) {
403                                                 putlog(MMLOG_REDIRECT, "sent 302 redirect for request for %s to %s", url, ret);
404
405                                                 /* send a 302 redirect */
406                                                 header = header_new();
407                                                 header->type = HTTP_RESP;
408                                                 header->code = 302;
409                                                 header->content_length = 0;
410
411                                                 if (*ret != '/') {
412                                                         if (connection->header->url_command != NULL) {
413                                                                 ptr = url_command_create(connection->header->url_command);
414                                                                 snprintf(url, sizeof(url), "http://%s%s", ptr, ret);
415                                                                 xfree(ptr);
416                                                         } else
417                                                                 snprintf(url, sizeof(url), "http://%s", ret);
418                                                 } else 
419                                                         snprintf(url, sizeof(url), "%s", ret);
420
421                                                 header->location = xstrdup(url);
422
423                                                 header_send(header, connection, CLIENT, HEADER_RESP);
424
425                                                 http_header_free(header);
426                                                 x = TRUE;
427                                         } else {
428                                                 /* just connect to a different host */
429                                                 if (ptr == NULL || *ret != '/') {
430                                                         FREE_AND_NULL(connection->header->host);
431                                                         connection->header->host = xstrndup(ret, (ptr != NULL) ? ptr - ret : ~0);
432                                                 }
433
434                                                 FREE_AND_NULL(connection->header->file);
435                                                 connection->header->file = xstrdup((ptr != NULL) ? ptr : "/");
436
437                                                 if (rl->port != -1)
438                                                         connection->header->port = rl->port;
439
440                                                 putlog(MMLOG_REDIRECT, "request for %s to %s%s", url, (connection->header->host != NULL) ? connection->header->host : "", connection->header->file);
441                                         }
442                                 } else {
443                                         /* change the Location: header from a 302 redirect */
444                                         snprintf(url, sizeof(url), "%s%s", (*ret == '/') ? "" : "http://", ret);
445
446                                         putlog(MMLOG_REDIRECT, "302 redirect for %s to %s", connection->rheader->location, url);
447
448                                         xfree(connection->rheader->location);
449
450                                         connection->rheader->location = xstrdup(url);
451                                 }
452
453                                 FREE_AND_NULL(ret);
454                                 reg_sub_match_free(rmatch);
455
456                                 break;
457                         }
458                 }
459         }
460
461         pthread_rwlock_unlock(&redirect_list->lock);
462
463         return x;
464 }