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