]> git.proxmox.com Git - systemd.git/blame - src/journal-remote/microhttpd-util.c
New upstream version 236
[systemd.git] / src / journal-remote / microhttpd-util.c
CommitLineData
52ad194e 1/* SPDX-License-Identifier: LGPL-2.1+ */
663996b3
MS
2/***
3 This file is part of systemd.
4
60f067b4 5 Copyright 2012 Lennart Poettering
663996b3
MS
6 Copyright 2012 Zbigniew Jędrzejewski-Szmek
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <stddef.h>
23#include <stdio.h>
60f067b4 24#include <string.h>
663996b3 25
f5e65279 26#if HAVE_GNUTLS
60f067b4
JS
27#include <gnutls/gnutls.h>
28#include <gnutls/x509.h>
29#endif
30
db2df898
MP
31#include "alloc-util.h"
32#include "log.h"
33#include "macro.h"
34#include "microhttpd-util.h"
35#include "string-util.h"
36#include "strv.h"
37#include "util.h"
38
663996b3 39void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
60f067b4
JS
40 char *f;
41
e735f4d4 42 f = strjoina("microhttpd: ", fmt);
60f067b4
JS
43
44 DISABLE_WARNING_FORMAT_NONLITERAL;
f47781d8 45 log_internalv(LOG_INFO, 0, NULL, 0, NULL, f, ap);
60f067b4
JS
46 REENABLE_WARNING;
47}
48
49
50static int mhd_respond_internal(struct MHD_Connection *connection,
51 enum MHD_RequestTerminationCode code,
8a584da2 52 const char *buffer,
60f067b4
JS
53 size_t size,
54 enum MHD_ResponseMemoryMode mode) {
55 struct MHD_Response *response;
56 int r;
57
58 assert(connection);
59
8a584da2 60 response = MHD_create_response_from_buffer(size, (char*) buffer, mode);
60f067b4
JS
61 if (!response)
62 return MHD_NO;
63
5a920b42 64 log_debug("Queueing response %u: %s", code, buffer);
60f067b4
JS
65 MHD_add_response_header(response, "Content-Type", "text/plain");
66 r = MHD_queue_response(connection, code, response);
67 MHD_destroy_response(response);
68
69 return r;
70}
71
72int mhd_respond(struct MHD_Connection *connection,
73 enum MHD_RequestTerminationCode code,
74 const char *message) {
75
8a584da2
MP
76 const char *fmt;
77
78 fmt = strjoina(message, "\n");
79
60f067b4 80 return mhd_respond_internal(connection, code,
8a584da2 81 fmt, strlen(message) + 1,
60f067b4
JS
82 MHD_RESPMEM_PERSISTENT);
83}
84
85int mhd_respond_oom(struct MHD_Connection *connection) {
8a584da2 86 return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE, "Out of memory.");
60f067b4
JS
87}
88
89int mhd_respondf(struct MHD_Connection *connection,
8a584da2 90 int error,
60f067b4
JS
91 enum MHD_RequestTerminationCode code,
92 const char *format, ...) {
93
8a584da2 94 const char *fmt;
60f067b4
JS
95 char *m;
96 int r;
97 va_list ap;
98
99 assert(connection);
100 assert(format);
101
8a584da2
MP
102 if (error < 0)
103 error = -error;
104 errno = -error;
105 fmt = strjoina(format, "\n");
60f067b4 106 va_start(ap, format);
81c58355
MB
107#pragma GCC diagnostic push
108#pragma GCC diagnostic ignored "-Wformat-nonliteral"
8a584da2 109 r = vasprintf(&m, fmt, ap);
81c58355 110#pragma GCC diagnostic pop
60f067b4
JS
111 va_end(ap);
112
113 if (r < 0)
114 return respond_oom(connection);
115
116 return mhd_respond_internal(connection, code, m, r, MHD_RESPMEM_MUST_FREE);
117}
118
f5e65279 119#if HAVE_GNUTLS
60f067b4 120
5eef597e
MP
121static struct {
122 const char *const names[4];
123 int level;
124 bool enabled;
125} gnutls_log_map[] = {
126 { {"0"}, LOG_DEBUG },
127 { {"1", "audit"}, LOG_WARNING, true}, /* gnutls session audit */
128 { {"2", "assert"}, LOG_DEBUG }, /* gnutls assert log */
129 { {"3", "hsk", "ext"}, LOG_DEBUG }, /* gnutls handshake log */
130 { {"4", "rec"}, LOG_DEBUG }, /* gnutls record log */
131 { {"5", "dtls"}, LOG_DEBUG }, /* gnutls DTLS log */
132 { {"6", "buf"}, LOG_DEBUG },
133 { {"7", "write", "read"}, LOG_DEBUG },
134 { {"8"}, LOG_DEBUG },
135 { {"9", "enc", "int"}, LOG_DEBUG },
60f067b4
JS
136};
137
e3bff60a 138static void log_func_gnutls(int level, const char *message) {
60f067b4
JS
139 assert_se(message);
140
5eef597e
MP
141 if (0 <= level && level < (int) ELEMENTSOF(gnutls_log_map)) {
142 if (gnutls_log_map[level].enabled)
f47781d8 143 log_internal(gnutls_log_map[level].level, 0, NULL, 0, NULL, "gnutls %d/%s: %s", level, gnutls_log_map[level].names[1], message);
5eef597e
MP
144 } else {
145 log_debug("Received GNUTLS message with unknown level %d.", level);
f47781d8 146 log_internal(LOG_DEBUG, 0, NULL, 0, NULL, "gnutls: %s", message);
5eef597e
MP
147 }
148}
149
e3bff60a
MP
150static void log_reset_gnutls_level(void) {
151 int i;
152
153 for (i = ELEMENTSOF(gnutls_log_map) - 1; i >= 0; i--)
154 if (gnutls_log_map[i].enabled) {
155 log_debug("Setting gnutls log level to %d", i);
156 gnutls_global_set_log_level(i);
157 break;
158 }
159}
160
161static int log_enable_gnutls_category(const char *cat) {
5eef597e
MP
162 unsigned i;
163
164 if (streq(cat, "all")) {
165 for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
166 gnutls_log_map[i].enabled = true;
167 log_reset_gnutls_level();
168 return 0;
169 } else
170 for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
171 if (strv_contains((char**)gnutls_log_map[i].names, cat)) {
172 gnutls_log_map[i].enabled = true;
173 log_reset_gnutls_level();
174 return 0;
175 }
176 log_error("No such log category: %s", cat);
177 return -EINVAL;
178}
179
e3bff60a
MP
180int setup_gnutls_logger(char **categories) {
181 char **cat;
182 int r;
60f067b4 183
e3bff60a
MP
184 gnutls_global_set_log_function(log_func_gnutls);
185
186 if (categories) {
187 STRV_FOREACH(cat, categories) {
188 r = log_enable_gnutls_category(*cat);
189 if (r < 0)
190 return r;
5eef597e 191 }
e3bff60a
MP
192 } else
193 log_reset_gnutls_level();
194
195 return 0;
60f067b4
JS
196}
197
198static int verify_cert_authorized(gnutls_session_t session) {
199 unsigned status;
200 gnutls_certificate_type_t type;
201 gnutls_datum_t out;
202 int r;
203
204 r = gnutls_certificate_verify_peers2(session, &status);
f47781d8
MP
205 if (r < 0)
206 return log_error_errno(r, "gnutls_certificate_verify_peers2 failed: %m");
60f067b4
JS
207
208 type = gnutls_certificate_type_get(session);
209 r = gnutls_certificate_verification_status_print(status, type, &out, 0);
f47781d8
MP
210 if (r < 0)
211 return log_error_errno(r, "gnutls_certificate_verification_status_print failed: %m");
60f067b4 212
e3bff60a
MP
213 log_debug("Certificate status: %s", out.data);
214 gnutls_free(out.data);
60f067b4
JS
215
216 return status == 0 ? 0 : -EPERM;
217}
218
219static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) {
220 const gnutls_datum_t *pcert;
221 unsigned listsize;
222 gnutls_x509_crt_t cert;
223 int r;
224
225 assert(session);
226 assert(client_cert);
227
228 pcert = gnutls_certificate_get_peers(session, &listsize);
229 if (!pcert || !listsize) {
230 log_error("Failed to retrieve certificate chain");
231 return -EINVAL;
232 }
233
234 r = gnutls_x509_crt_init(&cert);
235 if (r < 0) {
236 log_error("Failed to initialize client certificate");
237 return r;
238 }
239
240 /* Note that by passing values between 0 and listsize here, you
241 can get access to the CA's certs */
242 r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER);
243 if (r < 0) {
244 log_error("Failed to import client certificate");
245 gnutls_x509_crt_deinit(cert);
246 return r;
247 }
248
249 *client_cert = cert;
250 return 0;
251}
252
253static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) {
254 size_t len = 0;
255 int r;
256
257 assert(buf);
258 assert(*buf == NULL);
259
260 r = gnutls_x509_crt_get_dn(client_cert, NULL, &len);
261 if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) {
262 log_error("gnutls_x509_crt_get_dn failed");
263 return r;
264 }
265
266 *buf = malloc(len);
267 if (!*buf)
268 return log_oom();
269
270 gnutls_x509_crt_get_dn(client_cert, *buf, &len);
271 return 0;
272}
273
e3bff60a
MP
274static inline void gnutls_x509_crt_deinitp(gnutls_x509_crt_t *p) {
275 gnutls_x509_crt_deinit(*p);
276}
277
5eef597e 278int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
60f067b4
JS
279 const union MHD_ConnectionInfo *ci;
280 gnutls_session_t session;
e3bff60a 281 _cleanup_(gnutls_x509_crt_deinitp) gnutls_x509_crt_t client_cert = NULL;
e842803a 282 _cleanup_free_ char *buf = NULL;
60f067b4
JS
283 int r;
284
285 assert(connection);
286 assert(code);
287
288 *code = 0;
289
290 ci = MHD_get_connection_info(connection,
291 MHD_CONNECTION_INFO_GNUTLS_SESSION);
292 if (!ci) {
293 log_error("MHD_get_connection_info failed: session is unencrypted");
294 *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN,
295 "Encrypted connection is required");
296 return -EPERM;
297 }
298 session = ci->tls_session;
299 assert(session);
300
301 r = get_client_cert(session, &client_cert);
302 if (r < 0) {
303 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
304 "Authorization through certificate is required");
305 return -EPERM;
306 }
307
308 r = get_auth_dn(client_cert, &buf);
309 if (r < 0) {
310 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
311 "Failed to determine distinguished name from certificate");
312 return -EPERM;
313 }
314
e3bff60a 315 log_debug("Connection from %s", buf);
60f067b4 316
5eef597e
MP
317 if (hostname) {
318 *hostname = buf;
319 buf = NULL;
320 }
321
60f067b4
JS
322 r = verify_cert_authorized(session);
323 if (r < 0) {
324 log_warning("Client is not authorized");
325 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
326 "Client certificate not signed by recognized authority");
327 }
328 return r;
329}
330
331#else
5eef597e 332int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
60f067b4 333 return -EPERM;
663996b3 334}
e3bff60a
MP
335
336int setup_gnutls_logger(char **categories) {
337 if (categories)
338 log_notice("Ignoring specified gnutls logging categories — gnutls not available.");
339 return 0;
340}
60f067b4 341#endif