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