2 Bacula® - The Network Backup Solution
4 Copyright (C) 2007-2011 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from many
7 others, a complete list can be found in the file AUTHORS.
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
14 Bacula® is a registered trademark of Kern Sibbald.
17 * DirComm, Director communications,class
19 * Kern Sibbald, January MMVII
27 #include "textinput.h"
30 static int tls_pem_callback(char *buf, int size, const void *userdata);
32 DirComm::DirComm(Console *parent, int conn): m_notifier(NULL), m_api_set(false)
37 m_at_main_prompt = false;
49 /* Terminate any open socket */
50 void DirComm::terminate()
54 m_notifier->setEnabled(false);
59 if (mainWin->m_connDebug)
60 Pmsg2(000, "DirComm %i terminating connections %s\n", m_conn, m_console->m_dir->name());
66 * Connect to Director.
68 bool DirComm::connect_dir()
79 foreach_res(cons, R_CONSOLE) {
83 if (m_sock && !is_bsock_open(m_sock)) {
84 mainWin->set_status( tr("Already connected."));
85 m_console->display_textf(_("Already connected\"%s\".\n"),
86 m_console->m_dir->name());
87 if (mainWin->m_connDebug) {
88 Pmsg2(000, "DirComm %i BAILING already connected %s\n", m_conn, m_console->m_dir->name());
93 if (mainWin->m_connDebug)Pmsg2(000, "DirComm %i connecting %s\n", m_conn, m_console->m_dir->name());
94 memset(jcr, 0, sizeof(JCR));
96 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_console->m_dir->address, m_console->m_dir->DIRport);
98 m_console->display_textf(_("Connecting to Director %s:%d\n\n"), m_console->m_dir->address, m_console->m_dir->DIRport);
101 /* Give GUI a chance */
102 app->processEvents();
105 /* If cons==NULL, default console will be used */
106 for (i=0; i<numcon; i++) {
107 cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)cons);
108 if (cons->director && strcasecmp(cons->director, m_console->m_dir->name()) == 0) {
111 if (i == (numcon - 1)) {
116 /* Look for the first non-linked console */
118 for (i=0; i<numcon; i++) {
119 cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)cons);
120 if (cons->director == NULL) {
123 if (i == (numcon - 1)) {
129 /* If no console, take first one */
131 cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
135 /* Initialize Console TLS context once */
136 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
137 /* Generate passphrase prompt */
138 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
141 /* Initialize TLS context:
142 * Args: CA certfile, CA certdir, Certfile, Keyfile,
143 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
145 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
146 cons->tls_ca_certdir, cons->tls_certfile,
147 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
149 if (!cons->tls_ctx) {
150 m_console->display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
151 m_console->m_dir->name());
152 if (mainWin->m_connDebug) {
153 Pmsg2(000, "DirComm %i BAILING Failed to initialize TLS context for Console %s\n", m_conn, m_console->m_dir->name());
159 /* Initialize Director TLS context once */
160 if (!m_console->m_dir->tls_ctx && (m_console->m_dir->tls_enable || m_console->m_dir->tls_require)) {
161 /* Generate passphrase prompt */
162 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
163 m_console->m_dir->name());
165 /* Initialize TLS context:
166 * Args: CA certfile, CA certdir, Certfile, Keyfile,
167 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
168 m_console->m_dir->tls_ctx = new_tls_context(m_console->m_dir->tls_ca_certfile,
169 m_console->m_dir->tls_ca_certdir, m_console->m_dir->tls_certfile,
170 m_console->m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
172 if (!m_console->m_dir->tls_ctx) {
173 m_console->display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
174 m_console->m_dir->name());
175 mainWin->set_status("Connection failed");
176 if (mainWin->m_connDebug) {
177 Pmsg2(000, "DirComm %i BAILING Failed to initialize TLS context for Director %s\n", m_conn, m_console->m_dir->name());
183 if (m_console->m_dir->heartbeat_interval) {
184 heart_beat = m_console->m_dir->heartbeat_interval;
186 heart_beat = cons->heartbeat_interval;
192 m_sock = new_bsock();
194 if (!m_sock->connect(NULL, 5, 15, heart_beat,
195 _("Director daemon"), m_console->m_dir->address,
196 NULL, m_console->m_dir->DIRport, 0)) {
200 if (m_sock == NULL) {
201 mainWin->set_status("Connection failed");
202 if (mainWin->m_connDebug) {
203 Pmsg2(000, "DirComm %i BAILING Connection failed %s\n", m_conn, m_console->m_dir->name());
207 /* Update page selector to green to indicate that Console is connected */
208 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
209 QBrush greenBrush(Qt::green);
210 QTreeWidgetItem *item = mainWin->getFromHash(m_console);
212 item->setForeground(0, greenBrush);
216 jcr->dir_bsock = m_sock;
218 if (!authenticate_director(jcr, m_console->m_dir, cons, buf, sizeof(buf))) {
219 m_console->display_text(buf);
220 if (mainWin->m_connDebug) {
221 Pmsg2(000, "DirComm %i BAILING Connection failed %s\n", m_conn, m_console->m_dir->name());
227 m_console->display_text(buf);
230 /* Give GUI a chance */
231 app->processEvents();
233 mainWin->set_status(_("Initializing ..."));
236 * Set up input notifier
238 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
239 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(notify_read_dir(int)));
240 m_notifier->setEnabled(true);
245 m_console->displayToPrompt(m_conn);
247 m_console->beginNewCommand(m_conn);
249 mainWin->set_status(_("Connected"));
251 if (mainWin->m_connDebug) {
252 Pmsg2(000, "Returning TRUE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
257 if (mainWin->m_connDebug) {
258 Pmsg2(000, "Returning FALSE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
265 * This should be moved into a bSocket class
275 int DirComm::write(const QString msg)
277 return write(msg.toUtf8().data());
280 int DirComm::write(const char *msg)
285 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
287 m_at_main_prompt = false;
288 if (mainWin->m_commDebug) Pmsg2(000, "conn %i send: %s\n", m_conn, msg);
290 * Ensure we send only one blank line. Multiple blank lines are
291 * simply discarded, it keeps the console output looking nicer.
293 if (m_sock->msglen == 0 || (m_sock->msglen == 1 && *m_sock->msg == '\n')) {
296 return m_sock->send();
298 return -1; /* discard multiple blanks */
301 m_sent_blank = false; /* clear flag */
302 return m_sock->send();
305 int DirComm::sock_read()
309 bool wasEnabled = notify(false);
310 stat = m_sock->recv();
313 stat = m_sock->recv();
319 * Blocking read from director
331 stat = m_sock->wait_data_intr(0, 50000);
335 app->processEvents();
336 if (m_api_set && m_console->is_messagesPending() && is_notify_enabled() && m_console->hasFocus()) {
337 if (mainWin->m_commDebug) Pmsg1(000, "conn %i process_events\n", m_conn);
338 m_console->messagesPending(false);
339 m_console->write_dir(m_conn, ".messages", false);
348 if (mainWin->m_commDebug) Pmsg2(000, "conn %i got: %s\n", m_conn, m_sock->msg);
350 m_console->display_text("\n");
352 m_at_main_prompt = false;
355 switch (m_sock->msglen) {
356 case BNET_MSGS_PENDING :
357 if (is_notify_enabled() && m_console->hasFocus()) {
358 m_console->messagesPending(false);
359 if (mainWin->m_commDebug) Pmsg1(000, "conn %i MSGS PENDING\n", m_conn);
360 m_console->write_dir(m_conn, ".messages", false);
361 m_console->displayToPrompt(m_conn);
364 m_console->messagesPending(true);
367 if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD OK\n", m_conn);
369 m_at_main_prompt = false;
370 if (--m_in_command < 0) {
373 mainWin->set_status(_("Command completed ..."));
376 if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD BEGIN\n", m_conn);
378 m_at_main_prompt = false;
380 mainWin->set_status(_("Processing command ..."));
382 case BNET_MAIN_PROMPT:
383 if (mainWin->m_commDebug) Pmsg1(000, "conn %i MAIN PROMPT\n", m_conn);
384 if (!m_at_prompt && ! m_at_main_prompt) {
386 m_at_main_prompt = true;
387 mainWin->set_status(_("At main prompt waiting for input ..."));
390 case BNET_SUB_PROMPT:
391 if (mainWin->m_commDebug) Pmsg2(000, "conn %i SUB_PROMPT m_in_select=%d\n", m_conn, m_in_select);
393 m_at_main_prompt = false;
394 mainWin->set_status(_("At prompt waiting for input ..."));
396 case BNET_TEXT_INPUT:
397 if (mainWin->m_commDebug) Pmsg4(000, "conn %i TEXT_INPUT at_prompt=%d m_in_select=%d notify=%d\n",
398 m_conn, m_at_prompt, m_in_select, is_notify_enabled());
399 //if (!m_in_select && is_notify_enabled()) {
402 new textInputDialog(m_console, m_conn);
404 if (mainWin->m_commDebug) Pmsg0(000, "!m_in_select && is_notify_enabled\n");
406 m_at_main_prompt = false;
407 mainWin->set_status(_("At prompt waiting for input ..."));
410 case BNET_CMD_FAILED:
411 if (mainWin->m_commDebug) Pmsg1(000, "CMD FAILED\n", m_conn);
412 if (--m_in_command < 0) {
415 mainWin->set_status(_("Command failed."));
417 /* We should not get this one */
419 if (mainWin->m_commDebug) Pmsg1(000, "conn %i EOD\n", m_conn);
420 mainWin->set_status_ready();
425 case BNET_START_SELECT:
426 if (mainWin->m_commDebug) Pmsg1(000, "conn %i START SELECT\n", m_conn);
428 new selectDialog(m_console, m_conn);
432 if (mainWin->m_commDebug) Pmsg1(000, "conn %i YESNO\n", m_conn);
433 new yesnoPopUp(m_console, m_conn);
436 if (mainWin->m_commDebug) Pmsg1(000, "conn %i RUN CMD\n", m_conn);
437 new runCmdPage(m_conn);
439 case BNET_START_RTREE:
440 if (mainWin->m_commDebug) Pmsg1(000, "conn %i START RTREE CMD\n", m_conn);
441 new restorePage(m_conn);
444 if (mainWin->m_commDebug) Pmsg1(000, "conn %i END RTREE CMD\n", m_conn);
447 if (mainWin->m_commDebug) Pmsg1(000, "conn %i ERROR MSG\n", m_conn);
448 stat = sock_read(); /* get the message */
449 m_console->display_text(msg());
450 QMessageBox::critical(m_console, "Error", msg(), QMessageBox::Ok);
451 m_console->beginNewCommand(m_conn);
454 case BNET_WARNING_MSG:
455 if (mainWin->m_commDebug) Pmsg1(000, "conn %i WARNING MSG\n", m_conn);
456 stat = sock_read(); /* get the message */
457 if (!m_console->m_warningPrevent) {
458 QMessageBox::critical(m_console, "Warning", msg(), QMessageBox::Ok);
462 if (mainWin->m_commDebug) Pmsg1(000, "conn %i INFO MSG\n", m_conn);
463 stat = sock_read(); /* get the message */
464 m_console->display_text(msg());
465 mainWin->set_status(msg());
473 if (m_sock->is_stop()) { /* error or term request */
474 if (mainWin->m_commDebug) Pmsg1(000, "conn %i BNET STOP\n", m_conn);
475 m_console->stopTimer();
477 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
478 QBrush redBrush(Qt::red);
479 QTreeWidgetItem *item = mainWin->getFromHash(m_console);
480 item->setForeground(0, redBrush);
482 m_notifier->setEnabled(false);
487 mainWin->set_status(_("Director disconnected."));
495 /* Called by signal when the Director has output for us */
496 void DirComm::notify_read_dir(int /* fd */)
499 if (!mainWin->m_notify) {
502 if (mainWin->m_commDebug) Pmsg1(000, "enter read_dir conn %i read_dir\n", m_conn);
503 stat = m_sock->wait_data(0, 5000);
505 if (mainWin->m_commDebug) Pmsg2(000, "read_dir conn %i stat=%d\n", m_conn, stat);
506 while (read() >= 0) {
507 m_console->display_text(msg());
510 if (mainWin->m_commDebug) Pmsg2(000, "exit read_dir conn %i stat=%d\n", m_conn, stat);
514 * When the notifier is enabled, read_dir() will automatically be
515 * called by the Qt event loop when ever there is any output
516 * from the Director, and read_dir() will then display it on
519 * When we are in a bat dialog, we want to control *all* output
520 * from the Directory, so we set notify to off.
521 * m_console->notify(false);
523 bool DirComm::notify(bool enable)
525 bool prev_enabled = false;
526 /* Set global flag */
527 mainWin->m_notify = enable;
529 prev_enabled = m_notifier->isEnabled();
530 m_notifier->setEnabled(enable);
532 if (mainWin->m_connDebug) Pmsg3(000, "conn=%i set_notify=%d prev=%d\n", m_conn, enable, prev_enabled);
533 } else if (mainWin->m_connDebug) {
534 Pmsg2(000, "m_notifier does not exist: %i %s\n", m_conn, m_console->m_dir->name());
539 bool DirComm::is_notify_enabled() const
545 * Call-back for reading a passphrase for an encrypted PEM file
546 * This function uses getpass(),
547 * which uses a static buffer and is NOT thread-safe.
549 static int tls_pem_callback(char *buf, int size, const void *userdata)
554 # if defined(HAVE_WIN32)
556 if (win32_cgets(buf, size) == NULL) {
563 const char *prompt = (const char *)userdata;
566 passwd = getpass(prompt);
567 bstrncpy(buf, passwd, size);