1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2012 Lennart Poettering
6 Copyright 2012 Zbigniew Jędrzejewski-Szmek
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
27 #include <gnutls/gnutls.h>
28 #include <gnutls/x509.h>
31 #include "alloc-util.h"
34 #include "microhttpd-util.h"
35 #include "string-util.h"
39 void microhttpd_logger(void *arg
, const char *fmt
, va_list ap
) {
42 f
= strjoina("microhttpd: ", fmt
);
44 DISABLE_WARNING_FORMAT_NONLITERAL
;
45 log_internalv(LOG_INFO
, 0, NULL
, 0, NULL
, f
, ap
);
50 static int mhd_respond_internal(struct MHD_Connection
*connection
,
51 enum MHD_RequestTerminationCode code
,
54 enum MHD_ResponseMemoryMode mode
) {
55 struct MHD_Response
*response
;
60 response
= MHD_create_response_from_buffer(size
, (char*) buffer
, mode
);
64 log_debug("Queueing response %u: %s", code
, buffer
);
65 MHD_add_response_header(response
, "Content-Type", "text/plain");
66 r
= MHD_queue_response(connection
, code
, response
);
67 MHD_destroy_response(response
);
72 int mhd_respond(struct MHD_Connection
*connection
,
73 enum MHD_RequestTerminationCode code
,
74 const char *message
) {
78 fmt
= strjoina(message
, "\n");
80 return mhd_respond_internal(connection
, code
,
81 fmt
, strlen(message
) + 1,
82 MHD_RESPMEM_PERSISTENT
);
85 int mhd_respond_oom(struct MHD_Connection
*connection
) {
86 return mhd_respond(connection
, MHD_HTTP_SERVICE_UNAVAILABLE
, "Out of memory.");
89 int mhd_respondf(struct MHD_Connection
*connection
,
91 enum MHD_RequestTerminationCode code
,
92 const char *format
, ...) {
105 fmt
= strjoina(format
, "\n");
106 va_start(ap
, format
);
107 #pragma GCC diagnostic push
108 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
109 r
= vasprintf(&m
, fmt
, ap
);
110 #pragma GCC diagnostic pop
114 return respond_oom(connection
);
116 return mhd_respond_internal(connection
, code
, m
, r
, MHD_RESPMEM_MUST_FREE
);
122 const char *const names
[4];
125 } gnutls_log_map
[] = {
126 { {"0"}, LOG_DEBUG
},
127 { {"1", "audit"}, LOG_WARNING
, true}, /* gnutls session audit */
128 { {"2", "assert"}, LOG_DEBUG
}, /* gnutls assert log */
129 { {"3", "hsk", "ext"}, LOG_DEBUG
}, /* gnutls handshake log */
130 { {"4", "rec"}, LOG_DEBUG
}, /* gnutls record log */
131 { {"5", "dtls"}, LOG_DEBUG
}, /* gnutls DTLS log */
132 { {"6", "buf"}, LOG_DEBUG
},
133 { {"7", "write", "read"}, LOG_DEBUG
},
134 { {"8"}, LOG_DEBUG
},
135 { {"9", "enc", "int"}, LOG_DEBUG
},
138 static void log_func_gnutls(int level
, const char *message
) {
141 if (0 <= level
&& level
< (int) ELEMENTSOF(gnutls_log_map
)) {
142 if (gnutls_log_map
[level
].enabled
)
143 log_internal(gnutls_log_map
[level
].level
, 0, NULL
, 0, NULL
, "gnutls %d/%s: %s", level
, gnutls_log_map
[level
].names
[1], message
);
145 log_debug("Received GNUTLS message with unknown level %d.", level
);
146 log_internal(LOG_DEBUG
, 0, NULL
, 0, NULL
, "gnutls: %s", message
);
150 static void log_reset_gnutls_level(void) {
153 for (i
= ELEMENTSOF(gnutls_log_map
) - 1; i
>= 0; i
--)
154 if (gnutls_log_map
[i
].enabled
) {
155 log_debug("Setting gnutls log level to %d", i
);
156 gnutls_global_set_log_level(i
);
161 static int log_enable_gnutls_category(const char *cat
) {
164 if (streq(cat
, "all")) {
165 for (i
= 0; i
< ELEMENTSOF(gnutls_log_map
); i
++)
166 gnutls_log_map
[i
].enabled
= true;
167 log_reset_gnutls_level();
170 for (i
= 0; i
< ELEMENTSOF(gnutls_log_map
); i
++)
171 if (strv_contains((char**)gnutls_log_map
[i
].names
, cat
)) {
172 gnutls_log_map
[i
].enabled
= true;
173 log_reset_gnutls_level();
176 log_error("No such log category: %s", cat
);
180 int setup_gnutls_logger(char **categories
) {
184 gnutls_global_set_log_function(log_func_gnutls
);
187 STRV_FOREACH(cat
, categories
) {
188 r
= log_enable_gnutls_category(*cat
);
193 log_reset_gnutls_level();
198 static int verify_cert_authorized(gnutls_session_t session
) {
200 gnutls_certificate_type_t type
;
204 r
= gnutls_certificate_verify_peers2(session
, &status
);
206 return log_error_errno(r
, "gnutls_certificate_verify_peers2 failed: %m");
208 type
= gnutls_certificate_type_get(session
);
209 r
= gnutls_certificate_verification_status_print(status
, type
, &out
, 0);
211 return log_error_errno(r
, "gnutls_certificate_verification_status_print failed: %m");
213 log_debug("Certificate status: %s", out
.data
);
214 gnutls_free(out
.data
);
216 return status
== 0 ? 0 : -EPERM
;
219 static int get_client_cert(gnutls_session_t session
, gnutls_x509_crt_t
*client_cert
) {
220 const gnutls_datum_t
*pcert
;
222 gnutls_x509_crt_t cert
;
228 pcert
= gnutls_certificate_get_peers(session
, &listsize
);
229 if (!pcert
|| !listsize
) {
230 log_error("Failed to retrieve certificate chain");
234 r
= gnutls_x509_crt_init(&cert
);
236 log_error("Failed to initialize client certificate");
240 /* Note that by passing values between 0 and listsize here, you
241 can get access to the CA's certs */
242 r
= gnutls_x509_crt_import(cert
, &pcert
[0], GNUTLS_X509_FMT_DER
);
244 log_error("Failed to import client certificate");
245 gnutls_x509_crt_deinit(cert
);
253 static int get_auth_dn(gnutls_x509_crt_t client_cert
, char **buf
) {
258 assert(*buf
== NULL
);
260 r
= gnutls_x509_crt_get_dn(client_cert
, NULL
, &len
);
261 if (r
!= GNUTLS_E_SHORT_MEMORY_BUFFER
) {
262 log_error("gnutls_x509_crt_get_dn failed");
270 gnutls_x509_crt_get_dn(client_cert
, *buf
, &len
);
274 static inline void gnutls_x509_crt_deinitp(gnutls_x509_crt_t
*p
) {
275 gnutls_x509_crt_deinit(*p
);
278 int check_permissions(struct MHD_Connection
*connection
, int *code
, char **hostname
) {
279 const union MHD_ConnectionInfo
*ci
;
280 gnutls_session_t session
;
281 _cleanup_(gnutls_x509_crt_deinitp
) gnutls_x509_crt_t client_cert
= NULL
;
282 _cleanup_free_
char *buf
= NULL
;
290 ci
= MHD_get_connection_info(connection
,
291 MHD_CONNECTION_INFO_GNUTLS_SESSION
);
293 log_error("MHD_get_connection_info failed: session is unencrypted");
294 *code
= mhd_respond(connection
, MHD_HTTP_FORBIDDEN
,
295 "Encrypted connection is required");
298 session
= ci
->tls_session
;
301 r
= get_client_cert(session
, &client_cert
);
303 *code
= mhd_respond(connection
, MHD_HTTP_UNAUTHORIZED
,
304 "Authorization through certificate is required");
308 r
= get_auth_dn(client_cert
, &buf
);
310 *code
= mhd_respond(connection
, MHD_HTTP_UNAUTHORIZED
,
311 "Failed to determine distinguished name from certificate");
315 log_debug("Connection from %s", buf
);
322 r
= verify_cert_authorized(session
);
324 log_warning("Client is not authorized");
325 *code
= mhd_respond(connection
, MHD_HTTP_UNAUTHORIZED
,
326 "Client certificate not signed by recognized authority");
332 int check_permissions(struct MHD_Connection
*connection
, int *code
, char **hostname
) {
336 int setup_gnutls_logger(char **categories
) {
338 log_notice("Ignoring specified gnutls logging categories — gnutls not available.");