chiark / gitweb /
µhttp-util: fix compilation without gnutls
[elogind.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 = strappenda("microhttpd: ", fmt);
42
43         DISABLE_WARNING_FORMAT_NONLITERAL;
44         log_metav(LOG_INFO, 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_meta(gnutls_log_map[level].level, NULL, 0, NULL,
130                                  "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_meta(LOG_DEBUG, NULL, 0, NULL, "gnutls: %s", message);
134         }
135 }
136
137 int log_enable_gnutls_category(const char *cat) {
138         unsigned i;
139
140         if (streq(cat, "all")) {
141                 for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
142                         gnutls_log_map[i].enabled = true;
143                 log_reset_gnutls_level();
144                 return 0;
145         } else
146                 for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
147                         if (strv_contains((char**)gnutls_log_map[i].names, cat)) {
148                                 gnutls_log_map[i].enabled = true;
149                                 log_reset_gnutls_level();
150                                 return 0;
151                         }
152         log_error("No such log category: %s", cat);
153         return -EINVAL;
154 }
155
156 void log_reset_gnutls_level(void) {
157         int i;
158
159         for (i = ELEMENTSOF(gnutls_log_map) - 1; i >= 0; i--)
160                 if (gnutls_log_map[i].enabled) {
161                         log_debug("Setting gnutls log level to %d", i);
162                         gnutls_global_set_log_level(i);
163                         break;
164                 }
165 }
166
167 static int verify_cert_authorized(gnutls_session_t session) {
168         unsigned status;
169         gnutls_certificate_type_t type;
170         gnutls_datum_t out;
171         int r;
172
173         r = gnutls_certificate_verify_peers2(session, &status);
174         if (r < 0) {
175                 log_error("gnutls_certificate_verify_peers2 failed: %s", strerror(-r));
176                 return r;
177         }
178
179         type = gnutls_certificate_type_get(session);
180         r = gnutls_certificate_verification_status_print(status, type, &out, 0);
181         if (r < 0) {
182                 log_error("gnutls_certificate_verification_status_print failed: %s", strerror(-r));
183                 return r;
184         }
185
186         log_info("Certificate status: %s", out.data);
187
188         return status == 0 ? 0 : -EPERM;
189 }
190
191 static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) {
192         const gnutls_datum_t *pcert;
193         unsigned listsize;
194         gnutls_x509_crt_t cert;
195         int r;
196
197         assert(session);
198         assert(client_cert);
199
200         pcert = gnutls_certificate_get_peers(session, &listsize);
201         if (!pcert || !listsize) {
202                 log_error("Failed to retrieve certificate chain");
203                 return -EINVAL;
204         }
205
206         r = gnutls_x509_crt_init(&cert);
207         if (r < 0) {
208                 log_error("Failed to initialize client certificate");
209                 return r;
210         }
211
212         /* Note that by passing values between 0 and listsize here, you
213            can get access to the CA's certs */
214         r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER);
215         if (r < 0) {
216                 log_error("Failed to import client certificate");
217                 gnutls_x509_crt_deinit(cert);
218                 return r;
219         }
220
221         *client_cert = cert;
222         return 0;
223 }
224
225 static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) {
226         size_t len = 0;
227         int r;
228
229         assert(buf);
230         assert(*buf == NULL);
231
232         r = gnutls_x509_crt_get_dn(client_cert, NULL, &len);
233         if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) {
234                 log_error("gnutls_x509_crt_get_dn failed");
235                 return r;
236         }
237
238         *buf = malloc(len);
239         if (!*buf)
240                 return log_oom();
241
242         gnutls_x509_crt_get_dn(client_cert, *buf, &len);
243         return 0;
244 }
245
246 int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
247         const union MHD_ConnectionInfo *ci;
248         gnutls_session_t session;
249         gnutls_x509_crt_t client_cert;
250         _cleanup_free_ char *buf = NULL;
251         int r;
252
253         assert(connection);
254         assert(code);
255
256         *code = 0;
257
258         ci = MHD_get_connection_info(connection,
259                                      MHD_CONNECTION_INFO_GNUTLS_SESSION);
260         if (!ci) {
261                 log_error("MHD_get_connection_info failed: session is unencrypted");
262                 *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN,
263                                     "Encrypted connection is required");
264                 return -EPERM;
265         }
266         session = ci->tls_session;
267         assert(session);
268
269         r = get_client_cert(session, &client_cert);
270         if (r < 0) {
271                 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
272                                     "Authorization through certificate is required");
273                 return -EPERM;
274         }
275
276         r = get_auth_dn(client_cert, &buf);
277         if (r < 0) {
278                 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
279                                     "Failed to determine distinguished name from certificate");
280                 return -EPERM;
281         }
282
283         log_info("Connection from %s", buf);
284
285         if (hostname) {
286                 *hostname = buf;
287                 buf = NULL;
288         }
289
290         r = verify_cert_authorized(session);
291         if (r < 0) {
292                 log_warning("Client is not authorized");
293                 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
294                                     "Client certificate not signed by recognized authority");
295         }
296         return r;
297 }
298
299 #else
300 int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
301         return -EPERM;
302 }
303 #endif