]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal-remote/microhttpd-util.c
journal-remote: fix certificate status memory leak
[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 #include "microhttpd-util.h"
28 #include "log.h"
29 #include "macro.h"
30 #include "util.h"
31 #include "strv.h"
32
33 #ifdef HAVE_GNUTLS
34 #include <gnutls/gnutls.h>
35 #include <gnutls/x509.h>
36 #endif
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 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, buffer, mode);
60 if (!response)
61 return MHD_NO;
62
63 log_debug("Queing 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 return mhd_respond_internal(connection, code,
76 (char*) message, strlen(message),
77 MHD_RESPMEM_PERSISTENT);
78 }
79
80 int mhd_respond_oom(struct MHD_Connection *connection) {
81 return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE, "Out of memory.\n");
82 }
83
84 int mhd_respondf(struct MHD_Connection *connection,
85 enum MHD_RequestTerminationCode code,
86 const char *format, ...) {
87
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
102 return mhd_respond_internal(connection, code, m, r, MHD_RESPMEM_MUST_FREE);
103 }
104
105 #ifdef HAVE_GNUTLS
106
107 static 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 },
122 };
123
124 void log_func_gnutls(int level, const char *message) {
125 assert_se(message);
126
127 if (0 <= level && level < (int) ELEMENTSOF(gnutls_log_map)) {
128 if (gnutls_log_map[level].enabled)
129 log_internal(gnutls_log_map[level].level, 0, NULL, 0, NULL, "gnutls %d/%s: %s", level, gnutls_log_map[level].names[1], message);
130 } else {
131 log_debug("Received GNUTLS message with unknown level %d.", level);
132 log_internal(LOG_DEBUG, 0, NULL, 0, NULL, "gnutls: %s", message);
133 }
134 }
135
136 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 log_error("No such log category: %s", cat);
152 return -EINVAL;
153 }
154
155 void log_reset_gnutls_level(void) {
156 int i;
157
158 for (i = ELEMENTSOF(gnutls_log_map) - 1; i >= 0; i--)
159 if (gnutls_log_map[i].enabled) {
160 log_debug("Setting gnutls log level to %d", i);
161 gnutls_global_set_log_level(i);
162 break;
163 }
164 }
165
166 static int verify_cert_authorized(gnutls_session_t session) {
167 unsigned status;
168 gnutls_certificate_type_t type;
169 gnutls_datum_t out;
170 int r;
171
172 r = gnutls_certificate_verify_peers2(session, &status);
173 if (r < 0)
174 return log_error_errno(r, "gnutls_certificate_verify_peers2 failed: %m");
175
176 type = gnutls_certificate_type_get(session);
177 r = gnutls_certificate_verification_status_print(status, type, &out, 0);
178 if (r < 0)
179 return log_error_errno(r, "gnutls_certificate_verification_status_print failed: %m");
180
181 log_info("Certificate status: %s", out.data);
182 gnutls_free(out.data);
183
184 return status == 0 ? 0 : -EPERM;
185 }
186
187 static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) {
188 const gnutls_datum_t *pcert;
189 unsigned listsize;
190 gnutls_x509_crt_t cert;
191 int r;
192
193 assert(session);
194 assert(client_cert);
195
196 pcert = gnutls_certificate_get_peers(session, &listsize);
197 if (!pcert || !listsize) {
198 log_error("Failed to retrieve certificate chain");
199 return -EINVAL;
200 }
201
202 r = gnutls_x509_crt_init(&cert);
203 if (r < 0) {
204 log_error("Failed to initialize client certificate");
205 return r;
206 }
207
208 /* Note that by passing values between 0 and listsize here, you
209 can get access to the CA's certs */
210 r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER);
211 if (r < 0) {
212 log_error("Failed to import client certificate");
213 gnutls_x509_crt_deinit(cert);
214 return r;
215 }
216
217 *client_cert = cert;
218 return 0;
219 }
220
221 static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) {
222 size_t len = 0;
223 int r;
224
225 assert(buf);
226 assert(*buf == NULL);
227
228 r = gnutls_x509_crt_get_dn(client_cert, NULL, &len);
229 if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) {
230 log_error("gnutls_x509_crt_get_dn failed");
231 return r;
232 }
233
234 *buf = malloc(len);
235 if (!*buf)
236 return log_oom();
237
238 gnutls_x509_crt_get_dn(client_cert, *buf, &len);
239 return 0;
240 }
241
242 int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
243 const union MHD_ConnectionInfo *ci;
244 gnutls_session_t session;
245 gnutls_x509_crt_t client_cert;
246 _cleanup_free_ char *buf = NULL;
247 int r;
248
249 assert(connection);
250 assert(code);
251
252 *code = 0;
253
254 ci = MHD_get_connection_info(connection,
255 MHD_CONNECTION_INFO_GNUTLS_SESSION);
256 if (!ci) {
257 log_error("MHD_get_connection_info failed: session is unencrypted");
258 *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN,
259 "Encrypted connection is required");
260 return -EPERM;
261 }
262 session = ci->tls_session;
263 assert(session);
264
265 r = get_client_cert(session, &client_cert);
266 if (r < 0) {
267 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
268 "Authorization through certificate is required");
269 return -EPERM;
270 }
271
272 r = get_auth_dn(client_cert, &buf);
273 if (r < 0) {
274 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
275 "Failed to determine distinguished name from certificate");
276 return -EPERM;
277 }
278
279 log_info("Connection from %s", buf);
280
281 if (hostname) {
282 *hostname = buf;
283 buf = NULL;
284 }
285
286 r = verify_cert_authorized(session);
287 if (r < 0) {
288 log_warning("Client is not authorized");
289 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
290 "Client certificate not signed by recognized authority");
291 }
292 return r;
293 }
294
295 #else
296 int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
297 return -EPERM;
298 }
299 #endif