doc: Fixed typo.
[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     FILE *in;
154     struct conn conn;
155     struct tcpconn tcp;
156     
157     memset(&conn, 0, sizeof(conn));
158     memset(&tcp, 0, sizeof(tcp));
159     in = mtstdopen(fd, 1, 60, "r+");
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 ns;
172     struct sockaddr_storage name;
173     socklen_t namelen;
174     
175     while(1) {
176         namelen = sizeof(name);
177         block(tcp->fd, EV_READ, 0);
178         ns = accept(tcp->fd, (struct sockaddr *)&name, &namelen);
179         if(ns < 0) {
180             flog(LOG_ERR, "accept: %s", strerror(errno));
181             goto out;
182         }
183         mustart(servetcp, ns, name, tcp);
184     }
185     
186 out:
187     close(tcp->fd);
188     free(tcp);
189 }
190
191 void handleplain(int argc, char **argp, char **argv)
192 {
193     int port, fd;
194     int i;
195     struct tcpport *tcp;
196     
197     port = 80;
198     for(i = 0; i < argc; i++) {
199         if(!strcmp(argp[i], "help")) {
200             printf("plain handler parameters:\n");
201             printf("\tport=TCP-PORT   [80]\n");
202             printf("\t\tThe TCP port to listen on.\n");
203             exit(0);
204         } else if(!strcmp(argp[i], "port")) {
205             port = atoi(argv[i]);
206         } else {
207             flog(LOG_ERR, "unknown parameter `%s' to plain handler", argp[i]);
208             exit(1);
209         }
210     }
211     if((fd = listensock6(port)) < 0) {
212         flog(LOG_ERR, "could not listen on IPv6 (port %i): %s", port, strerror(errno));
213         exit(1);
214     }
215     omalloc(tcp);
216     tcp->fd = fd;
217     tcp->sport = port;
218     mustart(listenloop, tcp);
219     if((fd = listensock4(port)) < 0) {
220         if(errno != EADDRINUSE) {
221             flog(LOG_ERR, "could not listen on IPv4 (port %i): %s", port, strerror(errno));
222             exit(1);
223         }
224     } else {
225         omalloc(tcp);
226         tcp->fd = fd;
227         tcp->sport = port;
228         mustart(listenloop, tcp);
229     }
230 }