Merged pidgin/main into default
[pidgin-git.git] / pidgin / gtkcertmgr.c
blob1d7801ad1bcd5dc0a5c160252ac7aae59f690910
1 /* pidgin
3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 #include "internal.h"
24 #include "core.h"
25 #include "pidgin.h"
26 #include "pidginstock.h"
28 #include "debug.h"
29 #include "notify.h"
30 #include "request.h"
31 #include "tls-certificate.h"
32 #include "tls-certificate-info.h"
34 #include "gtk3compat.h"
35 #include "gtkblist.h"
36 #include "gtkutils.h"
38 #include "gtkcertmgr.h"
40 /*****************************************************************************
41 * X.509 certificate management interface *
42 *****************************************************************************/
44 typedef struct {
45 GtkWidget *mgmt_widget;
46 GtkTreeView *listview;
47 GtkTreeSelection *listselect;
48 GtkWidget *importbutton;
49 GtkWidget *exportbutton;
50 GtkWidget *infobutton;
51 GtkWidget *deletebutton;
52 } tls_peers_mgmt_data;
54 tls_peers_mgmt_data *tpm_dat = NULL;
56 /* Columns
57 See http://developer.gnome.org/doc/API/2.0/gtk/TreeWidget.html */
58 enum
60 TPM_HOSTNAME_COLUMN,
61 TPM_N_COLUMNS
64 static void
65 tls_peers_mgmt_destroy(GtkWidget *mgmt_widget, gpointer data)
67 purple_debug_info("certmgr",
68 "tls peers self-destructs\n");
70 purple_signals_disconnect_by_handle(tpm_dat);
71 purple_request_close_with_handle(tpm_dat);
72 g_free(tpm_dat); tpm_dat = NULL;
75 static void
76 tls_peers_mgmt_repopulate_list(void)
78 GtkTreeView *listview = tpm_dat->listview;
79 GList *idlist, *l;
81 GtkListStore *store = GTK_LIST_STORE(
82 gtk_tree_view_get_model(GTK_TREE_VIEW(listview)));
84 /* First, delete everything in the list */
85 gtk_list_store_clear(store);
87 /* Grab the available certificates */
88 idlist = purple_tls_certificate_list_ids();
90 /* Populate the listview */
91 for (l = idlist; l; l = l->next) {
92 GtkTreeIter iter;
93 gtk_list_store_append(store, &iter);
95 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
96 TPM_HOSTNAME_COLUMN, l->data,
97 -1);
100 purple_tls_certificate_free_ids(idlist);
103 static void
104 tls_peers_mgmt_select_chg_cb(GtkTreeSelection *ignored, gpointer data)
106 GtkTreeSelection *select = tpm_dat->listselect;
107 GtkTreeIter iter;
108 GtkTreeModel *model;
110 /* See if things are selected */
111 if (gtk_tree_selection_get_selected(select, &model, &iter)) {
112 /* Enable buttons if something is selected */
113 gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->exportbutton), TRUE);
114 gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->infobutton), TRUE);
115 gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->deletebutton), TRUE);
116 } else {
117 /* Otherwise, disable them */
118 gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->exportbutton), FALSE);
119 gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->infobutton), FALSE);
120 gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->deletebutton), FALSE);
125 static void
126 tls_peers_mgmt_import_ok2_cb(gpointer data, const char *result)
128 GTlsCertificate *crt = data;
129 GError *error = NULL;
131 /* TODO: Perhaps prompt if you're overwriting a cert? */
133 /* Trust the certificate */
134 if (result && *result) {
135 if(!purple_tls_certificate_trust(result, crt, &error)) {
136 purple_debug_error("gtkcertmgr/tls_peers_mgmt",
137 "Error trusting certificate '%s': %s",
138 result, error->message);
139 g_clear_error(&error);
142 tls_peers_mgmt_repopulate_list();
145 /* And this certificate is not needed any more */
146 g_object_unref(crt);
149 static void
150 tls_peers_mgmt_import_cancel2_cb(gpointer data, const char *result)
152 GTlsCertificate *crt = data;
153 g_object_unref(crt);
156 static void
157 tls_peers_mgmt_import_ok_cb(gpointer data, const char *filename)
159 GTlsCertificate *crt;
160 GError *error = NULL;
162 /* Now load the certificate from disk */
163 crt = g_tls_certificate_new_from_file(filename, &error);
165 /* Did it work? */
166 if (crt != NULL) {
167 gchar *default_hostname;
168 PurpleTlsCertificateInfo *info;
170 /* Get name to add trust as */
171 /* Make a guess about what the hostname should be */
172 info = purple_tls_certificate_get_info(crt);
173 default_hostname = purple_tls_certificate_info_get_subject_name(info);
174 purple_tls_certificate_info_free(info);
176 /* TODO: Find a way to make sure that crt gets destroyed
177 if the window gets closed unusually, such as by handle
178 deletion */
179 /* TODO: Display some more information on the certificate? */
180 purple_request_input(tpm_dat,
181 _("Certificate Import"),
182 _("Specify a hostname"),
183 _("Type the host name for this certificate."),
184 default_hostname,
185 FALSE, /* Not multiline */
186 FALSE, /* Not masked? */
187 NULL, /* No hints? */
188 _("OK"),
189 G_CALLBACK(tls_peers_mgmt_import_ok2_cb),
190 _("Cancel"),
191 G_CALLBACK(tls_peers_mgmt_import_cancel2_cb),
192 NULL, /* No additional parameters */
193 crt /* Pass cert instance to callback*/
196 g_free(default_hostname);
197 } else {
198 /* Errors! Oh no! */
199 /* TODO: Perhaps find a way to be specific about what just
200 went wrong? */
201 gchar * secondary;
203 purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
204 "File %s couldn't be imported: %s",
205 filename, error->message);
206 g_clear_error(&error);
208 secondary = g_strdup_printf(_("File %s could not be imported.\nMake sure that the file is readable and in PEM format.\n"), filename);
209 purple_notify_error(NULL,
210 _("Certificate Import Error"),
211 _("X.509 certificate import failed"),
212 secondary, NULL);
213 g_free(secondary);
217 static void
218 tls_peers_mgmt_import_cb(GtkWidget *button, gpointer data)
220 /* TODO: need to tell the user that we want a .PEM file! */
221 purple_request_file(tpm_dat,
222 _("Select a PEM certificate"),
223 "certificate.pem",
224 FALSE, /* Not a save dialog */
225 G_CALLBACK(tls_peers_mgmt_import_ok_cb),
226 NULL, /* Do nothing if cancelled */
227 NULL, NULL); /* No extra parameters */
230 static void
231 tls_peers_mgmt_export_ok_cb(gpointer data, const char *filename)
233 GTlsCertificate *crt = data;
234 gchar *pem = NULL;
235 GError *error = NULL;
237 g_assert(filename);
239 g_object_get(crt, "certificate-pem", &pem, NULL);
241 if (!g_file_set_contents(filename, pem, -1, &error)) {
242 /* Errors! Oh no! */
243 /* TODO: Perhaps find a way to be specific about what just
244 went wrong? */
245 gchar * secondary;
247 purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
248 "File %s couldn't be exported: %s",
249 filename, error->message);
250 g_clear_error(&error);
252 secondary = g_strdup_printf(_("Export to file %s failed.\nCheck that you have write permission to the target path\n"), filename);
253 purple_notify_error(NULL,
254 _("Certificate Export Error"),
255 _("X.509 certificate export failed"),
256 secondary, NULL);
257 g_free(secondary);
258 } else {
259 tls_peers_mgmt_repopulate_list();
262 g_free(pem);
263 g_object_unref(crt);
266 static void
267 tls_peers_mgmt_export_cb(GtkWidget *button, gpointer data)
269 GTlsCertificate *crt;
270 GtkTreeSelection *select = tpm_dat->listselect;
271 GtkTreeIter iter;
272 GtkTreeModel *model;
273 gchar *id;
274 GError *error = NULL;
276 /* See if things are selected */
277 if (!gtk_tree_selection_get_selected(select, &model, &iter)) {
278 purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
279 "Export clicked with no selection?\n");
280 return;
283 /* Retrieve the selected hostname */
284 gtk_tree_model_get(model, &iter, TPM_HOSTNAME_COLUMN, &id, -1);
286 /* Extract the certificate from the pool now to make sure it doesn't
287 get deleted out from under us */
288 crt = purple_tls_certificate_new_from_id(id, &error);
290 if (NULL == crt) {
291 purple_debug_error("gtkcertmgr/tls_peers_mgmt",
292 "Error fetching trusted cert '%s': %s\n",
293 id, error->message);
294 g_clear_error(&error);
295 g_free(id);
296 return;
298 g_free(id);
300 /* TODO: inform user that it will be a PEM? */
301 purple_request_file(tpm_dat,
302 _("PEM X.509 Certificate Export"),
303 "certificate.pem",
304 TRUE, /* Is a save dialog */
305 G_CALLBACK(tls_peers_mgmt_export_ok_cb),
306 G_CALLBACK(g_object_unref),
307 NULL, /* No extra parameters */
308 crt); /* Pass the certificate on to the callback */
311 static void
312 tls_peers_mgmt_info_cb(GtkWidget *button, gpointer data)
314 GtkTreeSelection *select = tpm_dat->listselect;
315 GtkTreeIter iter;
316 GtkTreeModel *model;
317 gchar *id;
318 GTlsCertificate *crt;
319 gchar *title;
320 GError *error = NULL;
322 /* See if things are selected */
323 if (!gtk_tree_selection_get_selected(select, &model, &iter)) {
324 purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
325 "Info clicked with no selection?\n");
326 return;
329 /* Retrieve the selected hostname */
330 gtk_tree_model_get(model, &iter, TPM_HOSTNAME_COLUMN, &id, -1);
332 /* Now retrieve the certificate */
333 crt = purple_tls_certificate_new_from_id(id, &error);
335 if (crt == NULL) {
336 purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
337 "Unable to fetch certificate '%s': %s",
338 id, error ? error->message : "unknown error");
339 g_clear_error(&error);
340 g_free(id);
343 /* Fire the notification */
344 title = g_strdup_printf(_("Certificate Information for %s"), id);
345 purple_request_certificate(tpm_dat, title, NULL, NULL, crt,
346 _("OK"), G_CALLBACK(g_object_unref),
347 _("Cancel"), G_CALLBACK(g_object_unref),
348 crt);
350 g_free(id);
351 g_free(title);
354 static void
355 tls_peers_mgmt_activated_cb(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, gpointer data)
357 tls_peers_mgmt_info_cb(NULL, NULL);
360 static void
361 tls_peers_mgmt_delete_confirm_cb(gchar *id, gint choice)
363 GError *error = NULL;
365 if (1 == choice) {
366 /* Yes, distrust was confirmed */
367 /* Now distrust the thing */
368 if (!purple_tls_certificate_distrust(id, &error)) {
369 purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
370 "Deletion failed on id %s: %s\n",
371 id, error->message);
372 g_clear_error(&error);
373 } else {
374 tls_peers_mgmt_repopulate_list();
378 g_free(id);
381 static void
382 tls_peers_mgmt_delete_cb(GtkWidget *button, gpointer data)
384 GtkTreeSelection *select = tpm_dat->listselect;
385 GtkTreeIter iter;
386 GtkTreeModel *model;
388 /* See if things are selected */
389 if (gtk_tree_selection_get_selected(select, &model, &iter)) {
391 gchar *id;
392 gchar *primary;
394 /* Retrieve the selected hostname */
395 gtk_tree_model_get(model, &iter, TPM_HOSTNAME_COLUMN, &id, -1);
397 /* Prompt to confirm deletion */
398 primary = g_strdup_printf(
399 _("Really delete certificate for %s?"), id );
401 purple_request_yes_no(tpm_dat, _("Confirm certificate delete"),
402 primary, NULL, /* Can this be NULL? */
403 0, /* "yes" is the default action */
404 NULL,
405 id, /* id ownership passed to callback */
406 tls_peers_mgmt_delete_confirm_cb,
407 tls_peers_mgmt_delete_confirm_cb );
409 g_free(primary);
411 } else {
412 purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
413 "Delete clicked with no selection?\n");
414 return;
418 static GtkWidget *
419 tls_peers_mgmt_build(void)
421 GtkWidget *bbox;
422 GtkListStore *store;
424 /* This block of variables will end up in tpm_dat */
425 GtkTreeView *listview;
426 GtkTreeSelection *select;
427 GtkWidget *importbutton;
428 GtkWidget *exportbutton;
429 GtkWidget *infobutton;
430 GtkWidget *deletebutton;
431 /** Element to return to the Certmgr window to put in the Notebook */
432 GtkWidget *mgmt_widget;
434 /* Create a struct to store context information about this window */
435 tpm_dat = g_new0(tls_peers_mgmt_data, 1);
437 tpm_dat->mgmt_widget = mgmt_widget = gtk_box_new(
438 GTK_ORIENTATION_HORIZONTAL, PIDGIN_HIG_BOX_SPACE);
439 gtk_container_set_border_width(GTK_CONTAINER(mgmt_widget),
440 PIDGIN_HIG_BOX_SPACE);
441 gtk_widget_show(mgmt_widget);
443 /* Ensure that everything gets cleaned up when the dialog box
444 is closed */
445 g_signal_connect(G_OBJECT(mgmt_widget), "destroy",
446 G_CALLBACK(tls_peers_mgmt_destroy), NULL);
448 /* List view */
449 store = gtk_list_store_new(TPM_N_COLUMNS, G_TYPE_STRING);
451 tpm_dat->listview = listview =
452 GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)));
453 g_object_unref(G_OBJECT(store));
456 GtkCellRenderer *renderer;
457 GtkTreeViewColumn *column;
459 /* Set up the display columns */
460 renderer = gtk_cell_renderer_text_new();
461 column = gtk_tree_view_column_new_with_attributes(
462 _("Hostname"),
463 renderer,
464 "text", TPM_HOSTNAME_COLUMN,
465 NULL);
466 gtk_tree_view_append_column(GTK_TREE_VIEW(listview), column);
468 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
469 TPM_HOSTNAME_COLUMN, GTK_SORT_ASCENDING);
472 /* Get the treeview selector into the struct */
473 tpm_dat->listselect = select =
474 gtk_tree_view_get_selection(GTK_TREE_VIEW(listview));
476 /* Force the selection mode */
477 gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
479 /* Use a callback to enable/disable the buttons based on whether
480 something is selected */
481 g_signal_connect(G_OBJECT(select), "changed",
482 G_CALLBACK(tls_peers_mgmt_select_chg_cb), NULL);
484 g_signal_connect(G_OBJECT(listview), "row-activated",
485 G_CALLBACK(tls_peers_mgmt_activated_cb), NULL);
487 gtk_box_pack_start(GTK_BOX(mgmt_widget),
488 pidgin_make_scrollable(GTK_WIDGET(listview), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
489 TRUE, TRUE, /* Take up lots of space */
491 gtk_widget_show(GTK_WIDGET(listview));
493 /* Fill the list for the first time */
494 tls_peers_mgmt_repopulate_list();
496 /* Right-hand side controls box */
497 bbox = gtk_button_box_new(GTK_ORIENTATION_VERTICAL);
498 gtk_box_pack_end(GTK_BOX(mgmt_widget), bbox,
499 FALSE, FALSE, /* Do not take up space */
501 gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
502 gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_START);
503 gtk_widget_show(bbox);
505 /* Import button */
506 tpm_dat->importbutton = importbutton =
507 gtk_button_new_from_stock(GTK_STOCK_OPEN);
508 gtk_box_pack_start(GTK_BOX(bbox), importbutton, FALSE, FALSE, 0);
509 gtk_widget_show(importbutton);
510 g_signal_connect(G_OBJECT(importbutton), "clicked",
511 G_CALLBACK(tls_peers_mgmt_import_cb), NULL);
514 /* Export button */
515 tpm_dat->exportbutton = exportbutton =
516 gtk_button_new_from_stock(GTK_STOCK_SAVE_AS);
517 gtk_box_pack_start(GTK_BOX(bbox), exportbutton, FALSE, FALSE, 0);
518 gtk_widget_show(exportbutton);
519 g_signal_connect(G_OBJECT(exportbutton), "clicked",
520 G_CALLBACK(tls_peers_mgmt_export_cb), NULL);
523 /* Info button */
524 tpm_dat->infobutton = infobutton =
525 gtk_button_new_from_stock(PIDGIN_STOCK_INFO);
526 gtk_box_pack_start(GTK_BOX(bbox), infobutton, FALSE, FALSE, 0);
527 gtk_widget_show(infobutton);
528 g_signal_connect(G_OBJECT(infobutton), "clicked",
529 G_CALLBACK(tls_peers_mgmt_info_cb), NULL);
532 /* Delete button */
533 tpm_dat->deletebutton = deletebutton =
534 gtk_button_new_from_stock(GTK_STOCK_DELETE);
535 gtk_box_pack_start(GTK_BOX(bbox), deletebutton, FALSE, FALSE, 0);
536 gtk_widget_show(deletebutton);
537 g_signal_connect(G_OBJECT(deletebutton), "clicked",
538 G_CALLBACK(tls_peers_mgmt_delete_cb), NULL);
540 /* Call the "selection changed" callback, which will probably disable
541 all the buttons since nothing is selected yet */
542 tls_peers_mgmt_select_chg_cb(select, NULL);
544 return mgmt_widget;
547 const PidginCertificateManager tls_peers_mgmt = {
548 tls_peers_mgmt_build, /* Widget creation function */
549 N_("SSL Servers")
552 /*****************************************************************************
553 * GTK+ main certificate manager *
554 *****************************************************************************/
555 typedef struct
557 GtkWidget *window;
558 GtkWidget *notebook;
560 GtkWidget *closebutton;
561 } CertMgrDialog;
563 /* If a certificate manager window is open, this will point to it.
564 So if it is set, don't open another one! */
565 CertMgrDialog *certmgr_dialog = NULL;
567 static gboolean
568 certmgr_close_cb(GtkWidget *w, CertMgrDialog *dlg)
570 /* TODO: Ignoring the arguments to this function may not be ideal,
571 but there *should* only be "one dialog to rule them all" at a time*/
572 pidgin_certmgr_hide();
573 return FALSE;
576 void
577 pidgin_certmgr_show(void)
579 CertMgrDialog *dlg;
580 GtkWidget *win;
581 GtkWidget *vbox;
583 /* Enumerate all the certificates on file */
585 GList *idlist;
586 GList *l;
588 purple_debug_info("gtkcertmgr",
589 "Enumerating X.509 certificates:\n");
591 idlist = purple_tls_certificate_list_ids();
593 for (l=idlist; l; l = l->next) {
594 purple_debug_info("gtkcertmgr",
595 "- %s\n",
596 l->data ? (gchar *) l->data : "(null)");
597 } /* idlist */
599 purple_tls_certificate_free_ids(idlist);
603 /* If the manager is already open, bring it to the front */
604 if (certmgr_dialog != NULL) {
605 gtk_window_present(GTK_WINDOW(certmgr_dialog->window));
606 return;
609 /* Create the dialog, and set certmgr_dialog so we never create
610 more than one at a time */
611 dlg = certmgr_dialog = g_new0(CertMgrDialog, 1);
613 win = dlg->window =
614 pidgin_create_dialog(_("Certificate Manager"),/* Title */
615 0, /*Window border*/
616 "certmgr", /* Role */
617 TRUE); /* Allow resizing */
618 g_signal_connect(G_OBJECT(win), "delete_event",
619 G_CALLBACK(certmgr_close_cb), dlg);
622 /* TODO: Retrieve the user-set window size and use it */
623 gtk_window_set_default_size(GTK_WINDOW(win), 400, 400);
625 /* Main vbox */
626 vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
628 /* Notebook of various certificate managers */
629 dlg->notebook = gtk_notebook_new();
630 gtk_box_pack_start(GTK_BOX(vbox), dlg->notebook,
631 TRUE, TRUE, /* Notebook should take extra space */
633 gtk_widget_show(dlg->notebook);
635 /* Close button */
636 dlg->closebutton = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE,
637 G_CALLBACK(certmgr_close_cb), dlg);
639 /* Add the defined certificate managers */
640 /* TODO: Find a way of determining whether each is shown or not */
641 /* TODO: Implement this correctly */
642 gtk_notebook_append_page(GTK_NOTEBOOK (dlg->notebook),
643 (tls_peers_mgmt.build)(),
644 gtk_label_new(_(tls_peers_mgmt.label)) );
646 gtk_widget_show(win);
649 void
650 pidgin_certmgr_hide(void)
652 /* If it isn't open, do nothing */
653 if (certmgr_dialog == NULL) {
654 return;
657 purple_signals_disconnect_by_handle(certmgr_dialog);
658 purple_prefs_disconnect_by_handle(certmgr_dialog);
660 gtk_widget_destroy(certmgr_dialog->window);
661 g_free(certmgr_dialog);
662 certmgr_dialog = NULL;