3 * Bacula GNOME Console interface to the Director
5 * Kern Sibbald, March MMII
10 Copyright (C) 2002-2005 Kern Sibbald
12 This program is free software; you can redistribute it and/or
13 modify it under the terms of the GNU General Public License
14 version 2 as amended with additional clauses defined in the
15 file LICENSE in the main source directory.
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 the file LICENSE for additional details.
27 #include "interface.h"
30 /* Imported functions */
31 int authenticate_director(JCR *jcr, DIRRES *director, CONRES *cons);
32 void select_restore_setup();
35 int generate_daemon_event(JCR *jcr, const char *event) { return 1; }
37 /* Exported variables */
38 GtkWidget *console; /* application window */
39 GtkWidget *text1; /* text window */
40 GtkWidget *entry1; /* entry box */
41 GtkWidget *status1; /* status bar */
42 GtkWidget *combo1; /* director combo */
43 GtkWidget *scroll1; /* main scroll bar */
44 GtkWidget *run_dialog; /* run dialog */
45 GtkWidget *dir_dialog; /* director selection dialog */
46 GtkWidget *restore_dialog; /* restore dialog */
47 GtkWidget *restore_file_selection;
48 GtkWidget *dir_select;
49 GtkWidget *about1; /* about box */
50 GtkWidget *label_dialog;
51 GdkFont *text_font = NULL;
52 PangoFontDescription *font_desc;
53 pthread_mutex_t cmd_mutex = PTHREAD_MUTEX_INITIALIZER;
54 pthread_cond_t cmd_wait;
57 BSOCK *UA_sock = NULL;
58 GList *job_list, *client_list, *fileset_list;
59 GList *messages_list, *pool_list, *storage_list;
60 GList *type_list, *level_list;
62 /* Forward referenced functions */
63 void terminate_console(int sig);
66 static gint message_handler(gpointer data);
67 static int initial_connect_to_director(gpointer data);
70 static void set_scroll_bar_to_end(void);
72 /* Static variables */
73 static char *configfile = NULL;
76 static bool director_reader_running = false;
77 static bool at_prompt = false;
78 static bool ready = false;
79 static bool quit = false;
81 static int numdir = 0;
83 #define CONFIG_FILE "./gnome-console.conf" /* default configuration file */
88 "Copyright (C) 2002-2005 Kern Sibbald\n"
89 "\nVersion: %s (%s) %s %s %s\n\n"
90 "Usage: gnome-console [-s] [-c config_file] [-d debug_level] [config_file]\n"
91 " -c <file> set configuration file to file\n"
92 " -dnn set debug level to nn\n"
94 " -t test - read configuration and exit\n"
95 " -? print this message.\n"
96 "\n"), VERSION, BDATE, HOST_OS, DISTNAME, DISTVER);
102 * Call-back for reading a passphrase for an encrypted PEM file
103 * This function uses getpass(), which uses a static buffer and is NOT thread-safe.
105 static int tls_pem_callback(char *buf, int size, const void *userdata)
108 const char *prompt = (const char *) userdata;
111 passwd = getpass(prompt);
112 bstrncpy(buf, passwd, size);
113 return (strlen(buf));
122 * Make a quick check to see that we have all the
125 static int check_resources()
133 foreach_res(director, R_DIRECTOR) {
135 /* tls_require implies tls_enable */
136 if (director->tls_require) {
138 director->tls_enable = true;
140 Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
146 if ((!director->tls_ca_certfile && !director->tls_ca_certdir) && director->tls_enable) {
147 Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
148 " or \"TLS CA Certificate Dir\" are defined for Director \"%s\" in %s."
149 " At least one CA certificate store is required.\n"),
150 director->hdr.name, configfile);
156 Emsg1(M_FATAL, 0, _("No Director resource defined in %s\n"
157 "Without that I don't how to speak to the Director :-(\n"), configfile);
162 /* Loop over Consoles */
163 foreach_res(cons, R_CONSOLE) {
164 /* tls_require implies tls_enable */
165 if (cons->tls_require) {
167 cons->tls_enable = true;
169 Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
175 if ((!cons->tls_ca_certfile && !cons->tls_ca_certdir) && cons->tls_enable) {
176 Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
177 " or \"TLS CA Certificate Dir\" are defined for Console \"%s\" in %s.\n"),
178 cons->hdr.name, configfile);
189 /*********************************************************************
191 * Main Bacula GNOME Console -- User Interface Program
194 int main(int argc, char *argv[])
197 int no_signals = TRUE;
198 int test_config = FALSE;
200 const char *gargv[2] = {"gnome-console", NULL};
201 CONFONTRES *con_font;
203 setlocale(LC_ALL, "");
204 bindtextdomain("bacula", LOCALEDIR);
205 textdomain("bacula");
208 my_name_is(argc, argv, "gnome-console");
209 init_msg(NULL, NULL);
210 working_directory = "/tmp";
212 struct sigaction sigignore;
213 sigignore.sa_flags = 0;
214 sigignore.sa_handler = SIG_IGN;
215 sigfillset(&sigignore.sa_mask);
216 sigaction(SIGPIPE, &sigignore, NULL);
218 if ((stat=pthread_cond_init(&cmd_wait, NULL)) != 0) {
219 Emsg1(M_ABORT, 0, _("Pthread cond init error = %s\n"),
223 gnome_init("bacula", VERSION, gargc, (char **)&gargv);
225 while ((ch = getopt(argc, argv, "bc:d:r:st?")) != -1) {
227 case 'c': /* configuration file */
228 if (configfile != NULL)
230 configfile = bstrdup(optarg);
234 debug_level = atoi(optarg);
235 if (debug_level <= 0)
239 case 's': /* turn off signals */
257 init_signals(terminate_console);
264 if (configfile == NULL) {
265 configfile = bstrdup(CONFIG_FILE);
268 parse_config(configfile);
270 if (init_tls() != 0) {
271 Emsg0(M_ERROR_TERM, 0, _("TLS library initialization failed.\n"));
274 if (!check_resources()) {
275 Emsg1(M_ERROR_TERM, 0, _("Please correct configuration file: %s\n"), configfile);
278 console = create_console();
279 gtk_window_set_default_size(GTK_WINDOW(console), 800, 700);
280 run_dialog = create_RunDialog();
281 label_dialog = create_label_dialog();
282 restore_dialog = create_RestoreDialog();
283 about1 = create_about1();
285 text1 = lookup_widget(console, "text1");
286 entry1 = lookup_widget(console, "entry1");
287 status1 = lookup_widget(console, "status1");
288 scroll1 = lookup_widget(console, "scroll1");
290 select_restore_setup();
292 gtk_widget_show(console);
295 * Thanks to Phil Stracchino for providing the font configuration code.
297 text_font = gdk_font_load("-misc-fixed-medium-r-normal-*-*-130-*-*-c-*-koi8-r");
299 text_font = gdk_font_load("-Bigelow & Holmes-lucida console-medium-r-semi condensed-*-12-0-100-100-m-0-iso8859-1");
300 * and, new automagic:font specification!
304 foreach_res(con_font, R_CONSOLE_FONT) {
305 if (!con_font->fontface) {
306 Dmsg1(400, "No fontface for %s\n", con_font->hdr.name);
309 text_font = gdk_font_load(con_font->fontface);
310 if (text_font == NULL) {
311 Dmsg2(400, "Load of requested ConsoleFont \"%s\" (%s) failed!\n",
312 con_font->hdr.name, con_font->fontface);
314 Dmsg2(400, "ConsoleFont \"%s\" (%s) loaded.\n",
315 con_font->hdr.name, con_font->fontface);
321 if (text_font == NULL) {
322 Dmsg1(400, "Attempting to load fallback font %s\n",
323 "-misc-fixed-medium-r-normal-*-*-130-*-*-c-*-iso8859-1");
324 text_font = gdk_font_load("-misc-fixed-medium-r-normal-*-*-130-*-*-c-*-iso8859-1");
326 font_desc = pango_font_description_from_string("LucidaTypewriter 9");
327 gtk_widget_modify_font(console, font_desc);
328 gtk_widget_modify_font(text1, font_desc);
329 gtk_widget_modify_font(entry1, font_desc);
330 gtk_widget_modify_font(status1, font_desc);
331 pango_font_description_free(font_desc);
334 terminate_console(0);
338 initial = gtk_timeout_add(100, initial_connect_to_director, (gpointer)NULL);
342 disconnect_from_director((gpointer)NULL);
347 * Every 5 seconds, ask the Director for our
350 static gint message_handler(gpointer data)
352 if (ready && UA_sock) {
353 bnet_fsend(UA_sock, ".messages");
358 int disconnect_from_director(gpointer data)
361 set_status(_(" Not Connected"));
364 bnet_sig(UA_sock, BNET_TERMINATE); /* send EOF */
372 * Called just after the main loop is started to allow
373 * us to connect to the Director.
375 static int initial_connect_to_director(gpointer data)
377 gtk_timeout_remove(initial);
378 if (connect_to_director(data)) {
379 start_director_reader(data);
381 gtk_timeout_add(5000, message_handler, (gpointer)NULL);
385 static GList *get_list(char *cmd)
392 while (bnet_recv(UA_sock) > 0) {
393 strip_trailing_junk(UA_sock->msg);
394 msg = (char *)malloc(strlen(UA_sock->msg) + 1);
395 strcpy(msg, UA_sock->msg);
396 options = g_list_append(options, msg);
402 static GList *get_and_fill_combo(GtkWidget *dialog, const char *combo_name, const char *dircmd)
407 combo = lookup_widget(dialog, combo_name);
408 options = get_list((char *)dircmd);
409 if (combo && options) {
410 gtk_combo_set_popdown_strings(GTK_COMBO(combo), options);
415 static void fill_combo(GtkWidget *dialog, const char *combo_name, GList *options)
419 combo = lookup_widget(dialog, combo_name);
420 if (combo && options) {
421 gtk_combo_set_popdown_strings(GTK_COMBO(combo), options);
428 * Connect to Director. If there are more than one, put up
429 * a modal dialog so that the user chooses one.
431 int connect_to_director(gpointer data)
444 foreach_res(dir, R_DIRECTOR) {
445 dirs = g_list_append(dirs, dir->hdr.name);
448 dir_dialog = create_SelectDirectorDialog();
449 combo = lookup_widget(dir_dialog, "combo1");
450 dir_select = lookup_widget(dir_dialog, "dirselect");
452 gtk_combo_set_popdown_strings(GTK_COMBO(combo), dirs);
454 gtk_widget_show(dir_dialog);
458 gchar *ecmd = gtk_editable_get_chars((GtkEditable *)dir_select, 0, -1);
459 dir = (DIRRES *)GetResWithName(R_DIRECTOR, ecmd);
461 g_free(ecmd); /* release director name string */
467 gtk_widget_destroy(dir_dialog);
470 /* Just take the first Director */
472 dir = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
480 memset(&jcr, 0, sizeof(jcr));
482 set_statusf(_(" Connecting to Director %s:%d"), dir->address,dir->DIRport);
483 set_textf(_("Connecting to Director %s:%d\n\n"), dir->address,dir->DIRport);
485 while (gtk_events_pending()) { /* fully paint screen */
486 gtk_main_iteration();
490 /* If cons==NULL, default console will be used */
491 CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
495 /* Initialize Console TLS context */
496 if (cons && (cons->tls_enable || cons->tls_require)) {
497 /* Generate passphrase prompt */
498 bsnprintf(buf, sizeof(buf), _("Passphrase for Console \"%s\" TLS private key: "), cons->hdr.name);
500 /* Initialize TLS context:
501 * Args: CA certfile, CA certdir, Certfile, Keyfile,
502 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
503 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
504 cons->tls_ca_certdir, cons->tls_certfile,
505 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
507 if (!cons->tls_ctx) {
508 bsnprintf(buf, sizeof(buf), _("Failed to initialize TLS context for Console \"%s\".\n"),
510 set_text(buf, strlen(buf));
511 terminate_console(0);
517 /* Initialize Director TLS context */
518 if (dir->tls_enable || dir->tls_require) {
519 /* Generate passphrase prompt */
520 bsnprintf(buf, sizeof(buf), _("Passphrase for Director \"%s\" TLS private key: "), dir->hdr.name);
522 /* Initialize TLS context:
523 * Args: CA certfile, CA certdir, Certfile, Keyfile,
524 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
525 dir->tls_ctx = new_tls_context(dir->tls_ca_certfile,
526 dir->tls_ca_certdir, dir->tls_certfile,
527 dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
530 bsnprintf(buf, sizeof(buf), _("Failed to initialize TLS context for Director \"%s\".\n"),
532 set_text(buf, strlen(buf));
533 terminate_console(0);
539 UA_sock = bnet_connect(NULL, 5, 15, _("Director daemon"), dir->address,
540 NULL, dir->DIRport, 0);
541 if (UA_sock == NULL) {
545 jcr.dir_bsock = UA_sock;
546 if (!authenticate_director(&jcr, dir, cons)) {
547 set_text(UA_sock->msg, UA_sock->msglen);
551 set_status(_(" Initializing ..."));
553 bnet_fsend(UA_sock, "autodisplay on");
555 /* Read and display all initial messages */
556 while (bnet_recv(UA_sock) > 0) {
557 set_text(UA_sock->msg, UA_sock->msglen);
561 while (gtk_events_pending()) {
562 gtk_main_iteration();
565 /* Fill the run_dialog combo boxes */
566 job_list = get_and_fill_combo(run_dialog, "combo_job", ".jobs");
567 client_list = get_and_fill_combo(run_dialog, "combo_client", ".clients");
568 fileset_list = get_and_fill_combo(run_dialog, "combo_fileset", ".filesets");
569 messages_list = get_and_fill_combo(run_dialog, "combo_messages", ".msgs");
570 pool_list = get_and_fill_combo(run_dialog, "combo_pool", ".pools");
571 storage_list = get_and_fill_combo(run_dialog, "combo_storage", ".storage");
572 type_list = get_and_fill_combo(run_dialog, "combo_type", ".types");
573 level_list = get_and_fill_combo(run_dialog, "combo_level", ".levels");
575 /* Fill the label dialog combo boxes */
576 fill_combo(label_dialog, "label_combo_storage", storage_list);
577 fill_combo(label_dialog, "label_combo_pool", pool_list);
580 /* Fill the restore_dialog combo boxes */
581 fill_combo(restore_dialog, "combo_restore_job", job_list);
582 fill_combo(restore_dialog, "combo_restore_client", client_list);
583 fill_combo(restore_dialog, "combo_restore_fileset", fileset_list);
584 fill_combo(restore_dialog, "combo_restore_pool", pool_list);
585 fill_combo(restore_dialog, "combo_restore_storage", storage_list);
587 set_status(_(" Connected"));
591 void write_director(const gchar *msg)
595 set_status(_(" Processing command ..."));
596 UA_sock->msglen = strlen(msg);
597 pm_strcpy(&UA_sock->msg, msg);
600 if (strcmp(msg, ".quit") == 0 || strcmp(msg, ".exit") == 0) {
601 disconnect_from_director((gpointer)NULL);
607 void read_director(gpointer data, gint fd, GdkInputCondition condition)
611 if (!UA_sock || UA_sock->fd != fd) {
614 stat = bnet_recv(UA_sock);
620 set_text(UA_sock->msg, UA_sock->msglen);
623 if (is_bnet_stop(UA_sock)) { /* error or term request */
627 /* Must be a signal -- either do something or ignore it */
628 if (UA_sock->msglen == BNET_PROMPT) {
630 set_status(_(" At prompt waiting for input ..."));
632 if (UA_sock->msglen == BNET_EOD) {
640 void start_director_reader(gpointer data)
643 if (director_reader_running || !UA_sock) {
646 tag = gdk_input_add(UA_sock->fd, GDK_INPUT_READ, read_director, NULL);
647 director_reader_running = true;
650 void stop_director_reader(gpointer data)
652 if (!director_reader_running) {
655 gdk_input_remove(tag);
656 gdk_input_remove(tag);
657 gdk_input_remove(tag);
658 gdk_input_remove(tag);
659 gdk_input_remove(tag);
660 gdk_input_remove(tag);
661 gdk_input_remove(tag);
662 gdk_input_remove(tag);
663 gdk_input_remove(tag);
664 gdk_input_remove(tag);
665 gdk_input_remove(tag);
666 director_reader_running = false;
671 /* Cleanup and then exit */
672 void terminate_console(int sig)
674 static bool already_here = false;
676 if (already_here) /* avoid recursive temination problems */
680 disconnect_from_director((gpointer)NULL);
686 /* Buffer approx 2000 lines -- assume 60 chars/line */
687 #define MAX_TEXT_CHARS (2000 * 60)
688 static int text_chars = 0;
690 static void truncate_text_chars()
692 GtkTextBuffer *textbuf;
693 GtkTextIter iter, iter2;
695 int del_chars = MAX_TEXT_CHARS / 4;
697 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
698 len = gtk_text_buffer_get_char_count(textbuf);
699 gtk_text_buffer_get_iter_at_offset (textbuf, &iter, 0);
700 gtk_text_buffer_get_iter_at_offset (textbuf, &iter2, del_chars);
701 gtk_text_buffer_delete (textbuf, &iter, &iter2);
702 text_chars -= del_chars;
703 len = gtk_text_buffer_get_char_count(textbuf);
704 gtk_text_iter_set_offset(&iter, len);
707 void set_textf(const char *fmt, ...)
712 va_start(arg_ptr, fmt);
713 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
718 void set_text(const char *buf, int len)
720 GtkTextBuffer *textbuf;
724 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
725 buf_len = gtk_text_buffer_get_char_count(textbuf);
726 gtk_text_buffer_get_iter_at_offset(textbuf, &iter, buf_len - 1);
727 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
729 if (text_chars > MAX_TEXT_CHARS) {
730 truncate_text_chars();
732 set_scroll_bar_to_end();
735 void set_statusf(const char *fmt, ...)
740 va_start(arg_ptr, fmt);
741 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
743 gtk_label_set_text(GTK_LABEL(status1), buf);
744 // set_scroll_bar_to_end();
748 void set_status_ready()
750 gtk_label_set_text(GTK_LABEL(status1), _(" Ready"));
752 // set_scroll_bar_to_end();
755 void set_status(const char *buf)
757 gtk_label_set_text(GTK_LABEL(status1), buf);
758 // set_scroll_bar_to_end();
762 static void set_scroll_bar_to_end(void)
764 GtkTextBuffer* textbuf = NULL;
768 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
769 buf_len = gtk_text_buffer_get_char_count(textbuf);
770 gtk_text_buffer_get_iter_at_offset(textbuf, &iter, buf_len - 1);
771 gtk_text_iter_set_offset(&iter, buf_len);
772 gtk_text_buffer_place_cursor(textbuf, &iter);
773 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(text1),
774 gtk_text_buffer_get_mark(textbuf, "insert"),