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