Line data Source code
1 : /*********************************************************************
2 : *
3 : * File : $Source: /cvsroot/ijbswa/current/ssl_common.c,v $
4 : *
5 : * Purpose : File with TLS/SSL extension. Contains methods for
6 : * creating, using and closing TLS/SSL connections that do
7 : * not depend on particular TLS/SSL library.
8 : *
9 : * Copyright : Written by and Copyright (c) 2017 Vaclav Svec. FIT CVUT.
10 : * Copyright (C) 2018-2021 by Fabian Keil <fk@fabiankeil.de>
11 : *
12 : * This program is free software; you can redistribute it
13 : * and/or modify it under the terms of the GNU General
14 : * Public License as published by the Free Software
15 : * Foundation; either version 2 of the License, or (at
16 : * your option) any later version.
17 : *
18 : * This program is distributed in the hope that it will
19 : * be useful, but WITHOUT ANY WARRANTY; without even the
20 : * implied warranty of MERCHANTABILITY or FITNESS FOR A
21 : * PARTICULAR PURPOSE. See the GNU General Public
22 : * License for more details.
23 : *
24 : * The GNU General Public License should be included with
25 : * this file. If not, you can view it at
26 : * http://www.gnu.org/copyleft/gpl.html
27 : * or write to the Free Software Foundation, Inc., 59
28 : * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 : *
30 : *********************************************************************/
31 : #include <string.h>
32 :
33 : #include <ctype.h>
34 : #include <unistd.h>
35 : #include "config.h"
36 : #include "project.h"
37 : #include "miscutil.h"
38 : #include "errlog.h"
39 : #include "ssl.h"
40 : #include "ssl_common.h"
41 :
42 : /*
43 : * Macros for ssl_common.c
44 : */
45 : #define CERT_SERIAL_NUM_LENGTH 4 /* Bytes of hash to be used for creating serial number of certificate. Min=2 and max=16 */
46 :
47 : /*********************************************************************
48 : *
49 : * Function : client_use_ssl
50 : *
51 : * Description : Tests if client in current client state structure
52 : * should use SSL connection or standard connection.
53 : *
54 : * Parameters :
55 : * 1 : csp = Current client state (buffers, headers, etc...)
56 : *
57 : * Returns : If client should use TLS/SSL connection, 1 is returned.
58 : * Otherwise 0 is returned.
59 : *
60 : *********************************************************************/
61 207981 : extern int client_use_ssl(const struct client_state *csp)
62 : {
63 207981 : return csp->http->client_ssl;
64 : }
65 :
66 :
67 : /*********************************************************************
68 : *
69 : * Function : server_use_ssl
70 : *
71 : * Description : Tests if server in current client state structure
72 : * should use SSL connection or standard connection.
73 : *
74 : * Parameters :
75 : * 1 : csp = Current client state (buffers, headers, etc...)
76 : *
77 : * Returns : If server should use TLS/SSL connection, 1 is returned.
78 : * Otherwise 0 is returned.
79 : *
80 : *********************************************************************/
81 533 : extern int server_use_ssl(const struct client_state *csp)
82 : {
83 533 : return csp->http->server_ssl;
84 : }
85 :
86 :
87 : /*********************************************************************
88 : *
89 : * Function : ssl_send_data_delayed
90 : *
91 : * Description : Sends the contents of buf (for n bytes) to given SSL
92 : * connection, optionally delaying the operation.
93 : *
94 : * Parameters :
95 : * 1 : ssl_attr = SSL context to send data to
96 : * 2 : buf = Pointer to data to be sent
97 : * 3 : len = Length of data to be sent to the SSL context
98 : * 4 : delay = Delay in milliseconds.
99 : *
100 : * Returns : 0 on success (entire buffer sent).
101 : * nonzero on error.
102 : *
103 : *********************************************************************/
104 4452 : extern int ssl_send_data_delayed(struct ssl_attr* ssl_attr,
105 : const unsigned char *buf, size_t len,
106 : unsigned int delay)
107 : {
108 4452 : size_t i = 0;
109 :
110 4452 : if (delay == 0)
111 : {
112 4452 : if (ssl_send_data(ssl_attr, buf, len) < 0)
113 : {
114 0 : return -1;
115 : }
116 : else
117 : {
118 4452 : return 0;
119 : }
120 : }
121 :
122 0 : while (i < len)
123 : {
124 : size_t write_length;
125 : enum { MAX_WRITE_LENGTH = 10 };
126 :
127 0 : if ((i + MAX_WRITE_LENGTH) > len)
128 : {
129 0 : write_length = len - i;
130 : }
131 : else
132 : {
133 0 : write_length = MAX_WRITE_LENGTH;
134 : }
135 :
136 0 : privoxy_millisleep(delay);
137 :
138 0 : if (ssl_send_data(ssl_attr, buf + i, write_length) < 0)
139 : {
140 0 : return -1;
141 : }
142 0 : i += write_length;
143 : }
144 :
145 0 : return 0;
146 :
147 : }
148 :
149 :
150 : /*********************************************************************
151 : *
152 : * Function : ssl_flush_socket
153 : *
154 : * Description : Send any pending "buffered" content with given
155 : * SSL connection. Alternative to function flush_socket.
156 : *
157 : * Parameters :
158 : * 1 : ssl_attr = SSL context to send buffer to
159 : * 2 : iob = The I/O buffer to flush, usually csp->iob.
160 : *
161 : * Returns : On success, the number of bytes send are returned (zero
162 : * indicates nothing was sent). On error, -1 is returned.
163 : *
164 : *********************************************************************/
165 208 : extern long ssl_flush_socket(struct ssl_attr *ssl_attr, struct iob *iob)
166 : {
167 : /* Computing length of buffer part to send */
168 208 : long len = iob->eod - iob->cur;
169 :
170 208 : if (len <= 0)
171 : {
172 132 : return(0);
173 : }
174 :
175 : /* Sending data to given SSl context */
176 76 : if (ssl_send_data(ssl_attr, (const unsigned char *)iob->cur, (size_t)len) < 0)
177 : {
178 0 : return -1;
179 : }
180 76 : iob->eod = iob->cur = iob->buf;
181 76 : return(len);
182 : }
183 :
184 :
185 : /*********************************************************************
186 : *
187 : * Function : close_client_and_server_ssl_connections
188 : *
189 : * Description : Checks if client or server should use secured
190 : * connection over SSL and if so, closes all of them.
191 : *
192 : * Parameters :
193 : * 1 : csp = Current client state (buffers, headers, etc...)
194 : *
195 : * Returns : N/A
196 : *
197 : *********************************************************************/
198 482 : extern void close_client_and_server_ssl_connections(struct client_state *csp)
199 : {
200 482 : if (client_use_ssl(csp) == 1)
201 : {
202 71 : close_client_ssl_connection(csp);
203 : }
204 482 : if (server_use_ssl(csp) == 1)
205 : {
206 71 : close_server_ssl_connection(csp);
207 : }
208 482 : }
209 :
210 :
211 : /*********************************************************************
212 : *
213 : * Function : tunnel_established_successfully
214 : *
215 : * Description : Check if parent proxy server response contains
216 : * information about successfully created connection with
217 : * destination server. (HTTP/... 2xx ...)
218 : *
219 : * Parameters :
220 : * 1 : server_response = Buffer with parent proxy server response
221 : * 2 : response_len = Length of server_response
222 : *
223 : * Returns : 1 => Connection created successfully
224 : * 0 => Connection wasn't created successfully
225 : *
226 : *********************************************************************/
227 0 : extern int tunnel_established_successfully(const char *server_response,
228 : unsigned int response_len)
229 : {
230 0 : unsigned int pos = 0;
231 :
232 0 : if (server_response == NULL)
233 : {
234 0 : return 0;
235 : }
236 :
237 : /* Tests if "HTTP/" string is at the begin of received response */
238 0 : if (strncmp(server_response, "HTTP/", 5) != 0)
239 : {
240 0 : return 0;
241 : }
242 :
243 0 : for (pos = 0; pos < response_len; pos++)
244 : {
245 0 : if (server_response[pos] == ' ')
246 : {
247 0 : break;
248 : }
249 : }
250 :
251 : /*
252 : * response_len -3 because of buffer end, response structure and 200 code.
253 : * There must be at least 3 chars after space.
254 : * End of buffer: ... 2xx'\0'
255 : * pos = |
256 : */
257 0 : if (pos >= (response_len - 3))
258 : {
259 0 : return 0;
260 : }
261 :
262 : /* Test HTTP status code */
263 0 : if (server_response[pos + 1] != '2')
264 : {
265 0 : return 0;
266 : }
267 :
268 0 : return 1;
269 : }
270 :
271 :
272 : /*********************************************************************
273 : *
274 : * Function : free_certificate_chain
275 : *
276 : * Description : Frees certificates linked list. This linked list is
277 : * used to save information about certificates in
278 : * trusted chain.
279 : *
280 : * Parameters :
281 : * 1 : csp = Current client state (buffers, headers, etc...)
282 : *
283 : * Returns : N/A
284 : *
285 : *********************************************************************/
286 0 : extern void free_certificate_chain(struct client_state *csp)
287 : {
288 0 : struct certs_chain *cert = csp->server_certs_chain.next;
289 :
290 : /* Cleaning buffers */
291 0 : memset(csp->server_certs_chain.info_buf, 0,
292 : sizeof(csp->server_certs_chain.info_buf));
293 0 : memset(csp->server_certs_chain.file_buf, 0,
294 : sizeof(csp->server_certs_chain.file_buf));
295 0 : csp->server_certs_chain.next = NULL;
296 :
297 : /* Freeing memory in whole linked list */
298 0 : while (cert != NULL)
299 : {
300 0 : struct certs_chain *cert_for_free = cert;
301 0 : cert = cert->next;
302 0 : freez(cert_for_free);
303 : }
304 0 : }
305 :
306 :
307 : /*********************************************************************
308 : *
309 : * Function : ssl_send_certificate_error
310 : *
311 : * Description : Sends info about invalid server certificate to client.
312 : * Sent message is including all trusted chain certificates,
313 : * that can be downloaded in web browser.
314 : *
315 : * Parameters :
316 : * 1 : csp = Current client state (buffers, headers, etc...)
317 : *
318 : * Returns : N/A
319 : *
320 : *********************************************************************/
321 0 : extern void ssl_send_certificate_error(struct client_state *csp)
322 : {
323 0 : struct ssl_attr *ssl_attr = &csp->ssl_client_attr;
324 0 : size_t message_len = 0;
325 0 : int ret = 0;
326 0 : struct certs_chain *cert = NULL;
327 0 : const size_t head_length = 63;
328 :
329 : /* Header of message with certificate information */
330 0 : const char message_begin[] =
331 : "HTTP/1.1 403 Certificate validation failed\r\n"
332 : "Content-Type: text/html\r\n"
333 : "Connection: close\r\n\r\n"
334 : "<!DOCTYPE html>\n"
335 : "<html><head><title>Server certificate verification failed</title></head>\n"
336 : "<body><h1>Server certificate verification failed</h1>\n"
337 : "<p><a href=\"https://" CGI_SITE_2_HOST "/\">Privoxy</a> was unable "
338 : "to securely connect to the destination server.</p>"
339 : "<p>Reason: ";
340 0 : const char message_end[] = "</body></html>\r\n\r\n";
341 : char reason[INVALID_CERT_INFO_BUF_SIZE];
342 0 : memset(reason, 0, sizeof(reason));
343 :
344 : /* Get verification message from verification return code */
345 0 : ssl_crt_verify_info(reason, sizeof(reason), csp);
346 :
347 : /*
348 : * Computing total length of message with all certificates inside
349 : */
350 0 : message_len = strlen(message_begin) + strlen(message_end)
351 0 : + strlen(reason) + strlen("</p>") + 1;
352 :
353 0 : cert = &(csp->server_certs_chain);
354 0 : while (cert->next != NULL)
355 : {
356 0 : size_t base64_len = 4 * ((strlen(cert->file_buf) + 2) / 3) + 1;
357 :
358 0 : message_len += strlen(cert->info_buf) + strlen("<pre></pre>\n")
359 0 : + base64_len + strlen("<a href=\"data:application"
360 : "/x-x509-ca-cert;base64,\">Download certificate</a>");
361 0 : cert = cert->next;
362 : }
363 :
364 : /*
365 : * Joining all blocks in one long message
366 : */
367 0 : char message[message_len];
368 0 : memset(message, 0, message_len);
369 :
370 0 : strlcpy(message, message_begin, message_len);
371 0 : strlcat(message, reason , message_len);
372 0 : strlcat(message, "</p>" , message_len);
373 :
374 0 : cert = &(csp->server_certs_chain);
375 0 : while (cert->next != NULL)
376 0 : {
377 0 : size_t olen = 0;
378 0 : size_t base64_len = 4 * ((strlen(cert->file_buf) + 2) / 3) + 1; /* +1 for terminating null*/
379 0 : char base64_buf[base64_len];
380 0 : memset(base64_buf, 0, base64_len);
381 :
382 : /* Encoding certificate into base64 code */
383 0 : ret = ssl_base64_encode((unsigned char*)base64_buf,
384 0 : base64_len, &olen, (const unsigned char*)cert->file_buf,
385 0 : strlen(cert->file_buf));
386 0 : if (ret != 0)
387 : {
388 0 : log_error(LOG_LEVEL_ERROR,
389 : "Encoding to base64 failed, buffer is to small");
390 : }
391 :
392 0 : strlcat(message, "<pre>", message_len);
393 0 : strlcat(message, cert->info_buf, message_len);
394 0 : strlcat(message, "</pre>\n", message_len);
395 :
396 0 : if (ret == 0)
397 : {
398 0 : strlcat(message, "<a href=\"data:application/x-x509-ca-cert;base64,",
399 : message_len);
400 0 : strlcat(message, base64_buf, message_len);
401 0 : strlcat(message, "\">Download certificate</a>", message_len);
402 : }
403 :
404 0 : cert = cert->next;
405 : }
406 0 : strlcat(message, message_end, message_len);
407 :
408 : /*
409 : * Sending final message to client
410 : */
411 0 : (void)ssl_send_data(ssl_attr, (const unsigned char *)message, strlen(message));
412 :
413 0 : free_certificate_chain(csp);
414 :
415 0 : log_error(LOG_LEVEL_CRUNCH, "Certificate error: %s: https://%s%s",
416 : reason, csp->http->hostport, csp->http->path);
417 0 : log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s https://%s%s %s\" 403 %lu",
418 : csp->ip_addr_str, csp->http->gpc, csp->http->hostport, csp->http->path,
419 : csp->http->version, message_len-head_length);
420 :
421 : #ifdef FEATURE_CONNECTION_KEEP_ALIVE
422 0 : csp->flags &= ~CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE;
423 0 : csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED;
424 : #endif
425 0 : }
426 :
427 :
428 : /*********************************************************************
429 : *
430 : * Function : file_exists
431 : *
432 : * Description : Tests if file exists and is readable.
433 : *
434 : * Parameters :
435 : * 1 : path = Path to tested file.
436 : *
437 : * Returns : 1 => File exists and is readable.
438 : * 0 => File doesn't exist or is not readable.
439 : *
440 : *********************************************************************/
441 0 : extern int file_exists(const char *path)
442 : {
443 : FILE *f;
444 0 : if ((f = fopen(path, "r")) != NULL)
445 : {
446 0 : fclose(f);
447 0 : return 1;
448 : }
449 :
450 0 : return 0;
451 : }
452 :
453 :
454 : /*********************************************************************
455 : *
456 : * Function : make_certs_path
457 : *
458 : * Description : Creates path to file from three pieces. This function
459 : * takes parameters and puts them in one new mallocated
460 : * char * in correct order. Returned variable must be freed
461 : * by caller. This function is mainly used for creating
462 : * paths of certificates and keys files.
463 : *
464 : * Parameters :
465 : * 1 : conf_dir = Name/path of directory where is the file.
466 : * '.' can be used for current directory.
467 : * 2 : file_name = Name of file in conf_dir without suffix.
468 : * 3 : suffix = Suffix of given file_name.
469 : *
470 : * Returns : path => Path was built up successfully
471 : * NULL => Path can't be built up
472 : *
473 : *********************************************************************/
474 0 : extern char *make_certs_path(const char *conf_dir, const char *file_name,
475 : const char *suffix)
476 : {
477 : /* Test if all given parameters are valid */
478 0 : if (conf_dir == NULL || *conf_dir == '\0' || file_name == NULL ||
479 0 : *file_name == '\0' || suffix == NULL || *suffix == '\0')
480 : {
481 0 : log_error(LOG_LEVEL_ERROR,
482 : "make_certs_path failed: bad input parameters");
483 0 : return NULL;
484 : }
485 :
486 0 : char *path = NULL;
487 0 : size_t path_size = strlen(conf_dir)
488 0 : + strlen(file_name) + strlen(suffix) + 2;
489 :
490 : /* Setting delimiter and editing path length */
491 : #if defined(_WIN32)
492 : char delim[] = "\\";
493 : path_size += 1;
494 : #else /* ifndef _WIN32 */
495 0 : char delim[] = "/";
496 : #endif /* ifndef _WIN32 */
497 :
498 : /*
499 : * Building up path from many parts
500 : */
501 : #if defined(unix)
502 0 : if (*conf_dir != '/' && basedir && *basedir)
503 : {
504 : /*
505 : * Replacing conf_dir with basedir. This new variable contains
506 : * absolute path to cwd.
507 : */
508 0 : path_size += strlen(basedir) + 2;
509 0 : path = zalloc_or_die(path_size);
510 :
511 0 : strlcpy(path, basedir, path_size);
512 0 : strlcat(path, delim, path_size);
513 0 : strlcat(path, conf_dir, path_size);
514 0 : strlcat(path, delim, path_size);
515 0 : strlcat(path, file_name, path_size);
516 0 : strlcat(path, suffix, path_size);
517 : }
518 : else
519 : #endif /* defined unix */
520 : {
521 0 : path = zalloc_or_die(path_size);
522 :
523 0 : strlcpy(path, conf_dir, path_size);
524 0 : strlcat(path, delim, path_size);
525 0 : strlcat(path, file_name, path_size);
526 0 : strlcat(path, suffix, path_size);
527 : }
528 :
529 0 : return path;
530 : }
531 :
532 :
533 : /*********************************************************************
534 : *
535 : * Function : get_certificate_serial
536 : *
537 : * Description : Computes serial number for new certificate from host
538 : * name hash. This hash must be already saved in csp
539 : * structure.
540 : *
541 : * Parameters :
542 : * 1 : csp = Current client state (buffers, headers, etc...)
543 : *
544 : * Returns : Serial number for new certificate
545 : *
546 : *********************************************************************/
547 0 : extern unsigned long get_certificate_serial(struct client_state *csp)
548 : {
549 0 : unsigned long exp = 1;
550 0 : unsigned long serial = 0;
551 :
552 0 : int i = CERT_SERIAL_NUM_LENGTH;
553 :
554 0 : for (; i >= 0; i--)
555 : {
556 0 : serial += exp * (unsigned)csp->http->hash_of_host[i];
557 0 : exp *= 256;
558 : }
559 0 : return serial;
560 : }
561 :
562 :
563 : /*********************************************************************
564 : *
565 : * Function : generate_certificate_valid_date
566 : *
567 : * Description : Turns a time_t into the format expected by mbedTLS.
568 : *
569 : * Parameters :
570 : * 1 : time_spec = The timestamp to convert
571 : * 2 : buffer = The buffer to write the date to
572 : * 3 : buffer_size = The size of the buffer
573 : * 4 : fmt = format
574 : *
575 : * Returns : 0 => The conversion worked
576 : * 1 => The conversion failed
577 : *
578 : *********************************************************************/
579 0 : static int generate_certificate_valid_date(time_t time_spec, char *buffer,
580 : size_t buffer_size, const char *fmt)
581 : {
582 : struct tm valid_date;
583 : struct tm *timeptr;
584 : size_t ret;
585 :
586 0 : timeptr = privoxy_gmtime_r(&time_spec, &valid_date);
587 0 : if (NULL == timeptr)
588 : {
589 0 : return 1;
590 : }
591 :
592 0 : ret = strftime(buffer, buffer_size, fmt, timeptr);
593 0 : if (ret <= 0)
594 : {
595 0 : return 1;
596 : }
597 :
598 0 : return 0;
599 :
600 : }
601 :
602 :
603 : /*********************************************************************
604 : *
605 : * Function : get_certificate_valid_from_date
606 : *
607 : * Description : Generates a "valid from" date in the format
608 : * expected by mbedTLS.
609 : *
610 : * Parameters :
611 : * 1 : buffer = The buffer to write the date to
612 : * 2 : buffer_size = The size of the buffer
613 : * 3 : fmt = format
614 : *
615 : * Returns : 0 => The generation worked
616 : * 1 => The generation failed
617 : *
618 : *********************************************************************/
619 0 : extern int get_certificate_valid_from_date(char *buffer, size_t buffer_size, const char *fmt)
620 : {
621 : time_t time_spec;
622 :
623 0 : time_spec = time(NULL);
624 : /* 1 month in the past */
625 0 : time_spec -= 30 * 24 * 60 * 60;
626 :
627 0 : return generate_certificate_valid_date(time_spec, buffer, buffer_size, fmt);
628 :
629 : }
630 :
631 :
632 : /*********************************************************************
633 : *
634 : * Function : get_certificate_valid_to_date
635 : *
636 : * Description : Generates a "valid to" date in the format
637 : * expected by mbedTLS.
638 : *
639 : * Parameters :
640 : * 1 : buffer = The buffer to write the date to
641 : * 2 : buffer_size = The size of the buffer
642 : * 3 : fmt = format
643 : *
644 : * Returns : 0 => The generation worked
645 : * 1 => The generation failed
646 : *
647 : *********************************************************************/
648 0 : extern int get_certificate_valid_to_date(char *buffer, size_t buffer_size, const char *fmt)
649 : {
650 : time_t time_spec;
651 :
652 0 : time_spec = time(NULL);
653 : /* Three months in the future */
654 0 : time_spec += 90 * 24 * 60 * 60;
655 :
656 0 : return generate_certificate_valid_date(time_spec, buffer, buffer_size, fmt);
657 :
658 : }
659 :
660 :
661 : /*********************************************************************
662 : *
663 : * Function : host_is_ip_address
664 : *
665 : * Description : Checks whether or not a host is specified by
666 : * IP address. Does not actually validate the
667 : * address.
668 : *
669 : * Parameters :
670 : * 1 : host = The host name to check
671 : *
672 : * Returns : 1 => Yes
673 : * 0 => No
674 : *
675 : *********************************************************************/
676 0 : extern int host_is_ip_address(const char *host)
677 : {
678 : const char *p;
679 :
680 0 : if (NULL != strstr(host, ":"))
681 : {
682 : /* Assume an IPv6 address. */
683 0 : return 1;
684 : }
685 :
686 0 : for (p = host; *p; p++)
687 : {
688 0 : if ((*p != '.') && !privoxy_isdigit(*p))
689 : {
690 : /* Not a dot or digit so it can't be an IPv4 address. */
691 0 : return 0;
692 : }
693 : }
694 :
695 : /*
696 : * Host only consists of dots and digits so
697 : * assume that is an IPv4 address.
698 : */
699 0 : return 1;
700 :
701 : }
702 :
703 :
704 : /*********************************************************************
705 : *
706 : * Function : enforce_sane_certificate_state
707 : *
708 : * Description : Makes sure the certificate state is sane.
709 : *
710 : * Parameters :
711 : * 1 : certificate = Path to the potentionally existing certifcate.
712 : * 2 : key = Path to the potentionally existing key.
713 : *
714 : * Returns : -1 => Error
715 : * 0 => Certificate state is sane
716 : *
717 : *********************************************************************/
718 0 : extern int enforce_sane_certificate_state(const char *certificate, const char *key)
719 : {
720 0 : const int certificate_exists = file_exists(certificate);
721 0 : const int key_exists = file_exists(key);
722 :
723 0 : if (!certificate_exists && key_exists)
724 : {
725 0 : log_error(LOG_LEVEL_ERROR,
726 : "A website key already exists but there's no matching certificate. "
727 : "Removing %s before creating a new key and certificate.", key);
728 0 : if (unlink(key))
729 : {
730 0 : log_error(LOG_LEVEL_ERROR, "Failed to unlink %s: %E", key);
731 :
732 0 : return -1;
733 : }
734 : }
735 0 : if (certificate_exists && !key_exists)
736 : {
737 0 : log_error(LOG_LEVEL_ERROR,
738 : "A certificate exists but there's no matching key. "
739 : "Removing %s before creating a new key and certificate.", certificate);
740 0 : if (unlink(certificate))
741 : {
742 0 : log_error(LOG_LEVEL_ERROR, "Failed to unlink %s: %E", certificate);
743 :
744 0 : return -1;
745 : }
746 : }
747 :
748 0 : return 0;
749 :
750 : }
|