Branch data 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
|