chiark / gitweb /
8a11fba04441953d7242c87e1e480920a295cf3e
[elogind.git] / src / journal-remote / 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 #include "strv.h"
32
33 #ifdef HAVE_GNUTLS
34 #include <gnutls/gnutls.h>
35 #include <gnutls/x509.h>
36 #endif
37
38 void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
39         char *f;
40
41         f = strjoina("microhttpd: ", fmt);
42
43         DISABLE_WARNING_FORMAT_NONLITERAL;
44         log_internalv(LOG_INFO, 0, NULL, 0, NULL, f, ap);
45         REENABLE_WARNING;
46 }
47
48
49 static int mhd_respond_internal(struct MHD_Connection *connection,
50                                 enum MHD_RequestTerminationCode code,
51                                 char *buffer,
52                                 size_t size,
53                                 enum MHD_ResponseMemoryMode mode) {
54         struct MHD_Response *response;
55         int r;
56
57         assert(connection);
58
59         response = MHD_create_response_from_buffer(size, buffer, mode);
60         if (!response)
61                 return MHD_NO;
62
63         log_debug("Queing response %u: %s", code, buffer);
64         MHD_add_response_header(response, "Content-Type", "text/plain");
65         r = MHD_queue_response(connection, code, response);
66         MHD_destroy_response(response);
67
68         return r;
69 }
70
71 int mhd_respond(struct MHD_Connection *connection,
72                 enum MHD_RequestTerminationCode code,
73                 const char *message) {
74
75         return mhd_respond_internal(connection, code,
76                                     (char*) message, strlen(message),
77                                     MHD_RESPMEM_PERSISTENT);
78 }
79
80 int mhd_respond_oom(struct MHD_Connection *connection) {
81         return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE,  "Out of memory.\n");
82 }
83
84 int mhd_respondf(struct MHD_Connection *connection,
85                  enum MHD_RequestTerminationCode code,
86                  const char *format, ...) {
87
88         char *m;
89         int r;
90         va_list ap;
91
92         assert(connection);
93         assert(format);
94
95         va_start(ap, format);
96         r = vasprintf(&m, format, ap);
97         va_end(ap);
98
99         if (r < 0)
100                 return respond_oom(connection);
101
102         return mhd_respond_internal(connection, code, m, r, MHD_RESPMEM_MUST_FREE);
103 }
104
105 #ifdef HAVE_GNUTLS
106
107 static struct {
108         const char *const names[4];
109         int level;
110         bool enabled;
111 } gnutls_log_map[] = {
112         { {"0"},                  LOG_DEBUG },
113         { {"1", "audit"},         LOG_WARNING, true}, /* gnutls session audit */
114         { {"2", "assert"},        LOG_DEBUG },        /* gnutls assert log */
115         { {"3", "hsk", "ext"},    LOG_DEBUG },        /* gnutls handshake log */
116         { {"4", "rec"},           LOG_DEBUG },        /* gnutls record log */
117         { {"5", "dtls"},          LOG_DEBUG },        /* gnutls DTLS log */
118         { {"6", "buf"},           LOG_DEBUG },
119         { {"7", "write", "read"}, LOG_DEBUG },
120         { {"8"},                  LOG_DEBUG },
121         { {"9", "enc", "int"},    LOG_DEBUG },
122 };
123
124 static void log_func_gnutls(int level, const char *message) {
125         assert_se(message);
126
127         if (0 <= level && level < (int) ELEMENTSOF(gnutls_log_map)) {
128                 if (gnutls_log_map[level].enabled)
129                         log_internal(gnutls_log_map[level].level, 0, NULL, 0, NULL, "gnutls %d/%s: %s", level, gnutls_log_map[level].names[1], message);
130         } else {
131                 log_debug("Received GNUTLS message with unknown level %d.", level);
132                 log_internal(LOG_DEBUG, 0, NULL, 0, NULL, "gnutls: %s", message);
133         }
134 }
135
136 static void log_reset_gnutls_level(void) {
137         int i;
138
139         for (i = ELEMENTSOF(gnutls_log_map) - 1; i >= 0; i--)
140                 if (gnutls_log_map[i].enabled) {
141                         log_debug("Setting gnutls log level to %d", i);
142                         gnutls_global_set_log_level(i);
143                         break;
144                 }
145 }
146
147 static int log_enable_gnutls_category(const char *cat) {
148         unsigned i;
149
150         if (streq(cat, "all")) {
151                 for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
152                         gnutls_log_map[i].enabled = true;
153                 log_reset_gnutls_level();
154                 return 0;
155         } else
156                 for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
157                         if (strv_contains((char**)gnutls_log_map[i].names, cat)) {
158                                 gnutls_log_map[i].enabled = true;
159                                 log_reset_gnutls_level();
160                                 return 0;
161                         }
162         log_error("No such log category: %s", cat);
163         return -EINVAL;
164 }
165
166 int setup_gnutls_logger(char **categories) {
167         char **cat;
168         int r;
169
170         gnutls_global_set_log_function(log_func_gnutls);
171
172         if (categories) {
173                 STRV_FOREACH(cat, categories) {
174                         r = log_enable_gnutls_category(*cat);
175                         if (r < 0)
176                                 return r;
177                 }
178         } else
179                 log_reset_gnutls_level();
180
181         return 0;
182 }
183
184 static int verify_cert_authorized(gnutls_session_t session) {
185         unsigned status;
186         gnutls_certificate_type_t type;
187         gnutls_datum_t out;
188         int r;
189
190         r = gnutls_certificate_verify_peers2(session, &status);
191         if (r < 0)
192                 return log_error_errno(r, "gnutls_certificate_verify_peers2 failed: %m");
193
194         type = gnutls_certificate_type_get(session);
195         r = gnutls_certificate_verification_status_print(status, type, &out, 0);
196         if (r < 0)
197                 return log_error_errno(r, "gnutls_certificate_verification_status_print failed: %m");
198
199         log_debug("Certificate status: %s", out.data);
200         gnutls_free(out.data);
201
202         return status == 0 ? 0 : -EPERM;
203 }
204
205 static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) {
206         const gnutls_datum_t *pcert;
207         unsigned listsize;
208         gnutls_x509_crt_t cert;
209         int r;
210
211         assert(session);
212         assert(client_cert);
213
214         pcert = gnutls_certificate_get_peers(session, &listsize);
215         if (!pcert || !listsize) {
216                 log_error("Failed to retrieve certificate chain");
217                 return -EINVAL;
218         }
219
220         r = gnutls_x509_crt_init(&cert);
221         if (r < 0) {
222                 log_error("Failed to initialize client certificate");
223                 return r;
224         }
225
226         /* Note that by passing values between 0 and listsize here, you
227            can get access to the CA's certs */
228         r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER);
229         if (r < 0) {
230                 log_error("Failed to import client certificate");
231                 gnutls_x509_crt_deinit(cert);
232                 return r;
233         }
234
235         *client_cert = cert;
236         return 0;
237 }
238
239 static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) {
240         size_t len = 0;
241         int r;
242
243         assert(buf);
244         assert(*buf == NULL);
245
246         r = gnutls_x509_crt_get_dn(client_cert, NULL, &len);
247         if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) {
248                 log_error("gnutls_x509_crt_get_dn failed");
249                 return r;
250         }
251
252         *buf = malloc(len);
253         if (!*buf)
254                 return log_oom();
255
256         gnutls_x509_crt_get_dn(client_cert, *buf, &len);
257         return 0;
258 }
259
260 static inline void gnutls_x509_crt_deinitp(gnutls_x509_crt_t *p) {
261         gnutls_x509_crt_deinit(*p);
262 }
263
264 int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
265         const union MHD_ConnectionInfo *ci;
266         gnutls_session_t session;
267         _cleanup_(gnutls_x509_crt_deinitp) gnutls_x509_crt_t client_cert = NULL;
268         _cleanup_free_ char *buf = NULL;
269         int r;
270
271         assert(connection);
272         assert(code);
273
274         *code = 0;
275
276         ci = MHD_get_connection_info(connection,
277                                      MHD_CONNECTION_INFO_GNUTLS_SESSION);
278         if (!ci) {
279                 log_error("MHD_get_connection_info failed: session is unencrypted");
280                 *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN,
281                                     "Encrypted connection is required");
282                 return -EPERM;
283         }
284         session = ci->tls_session;
285         assert(session);
286
287         r = get_client_cert(session, &client_cert);
288         if (r < 0) {
289                 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
290                                     "Authorization through certificate is required");
291                 return -EPERM;
292         }
293
294         r = get_auth_dn(client_cert, &buf);
295         if (r < 0) {
296                 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
297                                     "Failed to determine distinguished name from certificate");
298                 return -EPERM;
299         }
300
301         log_debug("Connection from %s", buf);
302
303         if (hostname) {
304                 *hostname = buf;
305                 buf = NULL;
306         }
307
308         r = verify_cert_authorized(session);
309         if (r < 0) {
310                 log_warning("Client is not authorized");
311                 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
312                                     "Client certificate not signed by recognized authority");
313         }
314         return r;
315 }
316
317 #else
318 int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
319         return -EPERM;
320 }
321
322 int setup_gnutls_logger(char **categories) {
323         if (categories)
324                 log_notice("Ignoring specified gnutls logging categories — gnutls not available.");
325         return 0;
326 }
327 #endif