2 This file is part of systemd.
4 Copyright 2012 Lennart Poettering
5 Copyright 2012 Zbigniew Jędrzejewski-Szmek
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 #include <gnutls/gnutls.h>
27 #include <gnutls/x509.h>
30 #include "alloc-util.h"
33 #include "microhttpd-util.h"
34 #include "string-util.h"
38 void microhttpd_logger(void *arg
, const char *fmt
, va_list ap
) {
41 f
= strjoina("microhttpd: ", fmt
);
43 DISABLE_WARNING_FORMAT_NONLITERAL
;
44 log_internalv(LOG_INFO
, 0, NULL
, 0, NULL
, f
, ap
);
49 static int mhd_respond_internal(struct MHD_Connection
*connection
,
50 enum MHD_RequestTerminationCode code
,
53 enum MHD_ResponseMemoryMode mode
) {
54 struct MHD_Response
*response
;
59 response
= MHD_create_response_from_buffer(size
, (char*) buffer
, mode
);
63 log_debug("Queueing response %u: %s", code
, buffer
);
64 MHD_add_response_header(response
, "Content-Type", "text/plain");
65 r
= MHD_queue_response(connection
, code
, response
);
66 MHD_destroy_response(response
);
71 int mhd_respond(struct MHD_Connection
*connection
,
72 enum MHD_RequestTerminationCode code
,
73 const char *message
) {
77 fmt
= strjoina(message
, "\n");
79 return mhd_respond_internal(connection
, code
,
80 fmt
, strlen(message
) + 1,
81 MHD_RESPMEM_PERSISTENT
);
84 int mhd_respond_oom(struct MHD_Connection
*connection
) {
85 return mhd_respond(connection
, MHD_HTTP_SERVICE_UNAVAILABLE
, "Out of memory.");
88 int mhd_respondf(struct MHD_Connection
*connection
,
90 enum MHD_RequestTerminationCode code
,
91 const char *format
, ...) {
104 fmt
= strjoina(format
, "\n");
105 va_start(ap
, format
);
106 #pragma GCC diagnostic push
107 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
108 r
= vasprintf(&m
, fmt
, ap
);
109 #pragma GCC diagnostic pop
113 return respond_oom(connection
);
115 return mhd_respond_internal(connection
, code
, m
, r
, MHD_RESPMEM_MUST_FREE
);
121 const char *const names
[4];
124 } gnutls_log_map
[] = {
125 { {"0"}, LOG_DEBUG
},
126 { {"1", "audit"}, LOG_WARNING
, true}, /* gnutls session audit */
127 { {"2", "assert"}, LOG_DEBUG
}, /* gnutls assert log */
128 { {"3", "hsk", "ext"}, LOG_DEBUG
}, /* gnutls handshake log */
129 { {"4", "rec"}, LOG_DEBUG
}, /* gnutls record log */
130 { {"5", "dtls"}, LOG_DEBUG
}, /* gnutls DTLS log */
131 { {"6", "buf"}, LOG_DEBUG
},
132 { {"7", "write", "read"}, LOG_DEBUG
},
133 { {"8"}, LOG_DEBUG
},
134 { {"9", "enc", "int"}, LOG_DEBUG
},
137 static void log_func_gnutls(int level
, const char *message
) {
140 if (0 <= level
&& level
< (int) ELEMENTSOF(gnutls_log_map
)) {
141 if (gnutls_log_map
[level
].enabled
)
142 log_internal(gnutls_log_map
[level
].level
, 0, NULL
, 0, NULL
, "gnutls %d/%s: %s", level
, gnutls_log_map
[level
].names
[1], message
);
144 log_debug("Received GNUTLS message with unknown level %d.", level
);
145 log_internal(LOG_DEBUG
, 0, NULL
, 0, NULL
, "gnutls: %s", message
);
149 static void log_reset_gnutls_level(void) {
152 for (i
= ELEMENTSOF(gnutls_log_map
) - 1; i
>= 0; i
--)
153 if (gnutls_log_map
[i
].enabled
) {
154 log_debug("Setting gnutls log level to %d", i
);
155 gnutls_global_set_log_level(i
);
160 static int log_enable_gnutls_category(const char *cat
) {
163 if (streq(cat
, "all")) {
164 for (i
= 0; i
< ELEMENTSOF(gnutls_log_map
); i
++)
165 gnutls_log_map
[i
].enabled
= true;
166 log_reset_gnutls_level();
169 for (i
= 0; i
< ELEMENTSOF(gnutls_log_map
); i
++)
170 if (strv_contains((char**)gnutls_log_map
[i
].names
, cat
)) {
171 gnutls_log_map
[i
].enabled
= true;
172 log_reset_gnutls_level();
175 log_error("No such log category: %s", cat
);
179 int setup_gnutls_logger(char **categories
) {
183 gnutls_global_set_log_function(log_func_gnutls
);
186 STRV_FOREACH(cat
, categories
) {
187 r
= log_enable_gnutls_category(*cat
);
192 log_reset_gnutls_level();
197 static int verify_cert_authorized(gnutls_session_t session
) {
199 gnutls_certificate_type_t type
;
203 r
= gnutls_certificate_verify_peers2(session
, &status
);
205 return log_error_errno(r
, "gnutls_certificate_verify_peers2 failed: %m");
207 type
= gnutls_certificate_type_get(session
);
208 r
= gnutls_certificate_verification_status_print(status
, type
, &out
, 0);
210 return log_error_errno(r
, "gnutls_certificate_verification_status_print failed: %m");
212 log_debug("Certificate status: %s", out
.data
);
213 gnutls_free(out
.data
);
215 return status
== 0 ? 0 : -EPERM
;
218 static int get_client_cert(gnutls_session_t session
, gnutls_x509_crt_t
*client_cert
) {
219 const gnutls_datum_t
*pcert
;
221 gnutls_x509_crt_t cert
;
227 pcert
= gnutls_certificate_get_peers(session
, &listsize
);
228 if (!pcert
|| !listsize
) {
229 log_error("Failed to retrieve certificate chain");
233 r
= gnutls_x509_crt_init(&cert
);
235 log_error("Failed to initialize client certificate");
239 /* Note that by passing values between 0 and listsize here, you
240 can get access to the CA's certs */
241 r
= gnutls_x509_crt_import(cert
, &pcert
[0], GNUTLS_X509_FMT_DER
);
243 log_error("Failed to import client certificate");
244 gnutls_x509_crt_deinit(cert
);
252 static int get_auth_dn(gnutls_x509_crt_t client_cert
, char **buf
) {
257 assert(*buf
== NULL
);
259 r
= gnutls_x509_crt_get_dn(client_cert
, NULL
, &len
);
260 if (r
!= GNUTLS_E_SHORT_MEMORY_BUFFER
) {
261 log_error("gnutls_x509_crt_get_dn failed");
269 gnutls_x509_crt_get_dn(client_cert
, *buf
, &len
);
273 static inline void gnutls_x509_crt_deinitp(gnutls_x509_crt_t
*p
) {
274 gnutls_x509_crt_deinit(*p
);
277 int check_permissions(struct MHD_Connection
*connection
, int *code
, char **hostname
) {
278 const union MHD_ConnectionInfo
*ci
;
279 gnutls_session_t session
;
280 _cleanup_(gnutls_x509_crt_deinitp
) gnutls_x509_crt_t client_cert
= NULL
;
281 _cleanup_free_
char *buf
= NULL
;
289 ci
= MHD_get_connection_info(connection
,
290 MHD_CONNECTION_INFO_GNUTLS_SESSION
);
292 log_error("MHD_get_connection_info failed: session is unencrypted");
293 *code
= mhd_respond(connection
, MHD_HTTP_FORBIDDEN
,
294 "Encrypted connection is required");
297 session
= ci
->tls_session
;
300 r
= get_client_cert(session
, &client_cert
);
302 *code
= mhd_respond(connection
, MHD_HTTP_UNAUTHORIZED
,
303 "Authorization through certificate is required");
307 r
= get_auth_dn(client_cert
, &buf
);
309 *code
= mhd_respond(connection
, MHD_HTTP_UNAUTHORIZED
,
310 "Failed to determine distinguished name from certificate");
314 log_debug("Connection from %s", buf
);
321 r
= verify_cert_authorized(session
);
323 log_warning("Client is not authorized");
324 *code
= mhd_respond(connection
, MHD_HTTP_UNAUTHORIZED
,
325 "Client certificate not signed by recognized authority");
331 int check_permissions(struct MHD_Connection
*connection
, int *code
, char **hostname
) {
335 int setup_gnutls_logger(char **categories
) {
337 log_notice("Ignoring specified gnutls logging categories — gnutls not available.");