e70b859d5f4a5b466325715017eacbcceac9cd4c
[claws.git] / src / ssl_certificate.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2001 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #if USE_SSL
25
26 #include <openssl/ssl.h>
27 #include <glib.h>
28 #include "ssl_certificate.h"
29 #include "alertpanel.h"
30 #include "utils.h"
31 #include "intl.h"
32 #include "prefs_common.h"
33 #include "socket.h"
34
35 static void ssl_certificate_destroy(SSLCertificate *cert);
36 static char *ssl_certificate_check_signer (X509 *cert); 
37
38 static char * get_fqdn(char *host)
39 {
40         struct hostent *hp;
41         hp = my_gethostbyname(host);
42         if (hp == NULL)
43                 return g_strdup(host); /*caller should free*/
44         else 
45                 return g_strdup(hp->h_name);
46 }
47
48 static char * readable_fingerprint(unsigned char *src, int len) 
49 {
50         int i=0;
51         char * ret;
52         
53         if (src == NULL)
54                 return NULL;
55         ret = g_strdup("");
56         while (i < len) {
57                 char *tmp2;
58                 if(i>0)
59                         tmp2 = g_strdup_printf("%s:%02X", ret, src[i]);
60                 else
61                         tmp2 = g_strdup_printf("%02X", src[i]);
62                 g_free(ret);
63                 ret = g_strdup(tmp2);
64                 g_free(tmp2);
65                 i++;
66         }
67         return ret;
68 }
69
70 SSLCertificate *ssl_certificate_new(X509 *x509_cert, gchar *host, gushort port)
71 {
72         SSLCertificate *cert = g_new0(SSLCertificate, 1);
73         
74         if (host == NULL || x509_cert == NULL) {
75                 ssl_certificate_destroy(cert);
76                 return NULL;
77         }
78         cert->x509_cert = X509_dup(x509_cert);
79         cert->host = get_fqdn(host);
80         cert->port = port;
81         return cert;
82 }
83
84 static void ssl_certificate_save (SSLCertificate *cert)
85 {
86         gchar *file, *port;
87         FILE *fp;
88
89         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
90                           "certs", G_DIR_SEPARATOR_S, NULL);
91         
92         if (!is_dir_exist(file))
93                 make_dir_hier(file);
94         g_free(file);
95
96         port = g_strdup_printf("%d", cert->port);
97         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
98                           "certs", G_DIR_SEPARATOR_S,
99                           cert->host, ".", port, ".cert", NULL);
100
101         g_free(port);
102         fp = fopen(file, "wb");
103         if (fp == NULL) {
104                 g_free(file);
105                 alertpanel_error(_("Can't save certificate !"));
106                 return;
107         }
108         i2d_X509_fp(fp, cert->x509_cert);
109         g_free(file);
110         fclose(fp);
111
112 }
113
114 char* ssl_certificate_to_string(SSLCertificate *cert)
115 {
116         char *ret, buf[100];
117         char *issuer_commonname, *issuer_location, *issuer_organization;
118         char *subject_commonname, *subject_location, *subject_organization;
119         char *fingerprint, *sig_status;
120         unsigned int n;
121         unsigned char md[EVP_MAX_MD_SIZE];      
122         
123         /* issuer */    
124         if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
125                                        NID_commonName, buf, 100) >= 0)
126                 issuer_commonname = g_strdup(buf);
127         else
128                 issuer_commonname = g_strdup(_("<not in certificate>"));
129         if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
130                                        NID_localityName, buf, 100) >= 0) {
131                 issuer_location = g_strdup(buf);
132                 if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
133                                        NID_countryName, buf, 100) >= 0)
134                         issuer_location = g_strconcat(issuer_location,", ",buf, NULL);
135         } else if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
136                                        NID_countryName, buf, 100) >= 0)
137                 issuer_location = g_strdup(buf);
138         else
139                 issuer_location = g_strdup(_("<not in certificate>"));
140
141         if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
142                                        NID_organizationName, buf, 100) >= 0)
143                 issuer_organization = g_strdup(buf);
144         else 
145                 issuer_organization = g_strdup(_("<not in certificate>"));
146          
147         /* subject */   
148         if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
149                                        NID_commonName, buf, 100) >= 0)
150                 subject_commonname = g_strdup(buf);
151         else
152                 subject_commonname = g_strdup(_("<not in certificate>"));
153         if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
154                                        NID_localityName, buf, 100) >= 0) {
155                 subject_location = g_strdup(buf);
156                 if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
157                                        NID_countryName, buf, 100) >= 0)
158                         subject_location = g_strconcat(subject_location,", ",buf, NULL);
159         } else if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
160                                        NID_countryName, buf, 100) >= 0)
161                 subject_location = g_strdup(buf);
162         else
163                 subject_location = g_strdup(_("<not in certificate>"));
164
165         if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
166                                        NID_organizationName, buf, 100) >= 0)
167                 subject_organization = g_strdup(buf);
168         else 
169                 subject_organization = g_strdup(_("<not in certificate>"));
170          
171         /* fingerprint */
172         X509_digest(cert->x509_cert, EVP_md5(), md, &n);
173         fingerprint = readable_fingerprint(md, (int)n);
174
175         /* signature */
176         sig_status = ssl_certificate_check_signer(cert->x509_cert);
177
178         ret = g_strdup_printf(_("  Owner: %s (%s) in %s\n  Signed by: %s (%s) in %s\n  Fingerprint: %s\n  Signature status: %s"),
179                                 subject_commonname, subject_organization, subject_location, 
180                                 issuer_commonname, issuer_organization, issuer_location, 
181                                 fingerprint,
182                                 (sig_status==NULL ? "correct":sig_status));
183
184         if (issuer_commonname)
185                 g_free(issuer_commonname);
186         if (issuer_location)
187                 g_free(issuer_location);
188         if (issuer_organization)
189                 g_free(issuer_organization);
190         if (subject_commonname)
191                 g_free(subject_commonname);
192         if (subject_location)
193                 g_free(subject_location);
194         if (subject_organization)
195                 g_free(subject_organization);
196         if (fingerprint)
197                 g_free(fingerprint);
198         if (sig_status)
199                 g_free(sig_status);
200         return ret;
201 }
202         
203 void ssl_certificate_destroy(SSLCertificate *cert) 
204 {
205         g_return_if_fail(cert != NULL);
206         if (cert->x509_cert)
207                 X509_free(cert->x509_cert);
208         if (cert->host) 
209                 g_free(cert->host);
210         g_free(cert);
211         cert = NULL;
212 }
213
214 SSLCertificate *ssl_certificate_find (gchar *host, gushort port)
215 {
216         gchar *file;
217         gchar *buf;
218         gchar *fqdn_host;
219         SSLCertificate *cert = NULL;
220         X509 *tmp_x509;
221         FILE *fp;
222
223         fqdn_host = get_fqdn(host);
224         buf = g_strdup_printf("%d", port);
225         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
226                           "certs", G_DIR_SEPARATOR_S,
227                           fqdn_host, ".", buf, ".cert", NULL);
228
229         g_free(buf);
230         fp = fopen(file, "rb");
231         if (fp == NULL) {
232                 g_free(file);
233                 g_free(fqdn_host);
234                 return NULL;
235         }
236         
237         
238         if ((tmp_x509 = d2i_X509_fp(fp, 0)) != NULL) {
239                 cert = ssl_certificate_new(tmp_x509, fqdn_host, port);
240                 X509_free(tmp_x509);
241         }
242         fclose(fp);
243         g_free(file);
244         g_free(fqdn_host);
245         
246         return cert;
247 }
248
249 static gboolean ssl_certificate_compare (SSLCertificate *cert_a, SSLCertificate *cert_b)
250 {
251         if (cert_a == NULL || cert_b == NULL)
252                 return FALSE;
253         else if (!X509_cmp(cert_a->x509_cert, cert_b->x509_cert))
254                 return TRUE;    
255         else
256                 return FALSE;
257 }
258
259 static char *ssl_certificate_check_signer (X509 *cert) 
260 {
261         X509_STORE_CTX store_ctx;
262         X509_STORE *store;
263         int ok = 0;
264         char *cert_file = NULL;
265         char *err_msg = NULL;
266
267         store = X509_STORE_new();
268         if (store == NULL) {
269                 printf("Can't create X509_STORE\n");
270                 return NULL;
271         }
272         if (X509_STORE_set_default_paths(store)) 
273                 ok++;
274         if (X509_STORE_load_locations(store, cert_file, NULL))
275                 ok++;
276
277         if (ok == 0) {
278                 X509_STORE_free (store);
279                 return g_strdup(_("Can't load X509 default paths"));
280         }
281         
282         X509_STORE_CTX_init (&store_ctx, store, cert, NULL);
283         ok = X509_verify_cert (&store_ctx);
284         
285         if (ok == 0) {
286                 err_msg = g_strdup(X509_verify_cert_error_string(
287                                         X509_STORE_CTX_get_error(&store_ctx)));
288                 debug_print("Can't check signer: %s\n", err_msg);
289                 X509_STORE_CTX_cleanup (&store_ctx);
290                 X509_STORE_free (store);
291                 return err_msg;
292                         
293         }
294         X509_STORE_CTX_cleanup (&store_ctx);
295         X509_STORE_free (store);
296         return NULL;
297 }
298
299 gboolean ssl_certificate_check (X509 *x509_cert, gchar *host, gushort port)
300 {
301         SSLCertificate *current_cert = ssl_certificate_new(x509_cert, host, port);
302         SSLCertificate *known_cert;
303
304         if (current_cert == NULL) {
305                 debug_print("Buggy certificate !\n");
306                 return FALSE;
307         }
308
309         known_cert = ssl_certificate_find (host, port);
310
311         if (known_cert == NULL) {
312                 gint val;
313                 gchar *err_msg, *cur_cert_str;
314                 
315                 cur_cert_str = ssl_certificate_to_string(current_cert);
316                 
317                 err_msg = g_strdup_printf(_("%s presented an unknown SSL certificate:\n%s"),
318                                           current_cert->host,
319                                           cur_cert_str);
320                 g_free (cur_cert_str);
321
322                 if (prefs_common.no_recv_err_panel) {
323                         log_error(_("%s\n\nMail won't be retrieved on this account until you save the certificate.\n(Uncheck the \"%s\" preference).\n"),
324                                         err_msg,
325                                         _("Don't popup error dialog on receive error"));
326                         g_free(err_msg);
327                         return FALSE;
328                 }
329                  
330                 val = alertpanel(_("Warning"),
331                                err_msg,
332                                _("Accept and save"), _("Cancel connection"), NULL);
333                 g_free(err_msg);
334
335                 switch (val) {
336                         case G_ALERTALTERNATE:
337                                 ssl_certificate_destroy(current_cert);
338                                 return FALSE;
339                         default:
340                                 ssl_certificate_save(current_cert);
341                                 ssl_certificate_destroy(current_cert);
342                                 return TRUE;
343                 }
344         }
345         else if (!ssl_certificate_compare (current_cert, known_cert)) {
346                 gint val;
347                 gchar *err_msg, *known_cert_str, *cur_cert_str, *sig_status;
348                 
349                 sig_status = ssl_certificate_check_signer(x509_cert);
350
351                 known_cert_str = ssl_certificate_to_string(known_cert);
352                 cur_cert_str = ssl_certificate_to_string(current_cert);
353                 err_msg = g_strdup_printf(_("%s's SSL certificate changed !\nWe have saved this one:\n%s\n\nIt is now:\n%s\n\nThis could mean the server answering is not the known one."),
354                                           current_cert->host,
355                                           known_cert_str,
356                                           cur_cert_str);
357                 g_free (cur_cert_str);
358                 g_free (known_cert_str);
359                 if (sig_status)
360                         g_free (sig_status);
361
362                 if (prefs_common.no_recv_err_panel) {
363                         log_error(_("%s\n\nMail won't be retrieved on this account until you save the certificate.\n(Uncheck the \"%s\" preference).\n"),
364                                         err_msg,
365                                         _("Don't popup error dialog on receive error"));
366                         g_free(err_msg);
367                         return FALSE;
368                 }
369
370                 val = alertpanel(_("Warning"),
371                                err_msg,
372                                _("Accept and save"), _("Cancel connection"), NULL);
373                 g_free(err_msg);
374
375                 switch (val) {
376                         case G_ALERTALTERNATE:
377                                 ssl_certificate_destroy(current_cert);
378                                 ssl_certificate_destroy(known_cert);
379                                 return FALSE;
380                         default:
381                                 ssl_certificate_save(current_cert);
382                                 ssl_certificate_destroy(current_cert);
383                                 ssl_certificate_destroy(known_cert);
384                                 return TRUE;
385                 }
386         }
387
388         ssl_certificate_destroy(current_cert);
389         ssl_certificate_destroy(known_cert);
390         return TRUE;
391 }
392
393 #endif /* USE_SSL */