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