htparser: Add an initial OpenSSL backend.
[ashd.git] / src / ssl-openssl.c
CommitLineData
3c5954e9
FT
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
39struct sslport {
40 int fd, sport;
41 SSL_CTX *ctx;
42};
43
44struct sslconn {
45 struct sslport *port;
46 int fd;
47 SSL *ssl;
48 struct sockaddr *name;
49 socklen_t namelen;
50};
51
52static 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
67static 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
99static 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
129static int sslclose(void *cookie)
130{
131 return(0);
132}
133
134static struct bufioops iofuns = {
135 .read = sslread,
136 .write = sslwrite,
137 .close = sslclose,
138};
139
140static 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
159static 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 }
190out:
191 SSL_free(ssl);
192 close(fd);
193}
194
195static 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
220out:
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
229void 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}