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