]> git.ipfire.org Git - thirdparty/systemd.git/blob - 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
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2012 Lennart Poettering
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>
24 #include <string.h>
25
26 #if HAVE_GNUTLS
27 #include <gnutls/gnutls.h>
28 #include <gnutls/x509.h>
29 #endif
30
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
39 void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
40 char *f;
41
42 f = strjoina("microhttpd: ", fmt);
43
44 DISABLE_WARNING_FORMAT_NONLITERAL;
45 log_internalv(LOG_INFO, 0, NULL, 0, NULL, f, ap);
46 REENABLE_WARNING;
47 }
48
49
50 static int mhd_respond_internal(struct MHD_Connection *connection,
51 enum MHD_RequestTerminationCode code,
52 const char *buffer,
53 size_t size,
54 enum MHD_ResponseMemoryMode mode) {
55 struct MHD_Response *response;
56 int r;
57
58 assert(connection);
59
60 response = MHD_create_response_from_buffer(size, (char*) buffer, mode);
61 if (!response)
62 return MHD_NO;
63
64 log_debug("Queueing response %u: %s", code, buffer);
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
72 int mhd_respond(struct MHD_Connection *connection,
73 enum MHD_RequestTerminationCode code,
74 const char *message) {
75
76 const char *fmt;
77
78 fmt = strjoina(message, "\n");
79
80 return mhd_respond_internal(connection, code,
81 fmt, strlen(message) + 1,
82 MHD_RESPMEM_PERSISTENT);
83 }
84
85 int mhd_respond_oom(struct MHD_Connection *connection) {
86 return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE, "Out of memory.");
87 }
88
89 int mhd_respondf(struct MHD_Connection *connection,
90 int error,
91 enum MHD_RequestTerminationCode code,
92 const char *format, ...) {
93
94 const char *fmt;
95 char *m;
96 int r;
97 va_list ap;
98
99 assert(connection);
100 assert(format);
101
102 if (error < 0)
103 error = -error;
104 errno = -error;
105 fmt = strjoina(format, "\n");
106 va_start(ap, format);
107 #pragma GCC diagnostic push
108 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
109 r = vasprintf(&m, fmt, ap);
110 #pragma GCC diagnostic pop
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
119 #if HAVE_GNUTLS
120
121 static 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 },
136 };
137
138 static void log_func_gnutls(int level, const char *message) {
139 assert_se(message);
140
141 if (0 <= level && level < (int) ELEMENTSOF(gnutls_log_map)) {
142 if (gnutls_log_map[level].enabled)
143 log_internal(gnutls_log_map[level].level, 0, NULL, 0, NULL, "gnutls %d/%s: %s", level, gnutls_log_map[level].names[1], message);
144 } else {
145 log_debug("Received GNUTLS message with unknown level %d.", level);
146 log_internal(LOG_DEBUG, 0, NULL, 0, NULL, "gnutls: %s", message);
147 }
148 }
149
150 static 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
161 static int log_enable_gnutls_category(const char *cat) {
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
180 int setup_gnutls_logger(char **categories) {
181 char **cat;
182 int r;
183
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;
191 }
192 } else
193 log_reset_gnutls_level();
194
195 return 0;
196 }
197
198 static 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);
205 if (r < 0)
206 return log_error_errno(r, "gnutls_certificate_verify_peers2 failed: %m");
207
208 type = gnutls_certificate_type_get(session);
209 r = gnutls_certificate_verification_status_print(status, type, &out, 0);
210 if (r < 0)
211 return log_error_errno(r, "gnutls_certificate_verification_status_print failed: %m");
212
213 log_debug("Certificate status: %s", out.data);
214 gnutls_free(out.data);
215
216 return status == 0 ? 0 : -EPERM;
217 }
218
219 static 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
253 static 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
274 static inline void gnutls_x509_crt_deinitp(gnutls_x509_crt_t *p) {
275 gnutls_x509_crt_deinit(*p);
276 }
277
278 int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
279 const union MHD_ConnectionInfo *ci;
280 gnutls_session_t session;
281 _cleanup_(gnutls_x509_crt_deinitp) gnutls_x509_crt_t client_cert = NULL;
282 _cleanup_free_ char *buf = NULL;
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
315 log_debug("Connection from %s", buf);
316
317 if (hostname) {
318 *hostname = buf;
319 buf = NULL;
320 }
321
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
332 int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
333 return -EPERM;
334 }
335
336 int setup_gnutls_logger(char **categories) {
337 if (categories)
338 log_notice("Ignoring specified gnutls logging categories — gnutls not available.");
339 return 0;
340 }
341 #endif