2 * @file gtkcertmgr.c GTK+ Certificate Manager API
8 * Pidgin is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
31 #include "pidginstock.h"
33 #include "certificate.h"
41 #include "gtkcertmgr.h"
43 /*****************************************************************************
44 * X.509 tls_peers management interface *
45 *****************************************************************************/
48 GtkWidget
*mgmt_widget
;
49 GtkTreeView
*listview
;
50 GtkTreeSelection
*listselect
;
51 GtkWidget
*importbutton
;
52 GtkWidget
*exportbutton
;
53 GtkWidget
*infobutton
;
54 GtkWidget
*deletebutton
;
55 PurpleCertificatePool
*tls_peers
;
56 } tls_peers_mgmt_data
;
58 tls_peers_mgmt_data
*tpm_dat
= NULL
;
61 See http://developer.gnome.org/doc/API/2.0/gtk/TreeWidget.html */
69 tls_peers_mgmt_destroy(GtkWidget
*mgmt_widget
, gpointer data
)
71 purple_debug_info("certmgr",
72 "tls peers self-destructs\n");
74 purple_signals_disconnect_by_handle(tpm_dat
);
75 purple_request_close_with_handle(tpm_dat
);
76 g_free(tpm_dat
); tpm_dat
= NULL
;
80 tls_peers_mgmt_repopulate_list(void)
82 GtkTreeView
*listview
= tpm_dat
->listview
;
83 PurpleCertificatePool
*tls_peers
;
86 GtkListStore
*store
= GTK_LIST_STORE(
87 gtk_tree_view_get_model(GTK_TREE_VIEW(listview
)));
89 /* First, delete everything in the list */
90 gtk_list_store_clear(store
);
92 /* Locate the "tls_peers" pool */
93 tls_peers
= purple_certificate_find_pool("x509", "tls_peers");
94 g_return_if_fail(tls_peers
);
96 /* Grab the loaded certificates */
97 idlist
= purple_certificate_pool_get_idlist(tls_peers
);
99 /* Populate the listview */
100 for (l
= idlist
; l
; l
= l
->next
) {
102 gtk_list_store_append(store
, &iter
);
104 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
105 TPM_HOSTNAME_COLUMN
, l
->data
,
108 purple_certificate_pool_destroy_idlist(idlist
);
112 tls_peers_mgmt_mod_cb(PurpleCertificatePool
*pool
, const gchar
*id
, gpointer data
)
114 g_assert (pool
== tpm_dat
->tls_peers
);
116 tls_peers_mgmt_repopulate_list();
120 tls_peers_mgmt_select_chg_cb(GtkTreeSelection
*ignored
, gpointer data
)
122 GtkTreeSelection
*select
= tpm_dat
->listselect
;
126 /* See if things are selected */
127 if (gtk_tree_selection_get_selected(select
, &model
, &iter
)) {
128 /* Enable buttons if something is selected */
129 gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat
->exportbutton
), TRUE
);
130 gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat
->infobutton
), TRUE
);
131 gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat
->deletebutton
), TRUE
);
133 /* Otherwise, disable them */
134 gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat
->exportbutton
), FALSE
);
135 gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat
->infobutton
), FALSE
);
136 gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat
->deletebutton
), FALSE
);
142 tls_peers_mgmt_import_ok2_cb(gpointer data
, const char *result
)
144 PurpleCertificate
*crt
= (PurpleCertificate
*) data
;
146 /* TODO: Perhaps prompt if you're overwriting a cert? */
148 /* Drop the certificate into the pool */
149 if (result
&& *result
)
150 purple_certificate_pool_store(tpm_dat
->tls_peers
, result
, crt
);
152 /* And this certificate is not needed any more */
153 purple_certificate_destroy(crt
);
157 tls_peers_mgmt_import_cancel2_cb(gpointer data
, const char *result
)
159 PurpleCertificate
*crt
= (PurpleCertificate
*) data
;
160 purple_certificate_destroy(crt
);
164 tls_peers_mgmt_import_ok_cb(gpointer data
, const char *filename
)
166 PurpleCertificateScheme
*x509
;
167 PurpleCertificate
*crt
;
169 /* Load the scheme of our tls_peers pool (ought to be x509) */
170 x509
= purple_certificate_pool_get_scheme(tpm_dat
->tls_peers
);
172 /* Now load the certificate from disk */
173 crt
= purple_certificate_import(x509
, filename
);
177 gchar
*default_hostname
;
178 /* Get name to add to pool as */
179 /* Make a guess about what the hostname should be */
180 default_hostname
= purple_certificate_get_subject_name(crt
);
181 /* TODO: Find a way to make sure that crt gets destroyed
182 if the window gets closed unusually, such as by handle
184 /* TODO: Display some more information on the certificate? */
185 purple_request_input(tpm_dat
,
186 _("Certificate Import"),
187 _("Specify a hostname"),
188 _("Type the host name for this certificate."),
190 FALSE
, /* Not multiline */
191 FALSE
, /* Not masked? */
192 NULL
, /* No hints? */
194 G_CALLBACK(tls_peers_mgmt_import_ok2_cb
),
196 G_CALLBACK(tls_peers_mgmt_import_cancel2_cb
),
197 NULL
, NULL
, NULL
, /* No account/who/conv*/
198 crt
/* Pass cert instance to callback*/
201 g_free(default_hostname
);
204 /* TODO: Perhaps find a way to be specific about what just
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"),
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"),
224 FALSE
, /* Not a save dialog */
225 G_CALLBACK(tls_peers_mgmt_import_ok_cb
),
226 NULL
, /* Do nothing if cancelled */
227 NULL
, NULL
, NULL
, NULL
);/* No account,conv,etc. */
231 tls_peers_mgmt_export_ok_cb(gpointer data
, const char *filename
)
233 PurpleCertificate
*crt
= (PurpleCertificate
*) data
;
237 if (!purple_certificate_export(filename
, crt
)) {
239 /* TODO: Perhaps find a way to be specific about what just
243 secondary
= g_strdup_printf(_("Export to file %s failed.\nCheck that you have write permission to the target path\n"), filename
);
244 purple_notify_error(NULL
,
245 _("Certificate Export Error"),
246 _("X.509 certificate export failed"),
251 purple_certificate_destroy(crt
);
255 tls_peers_mgmt_export_cancel_cb(gpointer data
, const char *filename
)
257 PurpleCertificate
*crt
= (PurpleCertificate
*) data
;
258 /* Pressing cancel just frees the duplicated certificate */
259 purple_certificate_destroy(crt
);
263 tls_peers_mgmt_export_cb(GtkWidget
*button
, gpointer data
)
265 PurpleCertificate
*crt
;
266 GtkTreeSelection
*select
= tpm_dat
->listselect
;
271 /* See if things are selected */
272 if (!gtk_tree_selection_get_selected(select
, &model
, &iter
)) {
273 purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
274 "Export clicked with no selection?\n");
278 /* Retrieve the selected hostname */
279 gtk_tree_model_get(model
, &iter
, TPM_HOSTNAME_COLUMN
, &id
, -1);
281 /* Extract the certificate from the pool now to make sure it doesn't
282 get deleted out from under us */
283 crt
= purple_certificate_pool_retrieve(tpm_dat
->tls_peers
, id
);
286 purple_debug_error("gtkcertmgr/tls_peers_mgmt",
287 "Id %s was not in the peers cache?!\n",
294 /* TODO: inform user that it will be a PEM? */
295 purple_request_file(tpm_dat
,
296 _("PEM X.509 Certificate Export"),
298 TRUE
, /* Is a save dialog */
299 G_CALLBACK(tls_peers_mgmt_export_ok_cb
),
300 G_CALLBACK(tls_peers_mgmt_export_cancel_cb
),
301 NULL
, NULL
, NULL
, /* No account,conv,etc. */
302 crt
); /* Pass the certificate on to the callback */
306 tls_peers_mgmt_info_cb(GtkWidget
*button
, gpointer data
)
308 GtkTreeSelection
*select
= tpm_dat
->listselect
;
312 PurpleCertificate
*crt
;
314 /* See if things are selected */
315 if (!gtk_tree_selection_get_selected(select
, &model
, &iter
)) {
316 purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
317 "Info clicked with no selection?\n");
321 /* Retrieve the selected hostname */
322 gtk_tree_model_get(model
, &iter
, TPM_HOSTNAME_COLUMN
, &id
, -1);
324 /* Now retrieve the certificate */
325 crt
= purple_certificate_pool_retrieve(tpm_dat
->tls_peers
, id
);
326 g_return_if_fail(crt
);
328 /* Fire the notification */
329 purple_certificate_display_x509(crt
);
332 purple_certificate_destroy(crt
);
336 tls_peers_mgmt_delete_confirm_cb(gchar
*id
, gint choice
)
339 /* Yes, delete was confirmed */
340 /* Now delete the thing */
341 if (!purple_certificate_pool_delete(tpm_dat
->tls_peers
, id
)) {
342 purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
343 "Deletion failed on id %s\n",
352 tls_peers_mgmt_delete_cb(GtkWidget
*button
, gpointer data
)
354 GtkTreeSelection
*select
= tpm_dat
->listselect
;
358 /* See if things are selected */
359 if (gtk_tree_selection_get_selected(select
, &model
, &iter
)) {
364 /* Retrieve the selected hostname */
365 gtk_tree_model_get(model
, &iter
, TPM_HOSTNAME_COLUMN
, &id
, -1);
367 /* Prompt to confirm deletion */
368 primary
= g_strdup_printf(
369 _("Really delete certificate for %s?"), id
);
371 purple_request_yes_no(tpm_dat
, _("Confirm certificate delete"),
372 primary
, NULL
, /* Can this be NULL? */
373 0, /* "yes" is the default action */
375 id
, /* id ownership passed to callback */
376 tls_peers_mgmt_delete_confirm_cb
,
377 tls_peers_mgmt_delete_confirm_cb
);
382 purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
383 "Delete clicked with no selection?\n");
389 tls_peers_mgmt_build(void)
395 /* This block of variables will end up in tpm_dat */
396 GtkTreeView
*listview
;
397 GtkTreeSelection
*select
;
398 GtkWidget
*importbutton
;
399 GtkWidget
*exportbutton
;
400 GtkWidget
*infobutton
;
401 GtkWidget
*deletebutton
;
402 /** Element to return to the Certmgr window to put in the Notebook */
403 GtkWidget
*mgmt_widget
;
405 /* Create a struct to store context information about this window */
406 tpm_dat
= g_new0(tls_peers_mgmt_data
, 1);
408 tpm_dat
->mgmt_widget
= mgmt_widget
=
409 gtk_hbox_new(FALSE
, /* Non-homogeneous */
410 PIDGIN_HIG_BOX_SPACE
);
411 gtk_container_set_border_width(GTK_CONTAINER(mgmt_widget
),
412 PIDGIN_HIG_BOX_SPACE
);
413 gtk_widget_show(mgmt_widget
);
415 /* Ensure that everything gets cleaned up when the dialog box
417 g_signal_connect(G_OBJECT(mgmt_widget
), "destroy",
418 G_CALLBACK(tls_peers_mgmt_destroy
), NULL
);
420 /* Scrolled window */
421 sw
= gtk_scrolled_window_new(NULL
,NULL
);
422 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw
),
423 GTK_POLICY_AUTOMATIC
, GTK_POLICY_ALWAYS
);
424 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw
), GTK_SHADOW_IN
);
425 gtk_box_pack_start(GTK_BOX(mgmt_widget
), GTK_WIDGET(sw
),
426 TRUE
, TRUE
, /* Take up lots of space */
428 gtk_widget_show(GTK_WIDGET(sw
));
431 store
= gtk_list_store_new(TPM_N_COLUMNS
, G_TYPE_STRING
);
433 tpm_dat
->listview
= listview
=
434 GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(store
)));
435 g_object_unref(G_OBJECT(store
));
438 GtkCellRenderer
*renderer
;
439 GtkTreeViewColumn
*column
;
441 /* Set up the display columns */
442 renderer
= gtk_cell_renderer_text_new();
443 column
= gtk_tree_view_column_new_with_attributes(
446 "text", TPM_HOSTNAME_COLUMN
,
448 gtk_tree_view_append_column(GTK_TREE_VIEW(listview
), column
);
450 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store
),
451 TPM_HOSTNAME_COLUMN
, GTK_SORT_ASCENDING
);
454 /* Get the treeview selector into the struct */
455 tpm_dat
->listselect
= select
=
456 gtk_tree_view_get_selection(GTK_TREE_VIEW(listview
));
458 /* Force the selection mode */
459 gtk_tree_selection_set_mode(select
, GTK_SELECTION_SINGLE
);
461 /* Use a callback to enable/disable the buttons based on whether
462 something is selected */
463 g_signal_connect(G_OBJECT(select
), "changed",
464 G_CALLBACK(tls_peers_mgmt_select_chg_cb
), NULL
);
466 gtk_container_add(GTK_CONTAINER(sw
), GTK_WIDGET(listview
));
467 gtk_widget_show(GTK_WIDGET(listview
));
469 /* Fill the list for the first time */
470 tls_peers_mgmt_repopulate_list();
472 /* Right-hand side controls box */
473 bbox
= gtk_vbutton_box_new();
474 gtk_box_pack_end(GTK_BOX(mgmt_widget
), bbox
,
475 FALSE
, FALSE
, /* Do not take up space */
477 gtk_box_set_spacing(GTK_BOX(bbox
), PIDGIN_HIG_BOX_SPACE
);
478 gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox
), GTK_BUTTONBOX_START
);
479 gtk_widget_show(bbox
);
482 /* TODO: This is the wrong stock button */
483 tpm_dat
->importbutton
= importbutton
=
484 gtk_button_new_from_stock(GTK_STOCK_ADD
);
485 gtk_box_pack_start(GTK_BOX(bbox
), importbutton
, FALSE
, FALSE
, 0);
486 gtk_widget_show(importbutton
);
487 g_signal_connect(G_OBJECT(importbutton
), "clicked",
488 G_CALLBACK(tls_peers_mgmt_import_cb
), NULL
);
492 /* TODO: This is the wrong stock button */
493 tpm_dat
->exportbutton
= exportbutton
=
494 gtk_button_new_from_stock(GTK_STOCK_SAVE
);
495 gtk_box_pack_start(GTK_BOX(bbox
), exportbutton
, FALSE
, FALSE
, 0);
496 gtk_widget_show(exportbutton
);
497 g_signal_connect(G_OBJECT(exportbutton
), "clicked",
498 G_CALLBACK(tls_peers_mgmt_export_cb
), NULL
);
502 tpm_dat
->infobutton
= infobutton
=
503 gtk_button_new_from_stock(PIDGIN_STOCK_INFO
);
504 gtk_box_pack_start(GTK_BOX(bbox
), infobutton
, FALSE
, FALSE
, 0);
505 gtk_widget_show(infobutton
);
506 g_signal_connect(G_OBJECT(infobutton
), "clicked",
507 G_CALLBACK(tls_peers_mgmt_info_cb
), NULL
);
511 tpm_dat
->deletebutton
= deletebutton
=
512 gtk_button_new_from_stock(GTK_STOCK_DELETE
);
513 gtk_box_pack_start(GTK_BOX(bbox
), deletebutton
, FALSE
, FALSE
, 0);
514 gtk_widget_show(deletebutton
);
515 g_signal_connect(G_OBJECT(deletebutton
), "clicked",
516 G_CALLBACK(tls_peers_mgmt_delete_cb
), NULL
);
518 /* Call the "selection changed" callback, which will probably disable
519 all the buttons since nothing is selected yet */
520 tls_peers_mgmt_select_chg_cb(select
, NULL
);
522 /* Bind us to the tls_peers pool */
523 tpm_dat
->tls_peers
= purple_certificate_find_pool("x509", "tls_peers");
525 /**** libpurple signals ****/
526 /* Respond to certificate add/remove by just reloading everything */
527 purple_signal_connect(tpm_dat
->tls_peers
, "certificate-stored",
528 tpm_dat
, PURPLE_CALLBACK(tls_peers_mgmt_mod_cb
),
530 purple_signal_connect(tpm_dat
->tls_peers
, "certificate-deleted",
531 tpm_dat
, PURPLE_CALLBACK(tls_peers_mgmt_mod_cb
),
537 const PidginCertificateManager tls_peers_mgmt
= {
538 tls_peers_mgmt_build
, /* Widget creation function */
542 /*****************************************************************************
543 * GTK+ main certificate manager *
544 *****************************************************************************/
550 GtkWidget
*closebutton
;
553 /* If a certificate manager window is open, this will point to it.
554 So if it is set, don't open another one! */
555 CertMgrDialog
*certmgr_dialog
= NULL
;
558 certmgr_close_cb(GtkWidget
*w
, CertMgrDialog
*dlg
)
560 /* TODO: Ignoring the arguments to this function may not be ideal,
561 but there *should* only be "one dialog to rule them all" at a time*/
562 pidgin_certmgr_hide();
567 pidgin_certmgr_show(void)
573 /* Enumerate all the certificates on file */
575 GList
*idlist
, *poollist
;
577 for ( poollist
= purple_certificate_get_pools();
579 poollist
= poollist
->next
) {
580 PurpleCertificatePool
*pool
= poollist
->data
;
583 purple_debug_info("gtkcertmgr",
584 "Pool %s found for scheme %s -"
585 "Enumerating certificates:\n",
586 pool
->name
, pool
->scheme_name
);
588 idlist
= purple_certificate_pool_get_idlist(pool
);
590 for (l
=idlist
; l
; l
= l
->next
) {
591 purple_debug_info("gtkcertmgr",
593 l
->data
? (gchar
*) l
->data
: "(null)");
595 purple_certificate_pool_destroy_idlist(idlist
);
600 /* If the manager is already open, bring it to the front */
601 if (certmgr_dialog
!= NULL
) {
602 gtk_window_present(GTK_WINDOW(certmgr_dialog
->window
));
606 /* Create the dialog, and set certmgr_dialog so we never create
607 more than one at a time */
608 dlg
= certmgr_dialog
= g_new0(CertMgrDialog
, 1);
611 pidgin_create_dialog(_("Certificate Manager"),/* Title */
612 PIDGIN_HIG_BORDER
, /*Window border*/
613 "certmgr", /* Role */
614 TRUE
); /* Allow resizing */
615 g_signal_connect(G_OBJECT(win
), "delete_event",
616 G_CALLBACK(certmgr_close_cb
), dlg
);
619 /* TODO: Retrieve the user-set window size and use it */
620 gtk_window_set_default_size(GTK_WINDOW(win
), 400, 400);
623 vbox
= pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win
), FALSE
, PIDGIN_HIG_BORDER
);
625 /* Notebook of various certificate managers */
626 dlg
->notebook
= gtk_notebook_new();
627 gtk_box_pack_start(GTK_BOX(vbox
), dlg
->notebook
,
628 TRUE
, TRUE
, /* Notebook should take extra space */
630 gtk_widget_show(dlg
->notebook
);
633 dlg
->closebutton
= pidgin_dialog_add_button(GTK_DIALOG(win
), GTK_STOCK_CLOSE
,
634 G_CALLBACK(certmgr_close_cb
), dlg
);
636 /* Add the defined certificate managers */
637 /* TODO: Find a way of determining whether each is shown or not */
638 /* TODO: Implement this correctly */
639 gtk_notebook_append_page(GTK_NOTEBOOK (dlg
->notebook
),
640 (tls_peers_mgmt
.build
)(),
641 gtk_label_new(_(tls_peers_mgmt
.label
)) );
643 gtk_widget_show(win
);
647 pidgin_certmgr_hide(void)
649 /* If it isn't open, do nothing */
650 if (certmgr_dialog
== NULL
) {
654 purple_signals_disconnect_by_handle(certmgr_dialog
);
655 purple_prefs_disconnect_by_handle(certmgr_dialog
);
657 gtk_widget_destroy(certmgr_dialog
->window
);
658 g_free(certmgr_dialog
);
659 certmgr_dialog
= NULL
;