1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
7 Copyright 2012 Zbigniew Jędrzejewski-Szmek
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.
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.
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/>.
28 #include <gnutls/gnutls.h>
29 #include <gnutls/x509.h>
32 #include "alloc-util.h"
35 #include "microhttpd-util.h"
36 #include "string-util.h"
40 void microhttpd_logger(void *arg
, const char *fmt
, va_list ap
) {
43 f
= strjoina("microhttpd: ", fmt
);
45 DISABLE_WARNING_FORMAT_NONLITERAL
;
46 log_internalv(LOG_INFO
, 0, NULL
, 0, NULL
, f
, ap
);
51 static int mhd_respond_internal(struct MHD_Connection
*connection
,
52 enum MHD_RequestTerminationCode code
,
55 enum MHD_ResponseMemoryMode mode
) {
56 struct MHD_Response
*response
;
61 response
= MHD_create_response_from_buffer(size
, buffer
, mode
);
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
);
73 int mhd_respond(struct MHD_Connection
*connection
,
74 enum MHD_RequestTerminationCode code
,
75 const char *message
) {
77 return mhd_respond_internal(connection
, code
,
78 (char*) message
, strlen(message
),
79 MHD_RESPMEM_PERSISTENT
);
82 int mhd_respond_oom(struct MHD_Connection
*connection
) {
83 return mhd_respond(connection
, MHD_HTTP_SERVICE_UNAVAILABLE
, "Out of memory.\n");
86 int mhd_respondf(struct MHD_Connection
*connection
,
87 enum MHD_RequestTerminationCode code
,
88 const char *format
, ...) {
98 r
= vasprintf(&m
, format
, ap
);
102 return respond_oom(connection
);
104 return mhd_respond_internal(connection
, code
, m
, r
, MHD_RESPMEM_MUST_FREE
);
110 const char *const names
[4];
113 } gnutls_log_map
[] = {
114 { {"0"}, LOG_DEBUG
},
115 { {"1", "audit"}, LOG_WARNING
, true}, /* gnutls session audit */
116 { {"2", "assert"}, LOG_DEBUG
}, /* gnutls assert log */
117 { {"3", "hsk", "ext"}, LOG_DEBUG
}, /* gnutls handshake log */
118 { {"4", "rec"}, LOG_DEBUG
}, /* gnutls record log */
119 { {"5", "dtls"}, LOG_DEBUG
}, /* gnutls DTLS log */
120 { {"6", "buf"}, LOG_DEBUG
},
121 { {"7", "write", "read"}, LOG_DEBUG
},
122 { {"8"}, LOG_DEBUG
},
123 { {"9", "enc", "int"}, LOG_DEBUG
},
126 static void log_func_gnutls(int level
, const char *message
) {
129 if (0 <= level
&& level
< (int) ELEMENTSOF(gnutls_log_map
)) {
130 if (gnutls_log_map
[level
].enabled
)
131 log_internal(gnutls_log_map
[level
].level
, 0, NULL
, 0, NULL
, "gnutls %d/%s: %s", level
, gnutls_log_map
[level
].names
[1], message
);
133 log_debug("Received GNUTLS message with unknown level %d.", level
);
134 log_internal(LOG_DEBUG
, 0, NULL
, 0, NULL
, "gnutls: %s", message
);
138 static void log_reset_gnutls_level(void) {
141 for (i
= ELEMENTSOF(gnutls_log_map
) - 1; i
>= 0; i
--)
142 if (gnutls_log_map
[i
].enabled
) {
143 log_debug("Setting gnutls log level to %d", i
);
144 gnutls_global_set_log_level(i
);
149 static int log_enable_gnutls_category(const char *cat
) {
152 if (streq(cat
, "all")) {
153 for (i
= 0; i
< ELEMENTSOF(gnutls_log_map
); i
++)
154 gnutls_log_map
[i
].enabled
= true;
155 log_reset_gnutls_level();
158 for (i
= 0; i
< ELEMENTSOF(gnutls_log_map
); i
++)
159 if (strv_contains((char**)gnutls_log_map
[i
].names
, cat
)) {
160 gnutls_log_map
[i
].enabled
= true;
161 log_reset_gnutls_level();
164 log_error("No such log category: %s", cat
);
168 int setup_gnutls_logger(char **categories
) {
172 gnutls_global_set_log_function(log_func_gnutls
);
175 STRV_FOREACH(cat
, categories
) {
176 r
= log_enable_gnutls_category(*cat
);
181 log_reset_gnutls_level();
186 static int verify_cert_authorized(gnutls_session_t session
) {
188 gnutls_certificate_type_t type
;
192 r
= gnutls_certificate_verify_peers2(session
, &status
);
194 return log_error_errno(r
, "gnutls_certificate_verify_peers2 failed: %m");
196 type
= gnutls_certificate_type_get(session
);
197 r
= gnutls_certificate_verification_status_print(status
, type
, &out
, 0);
199 return log_error_errno(r
, "gnutls_certificate_verification_status_print failed: %m");
201 log_debug("Certificate status: %s", out
.data
);
202 gnutls_free(out
.data
);
204 return status
== 0 ? 0 : -EPERM
;
207 static int get_client_cert(gnutls_session_t session
, gnutls_x509_crt_t
*client_cert
) {
208 const gnutls_datum_t
*pcert
;
210 gnutls_x509_crt_t cert
;
216 pcert
= gnutls_certificate_get_peers(session
, &listsize
);
217 if (!pcert
|| !listsize
) {
218 log_error("Failed to retrieve certificate chain");
222 r
= gnutls_x509_crt_init(&cert
);
224 log_error("Failed to initialize client certificate");
228 /* Note that by passing values between 0 and listsize here, you
229 can get access to the CA's certs */
230 r
= gnutls_x509_crt_import(cert
, &pcert
[0], GNUTLS_X509_FMT_DER
);
232 log_error("Failed to import client certificate");
233 gnutls_x509_crt_deinit(cert
);
241 static int get_auth_dn(gnutls_x509_crt_t client_cert
, char **buf
) {
246 assert(*buf
== NULL
);
248 r
= gnutls_x509_crt_get_dn(client_cert
, NULL
, &len
);
249 if (r
!= GNUTLS_E_SHORT_MEMORY_BUFFER
) {
250 log_error("gnutls_x509_crt_get_dn failed");
258 gnutls_x509_crt_get_dn(client_cert
, *buf
, &len
);
262 static inline void gnutls_x509_crt_deinitp(gnutls_x509_crt_t
*p
) {
263 gnutls_x509_crt_deinit(*p
);
266 int check_permissions(struct MHD_Connection
*connection
, int *code
, char **hostname
) {
267 const union MHD_ConnectionInfo
*ci
;
268 gnutls_session_t session
;
269 _cleanup_(gnutls_x509_crt_deinitp
) gnutls_x509_crt_t client_cert
= NULL
;
270 _cleanup_free_
char *buf
= NULL
;
278 ci
= MHD_get_connection_info(connection
,
279 MHD_CONNECTION_INFO_GNUTLS_SESSION
);
281 log_error("MHD_get_connection_info failed: session is unencrypted");
282 *code
= mhd_respond(connection
, MHD_HTTP_FORBIDDEN
,
283 "Encrypted connection is required");
286 session
= ci
->tls_session
;
289 r
= get_client_cert(session
, &client_cert
);
291 *code
= mhd_respond(connection
, MHD_HTTP_UNAUTHORIZED
,
292 "Authorization through certificate is required");
296 r
= get_auth_dn(client_cert
, &buf
);
298 *code
= mhd_respond(connection
, MHD_HTTP_UNAUTHORIZED
,
299 "Failed to determine distinguished name from certificate");
303 log_debug("Connection from %s", buf
);
310 r
= verify_cert_authorized(session
);
312 log_warning("Client is not authorized");
313 *code
= mhd_respond(connection
, MHD_HTTP_UNAUTHORIZED
,
314 "Client certificate not signed by recognized authority");
320 int check_permissions(struct MHD_Connection
*connection
, int *code
, char **hostname
) {
324 int setup_gnutls_logger(char **categories
) {
326 log_notice("Ignoring specified gnutls logging categories — gnutls not available.");