chiark / gitweb /
f7f12e1a8e0a37d04318049da2213c56afad8332
[elogind.git] / src / journal / microhttpd-util.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Lennart Poettering
7   Copyright 2012 Zbigniew JÄ™drzejewski-Szmek
8
9   systemd is free software; you can redistribute it and/or modify it
10   under the terms of the GNU Lesser General Public License as published by
11   the Free Software Foundation; either version 2.1 of the License, or
12   (at your option) any later version.
13
14   systemd is distributed in the hope that it will be useful, but
15   WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public License
20   along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <stddef.h>
24 #include <stdio.h>
25 #include <string.h>
26
27 #include "microhttpd-util.h"
28 #include "log.h"
29 #include "macro.h"
30 #include "util.h"
31
32 #ifdef HAVE_GNUTLS
33 #include <gnutls/gnutls.h>
34 #include <gnutls/x509.h>
35 #endif
36
37 void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
38         _cleanup_free_ char *f = NULL;
39
40         if (asprintf(&f, "microhttpd: %s", fmt) <= 0) {
41                 log_oom();
42                 return;
43         }
44
45         DISABLE_WARNING_FORMAT_NONLITERAL;
46         log_metav(LOG_INFO, NULL, 0, NULL, f, ap);
47         REENABLE_WARNING;
48 }
49
50
51 int respond_oom_internal(struct MHD_Connection *connection) {
52         const char *m = "Out of memory.\n";
53
54         struct MHD_Response *response;
55         int ret;
56
57         assert(connection);
58
59         response = MHD_create_response_from_buffer(strlen(m), (char*) m, MHD_RESPMEM_PERSISTENT);
60         if (!response)
61                 return MHD_NO;
62
63         MHD_add_response_header(response, "Content-Type", "text/plain");
64         ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
65         MHD_destroy_response(response);
66
67         return ret;
68 }
69
70 _printf_(3,4)
71 int respond_error(struct MHD_Connection *connection,
72                   unsigned code,
73                   const char *format, ...) {
74
75         struct MHD_Response *response;
76         char *m;
77         int r;
78         va_list ap;
79
80         assert(connection);
81         assert(format);
82
83         va_start(ap, format);
84         r = vasprintf(&m, format, ap);
85         va_end(ap);
86
87         if (r < 0)
88                 return respond_oom(connection);
89
90         response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
91         if (!response) {
92                 free(m);
93                 return respond_oom(connection);
94         }
95
96         log_debug("Queing response %u: %s", code, m);
97         MHD_add_response_header(response, "Content-Type", "text/plain");
98         r = MHD_queue_response(connection, code, response);
99         MHD_destroy_response(response);
100
101         return r;
102 }
103
104 #ifdef HAVE_GNUTLS
105
106 static int log_level_map[] = {
107         LOG_DEBUG,
108         LOG_WARNING, /* gnutls session audit */
109         LOG_DEBUG,   /* gnutls debug log */
110         LOG_WARNING, /* gnutls assert log */
111         LOG_INFO,    /* gnutls handshake log */
112         LOG_DEBUG,   /* gnutls record log */
113         LOG_DEBUG,   /* gnutls dtls log */
114         LOG_DEBUG,
115         LOG_DEBUG,
116         LOG_DEBUG,
117         LOG_DEBUG,   /* gnutls hard log */
118         LOG_DEBUG,   /* gnutls read log */
119         LOG_DEBUG,   /* gnutls write log */
120         LOG_DEBUG,   /* gnutls io log */
121         LOG_DEBUG,   /* gnutls buffers log */
122 };
123
124 void log_func_gnutls(int level, const char *message) {
125         int ourlevel;
126
127         assert_se(message);
128
129         if (0 <= level && level < (int) ELEMENTSOF(log_level_map))
130                 ourlevel = log_level_map[level];
131         else
132                 level = LOG_DEBUG;
133
134         log_meta(ourlevel, NULL, 0, NULL, "gnutls: %s", message);
135 }
136
137 static int verify_cert_authorized(gnutls_session_t session) {
138         unsigned status;
139         gnutls_certificate_type_t type;
140         gnutls_datum_t out;
141         int r;
142
143         r = gnutls_certificate_verify_peers2(session, &status);
144         if (r < 0) {
145                 log_error("gnutls_certificate_verify_peers2 failed: %s", strerror(-r));
146                 return r;
147         }
148
149         type = gnutls_certificate_type_get(session);
150         r = gnutls_certificate_verification_status_print(status, type, &out, 0);
151         if (r < 0) {
152                 log_error("gnutls_certificate_verification_status_print failed: %s", strerror(-r));
153                 return r;
154         }
155
156         log_info("Certificate status: %s", out.data);
157
158         return status == 0 ? 0 : -EPERM;
159 }
160
161 static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) {
162         const gnutls_datum_t *pcert;
163         unsigned listsize;
164         gnutls_x509_crt_t cert;
165         int r;
166
167         assert(session);
168         assert(client_cert);
169
170         pcert = gnutls_certificate_get_peers(session, &listsize);
171         if (!pcert || !listsize) {
172                 log_error("Failed to retrieve certificate chain");
173                 return -EINVAL;
174         }
175
176         r = gnutls_x509_crt_init(&cert);
177         if (r < 0) {
178                 log_error("Failed to initialize client certificate");
179                 return r;
180         }
181
182         /* Note that by passing values between 0 and listsize here, you
183            can get access to the CA's certs */
184         r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER);
185         if (r < 0) {
186                 log_error("Failed to import client certificate");
187                 gnutls_x509_crt_deinit(cert);
188                 return r;
189         }
190
191         *client_cert = cert;
192         return 0;
193 }
194
195 static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) {
196         size_t len = 0;
197         int r;
198
199         assert(buf);
200         assert(*buf == NULL);
201
202         r = gnutls_x509_crt_get_dn(client_cert, NULL, &len);
203         if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) {
204                 log_error("gnutls_x509_crt_get_dn failed");
205                 return r;
206         }
207
208         *buf = malloc(len);
209         if (!*buf)
210                 return log_oom();
211
212         gnutls_x509_crt_get_dn(client_cert, *buf, &len);
213         return 0;
214 }
215
216 int check_permissions(struct MHD_Connection *connection, int *code) {
217         const union MHD_ConnectionInfo *ci;
218         gnutls_session_t session;
219         gnutls_x509_crt_t client_cert;
220         char _cleanup_free_ *buf = NULL;
221         int r;
222
223         assert(connection);
224         assert(code);
225
226         *code = 0;
227
228         ci = MHD_get_connection_info(connection,
229                                      MHD_CONNECTION_INFO_GNUTLS_SESSION);
230         if (!ci) {
231                 log_error("MHD_get_connection_info failed: session is unencrypted");
232                 *code = respond_error(connection, MHD_HTTP_FORBIDDEN,
233                                       "Encrypted connection is required");
234                 return -EPERM;
235         }
236         session = ci->tls_session;
237         assert(session);
238
239         r = get_client_cert(session, &client_cert);
240         if (r < 0) {
241                 *code = respond_error(connection, MHD_HTTP_UNAUTHORIZED,
242                                       "Authorization through certificate is required");
243                 return -EPERM;
244         }
245
246         r = get_auth_dn(client_cert, &buf);
247         if (r < 0) {
248                 *code = respond_error(connection, MHD_HTTP_UNAUTHORIZED,
249                                       "Failed to determine distinguished name from certificate");
250                 return -EPERM;
251         }
252
253         log_info("Connection from DN %s", buf);
254
255         r = verify_cert_authorized(session);
256         if (r < 0) {
257                 log_warning("Client is not authorized");
258                 *code = respond_error(connection, MHD_HTTP_UNAUTHORIZED,
259                                       "Client certificate not signed by recognized authority");
260         }
261         return r;
262 }
263
264 #else
265 int check_permissions(struct MHD_Connection *connection, int *code) {
266         return -EPERM;
267 }
268 #endif