htparser: Remember to properly make ssl-openssl.c conditional.
[ashd.git] / src / ssl-openssl.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 <fcntl.h>
20 #include <unistd.h>
21 #include <sys/socket.h>
22 #include <arpa/inet.h>
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 #include <utils.h>
28 #include <mt.h>
29 #include <mtio.h>
30 #include <req.h>
31 #include <log.h>
32 #include <bufio.h>
33
34 #include "htparser.h"
35
36 #ifdef HAVE_OPENSSL
37
38 #include <openssl/ssl.h>
39 #include <openssl/err.h>
40
41 struct sslport {
42     int fd, sport;
43     SSL_CTX *ctx;
44 };
45
46 struct sslconn {
47     struct sslport *port;
48     int fd;
49     SSL *ssl;
50     struct sockaddr *name;
51     socklen_t namelen;
52 };
53
54 static int tlsblock(int fd, int err, int to)
55 {
56     if(err == SSL_ERROR_WANT_READ) {
57         if(block(fd, EV_READ, to) <= 0)
58             return(1);
59         return(0);
60     } else if(err == SSL_ERROR_WANT_WRITE) {
61         if(block(fd, EV_WRITE, to) <= 0)
62             return(1);
63         return(0);
64     } else {
65         return(1);
66     }
67 }
68
69 static ssize_t sslread(void *cookie, void *buf, size_t len)
70 {
71     struct sslconn *sdat = cookie;
72     int ret, err, nb;
73     size_t off;
74     
75     off = 0;
76     while(off < len) {
77         nb = ((len - off) > INT_MAX) ? INT_MAX : (len - off);
78         if((ret = SSL_read(sdat->ssl, buf, nb)) <= 0) {
79             if(off > 0)
80                 return(off);
81             err = SSL_get_error(sdat->ssl, ret);
82             if(err == SSL_ERROR_ZERO_RETURN) {
83                 return(0);
84             } else if((err == SSL_ERROR_WANT_READ) || (err == SSL_ERROR_WANT_WRITE)) {
85                 if(tlsblock(sdat->fd, err, 60)) {
86                     errno = ETIMEDOUT;
87                     return(-1);
88                 }
89             } else {
90                 if(err != SSL_ERROR_SYSCALL)
91                     errno = EPROTO;
92                 return(-1);
93             }
94         } else {
95             off += ret;
96         }
97     }
98     return(off);
99 }
100
101 static ssize_t sslwrite(void *cookie, const void *buf, size_t len)
102 {
103     struct sslconn *sdat = cookie;
104     int ret, err, nb;
105     size_t off;
106     
107     off = 0;
108     while(off < len) {
109         nb = ((len - off) > INT_MAX) ? INT_MAX : (len - off);
110         if((ret = SSL_write(sdat->ssl, buf, nb)) <= 0) {
111             if(off > 0)
112                 return(off);
113             err = SSL_get_error(sdat->ssl, ret);
114             if((err == SSL_ERROR_WANT_READ) || (err == SSL_ERROR_WANT_WRITE)) {
115                 if(tlsblock(sdat->fd, err, 60)) {
116                     errno = ETIMEDOUT;
117                     return(-1);
118                 }
119             } else {
120                 if(err != SSL_ERROR_SYSCALL)
121                     errno = EIO;
122                 return(-1);
123             }
124         } else {
125             off += ret;
126         }
127     }
128     return(off);
129 }
130
131 static int sslclose(void *cookie)
132 {
133     return(0);
134 }
135
136 static struct bufioops iofuns = {
137     .read = sslread,
138     .write = sslwrite,
139     .close = sslclose,
140 };
141
142 static int initreq(struct conn *conn, struct hthead *req)
143 {
144     struct sslconn *sdat = conn->pdata;
145     struct sockaddr_storage sa;
146     socklen_t salen;
147     
148     headappheader(req, "X-Ash-Address", formathaddress(sdat->name, sdat->namelen));
149     if(sdat->name->sa_family == AF_INET)
150         headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in *)sdat->name)->sin_port)));
151     else if(sdat->name->sa_family == AF_INET6)
152         headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in6 *)sdat->name)->sin6_port)));
153     salen = sizeof(sa);
154     if(!getsockname(sdat->fd, (struct sockaddr *)&sa, &salen))
155         headappheader(req, "X-Ash-Server-Address", formathaddress((struct sockaddr *)&sa, salen));
156     headappheader(req, "X-Ash-Server-Port", sprintf3("%i", sdat->port->sport));
157     headappheader(req, "X-Ash-Protocol", "https");
158     return(0);
159 }
160
161 static void servessl(struct muth *muth, va_list args)
162 {
163     vavar(int, fd);
164     vavar(struct sockaddr_storage, name);
165     vavar(struct sslport *, pd);
166     int ret;
167     SSL *ssl;
168     struct conn conn;
169     struct sslconn sdat;
170     
171     fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
172     ssl = SSL_new(pd->ctx);
173     SSL_set_fd(ssl, fd);
174     while((ret = SSL_accept(ssl)) <= 0) {
175         if(tlsblock(fd, SSL_get_error(ssl, ret), 60))
176             goto out;
177     }
178     memset(&conn, 0, sizeof(conn));
179     memset(&sdat, 0, sizeof(sdat));
180     conn.pdata = &sdat;
181     conn.initreq = initreq;
182     sdat.port = pd;
183     sdat.fd = fd;
184     sdat.ssl = ssl;
185     sdat.name = (struct sockaddr *)&name;
186     sdat.namelen = sizeof(name);
187     serve(bioopen(&sdat, &iofuns), fd, &conn);
188     while((ret = SSL_shutdown(ssl)) < 0) {
189         if(tlsblock(fd, SSL_get_error(ssl, ret), 60))
190             goto out;
191     }
192 out:
193     SSL_free(ssl);
194     close(fd);
195 }
196
197 static void listenloop(struct muth *muth, va_list args)
198 {
199     vavar(struct sslport *, pd);
200     int i, ns, n;
201     struct sockaddr_storage name;
202     socklen_t namelen;
203     
204     fcntl(pd->fd, F_SETFL, fcntl(pd->fd, F_GETFL) | O_NONBLOCK);
205     while(1) {
206         namelen = sizeof(name);
207         if(block(pd->fd, EV_READ, 0) == 0)
208             goto out;
209         for(n = 0; n < 100; n++) {
210             if((ns = accept(pd->fd, (struct sockaddr *)&name, &namelen)) < 0) {
211                 if(errno == EAGAIN)
212                     break;
213                 if(errno == ECONNABORTED)
214                     continue;
215                 flog(LOG_ERR, "accept: %s", strerror(errno));
216                 goto out;
217             }
218             mustart(servessl, ns, name, pd);
219         }
220     }
221     
222 out:
223     close(pd->fd);
224     free(pd);
225     for(i = 0; i < listeners.d; i++) {
226         if(listeners.b[i] == muth)
227             bufdel(listeners, i);
228     }
229 }
230
231 void handleossl(int argc, char **argp, char **argv)
232 {
233     int i, port, fd;
234     SSL_CTX *ctx;
235     char *crtfile, *keyfile;
236     struct sslport *pd;
237     
238     ctx = SSL_CTX_new(TLS_server_method());
239     if(!ctx) {
240         flog(LOG_ERR, "ssl: could not create context: %s", ERR_error_string(ERR_get_error(), NULL));
241         exit(1);
242     }
243     port = 443;
244     for(i = 0; i < argc; i++) {
245         if(!strcmp(argp[i], "help")) {
246             printf("ssl handler parameters:\n");
247             printf("\tcert=CERT-FILE  [mandatory]\n");
248             printf("\t\tThe name of the file to read the certificate from.\n");
249             printf("\tkey=KEY-FILE    [same as CERT-FILE]\n");
250             printf("\t\tThe name of the file to read the private key from.\n");
251             printf("\tport=PORT       [443]\n");
252             printf("\t\tThe TCP port to listen on.\n");
253             exit(0);
254         } else if(!strcmp(argp[i], "cert")) {
255             crtfile = argv[i];
256         } else if(!strcmp(argp[i], "key")) {
257             keyfile = argv[i];
258         } else if(!strcmp(argp[i], "port")) {
259             port = atoi(argv[i]);
260         } else {
261             flog(LOG_ERR, "unknown parameter `%s' to ssl handler", argp[i]);
262             exit(1);
263         }
264     }
265     if(crtfile == NULL) {
266         flog(LOG_ERR, "ssl: needs certificate file at the very least");
267         exit(1);
268     }
269     if(keyfile == NULL)
270         keyfile = crtfile;
271     if(SSL_CTX_use_certificate_file(ctx, crtfile, SSL_FILETYPE_PEM) <= 0) {
272         flog(LOG_ERR, "ssl: could not load certificate: %s", ERR_error_string(ERR_get_error(), NULL));
273         exit(1);
274     }
275     if(SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM) <= 0) {
276         flog(LOG_ERR, "ssl: could not load certificate: %s", ERR_error_string(ERR_get_error(), NULL));
277         exit(1);
278     }
279     if(!SSL_CTX_check_private_key(ctx)) {
280         flog(LOG_ERR, "ssl: key and certificate do not match");
281         exit(1);
282     }
283     if((fd = listensock6(port)) < 0) {
284         flog(LOG_ERR, "could not listen on IPv65 port (port %i): %s", port, strerror(errno));
285         exit(1);
286     }
287     omalloc(pd);
288     pd->fd = fd;
289     pd->sport = port;
290     pd->ctx = ctx;
291     bufadd(listeners, mustart(listenloop, pd));
292     if((fd = listensock4(port)) < 0) {
293         if(errno != EADDRINUSE) {
294             flog(LOG_ERR, "could not listen on IPv4 port (port %i): Is", port, strerror(errno));
295             exit(1);
296         }
297     } else {
298         omalloc(pd);
299         pd->fd = fd;
300         pd->sport = port;
301         pd->ctx = ctx;
302         bufadd(listeners, mustart(listenloop, pd));
303     }
304 }
305
306 #endif