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