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