htparser: Use bufio instead of stdio for greater responsiveness.
[ashd.git] / src / plaintcp.c
1 /*
2     ashd - A Sane HTTP Daemon
3     Copyright (C) 2008  Fredrik Tolf <fredrik@dolda2000.com>
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 3 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, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <stdio.h>
22 #include <sys/socket.h>
23 #include <netinet/in.h>
24 #include <arpa/inet.h>
25 #include <fcntl.h>
26 #include <errno.h>
27 #include <string.h>
28
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
32 #include <utils.h>
33 #include <req.h>
34 #include <mt.h>
35 #include <mtio.h>
36 #include <log.h>
37
38 #include "htparser.h"
39
40 struct tcpport {
41     int fd;
42     int sport;
43 };
44
45 struct tcpconn {
46     struct sockaddr_storage name;
47     struct tcpport *port;
48     int fd;
49 };
50
51 int listensock4(int port)
52 {
53     struct sockaddr_in name;
54     int fd;
55     int valbuf;
56     
57     memset(&name, 0, sizeof(name));
58     name.sin_family = AF_INET;
59     name.sin_port = htons(port);
60     if((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
61         return(-1);
62     valbuf = 1;
63     setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &valbuf, sizeof(valbuf));
64     if(bind(fd, (struct sockaddr *)&name, sizeof(name))) {
65         close(fd);
66         return(-1);
67     }
68     if(listen(fd, 128) < 0) {
69         close(fd);
70         return(-1);
71     }
72     fcntl(fd, F_SETFD, FD_CLOEXEC);
73     return(fd);
74 }
75
76 int listensock6(int port)
77 {
78     struct sockaddr_in6 name;
79     int fd;
80     int valbuf;
81     
82     memset(&name, 0, sizeof(name));
83     name.sin6_family = AF_INET6;
84     name.sin6_port = htons(port);
85     if((fd = socket(PF_INET6, SOCK_STREAM, 0)) < 0)
86         return(-1);
87     valbuf = 1;
88     setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &valbuf, sizeof(valbuf));
89     if(bind(fd, (struct sockaddr *)&name, sizeof(name))) {
90         close(fd);
91         return(-1);
92     }
93     if(listen(fd, 128) < 0) {
94         close(fd);
95         return(-1);
96     }
97     fcntl(fd, F_SETFD, FD_CLOEXEC);
98     return(fd);
99 }
100
101 char *formathaddress(struct sockaddr *name, socklen_t namelen)
102 {
103     static char buf[128];
104     struct sockaddr_in *v4;
105     struct sockaddr_in6 *v6;
106
107     switch(name->sa_family) {
108     case AF_INET:
109         v4 = (struct sockaddr_in *)name;
110         if(!inet_ntop(AF_INET, &v4->sin_addr, buf, sizeof(buf)))
111             return(NULL);
112         return(buf);
113     case AF_INET6:
114         v6 = (struct sockaddr_in6 *)name;
115         if(IN6_IS_ADDR_V4MAPPED(&v6->sin6_addr)) {
116             if(!inet_ntop(AF_INET, ((char *)&v6->sin6_addr) + 12, buf, sizeof(buf)))
117                 return(NULL);
118         } else {
119             if(!inet_ntop(AF_INET6, &v6->sin6_addr, buf, sizeof(buf)))
120                 return(NULL);
121         }
122         return(buf);
123     default:
124         errno = EPFNOSUPPORT;
125         return(NULL);
126     }
127 }
128
129 static int initreq(struct conn *conn, struct hthead *req)
130 {
131     struct tcpconn *tcp = conn->pdata;
132     struct sockaddr_storage sa;
133     socklen_t salen;
134     
135     headappheader(req, "X-Ash-Address", formathaddress((struct sockaddr *)&tcp->name, sizeof(sa)));
136     if(tcp->name.ss_family == AF_INET)
137         headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in *)&tcp->name)->sin_port)));
138     else if(tcp->name.ss_family == AF_INET6)
139         headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in6 *)&tcp->name)->sin6_port)));
140     salen = sizeof(sa);
141     if(!getsockname(tcp->fd, (struct sockaddr *)&sa, &salen))
142         headappheader(req, "X-Ash-Server-Address", formathaddress((struct sockaddr *)&sa, sizeof(sa)));
143     headappheader(req, "X-Ash-Server-Port", sprintf3("%i", tcp->port->sport));
144     headappheader(req, "X-Ash-Protocol", "http");
145     return(0);
146 }
147
148 void servetcp(struct muth *muth, va_list args)
149 {
150     vavar(int, fd);
151     vavar(struct sockaddr_storage, name);
152     vavar(struct tcpport *, stcp);
153     struct bufio *in;
154     struct conn conn;
155     struct tcpconn tcp;
156     
157     memset(&conn, 0, sizeof(conn));
158     memset(&tcp, 0, sizeof(tcp));
159     in = mtbioopen(fd, 1, 60, "r+", NULL);
160     conn.pdata = &tcp;
161     conn.initreq = initreq;
162     tcp.fd = fd;
163     tcp.name = name;
164     tcp.port = stcp;
165     serve(in, &conn);
166 }
167
168 static void listenloop(struct muth *muth, va_list args)
169 {
170     vavar(struct tcpport *, tcp);
171     int i, ns, n;
172     struct sockaddr_storage name;
173     socklen_t namelen;
174     
175     fcntl(tcp->fd, F_SETFL, fcntl(tcp->fd, F_GETFL) | O_NONBLOCK);
176     while(1) {
177         namelen = sizeof(name);
178         if(block(tcp->fd, EV_READ, 0) == 0)
179             goto out;
180         n = 0;
181         while(1) {
182             ns = accept(tcp->fd, (struct sockaddr *)&name, &namelen);
183             if(ns < 0) {
184                 if(errno == EAGAIN)
185                     break;
186                 if(errno == ECONNABORTED)
187                     continue;
188                 flog(LOG_ERR, "accept: %s", strerror(errno));
189                 goto out;
190             }
191             mustart(servetcp, ns, name, tcp);
192             if(++n >= 100)
193                 break;
194         }
195     }
196     
197 out:
198     close(tcp->fd);
199     free(tcp);
200     for(i = 0; i < listeners.d; i++) {
201         if(listeners.b[i] == muth)
202             bufdel(listeners, i);
203     }
204 }
205
206 void handleplain(int argc, char **argp, char **argv)
207 {
208     int port, fd;
209     int i;
210     struct tcpport *tcp;
211     
212     port = 80;
213     for(i = 0; i < argc; i++) {
214         if(!strcmp(argp[i], "help")) {
215             printf("plain handler parameters:\n");
216             printf("\tport=TCP-PORT   [80]\n");
217             printf("\t\tThe TCP port to listen on.\n");
218             exit(0);
219         } else if(!strcmp(argp[i], "port")) {
220             port = atoi(argv[i]);
221         } else {
222             flog(LOG_ERR, "unknown parameter `%s' to plain handler", argp[i]);
223             exit(1);
224         }
225     }
226     if((fd = listensock6(port)) < 0) {
227         flog(LOG_ERR, "could not listen on IPv6 (port %i): %s", port, strerror(errno));
228         exit(1);
229     }
230     omalloc(tcp);
231     tcp->fd = fd;
232     tcp->sport = port;
233     bufadd(listeners, mustart(listenloop, tcp));
234     if((fd = listensock4(port)) < 0) {
235         if(errno != EADDRINUSE) {
236             flog(LOG_ERR, "could not listen on IPv4 (port %i): %s", port, strerror(errno));
237             exit(1);
238         }
239     } else {
240         omalloc(tcp);
241         tcp->fd = fd;
242         tcp->sport = port;
243         bufadd(listeners, mustart(listenloop, tcp));
244     }
245 }