chiark / gitweb /
treewide: no need to negate errno for log_*_errno()
[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_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                 log_error_errno(r, "gnutls_certificate_verify_peers2 failed: %m");
175                 return r;
176         }
177
178         type = gnutls_certificate_type_get(session);
179         r = gnutls_certificate_verification_status_print(status, type, &out, 0);
180         if (r < 0) {
181                 log_error_errno(r, "gnutls_certificate_verification_status_print failed: %m");
182                 return r;
183         }
184
185         log_info("Certificate status: %s", out.data);
186
187         return status == 0 ? 0 : -EPERM;
188 }
189
190 static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) {
191         const gnutls_datum_t *pcert;
192         unsigned listsize;
193         gnutls_x509_crt_t cert;
194         int r;
195
196         assert(session);
197         assert(client_cert);
198
199         pcert = gnutls_certificate_get_peers(session, &listsize);
200         if (!pcert || !listsize) {
201                 log_error("Failed to retrieve certificate chain");
202                 return -EINVAL;
203         }
204
205         r = gnutls_x509_crt_init(&cert);
206         if (r < 0) {
207                 log_error("Failed to initialize client certificate");
208                 return r;
209         }
210
211         /* Note that by passing values between 0 and listsize here, you
212            can get access to the CA's certs */
213         r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER);
214         if (r < 0) {
215                 log_error("Failed to import client certificate");
216                 gnutls_x509_crt_deinit(cert);
217                 return r;
218         }
219
220         *client_cert = cert;
221         return 0;
222 }
223
224 static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) {
225         size_t len = 0;
226         int r;
227
228         assert(buf);
229         assert(*buf == NULL);
230
231         r = gnutls_x509_crt_get_dn(client_cert, NULL, &len);
232         if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) {
233                 log_error("gnutls_x509_crt_get_dn failed");
234                 return r;
235         }
236
237         *buf = malloc(len);
238         if (!*buf)
239                 return log_oom();
240
241         gnutls_x509_crt_get_dn(client_cert, *buf, &len);
242         return 0;
243 }
244
245 int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
246         const union MHD_ConnectionInfo *ci;
247         gnutls_session_t session;
248         gnutls_x509_crt_t client_cert;
249         _cleanup_free_ char *buf = NULL;
250         int r;
251
252         assert(connection);
253         assert(code);
254
255         *code = 0;
256
257         ci = MHD_get_connection_info(connection,
258                                      MHD_CONNECTION_INFO_GNUTLS_SESSION);
259         if (!ci) {
260                 log_error("MHD_get_connection_info failed: session is unencrypted");
261                 *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN,
262                                     "Encrypted connection is required");
263                 return -EPERM;
264         }
265         session = ci->tls_session;
266         assert(session);
267
268         r = get_client_cert(session, &client_cert);
269         if (r < 0) {
270                 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
271                                     "Authorization through certificate is required");
272                 return -EPERM;
273         }
274
275         r = get_auth_dn(client_cert, &buf);
276         if (r < 0) {
277                 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
278                                     "Failed to determine distinguished name from certificate");
279                 return -EPERM;
280         }
281
282         log_info("Connection from %s", buf);
283
284         if (hostname) {
285                 *hostname = buf;
286                 buf = NULL;
287         }
288
289         r = verify_cert_authorized(session);
290         if (r < 0) {
291                 log_warning("Client is not authorized");
292                 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
293                                     "Client certificate not signed by recognized authority");
294         }
295         return r;
296 }
297
298 #else
299 int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
300         return -EPERM;
301 }
302 #endif