Line data Source code
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 0 : void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
21 : char *f;
22 :
23 0 : f = strjoina("microhttpd: ", fmt);
24 :
25 : DISABLE_WARNING_FORMAT_NONLITERAL;
26 0 : log_internalv(LOG_INFO, 0, NULL, 0, NULL, f, ap);
27 : REENABLE_WARNING;
28 0 : }
29 :
30 0 : 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 0 : assert(connection);
36 :
37 0 : _cleanup_(MHD_destroy_responsep) struct MHD_Response *response
38 0 : = MHD_create_response_from_buffer(size, (char*) buffer, mode);
39 0 : if (!response)
40 0 : return MHD_NO;
41 :
42 0 : log_debug("Queueing response %u: %s", code, buffer);
43 0 : MHD_add_response_header(response, "Content-Type", "text/plain");
44 0 : return MHD_queue_response(connection, code, response);
45 : }
46 :
47 0 : int mhd_respond(struct MHD_Connection *connection,
48 : enum MHD_RequestTerminationCode code,
49 : const char *message) {
50 :
51 : const char *fmt;
52 :
53 0 : fmt = strjoina(message, "\n");
54 :
55 0 : return mhd_respond_internal(connection, code,
56 0 : fmt, strlen(message) + 1,
57 : MHD_RESPMEM_PERSISTENT);
58 : }
59 :
60 0 : int mhd_respond_oom(struct MHD_Connection *connection) {
61 0 : return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE, "Out of memory.");
62 : }
63 :
64 0 : 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 0 : assert(connection);
75 0 : assert(format);
76 :
77 0 : if (error < 0)
78 0 : error = -error;
79 0 : errno = -error;
80 0 : fmt = strjoina(format, "\n");
81 0 : va_start(ap, format);
82 : #pragma GCC diagnostic push
83 : #pragma GCC diagnostic ignored "-Wformat-nonliteral"
84 0 : r = vasprintf(&m, fmt, ap);
85 : #pragma GCC diagnostic pop
86 0 : va_end(ap);
87 :
88 0 : if (r < 0)
89 0 : return respond_oom(connection);
90 :
91 0 : 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 0 : static void log_func_gnutls(int level, const char *message) {
114 0 : assert_se(message);
115 :
116 0 : if (0 <= level && level < (int) ELEMENTSOF(gnutls_log_map)) {
117 0 : if (gnutls_log_map[level].enabled)
118 0 : 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 0 : log_debug("Received GNUTLS message with unknown level %d.", level);
121 0 : log_internal(LOG_DEBUG, 0, NULL, 0, NULL, "gnutls: %s", message);
122 : }
123 0 : }
124 :
125 0 : static void log_reset_gnutls_level(void) {
126 : int i;
127 :
128 0 : for (i = ELEMENTSOF(gnutls_log_map) - 1; i >= 0; i--)
129 0 : if (gnutls_log_map[i].enabled) {
130 0 : log_debug("Setting gnutls log level to %d", i);
131 0 : gnutls_global_set_log_level(i);
132 0 : break;
133 : }
134 0 : }
135 :
136 0 : static int log_enable_gnutls_category(const char *cat) {
137 : unsigned i;
138 :
139 0 : if (streq(cat, "all")) {
140 0 : for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
141 0 : gnutls_log_map[i].enabled = true;
142 0 : log_reset_gnutls_level();
143 0 : return 0;
144 : } else
145 0 : for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
146 0 : if (strv_contains((char**)gnutls_log_map[i].names, cat)) {
147 0 : gnutls_log_map[i].enabled = true;
148 0 : log_reset_gnutls_level();
149 0 : return 0;
150 : }
151 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No such log category: %s", cat);
152 : }
153 :
154 0 : int setup_gnutls_logger(char **categories) {
155 : char **cat;
156 : int r;
157 :
158 0 : gnutls_global_set_log_function(log_func_gnutls);
159 :
160 0 : if (categories) {
161 0 : STRV_FOREACH(cat, categories) {
162 0 : r = log_enable_gnutls_category(*cat);
163 0 : if (r < 0)
164 0 : return r;
165 : }
166 : } else
167 0 : log_reset_gnutls_level();
168 :
169 0 : return 0;
170 : }
171 :
172 0 : 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 0 : r = gnutls_certificate_verify_peers2(session, &status);
179 0 : if (r < 0)
180 0 : return log_error_errno(r, "gnutls_certificate_verify_peers2 failed: %m");
181 :
182 0 : type = gnutls_certificate_type_get(session);
183 0 : r = gnutls_certificate_verification_status_print(status, type, &out, 0);
184 0 : if (r < 0)
185 0 : return log_error_errno(r, "gnutls_certificate_verification_status_print failed: %m");
186 :
187 0 : log_debug("Certificate status: %s", out.data);
188 0 : gnutls_free(out.data);
189 :
190 0 : return status == 0 ? 0 : -EPERM;
191 : }
192 :
193 0 : 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 0 : assert(session);
200 0 : assert(client_cert);
201 :
202 0 : pcert = gnutls_certificate_get_peers(session, &listsize);
203 0 : if (!pcert || !listsize)
204 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
205 : "Failed to retrieve certificate chain");
206 :
207 0 : r = gnutls_x509_crt_init(&cert);
208 0 : if (r < 0) {
209 0 : log_error("Failed to initialize client certificate");
210 0 : 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 0 : r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER);
216 0 : if (r < 0) {
217 0 : log_error("Failed to import client certificate");
218 0 : gnutls_x509_crt_deinit(cert);
219 0 : return r;
220 : }
221 :
222 0 : *client_cert = cert;
223 0 : return 0;
224 : }
225 :
226 0 : static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) {
227 0 : size_t len = 0;
228 : int r;
229 :
230 0 : assert(buf);
231 0 : assert(*buf == NULL);
232 :
233 0 : r = gnutls_x509_crt_get_dn(client_cert, NULL, &len);
234 0 : if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) {
235 0 : log_error("gnutls_x509_crt_get_dn failed");
236 0 : return r;
237 : }
238 :
239 0 : *buf = malloc(len);
240 0 : if (!*buf)
241 0 : return log_oom();
242 :
243 0 : gnutls_x509_crt_get_dn(client_cert, *buf, &len);
244 0 : return 0;
245 : }
246 :
247 0 : static void gnutls_x509_crt_deinitp(gnutls_x509_crt_t *p) {
248 0 : gnutls_x509_crt_deinit(*p);
249 0 : }
250 :
251 0 : int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
252 : const union MHD_ConnectionInfo *ci;
253 : gnutls_session_t session;
254 0 : _cleanup_(gnutls_x509_crt_deinitp) gnutls_x509_crt_t client_cert = NULL;
255 0 : _cleanup_free_ char *buf = NULL;
256 : int r;
257 :
258 0 : assert(connection);
259 0 : assert(code);
260 :
261 0 : *code = 0;
262 :
263 0 : ci = MHD_get_connection_info(connection,
264 : MHD_CONNECTION_INFO_GNUTLS_SESSION);
265 0 : if (!ci) {
266 0 : log_error("MHD_get_connection_info failed: session is unencrypted");
267 0 : *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN,
268 : "Encrypted connection is required");
269 0 : return -EPERM;
270 : }
271 0 : session = ci->tls_session;
272 0 : assert(session);
273 :
274 0 : r = get_client_cert(session, &client_cert);
275 0 : if (r < 0) {
276 0 : *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
277 : "Authorization through certificate is required");
278 0 : return -EPERM;
279 : }
280 :
281 0 : r = get_auth_dn(client_cert, &buf);
282 0 : if (r < 0) {
283 0 : *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
284 : "Failed to determine distinguished name from certificate");
285 0 : return -EPERM;
286 : }
287 :
288 0 : log_debug("Connection from %s", buf);
289 :
290 0 : if (hostname)
291 0 : *hostname = TAKE_PTR(buf);
292 :
293 0 : r = verify_cert_authorized(session);
294 0 : if (r < 0) {
295 0 : log_warning("Client is not authorized");
296 0 : *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
297 : "Client certificate not signed by recognized authority");
298 : }
299 0 : 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
|