chiark / gitweb /
microhttpd-util: use static buffer for static messages
[elogind.git] / src / journal / 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
32 #ifdef HAVE_GNUTLS
33 #include <gnutls/gnutls.h>
34 #include <gnutls/x509.h>
35 #endif
36
37 void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
38         _cleanup_free_ char *f = NULL;
39
40         if (asprintf(&f, "microhttpd: %s", fmt) <= 0) {
41                 log_oom();
42                 return;
43         }
44
45         DISABLE_WARNING_FORMAT_NONLITERAL;
46         log_metav(LOG_INFO, NULL, 0, NULL, f, ap);
47         REENABLE_WARNING;
48 }
49
50
51 static int mhd_respond_internal(struct MHD_Connection *connection,
52                                 enum MHD_RequestTerminationCode code,
53                                 char *buffer,
54                                 size_t size,
55                                 enum MHD_ResponseMemoryMode mode) {
56         struct MHD_Response *response;
57         int r;
58
59         assert(connection);
60
61         response = MHD_create_response_from_buffer(size, buffer, mode);
62         if (!response)
63                 return MHD_NO;
64
65         log_debug("Queing response %u: %s", code, buffer);
66         MHD_add_response_header(response, "Content-Type", "text/plain");
67         r = MHD_queue_response(connection, code, response);
68         MHD_destroy_response(response);
69
70         return r;
71 }
72
73 int mhd_respond(struct MHD_Connection *connection,
74                 enum MHD_RequestTerminationCode code,
75                 const char *message) {
76
77         return mhd_respond_internal(connection, code,
78                                     (char*) message, strlen(message),
79                                     MHD_RESPMEM_PERSISTENT);
80 }
81
82 int mhd_respond_oom(struct MHD_Connection *connection) {
83         return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE,  "Out of memory.\n");
84 }
85
86 int mhd_respondf(struct MHD_Connection *connection,
87                  enum MHD_RequestTerminationCode code,
88                  const char *format, ...) {
89
90         char *m;
91         int r;
92         va_list ap;
93
94         assert(connection);
95         assert(format);
96
97         va_start(ap, format);
98         r = vasprintf(&m, format, ap);
99         va_end(ap);
100
101         if (r < 0)
102                 return respond_oom(connection);
103
104         r = mhd_respond_internal(connection, code, m, r, MHD_RESPMEM_MUST_FREE);
105         if (r == MHD_NO)
106                 free(m);
107         return r;
108 }
109
110 #ifdef HAVE_GNUTLS
111
112 static int log_level_map[] = {
113         LOG_DEBUG,
114         LOG_WARNING, /* gnutls session audit */
115         LOG_DEBUG,   /* gnutls debug log */
116         LOG_WARNING, /* gnutls assert log */
117         LOG_INFO,    /* gnutls handshake log */
118         LOG_DEBUG,   /* gnutls record log */
119         LOG_DEBUG,   /* gnutls dtls log */
120         LOG_DEBUG,
121         LOG_DEBUG,
122         LOG_DEBUG,
123         LOG_DEBUG,   /* gnutls hard log */
124         LOG_DEBUG,   /* gnutls read log */
125         LOG_DEBUG,   /* gnutls write log */
126         LOG_DEBUG,   /* gnutls io log */
127         LOG_DEBUG,   /* gnutls buffers log */
128 };
129
130 void log_func_gnutls(int level, const char *message) {
131         int ourlevel;
132
133         assert_se(message);
134
135         if (0 <= level && level < (int) ELEMENTSOF(log_level_map))
136                 ourlevel = log_level_map[level];
137         else
138                 level = LOG_DEBUG;
139
140         log_meta(ourlevel, NULL, 0, NULL, "gnutls: %s", message);
141 }
142
143 static int verify_cert_authorized(gnutls_session_t session) {
144         unsigned status;
145         gnutls_certificate_type_t type;
146         gnutls_datum_t out;
147         int r;
148
149         r = gnutls_certificate_verify_peers2(session, &status);
150         if (r < 0) {
151                 log_error("gnutls_certificate_verify_peers2 failed: %s", strerror(-r));
152                 return r;
153         }
154
155         type = gnutls_certificate_type_get(session);
156         r = gnutls_certificate_verification_status_print(status, type, &out, 0);
157         if (r < 0) {
158                 log_error("gnutls_certificate_verification_status_print failed: %s", strerror(-r));
159                 return r;
160         }
161
162         log_info("Certificate status: %s", out.data);
163
164         return status == 0 ? 0 : -EPERM;
165 }
166
167 static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) {
168         const gnutls_datum_t *pcert;
169         unsigned listsize;
170         gnutls_x509_crt_t cert;
171         int r;
172
173         assert(session);
174         assert(client_cert);
175
176         pcert = gnutls_certificate_get_peers(session, &listsize);
177         if (!pcert || !listsize) {
178                 log_error("Failed to retrieve certificate chain");
179                 return -EINVAL;
180         }
181
182         r = gnutls_x509_crt_init(&cert);
183         if (r < 0) {
184                 log_error("Failed to initialize client certificate");
185                 return r;
186         }
187
188         /* Note that by passing values between 0 and listsize here, you
189            can get access to the CA's certs */
190         r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER);
191         if (r < 0) {
192                 log_error("Failed to import client certificate");
193                 gnutls_x509_crt_deinit(cert);
194                 return r;
195         }
196
197         *client_cert = cert;
198         return 0;
199 }
200
201 static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) {
202         size_t len = 0;
203         int r;
204
205         assert(buf);
206         assert(*buf == NULL);
207
208         r = gnutls_x509_crt_get_dn(client_cert, NULL, &len);
209         if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) {
210                 log_error("gnutls_x509_crt_get_dn failed");
211                 return r;
212         }
213
214         *buf = malloc(len);
215         if (!*buf)
216                 return log_oom();
217
218         gnutls_x509_crt_get_dn(client_cert, *buf, &len);
219         return 0;
220 }
221
222 int check_permissions(struct MHD_Connection *connection, int *code) {
223         const union MHD_ConnectionInfo *ci;
224         gnutls_session_t session;
225         gnutls_x509_crt_t client_cert;
226         char _cleanup_free_ *buf = NULL;
227         int r;
228
229         assert(connection);
230         assert(code);
231
232         *code = 0;
233
234         ci = MHD_get_connection_info(connection,
235                                      MHD_CONNECTION_INFO_GNUTLS_SESSION);
236         if (!ci) {
237                 log_error("MHD_get_connection_info failed: session is unencrypted");
238                 *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN,
239                                     "Encrypted connection is required");
240                 return -EPERM;
241         }
242         session = ci->tls_session;
243         assert(session);
244
245         r = get_client_cert(session, &client_cert);
246         if (r < 0) {
247                 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
248                                     "Authorization through certificate is required");
249                 return -EPERM;
250         }
251
252         r = get_auth_dn(client_cert, &buf);
253         if (r < 0) {
254                 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
255                                     "Failed to determine distinguished name from certificate");
256                 return -EPERM;
257         }
258
259         log_info("Connection from DN %s", buf);
260
261         r = verify_cert_authorized(session);
262         if (r < 0) {
263                 log_warning("Client is not authorized");
264                 *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
265                                     "Client certificate not signed by recognized authority");
266         }
267         return r;
268 }
269
270 #else
271 int check_permissions(struct MHD_Connection *connection, int *code) {
272         return -EPERM;
273 }
274 #endif