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