ChangeLog entries for what I've plucked. Some are better than what they were before.
[pidgin-git.git] / pidgin / gtkcertmgr.c
blob9f498959451ddc65c5a56bf8e2f388f4981b17c7
1 /*
2 * @file gtkcertmgr.c GTK+ Certificate Manager API
3 * @ingroup pidgin
4 */
6 /* pidgin
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
28 #include "internal.h"
29 #include "core.h"
30 #include "pidgin.h"
31 #include "pidginstock.h"
33 #include "certificate.h"
34 #include "debug.h"
35 #include "notify.h"
36 #include "request.h"
38 #include "gtkblist.h"
39 #include "gtkutils.h"
41 #include "gtkcertmgr.h"
43 /*****************************************************************************
44 * X.509 tls_peers management interface *
45 *****************************************************************************/
47 typedef struct {
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;
60 /* Columns
61 See http://developer.gnome.org/doc/API/2.0/gtk/TreeWidget.html */
62 enum
64 TPM_HOSTNAME_COLUMN,
65 TPM_N_COLUMNS
68 static void
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;
79 static void
80 tls_peers_mgmt_repopulate_list(void)
82 GtkTreeView *listview = tpm_dat->listview;
83 PurpleCertificatePool *tls_peers;
84 GList *idlist, *l;
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) {
101 GtkTreeIter iter;
102 gtk_list_store_append(store, &iter);
104 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
105 TPM_HOSTNAME_COLUMN, l->data,
106 -1);
108 purple_certificate_pool_destroy_idlist(idlist);
111 static void
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();
119 static void
120 tls_peers_mgmt_select_chg_cb(GtkTreeSelection *ignored, gpointer data)
122 GtkTreeSelection *select = tpm_dat->listselect;
123 GtkTreeIter iter;
124 GtkTreeModel *model;
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);
132 } else {
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);
141 static void
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);
156 static void
157 tls_peers_mgmt_import_cancel2_cb(gpointer data, const char *result)
159 PurpleCertificate *crt = (PurpleCertificate *) data;
160 purple_certificate_destroy(crt);
163 static void
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);
175 /* Did it work? */
176 if (crt != NULL) {
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
183 deletion */
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."),
189 default_hostname,
190 FALSE, /* Not multiline */
191 FALSE, /* Not masked? */
192 NULL, /* No hints? */
193 _("OK"),
194 G_CALLBACK(tls_peers_mgmt_import_ok2_cb),
195 _("Cancel"),
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);
202 } else {
203 /* Errors! Oh no! */
204 /* TODO: Perhaps find a way to be specific about what just
205 went wrong? */
206 gchar * secondary;
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);
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, NULL, NULL );/* No account,conv,etc. */
230 static void
231 tls_peers_mgmt_export_ok_cb(gpointer data, const char *filename)
233 PurpleCertificate *crt = (PurpleCertificate *) data;
235 g_assert(filename);
237 if (!purple_certificate_export(filename, crt)) {
238 /* Errors! Oh no! */
239 /* TODO: Perhaps find a way to be specific about what just
240 went wrong? */
241 gchar * secondary;
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"),
247 secondary);
248 g_free(secondary);
251 purple_certificate_destroy(crt);
254 static void
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);
262 static void
263 tls_peers_mgmt_export_cb(GtkWidget *button, gpointer data)
265 PurpleCertificate *crt;
266 GtkTreeSelection *select = tpm_dat->listselect;
267 GtkTreeIter iter;
268 GtkTreeModel *model;
269 gchar *id;
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");
275 return;
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);
285 if (NULL == crt) {
286 purple_debug_error("gtkcertmgr/tls_peers_mgmt",
287 "Id %s was not in the peers cache?!\n",
288 id);
289 g_free(id);
290 return;
292 g_free(id);
294 /* TODO: inform user that it will be a PEM? */
295 purple_request_file(tpm_dat,
296 _("PEM X.509 Certificate Export"),
297 "certificate.pem",
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 */
305 static void
306 tls_peers_mgmt_info_cb(GtkWidget *button, gpointer data)
308 GtkTreeSelection *select = tpm_dat->listselect;
309 GtkTreeIter iter;
310 GtkTreeModel *model;
311 gchar *id;
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");
318 return;
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);
331 g_free(id);
332 purple_certificate_destroy(crt);
335 static void
336 tls_peers_mgmt_delete_confirm_cb(gchar *id, gint choice)
338 if (1 == 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",
344 id);
348 g_free(id);
351 static void
352 tls_peers_mgmt_delete_cb(GtkWidget *button, gpointer data)
354 GtkTreeSelection *select = tpm_dat->listselect;
355 GtkTreeIter iter;
356 GtkTreeModel *model;
358 /* See if things are selected */
359 if (gtk_tree_selection_get_selected(select, &model, &iter)) {
361 gchar *id;
362 gchar *primary;
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 */
374 NULL, NULL, NULL,
375 id, /* id ownership passed to callback */
376 tls_peers_mgmt_delete_confirm_cb,
377 tls_peers_mgmt_delete_confirm_cb );
379 g_free(primary);
381 } else {
382 purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
383 "Delete clicked with no selection?\n");
384 return;
388 static GtkWidget *
389 tls_peers_mgmt_build(void)
391 GtkWidget *bbox;
392 GtkListStore *store;
393 GtkWidget *sw;
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
416 is closed */
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));
430 /* List view */
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(
444 _("Hostname"),
445 renderer,
446 "text", TPM_HOSTNAME_COLUMN,
447 NULL);
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);
481 /* Import button */
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);
491 /* Export button */
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);
501 /* Info button */
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);
510 /* Delete button */
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),
529 NULL);
530 purple_signal_connect(tpm_dat->tls_peers, "certificate-deleted",
531 tpm_dat, PURPLE_CALLBACK(tls_peers_mgmt_mod_cb),
532 NULL);
534 return mgmt_widget;
537 const PidginCertificateManager tls_peers_mgmt = {
538 tls_peers_mgmt_build, /* Widget creation function */
539 N_("SSL Servers")
542 /*****************************************************************************
543 * GTK+ main certificate manager *
544 *****************************************************************************/
545 typedef struct
547 GtkWidget *window;
548 GtkWidget *notebook;
550 GtkWidget *closebutton;
551 } CertMgrDialog;
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;
557 static gboolean
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();
563 return FALSE;
566 void
567 pidgin_certmgr_show(void)
569 CertMgrDialog *dlg;
570 GtkWidget *win;
571 GtkWidget *vbox;
573 /* Enumerate all the certificates on file */
575 GList *idlist, *poollist;
577 for ( poollist = purple_certificate_get_pools();
578 poollist;
579 poollist = poollist->next ) {
580 PurpleCertificatePool *pool = poollist->data;
581 GList *l;
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",
592 "- %s\n",
593 l->data ? (gchar *) l->data : "(null)");
594 } /* idlist */
595 purple_certificate_pool_destroy_idlist(idlist);
596 } /* poollist */
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));
603 return;
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);
610 win = dlg->window =
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);
622 /* Main vbox */
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);
632 /* Close button */
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);
646 void
647 pidgin_certmgr_hide(void)
649 /* If it isn't open, do nothing */
650 if (certmgr_dialog == NULL) {
651 return;
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;