]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal-remote/microhttpd-util.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / journal-remote / microhttpd-util.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
e64690a8
ZJS
2/***
3 This file is part of systemd.
4
f12be7e8 5 Copyright 2012 Lennart Poettering
e64690a8
ZJS
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>
f12be7e8 24#include <string.h>
e64690a8 25
349cc4a5 26#if HAVE_GNUTLS
f12be7e8
ZJS
27#include <gnutls/gnutls.h>
28#include <gnutls/x509.h>
29#endif
30
b5efdb8a 31#include "alloc-util.h"
07630cea
LP
32#include "log.h"
33#include "macro.h"
cf0fbc49 34#include "microhttpd-util.h"
07630cea
LP
35#include "string-util.h"
36#include "strv.h"
37#include "util.h"
07630cea 38
e64690a8 39void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
4dd5da7f 40 char *f;
bcfce235 41
63c372cb 42 f = strjoina("microhttpd: ", fmt);
bcfce235
LP
43
44 DISABLE_WARNING_FORMAT_NONLITERAL;
79008bdd 45 log_internalv(LOG_INFO, 0, NULL, 0, NULL, f, ap);
bcfce235 46 REENABLE_WARNING;
e64690a8 47}
cafc7f91 48
f12be7e8 49
e7216d11
ZJS
50static int mhd_respond_internal(struct MHD_Connection *connection,
51 enum MHD_RequestTerminationCode code,
f5e757f1 52 const char *buffer,
e7216d11
ZJS
53 size_t size,
54 enum MHD_ResponseMemoryMode mode) {
f12be7e8 55 struct MHD_Response *response;
e7216d11 56 int r;
f12be7e8
ZJS
57
58 assert(connection);
59
f5e757f1 60 response = MHD_create_response_from_buffer(size, (char*) buffer, mode);
f12be7e8
ZJS
61 if (!response)
62 return MHD_NO;
63
595bfe7d 64 log_debug("Queueing response %u: %s", code, buffer);
f12be7e8 65 MHD_add_response_header(response, "Content-Type", "text/plain");
e7216d11 66 r = MHD_queue_response(connection, code, response);
f12be7e8
ZJS
67 MHD_destroy_response(response);
68
e7216d11 69 return r;
f12be7e8
ZJS
70}
71
e7216d11
ZJS
72int mhd_respond(struct MHD_Connection *connection,
73 enum MHD_RequestTerminationCode code,
74 const char *message) {
75
f5e757f1
ZJS
76 const char *fmt;
77
78 fmt = strjoina(message, "\n");
79
e7216d11 80 return mhd_respond_internal(connection, code,
f5e757f1 81 fmt, strlen(message) + 1,
e7216d11
ZJS
82 MHD_RESPMEM_PERSISTENT);
83}
84
85int mhd_respond_oom(struct MHD_Connection *connection) {
f5e757f1 86 return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE, "Out of memory.");
e7216d11
ZJS
87}
88
89int mhd_respondf(struct MHD_Connection *connection,
1b4cd646 90 int error,
e7216d11
ZJS
91 enum MHD_RequestTerminationCode code,
92 const char *format, ...) {
f12be7e8 93
f5e757f1 94 const char *fmt;
f12be7e8
ZJS
95 char *m;
96 int r;
97 va_list ap;
98
99 assert(connection);
100 assert(format);
101
1b4cd646
ZJS
102 if (error < 0)
103 error = -error;
104 errno = -error;
f5e757f1 105 fmt = strjoina(format, "\n");
f12be7e8 106 va_start(ap, format);
c8304ba9
ZJS
107#pragma GCC diagnostic push
108#pragma GCC diagnostic ignored "-Wformat-nonliteral"
f5e757f1 109 r = vasprintf(&m, fmt, ap);
c8304ba9 110#pragma GCC diagnostic pop
f12be7e8
ZJS
111 va_end(ap);
112
113 if (r < 0)
114 return respond_oom(connection);
115
4dd5da7f 116 return mhd_respond_internal(connection, code, m, r, MHD_RESPMEM_MUST_FREE);
f12be7e8
ZJS
117}
118
349cc4a5 119#if HAVE_GNUTLS
cafc7f91 120
5937a4d4
ZJS
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 },
cafc7f91
ZJS
136};
137
d357562c 138static void log_func_gnutls(int level, const char *message) {
cafc7f91
ZJS
139 assert_se(message);
140
5937a4d4
ZJS
141 if (0 <= level && level < (int) ELEMENTSOF(gnutls_log_map)) {
142 if (gnutls_log_map[level].enabled)
79008bdd 143 log_internal(gnutls_log_map[level].level, 0, NULL, 0, NULL, "gnutls %d/%s: %s", level, gnutls_log_map[level].names[1], message);
5937a4d4
ZJS
144 } else {
145 log_debug("Received GNUTLS message with unknown level %d.", level);
79008bdd 146 log_internal(LOG_DEBUG, 0, NULL, 0, NULL, "gnutls: %s", message);
5937a4d4
ZJS
147 }
148}
149
d357562c
ZJS
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) {
5937a4d4
ZJS
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
d357562c
ZJS
180int setup_gnutls_logger(char **categories) {
181 char **cat;
182 int r;
cafc7f91 183
d357562c
ZJS
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;
5937a4d4 191 }
d357562c
ZJS
192 } else
193 log_reset_gnutls_level();
194
195 return 0;
cafc7f91
ZJS
196}
197
f12be7e8
ZJS
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);
23bbb0de
MS
205 if (r < 0)
206 return log_error_errno(r, "gnutls_certificate_verify_peers2 failed: %m");
f12be7e8
ZJS
207
208 type = gnutls_certificate_type_get(session);
209 r = gnutls_certificate_verification_status_print(status, type, &out, 0);
23bbb0de
MS
210 if (r < 0)
211 return log_error_errno(r, "gnutls_certificate_verification_status_print failed: %m");
f12be7e8 212
0e72da6f 213 log_debug("Certificate status: %s", out.data);
9c3cf969 214 gnutls_free(out.data);
f12be7e8
ZJS
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
32c3d714
MS
274static inline void gnutls_x509_crt_deinitp(gnutls_x509_crt_t *p) {
275 gnutls_x509_crt_deinit(*p);
276}
277
8201af08 278int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
f12be7e8
ZJS
279 const union MHD_ConnectionInfo *ci;
280 gnutls_session_t session;
32c3d714 281 _cleanup_(gnutls_x509_crt_deinitp) gnutls_x509_crt_t client_cert = NULL;
c8b32e11 282 _cleanup_free_ char *buf = NULL;
f12be7e8
ZJS
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) {
cc64d017 293 log_error("MHD_get_connection_info failed: session is unencrypted");
e7216d11
ZJS
294 *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN,
295 "Encrypted connection is required");
cc64d017 296 return -EPERM;
f12be7e8
ZJS
297 }
298 session = ci->tls_session;
299 assert(session);
300
301 r = get_client_cert(session, &client_cert);
302 if (r < 0) {
e7216d11
ZJS
303 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
304 "Authorization through certificate is required");
f12be7e8
ZJS
305 return -EPERM;
306 }
307
308 r = get_auth_dn(client_cert, &buf);
309 if (r < 0) {
e7216d11
ZJS
310 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
311 "Failed to determine distinguished name from certificate");
f12be7e8
ZJS
312 return -EPERM;
313 }
314
0e72da6f 315 log_debug("Connection from %s", buf);
f12be7e8 316
8201af08
ZJS
317 if (hostname) {
318 *hostname = buf;
319 buf = NULL;
320 }
321
f12be7e8
ZJS
322 r = verify_cert_authorized(session);
323 if (r < 0) {
cc64d017 324 log_warning("Client is not authorized");
e7216d11
ZJS
325 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
326 "Client certificate not signed by recognized authority");
f12be7e8
ZJS
327 }
328 return r;
329}
330
331#else
93c0969c 332int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
f12be7e8
ZJS
333 return -EPERM;
334}
d357562c
ZJS
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}
cafc7f91 341#endif