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
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
26 #include "pidginstock.h"
31 #include "tls-certificate.h"
32 #include "tls-certificate-info.h"
34 #include "gtk3compat.h"
38 #include "gtkcertmgr.h"
40 /*****************************************************************************
41 * X.509 certificate management interface *
42 *****************************************************************************/
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
;
57 See http://developer.gnome.org/doc/API/2.0/gtk/TreeWidget.html */
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
;
76 tls_peers_mgmt_repopulate_list(void)
78 GtkTreeView
*listview
= tpm_dat
->listview
;
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
) {
93 gtk_list_store_append(store
, &iter
);
95 gtk_list_store_set(GTK_LIST_STORE(store
), &iter
,
96 TPM_HOSTNAME_COLUMN
, l
->data
,
100 purple_tls_certificate_free_ids(idlist
);
104 tls_peers_mgmt_select_chg_cb(GtkTreeSelection
*ignored
, gpointer data
)
106 GtkTreeSelection
*select
= tpm_dat
->listselect
;
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
);
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
);
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 */
150 tls_peers_mgmt_import_cancel2_cb(gpointer data
, const char *result
)
152 GTlsCertificate
*crt
= data
;
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
);
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
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."),
185 FALSE
, /* Not multiline */
186 FALSE
, /* Not masked? */
187 NULL
, /* No hints? */
189 G_CALLBACK(tls_peers_mgmt_import_ok2_cb
),
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
);
199 /* TODO: Perhaps find a way to be specific about what just
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"),
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
); /* No extra parameters */
231 tls_peers_mgmt_export_ok_cb(gpointer data
, const char *filename
)
233 GTlsCertificate
*crt
= data
;
235 GError
*error
= NULL
;
239 g_object_get(crt
, "certificate-pem", &pem
, NULL
);
241 if (!g_file_set_contents(filename
, pem
, -1, &error
)) {
243 /* TODO: Perhaps find a way to be specific about what just
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"),
259 tls_peers_mgmt_repopulate_list();
267 tls_peers_mgmt_export_cb(GtkWidget
*button
, gpointer data
)
269 GTlsCertificate
*crt
;
270 GtkTreeSelection
*select
= tpm_dat
->listselect
;
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");
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
);
291 purple_debug_error("gtkcertmgr/tls_peers_mgmt",
292 "Error fetching trusted cert '%s': %s\n",
294 g_clear_error(&error
);
300 /* TODO: inform user that it will be a PEM? */
301 purple_request_file(tpm_dat
,
302 _("PEM X.509 Certificate Export"),
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 */
312 tls_peers_mgmt_info_cb(GtkWidget
*button
, gpointer data
)
314 GtkTreeSelection
*select
= tpm_dat
->listselect
;
318 GTlsCertificate
*crt
;
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");
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
);
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
);
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
),
355 tls_peers_mgmt_activated_cb(GtkTreeView
*treeview
, GtkTreePath
*path
, GtkTreeViewColumn
*column
, gpointer data
)
357 tls_peers_mgmt_info_cb(NULL
, NULL
);
361 tls_peers_mgmt_delete_confirm_cb(gchar
*id
, gint choice
)
363 GError
*error
= NULL
;
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",
372 g_clear_error(&error
);
374 tls_peers_mgmt_repopulate_list();
382 tls_peers_mgmt_delete_cb(GtkWidget
*button
, gpointer data
)
384 GtkTreeSelection
*select
= tpm_dat
->listselect
;
388 /* See if things are selected */
389 if (gtk_tree_selection_get_selected(select
, &model
, &iter
)) {
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 */
405 id
, /* id ownership passed to callback */
406 tls_peers_mgmt_delete_confirm_cb
,
407 tls_peers_mgmt_delete_confirm_cb
);
412 purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
413 "Delete clicked with no selection?\n");
419 tls_peers_mgmt_build(void)
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
445 g_signal_connect(G_OBJECT(mgmt_widget
), "destroy",
446 G_CALLBACK(tls_peers_mgmt_destroy
), NULL
);
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(
464 "text", TPM_HOSTNAME_COLUMN
,
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
);
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
);
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
);
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
);
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
);
547 const PidginCertificateManager tls_peers_mgmt
= {
548 tls_peers_mgmt_build
, /* Widget creation function */
552 /*****************************************************************************
553 * GTK+ main certificate manager *
554 *****************************************************************************/
560 GtkWidget
*closebutton
;
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
;
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();
577 pidgin_certmgr_show(void)
583 /* Enumerate all the certificates on file */
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",
596 l
->data
? (gchar
*) l
->data
: "(null)");
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
));
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);
614 pidgin_create_dialog(_("Certificate Manager"),/* Title */
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);
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
);
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
);
650 pidgin_certmgr_hide(void)
652 /* If it isn't open, do nothing */
653 if (certmgr_dialog
== NULL
) {
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
;