]> git.ipfire.org Git - thirdparty/systemd.git/blob - 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
1 /***
2 This file is part of systemd.
3
4 Copyright 2012 Lennart Poettering
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>
23 #include <string.h>
24
25 #if HAVE_GNUTLS
26 #include <gnutls/gnutls.h>
27 #include <gnutls/x509.h>
28 #endif
29
30 #include "alloc-util.h"
31 #include "log.h"
32 #include "macro.h"
33 #include "microhttpd-util.h"
34 #include "string-util.h"
35 #include "strv.h"
36 #include "util.h"
37
38 void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
39 char *f;
40
41 f = strjoina("microhttpd: ", fmt);
42
43 DISABLE_WARNING_FORMAT_NONLITERAL;
44 log_internalv(LOG_INFO, 0, NULL, 0, NULL, f, ap);
45 REENABLE_WARNING;
46 }
47
48
49 static int mhd_respond_internal(struct MHD_Connection *connection,
50 enum MHD_RequestTerminationCode code,
51 const char *buffer,
52 size_t size,
53 enum MHD_ResponseMemoryMode mode) {
54 struct MHD_Response *response;
55 int r;
56
57 assert(connection);
58
59 response = MHD_create_response_from_buffer(size, (char*) buffer, mode);
60 if (!response)
61 return MHD_NO;
62
63 log_debug("Queueing response %u: %s", code, buffer);
64 MHD_add_response_header(response, "Content-Type", "text/plain");
65 r = MHD_queue_response(connection, code, response);
66 MHD_destroy_response(response);
67
68 return r;
69 }
70
71 int mhd_respond(struct MHD_Connection *connection,
72 enum MHD_RequestTerminationCode code,
73 const char *message) {
74
75 const char *fmt;
76
77 fmt = strjoina(message, "\n");
78
79 return mhd_respond_internal(connection, code,
80 fmt, strlen(message) + 1,
81 MHD_RESPMEM_PERSISTENT);
82 }
83
84 int mhd_respond_oom(struct MHD_Connection *connection) {
85 return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE, "Out of memory.");
86 }
87
88 int mhd_respondf(struct MHD_Connection *connection,
89 int error,
90 enum MHD_RequestTerminationCode code,
91 const char *format, ...) {
92
93 const char *fmt;
94 char *m;
95 int r;
96 va_list ap;
97
98 assert(connection);
99 assert(format);
100
101 if (error < 0)
102 error = -error;
103 errno = -error;
104 fmt = strjoina(format, "\n");
105 va_start(ap, format);
106 #pragma GCC diagnostic push
107 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
108 r = vasprintf(&m, fmt, ap);
109 #pragma GCC diagnostic pop
110 va_end(ap);
111
112 if (r < 0)
113 return respond_oom(connection);
114
115 return mhd_respond_internal(connection, code, m, r, MHD_RESPMEM_MUST_FREE);
116 }
117
118 #if HAVE_GNUTLS
119
120 static 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 },
135 };
136
137 static void log_func_gnutls(int level, const char *message) {
138 assert_se(message);
139
140 if (0 <= level && level < (int) ELEMENTSOF(gnutls_log_map)) {
141 if (gnutls_log_map[level].enabled)
142 log_internal(gnutls_log_map[level].level, 0, NULL, 0, NULL, "gnutls %d/%s: %s", level, gnutls_log_map[level].names[1], message);
143 } else {
144 log_debug("Received GNUTLS message with unknown level %d.", level);
145 log_internal(LOG_DEBUG, 0, NULL, 0, NULL, "gnutls: %s", message);
146 }
147 }
148
149 static 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
160 static int log_enable_gnutls_category(const char *cat) {
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
179 int setup_gnutls_logger(char **categories) {
180 char **cat;
181 int r;
182
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;
190 }
191 } else
192 log_reset_gnutls_level();
193
194 return 0;
195 }
196
197 static 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);
204 if (r < 0)
205 return log_error_errno(r, "gnutls_certificate_verify_peers2 failed: %m");
206
207 type = gnutls_certificate_type_get(session);
208 r = gnutls_certificate_verification_status_print(status, type, &out, 0);
209 if (r < 0)
210 return log_error_errno(r, "gnutls_certificate_verification_status_print failed: %m");
211
212 log_debug("Certificate status: %s", out.data);
213 gnutls_free(out.data);
214
215 return status == 0 ? 0 : -EPERM;
216 }
217
218 static 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
252 static 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
273 static inline void gnutls_x509_crt_deinitp(gnutls_x509_crt_t *p) {
274 gnutls_x509_crt_deinit(*p);
275 }
276
277 int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
278 const union MHD_ConnectionInfo *ci;
279 gnutls_session_t session;
280 _cleanup_(gnutls_x509_crt_deinitp) gnutls_x509_crt_t client_cert = NULL;
281 _cleanup_free_ char *buf = NULL;
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) {
292 log_error("MHD_get_connection_info failed: session is unencrypted");
293 *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN,
294 "Encrypted connection is required");
295 return -EPERM;
296 }
297 session = ci->tls_session;
298 assert(session);
299
300 r = get_client_cert(session, &client_cert);
301 if (r < 0) {
302 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
303 "Authorization through certificate is required");
304 return -EPERM;
305 }
306
307 r = get_auth_dn(client_cert, &buf);
308 if (r < 0) {
309 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
310 "Failed to determine distinguished name from certificate");
311 return -EPERM;
312 }
313
314 log_debug("Connection from %s", buf);
315
316 if (hostname) {
317 *hostname = buf;
318 buf = NULL;
319 }
320
321 r = verify_cert_authorized(session);
322 if (r < 0) {
323 log_warning("Client is not authorized");
324 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
325 "Client certificate not signed by recognized authority");
326 }
327 return r;
328 }
329
330 #else
331 int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
332 return -EPERM;
333 }
334
335 int setup_gnutls_logger(char **categories) {
336 if (categories)
337 log_notice("Ignoring specified gnutls logging categories — gnutls not available.");
338 return 0;
339 }
340 #endif