Bug 769. Rewriting Location headers in proxy responses
[pazpar2-moved-to-github.git] / src / http.c
1 /*
2  * $Id: http.c,v 1.10 2007-02-05 16:15:41 quinn Exp $
3  */
4
5 #include <stdio.h>
6 #include <sys/socket.h>
7 #include <sys/types.h>
8 #include <sys/uio.h>
9 #include <unistd.h>
10 #include <stdlib.h>
11 #include <strings.h>
12 #include <ctype.h>
13 #include <fcntl.h>
14 #include <netdb.h>
15 #include <errno.h>
16 #include <assert.h>
17
18 #if HAVE_CONFIG_H
19 #include <cconfig.h>
20 #endif
21
22 #include <netinet/in.h>
23
24 #include <yaz/yaz-util.h>
25 #include <yaz/comstack.h>
26 #include <netdb.h>
27
28 #include "util.h"
29 #include "eventl.h"
30 #include "pazpar2.h"
31 #include "http.h"
32 #include "http_command.h"
33
34 static void proxy_io(IOCHAN i, int event);
35 static struct http_channel *http_create(void);
36 static void http_destroy(IOCHAN i);
37
38 extern IOCHAN channel_list;
39
40 static struct sockaddr_in *proxy_addr = 0; // If this is set, we proxy normal HTTP requests
41 static char proxy_url[256] = "";
42 static char myurl[256] = "";
43 static struct http_buf *http_buf_freelist = 0;
44 static struct http_channel *http_channel_freelist = 0;
45
46 static struct http_buf *http_buf_create()
47 {
48     struct http_buf *r;
49
50     if (http_buf_freelist)
51     {
52         r = http_buf_freelist;
53         http_buf_freelist = http_buf_freelist->next;
54     }
55     else
56         r = xmalloc(sizeof(struct http_buf));
57     r->offset = 0;
58     r->len = 0;
59     r->next = 0;
60     return r;
61 }
62
63 static void http_buf_destroy(struct http_buf *b)
64 {
65     b->next = http_buf_freelist;
66     http_buf_freelist = b;
67 }
68
69 static void http_buf_destroy_queue(struct http_buf *b)
70 {
71     struct http_buf *p;
72     while (b)
73     {
74         p = b->next;
75         http_buf_destroy(b);
76         b = p;
77     }
78 }
79
80 #ifdef GAGA
81 // Calculate length of chain
82 static int http_buf_len(struct http_buf *b)
83 {
84     int sum = 0;
85     for (; b; b = b->next)
86         sum += b->len;
87     return sum;
88 }
89 #endif
90
91 static struct http_buf *http_buf_bybuf(char *b, int len)
92 {
93     struct http_buf *res = 0;
94     struct http_buf **p = &res;
95
96     while (len)
97     {
98         int tocopy = len;
99         if (tocopy > HTTP_BUF_SIZE)
100             tocopy = HTTP_BUF_SIZE;
101         *p = http_buf_create();
102         memcpy((*p)->buf, b, tocopy);
103         (*p)->len = tocopy;
104         len -= tocopy;
105         b += tocopy;
106         p = &(*p)->next;
107     }
108     return res;
109 }
110
111 // Add a (chain of) buffers to the end of an existing queue.
112 static void http_buf_enqueue(struct http_buf **queue, struct http_buf *b)
113 {
114     while (*queue)
115         queue = &(*queue)->next;
116     *queue = b;
117 }
118
119 static struct http_buf *http_buf_bywrbuf(WRBUF wrbuf)
120 {
121     // Heavens to Betsy (buf)!
122     return http_buf_bybuf(wrbuf_buf(wrbuf), wrbuf_len(wrbuf));
123 }
124
125 // Non-destructively collapse chain of buffers into a string (max *len)
126 // Return
127 static int http_buf_peek(struct http_buf *b, char *buf, int len)
128 {
129     int rd = 0;
130     while (b && rd < len)
131     {
132         int toread = len - rd;
133         if (toread > b->len)
134             toread = b->len;
135         memcpy(buf + rd, b->buf + b->offset, toread);
136         rd += toread;
137         b = b->next;
138     }
139     buf[rd] = '\0';
140     return rd;
141 }
142
143 // Ddestructively munch up to len  from head of queue.
144 static int http_buf_read(struct http_buf **b, char *buf, int len)
145 {
146     int rd = 0;
147     while ((*b) && rd < len)
148     {
149         int toread = len - rd;
150         if (toread > (*b)->len)
151             toread = (*b)->len;
152         memcpy(buf + rd, (*b)->buf + (*b)->offset, toread);
153         rd += toread;
154         if (toread < (*b)->len)
155         {
156             (*b)->len -= toread;
157             (*b)->offset += toread;
158             break;
159         }
160         else
161         {
162             struct http_buf *n = (*b)->next;
163             http_buf_destroy(*b);
164             *b = n;
165         }
166     }
167     buf[rd] = '\0';
168     return rd;
169 }
170
171 void static urldecode(char *i, char *o)
172 {
173     while (*i)
174     {
175         if (*i == '+')
176         {
177             *(o++) = ' ';
178             i++;
179         }
180         else if (*i == '%')
181         {
182             i++;
183             sscanf(i, "%2hhx", o);
184             i += 2;
185             o++;
186         }
187         else
188             *(o++) = *(i++);
189     }
190     *o = '\0';
191 }
192
193 void http_addheader(struct http_response *r, const char *name, const char *value)
194 {
195     struct http_channel *c = r->channel;
196     struct http_header *h = nmem_malloc(c->nmem, sizeof *h);
197     h->name = nmem_strdup(c->nmem, name);
198     h->value = nmem_strdup(c->nmem, value);
199     h->next = r->headers;
200     r->headers = h;
201 }
202
203 char *http_argbyname(struct http_request *r, char *name)
204 {
205     struct http_argument *p;
206     if (!name)
207         return 0;
208     for (p = r->arguments; p; p = p->next)
209         if (!strcmp(p->name, name))
210             return p->value;
211     return 0;
212 }
213
214 char *http_headerbyname(struct http_header *h, char *name)
215 {
216     for (; h; h = h->next)
217         if (!strcmp(h->name, name))
218             return h->value;
219     return 0;
220 }
221
222 struct http_response *http_create_response(struct http_channel *c)
223 {
224     struct http_response *r = nmem_malloc(c->nmem, sizeof(*r));
225     strcpy(r->code, "200");
226     r->msg = "OK";
227     r->channel = c;
228     r->headers = 0;
229     r->payload = 0;
230     return r;
231 }
232
233 // Check if buf contains a package (minus payload)
234 static int package_check(const char *buf)
235 {
236     int len = 0;
237     while (*buf) // Check if we have a sequence of lines terminated by an empty line
238     {
239         char *b = strstr(buf, "\r\n");
240
241         if (!b)
242             return 0;
243
244         len += (b - buf) + 2;
245         if (b == buf)
246             return len;
247         buf = b + 2;
248     }
249     return 0;
250 }
251
252 // Check if we have a request. Return 0 or length
253 // (including trailing CRNL) FIXME: Does not deal gracefully with requests
254 // carrying payload but this is kind of OK since we will reject anything
255 // other than an empty GET
256 static int request_check(struct http_buf *queue)
257 {
258     char tmp[4096];
259
260     http_buf_peek(queue, tmp, 4096);
261     return package_check(tmp);
262 }
263
264 struct http_response *http_parse_response_buf(struct http_channel *c, const char *buf, int len)
265 {
266     char tmp[4096];
267     struct http_response *r = http_create_response(c);
268     char *p, *p2;
269     struct http_header **hp = &r->headers;
270
271     if (len >= 4096)
272         return 0;
273     memcpy(tmp, buf, len);
274     for (p = tmp; *p && *p != ' '; p++) // Skip HTTP version
275         ;
276     p++;
277     // Response code
278     for (p2 = p; *p2 && *p2 != ' ' && p2 - p < 3; p2++)
279         r->code[p2 - p] = *p2;
280     if (!(p = strstr(tmp, "\r\n")))
281         return 0;
282     p += 2;
283     while (*p)
284     {
285         if (!(p2 = strstr(p, "\r\n")))
286             return 0;
287         if (p == p2) // End of headers
288             break;
289         else
290         {
291             struct http_header *h = *hp = nmem_malloc(c->nmem, sizeof(*h));
292             char *value = strchr(p, ':');
293             if (!value)
294                 return 0;
295             *(value++) = '\0';
296             h->name = nmem_strdup(c->nmem, p);
297             while (isspace(*value))
298                 value++;
299             if (value >= p2)  // Empty header;
300             {
301                 h->value = "";
302                 p = p2 + 2;
303                 continue;
304             }
305             *p2 = '\0';
306             h->value = nmem_strdup(c->nmem, value);
307             h->next = 0;
308             hp = &h->next;
309             p = p2 + 2;
310         }
311     }
312     return r;
313 }
314
315 struct http_request *http_parse_request(struct http_channel *c, struct http_buf **queue,
316         int len)
317 {
318     struct http_request *r = nmem_malloc(c->nmem, sizeof(*r));
319     char *p, *p2;
320     char tmp[4096];
321     char *buf = tmp;
322
323     if (len > 4096)
324         return 0;
325     if (http_buf_read(queue, buf, len) < len)
326         return 0;
327
328     r->search = "";
329     r->channel = c;
330     r->arguments = 0;
331     r->headers = 0;
332     // Parse first line
333     for (p = buf, p2 = r->method; *p && *p != ' ' && p - buf < 19; p++)
334         *(p2++) = *p;
335     if (*p != ' ')
336     {
337         yaz_log(YLOG_WARN, "Unexpected HTTP method in request");
338         return 0;
339     }
340     *p2 = '\0';
341
342     if (!(buf = strchr(buf, ' ')))
343     {
344         yaz_log(YLOG_WARN, "Syntax error in request (1)");
345         return 0;
346     }
347     buf++;
348     if (!(p = strchr(buf, ' ')))
349     {
350         yaz_log(YLOG_WARN, "Syntax error in request (2)");
351         return 0;
352     }
353     *(p++) = '\0';
354     if ((p2 = strchr(buf, '?'))) // Do we have arguments?
355         *(p2++) = '\0';
356     r->path = nmem_strdup(c->nmem, buf);
357     if (p2)
358     {
359         r->search = nmem_strdup(c->nmem, p2);
360         // Parse Arguments
361         while (*p2)
362         {
363             struct http_argument *a;
364             char *equal = strchr(p2, '=');
365             char *eoa = strchr(p2, '&');
366             if (!equal)
367             {
368                 yaz_log(YLOG_WARN, "Expected '=' in argument");
369                 return 0;
370             }
371             if (!eoa)
372                 eoa = equal + strlen(equal); // last argument
373             else
374                 *(eoa++) = '\0';
375             a = nmem_malloc(c->nmem, sizeof(struct http_argument));
376             *(equal++) = '\0';
377             a->name = nmem_strdup(c->nmem, p2);
378             urldecode(equal, equal);
379             a->value = nmem_strdup(c->nmem, equal);
380             a->next = r->arguments;
381             r->arguments = a;
382             p2 = eoa;
383         }
384     }
385     buf = p;
386
387     if (strncmp(buf, "HTTP/", 5))
388         strcpy(r->http_version, "1.0");
389     else
390     {
391         buf += 5;
392         if (!(p = strstr(buf, "\r\n")))
393             return 0;
394         *(p++) = '\0';
395         p++;
396         strcpy(r->http_version, buf);
397         buf = p;
398     }
399     strcpy(c->version, r->http_version);
400
401     r->headers = 0;
402     while (*buf)
403     {
404         if (!(p = strstr(buf, "\r\n")))
405             return 0;
406         if (p == buf)
407             break;
408         else
409         {
410             struct http_header *h = nmem_malloc(c->nmem, sizeof(*h));
411             if (!(p2 = strchr(buf, ':')))
412                 return 0;
413             *(p2++) = '\0';
414             h->name = nmem_strdup(c->nmem, buf);
415             while (isspace(*p2))
416                 p2++;
417             if (p2 >= p) // Empty header?
418             {
419                 buf = p + 2;
420                 continue;
421             }
422             *p = '\0';
423             h->value = nmem_strdup(c->nmem, p2);
424             h->next = r->headers;
425             r->headers = h;
426             buf = p + 2;
427         }
428     }
429
430     return r;
431 }
432
433 static struct http_buf *http_serialize_response(struct http_channel *c,
434         struct http_response *r)
435 {
436     struct http_header *h;
437
438     wrbuf_rewind(c->wrbuf);
439     wrbuf_printf(c->wrbuf, "HTTP/1.1 %s %s\r\n", r->code, r->msg);
440     for (h = r->headers; h; h = h->next)
441         wrbuf_printf(c->wrbuf, "%s: %s\r\n", h->name, h->value);
442     if (r->payload)
443     {
444         wrbuf_printf(c->wrbuf, "Content-length: %d\r\n", r->payload ?
445                 (int) strlen(r->payload) : 0);
446         wrbuf_printf(c->wrbuf, "Content-type: text/xml\r\n");
447     }
448     wrbuf_puts(c->wrbuf, "\r\n");
449
450     if (r->payload)
451         wrbuf_puts(c->wrbuf, r->payload);
452
453     return http_buf_bywrbuf(c->wrbuf);
454 }
455
456 // Serialize a HTTP request
457 static struct http_buf *http_serialize_request(struct http_request *r)
458 {
459     struct http_channel *c = r->channel;
460     struct http_header *h;
461     struct http_argument *a;
462
463     wrbuf_rewind(c->wrbuf);
464     wrbuf_printf(c->wrbuf, "%s %s", r->method, r->path);
465
466     if (r->arguments)
467     {
468         wrbuf_putc(c->wrbuf, '?');
469         for (a = r->arguments; a; a = a->next) {
470             if (a != r->arguments)
471                 wrbuf_putc(c->wrbuf, '&');
472             wrbuf_printf(c->wrbuf, "%s=%s", a->name, a->value);
473         }
474     }
475
476     wrbuf_printf(c->wrbuf, " HTTP/%s\r\n", r->http_version);
477
478     for (h = r->headers; h; h = h->next)
479         wrbuf_printf(c->wrbuf, "%s: %s\r\n", h->name, h->value);
480
481     wrbuf_puts(c->wrbuf, "\r\n");
482     
483     return http_buf_bywrbuf(c->wrbuf);
484 }
485
486
487 static int http_weshouldproxy(struct http_request *rq)
488 {
489     if (proxy_addr && !strstr(rq->path, "search.pz2"))
490         return 1;
491     return 0;
492 }
493
494 static int http_proxy(struct http_request *rq)
495 {
496     struct http_channel *c = rq->channel;
497     struct http_proxy *p = c->proxy;
498     struct http_header *hp;
499     struct http_buf *requestbuf;
500
501     if (!p) // This is a new connection. Create a proxy channel
502     {
503         int sock;
504         struct protoent *pe;
505         int one = 1;
506         int flags;
507
508         if (!(pe = getprotobyname("tcp"))) {
509             abort();
510         }
511         if ((sock = socket(PF_INET, SOCK_STREAM, pe->p_proto)) < 0)
512         {
513             yaz_log(YLOG_WARN|YLOG_ERRNO, "socket");
514             return -1;
515         }
516         if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)
517                         &one, sizeof(one)) < 0)
518             abort();
519         if ((flags = fcntl(sock, F_GETFL, 0)) < 0) 
520             yaz_log(YLOG_FATAL|YLOG_ERRNO, "fcntl");
521         if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0)
522             yaz_log(YLOG_FATAL|YLOG_ERRNO, "fcntl2");
523         if (connect(sock, (struct sockaddr *) proxy_addr, sizeof(*proxy_addr)) < 0)
524             if (errno != EINPROGRESS)
525             {
526                 yaz_log(YLOG_WARN|YLOG_ERRNO, "Proxy connect");
527                 return -1;
528             }
529
530         p = xmalloc(sizeof(struct http_proxy));
531         p->oqueue = 0;
532         p->channel = c;
533         p->first_response = 1;
534         c->proxy = p;
535         // We will add EVENT_OUTPUT below
536         p->iochan = iochan_create(sock, proxy_io, EVENT_INPUT);
537         iochan_setdata(p->iochan, p);
538         p->iochan->next = channel_list;
539         channel_list = p->iochan;
540     }
541
542     // Modify Host: header
543     for (hp = rq->headers; hp; hp = hp->next)
544         if (!strcmp(hp->name, "Host"))
545             break;
546     if (!hp)
547     {
548         yaz_log(YLOG_WARN, "Failed to find Host header in proxy");
549         return -1;
550     }
551     hp->value = nmem_strdup(c->nmem, proxy_url);
552     requestbuf = http_serialize_request(rq);
553     http_buf_enqueue(&p->oqueue, requestbuf);
554     iochan_setflag(p->iochan, EVENT_OUTPUT);
555     return 0;
556 }
557
558 void http_send_response(struct http_channel *ch)
559 {
560     struct http_response *rs = ch->response;
561     struct http_buf *hb;
562
563     assert(rs);
564     hb = http_serialize_response(ch, rs);
565     if (!hb)
566     {
567         yaz_log(YLOG_WARN, "Failed to serialize HTTP response");
568         http_destroy(ch->iochan);
569     }
570     else
571     {
572         http_buf_enqueue(&ch->oqueue, hb);
573         iochan_setflag(ch->iochan, EVENT_OUTPUT);
574         ch->state = Http_Idle;
575     }
576 }
577
578 static void http_io(IOCHAN i, int event)
579 {
580     struct http_channel *hc = iochan_getdata(i);
581
582     switch (event)
583     {
584         int res, reqlen;
585         struct http_buf *htbuf;
586
587         case EVENT_INPUT:
588             htbuf = http_buf_create();
589             res = read(iochan_getfd(i), htbuf->buf, HTTP_BUF_SIZE -1);
590             if (res == -1 && errno == EAGAIN)
591             {
592                 http_buf_destroy(htbuf);
593                 return;
594             }
595             if (res <= 0)
596             {
597                 http_buf_destroy(htbuf);
598                 http_destroy(i);
599                 return;
600             }
601             if (res > 0)
602             {
603                 htbuf->buf[res] = '\0';
604                 htbuf->len = res;
605                 http_buf_enqueue(&hc->iqueue, htbuf);
606             }
607
608             if (hc->state == Http_Busy)
609                 return;
610             if ((reqlen = request_check(hc->iqueue)) <= 2)
611                 return;
612
613             nmem_reset(hc->nmem);
614             if (!(hc->request = http_parse_request(hc, &hc->iqueue, reqlen)))
615             {
616                 yaz_log(YLOG_WARN, "Failed to parse request");
617                 http_destroy(i);
618                 return;
619             }
620             hc->response = 0;
621             yaz_log(YLOG_LOG, "Request: %s %s%s%s", hc->request->method,
622                     hc->request->path,
623                     *hc->request->search ? "?" : "",
624                     hc->request->search);
625             if (http_weshouldproxy(hc->request))
626                 http_proxy(hc->request);
627             else
628             {
629                 // Execute our business logic!
630                 hc->state = Http_Busy;
631                 http_command(hc);
632             }
633             if (hc->iqueue)
634             {
635                 yaz_log(YLOG_DEBUG, "We think we have more input to read. Forcing event");
636                 iochan_setevent(i, EVENT_INPUT);
637             }
638
639             break;
640
641         case EVENT_OUTPUT:
642             if (hc->oqueue)
643             {
644                 struct http_buf *wb = hc->oqueue;
645                 res = write(iochan_getfd(hc->iochan), wb->buf + wb->offset, wb->len);
646                 if (res <= 0)
647                 {
648                     yaz_log(YLOG_WARN|YLOG_ERRNO, "write");
649                     http_destroy(i);
650                     return;
651                 }
652                 if (res == wb->len)
653                 {
654                     hc->oqueue = hc->oqueue->next;
655                     http_buf_destroy(wb);
656                 }
657                 else
658                 {
659                     wb->len -= res;
660                     wb->offset += res;
661                 }
662                 if (!hc->oqueue) {
663                     if (!strcmp(hc->version, "1.0"))
664                     {
665                         http_destroy(i);
666                         return;
667                     }
668                     else
669                     {
670                         iochan_clearflag(i, EVENT_OUTPUT);
671                         if (hc->iqueue)
672                             iochan_setevent(hc->iochan, EVENT_INPUT);
673                     }
674                 }
675             }
676
677             if (!hc->oqueue && hc->proxy && !hc->proxy->iochan) 
678                 http_destroy(i); // Server closed; we're done
679             break;
680         default:
681             yaz_log(YLOG_WARN, "Unexpected event on connection");
682             http_destroy(i);
683     }
684 }
685
686 // If this hostname contains our proxy host as a prefix, replace with myurl
687 static char *sub_hostname(struct http_channel *c, char *buf)
688 {
689     char tmp[1024];
690     if (strlen(buf) > 1023)
691         return buf;
692     if (strncmp(buf, "http://", 7))
693         return buf;
694     if (!strncmp(buf + 7, proxy_url, strlen(proxy_url)))
695     {
696         strcpy(tmp, myurl);
697         strcat(tmp, buf + strlen(proxy_url) + 7);
698         return nmem_strdup(c->nmem, tmp);
699     }
700     return buf;
701 }
702
703 // Handles I/O on a client connection to a backend web server (proxy mode)
704 static void proxy_io(IOCHAN pi, int event)
705 {
706     struct http_proxy *pc = iochan_getdata(pi);
707     struct http_channel *hc = pc->channel;
708
709     switch (event)
710     {
711         int res;
712         struct http_buf *htbuf;
713
714         case EVENT_INPUT:
715             htbuf = http_buf_create();
716             res = read(iochan_getfd(pi), htbuf->buf, HTTP_BUF_SIZE -1);
717             if (res == 0 || (res < 0 && errno != EINPROGRESS))
718             {
719                 if (hc->oqueue)
720                 {
721                     yaz_log(YLOG_WARN, "Proxy read came up short");
722                     // Close channel and alert client HTTP channel that we're gone
723                     http_buf_destroy(htbuf);
724                     close(iochan_getfd(pi));
725                     iochan_destroy(pi);
726                     pc->iochan = 0;
727                 }
728                 else
729                 {
730                     http_destroy(hc->iochan);
731                     return;
732                 }
733             }
734             else
735             {
736                 htbuf->buf[res] = '\0';
737                 htbuf->len = res;
738                 int offset = 0;
739                 if (pc->first_response) // Check if this is a redirect
740                 {
741                     int len;
742                     if ((len = package_check(htbuf->buf)))
743                     {
744                         struct http_response *res = http_parse_response_buf(hc, htbuf->buf, len);
745                         if (res)
746                         {
747                             struct http_header *h;
748                             for (h = res->headers; h; h = h->next)
749                                 if (!strcmp(h->name, "Location"))
750                                 {
751                                     // We found a location header. Rewrite it.
752                                     struct http_buf *buf;
753                                     h->value = sub_hostname(hc, h->value);
754                                     buf = http_serialize_response(hc, res);
755                                     http_buf_enqueue(&hc->oqueue, buf);
756                                     offset = len;
757                                     break;
758                                 }
759                         }
760                     }
761                     pc->first_response = 0;
762                 }
763                 // Write any remaining payload
764                 if (htbuf->len - offset > 0)
765                 {
766                     if (offset > 0)
767                     {
768                         memmove(htbuf->buf, htbuf->buf + offset, htbuf->len - offset);
769                         htbuf->len -= offset;
770                     }
771                     http_buf_enqueue(&hc->oqueue, htbuf + offset);
772                 }
773             }
774             iochan_setflag(hc->iochan, EVENT_OUTPUT);
775             break;
776         case EVENT_OUTPUT:
777             if (!(htbuf = pc->oqueue))
778             {
779                 iochan_clearflag(pi, EVENT_OUTPUT);
780                 return;
781             }
782             res = write(iochan_getfd(pi), htbuf->buf + htbuf->offset, htbuf->len);
783             if (res <= 0)
784             {
785                 yaz_log(YLOG_WARN|YLOG_ERRNO, "write");
786                 http_destroy(hc->iochan);
787                 return;
788             }
789             if (res == htbuf->len)
790             {
791                 struct http_buf *np = htbuf->next;
792                 http_buf_destroy(htbuf);
793                 pc->oqueue = np;
794             }
795             else
796             {
797                 htbuf->len -= res;
798                 htbuf->offset += res;
799             }
800
801             if (!pc->oqueue) {
802                 iochan_setflags(pi, EVENT_INPUT); // Turns off output flag
803             }
804             break;
805         default:
806             yaz_log(YLOG_WARN, "Unexpected event on connection");
807             http_destroy(hc->iochan);
808     }
809 }
810
811 // Cleanup channel
812 static void http_destroy(IOCHAN i)
813 {
814     struct http_channel *s = iochan_getdata(i);
815
816     if (s->proxy)
817     {
818         if (s->proxy->iochan)
819         {
820             close(iochan_getfd(s->proxy->iochan));
821             iochan_destroy(s->proxy->iochan);
822         }
823         http_buf_destroy_queue(s->proxy->oqueue);
824         xfree(s->proxy);
825     }
826     s->next = http_channel_freelist;
827     http_channel_freelist = s;
828     close(iochan_getfd(i));
829     iochan_destroy(i);
830 }
831
832 static struct http_channel *http_create(void)
833 {
834     struct http_channel *r = http_channel_freelist;
835
836     if (r)
837     {
838         http_channel_freelist = r->next;
839         nmem_reset(r->nmem);
840         wrbuf_rewind(r->wrbuf);
841     }
842     else
843     {
844         r = xmalloc(sizeof(struct http_channel));
845         r->nmem = nmem_create();
846         r->wrbuf = wrbuf_alloc();
847     }
848     r->proxy = 0;
849     r->iochan = 0;
850     r->iqueue = r->oqueue = 0;
851     r->state = Http_Idle;
852     r->request = 0;
853     r->response = 0;
854     return r;
855 }
856
857
858 /* Accept a new command connection */
859 static void http_accept(IOCHAN i, int event)
860 {
861     struct sockaddr_in addr;
862     int fd = iochan_getfd(i);
863     socklen_t len;
864     int s;
865     IOCHAN c;
866     int flags;
867     struct http_channel *ch;
868
869     len = sizeof addr;
870     if ((s = accept(fd, (struct sockaddr *) &addr, &len)) < 0)
871     {
872         yaz_log(YLOG_WARN|YLOG_ERRNO, "accept");
873         return;
874     }
875     if ((flags = fcntl(s, F_GETFL, 0)) < 0) 
876         yaz_log(YLOG_FATAL|YLOG_ERRNO, "fcntl");
877     if (fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0)
878         yaz_log(YLOG_FATAL|YLOG_ERRNO, "fcntl2");
879
880     yaz_log(YLOG_DEBUG, "New command connection");
881     c = iochan_create(s, http_io, EVENT_INPUT | EVENT_EXCEPT);
882
883     ch = http_create();
884     ch->iochan = c;
885     iochan_setdata(c, ch);
886
887     c->next = channel_list;
888     channel_list = c;
889 }
890
891 /* Create a http-channel listener, syntax [host:]port */
892 void http_init(const char *addr)
893 {
894     IOCHAN c;
895     int l;
896     struct protoent *p;
897     struct sockaddr_in myaddr;
898     int one = 1;
899     const char *pp;
900     int port;
901
902     yaz_log(YLOG_LOG, "HTTP listener is %s", addr);
903
904     memset(&myaddr, 0, sizeof myaddr);
905     myaddr.sin_family = AF_INET;
906     pp = strchr(addr, ':');
907     if (pp)
908     {
909         int len = pp - addr;
910         char hostname[128];
911         struct hostent *he;
912
913         strncpy(hostname, addr, len);
914         hostname[len] = '\0';
915         if (!(he = gethostbyname(hostname)))
916         {
917             yaz_log(YLOG_FATAL, "Unable to resolve '%s'", hostname);
918             exit(1);
919         }
920         memcpy(&myaddr.sin_addr.s_addr, he->h_addr_list[0], he->h_length);
921         port = atoi(pp + 1);
922     }
923     else
924     {
925         port = atoi(addr);
926         myaddr.sin_addr.s_addr = INADDR_ANY;
927     }
928     myaddr.sin_port = htons(port);
929
930     if (!(p = getprotobyname("tcp"))) {
931         abort();
932     }
933     if ((l = socket(PF_INET, SOCK_STREAM, p->p_proto)) < 0)
934         yaz_log(YLOG_FATAL|YLOG_ERRNO, "socket");
935     if (setsockopt(l, SOL_SOCKET, SO_REUSEADDR, (char*)
936                     &one, sizeof(one)) < 0)
937         abort();
938
939     if (bind(l, (struct sockaddr *) &myaddr, sizeof myaddr) < 0) 
940         yaz_log(YLOG_FATAL|YLOG_ERRNO, "bind");
941     if (listen(l, SOMAXCONN) < 0) 
942         yaz_log(YLOG_FATAL|YLOG_ERRNO, "listen");
943
944     c = iochan_create(l, http_accept, EVENT_INPUT | EVENT_EXCEPT);
945     c->next = channel_list;
946     channel_list = c;
947 }
948
949 void http_set_proxyaddr(char *host, char *base_url)
950 {
951     char *p;
952     int port;
953     struct hostent *he;
954
955     strcpy(myurl, base_url);
956     strcpy(proxy_url, host);
957     p = strchr(host, ':');
958     yaz_log(YLOG_DEBUG, "Proxying for %s", host);
959     if (p) {
960         port = atoi(p + 1);
961         *p = '\0';
962     }
963     else
964         port = 80;
965     if (!(he = gethostbyname(host))) 
966     {
967         fprintf(stderr, "Failed to lookup '%s'\n", host);
968         exit(1);
969     }
970     proxy_addr = xmalloc(sizeof(struct sockaddr_in));
971     proxy_addr->sin_family = he->h_addrtype;
972     memcpy(&proxy_addr->sin_addr.s_addr, he->h_addr_list[0], he->h_length);
973     proxy_addr->sin_port = htons(port);
974 }
975
976 /*
977  * Local variables:
978  * c-basic-offset: 4
979  * indent-tabs-mode: nil
980  * End:
981  * vim: shiftwidth=4 tabstop=8 expandtab
982  */