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