2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2016 Kern Sibbald
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many 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 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
20 * DirComm, Director communications,class
22 * Kern Sibbald, January MMVII
30 #include "textinput.h"
33 static int tls_pem_callback(char *buf, int size, const void *userdata);
35 DirComm::DirComm(Console *parent, int conn): m_notifier(NULL), m_api_set(false)
40 m_at_main_prompt = false;
52 /* Terminate any open socket */
53 void DirComm::terminate()
57 m_notifier->setEnabled(false);
62 if (mainWin->m_connDebug)
63 Pmsg2(000, "DirComm %i terminating connections %s\n", m_conn, m_console->m_dir->name());
69 * Connect to Director.
71 bool DirComm::connect_dir()
82 foreach_res(cons, R_CONSOLE) {
86 if (m_sock && !is_bsock_open(m_sock)) {
87 mainWin->set_status( tr("Already connected."));
88 m_console->display_textf(_("Already connected\"%s\".\n"),
89 m_console->m_dir->name());
90 if (mainWin->m_connDebug) {
91 Pmsg2(000, "DirComm %i BAILING already connected %s\n", m_conn, m_console->m_dir->name());
96 if (mainWin->m_connDebug)Pmsg2(000, "DirComm %i connecting %s\n", m_conn, m_console->m_dir->name());
97 memset(jcr, 0, sizeof(JCR));
99 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_console->m_dir->address, m_console->m_dir->DIRport);
101 m_console->display_textf(_("Connecting to Director %s:%d\n\n"), m_console->m_dir->address, m_console->m_dir->DIRport);
104 /* Give GUI a chance */
105 app->processEvents();
108 /* If cons==NULL, default console will be used */
109 for (i=0; i<numcon; i++) {
110 cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)cons);
111 if (cons->director && strcasecmp(cons->director, m_console->m_dir->name()) == 0) {
114 if (i == (numcon - 1)) {
119 /* Look for the first non-linked console */
121 for (i=0; i<numcon; i++) {
122 cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)cons);
123 if (cons->director == NULL) {
126 if (i == (numcon - 1)) {
132 /* If no console, take first one */
134 cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
138 /* Initialize Console TLS context once */
139 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
140 /* Generate passphrase prompt */
141 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
144 /* Initialize TLS context:
145 * Args: CA certfile, CA certdir, Certfile, Keyfile,
146 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
148 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
149 cons->tls_ca_certdir, cons->tls_certfile,
150 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
152 if (!cons->tls_ctx) {
153 m_console->display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
154 m_console->m_dir->name());
155 if (mainWin->m_connDebug) {
156 Pmsg2(000, "DirComm %i BAILING Failed to initialize TLS context for Console %s\n", m_conn, m_console->m_dir->name());
162 /* Initialize Director TLS context once */
163 if (!m_console->m_dir->tls_ctx && (m_console->m_dir->tls_enable || m_console->m_dir->tls_require)) {
164 /* Generate passphrase prompt */
165 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
166 m_console->m_dir->name());
168 /* Initialize TLS context:
169 * Args: CA certfile, CA certdir, Certfile, Keyfile,
170 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
171 m_console->m_dir->tls_ctx = new_tls_context(m_console->m_dir->tls_ca_certfile,
172 m_console->m_dir->tls_ca_certdir, m_console->m_dir->tls_certfile,
173 m_console->m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
175 if (!m_console->m_dir->tls_ctx) {
176 m_console->display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
177 m_console->m_dir->name());
178 mainWin->set_status("Connection failed");
179 if (mainWin->m_connDebug) {
180 Pmsg2(000, "DirComm %i BAILING Failed to initialize TLS context for Director %s\n", m_conn, m_console->m_dir->name());
186 if (m_console->m_dir->heartbeat_interval) {
187 heart_beat = m_console->m_dir->heartbeat_interval;
189 heart_beat = cons->heartbeat_interval;
195 m_sock = new_bsock();
197 if (!m_sock->connect(NULL, 5, 15, heart_beat,
198 _("Director daemon"), m_console->m_dir->address,
199 NULL, m_console->m_dir->DIRport, 0)) {
203 if (m_sock == NULL) {
204 mainWin->set_status("Connection failed");
205 if (mainWin->m_connDebug) {
206 Pmsg2(000, "DirComm %i BAILING Connection failed %s\n", m_conn, m_console->m_dir->name());
210 /* Update page selector to green to indicate that Console is connected */
211 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
212 QBrush greenBrush(Qt::green);
213 QTreeWidgetItem *item = mainWin->getFromHash(m_console);
215 item->setForeground(0, greenBrush);
219 jcr->dir_bsock = m_sock;
221 if (!authenticate_director(jcr, m_console->m_dir, cons, buf, sizeof(buf))) {
222 m_console->display_text(buf);
223 if (mainWin->m_connDebug) {
224 Pmsg2(000, "DirComm %i BAILING Connection failed %s\n", m_conn, m_console->m_dir->name());
230 m_console->display_text(buf);
233 /* Give GUI a chance */
234 app->processEvents();
236 mainWin->set_status(_("Initializing ..."));
239 * Set up input notifier
241 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
242 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(notify_read_dir(int)));
243 m_notifier->setEnabled(true);
248 m_console->displayToPrompt(m_conn);
250 m_console->beginNewCommand(m_conn);
252 mainWin->set_status(_("Connected"));
254 if (mainWin->m_connDebug) {
255 Pmsg2(000, "Returning TRUE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
260 if (mainWin->m_connDebug) {
261 Pmsg2(000, "Returning FALSE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
268 * This should be moved into a bSocket class
278 int DirComm::write(const QString msg)
280 return write(msg.toUtf8().data());
283 int DirComm::write(const char *msg)
288 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
290 m_at_main_prompt = false;
291 if (mainWin->m_commDebug) Pmsg2(000, "conn %i send: %s\n", m_conn, msg);
293 * Ensure we send only one blank line. Multiple blank lines are
294 * simply discarded, it keeps the console output looking nicer.
296 if (m_sock->msglen == 0 || (m_sock->msglen == 1 && *m_sock->msg == '\n')) {
299 return m_sock->send();
301 return -1; /* discard multiple blanks */
304 m_sent_blank = false; /* clear flag */
305 return m_sock->send();
308 int DirComm::sock_read()
312 bool wasEnabled = notify(false);
313 stat = m_sock->recv();
316 stat = m_sock->recv();
322 * Blocking read from director
334 stat = m_sock->wait_data_intr(0, 50000);
338 app->processEvents();
339 if (m_api_set && m_console->is_messagesPending() && is_notify_enabled() && m_console->hasFocus()) {
340 if (mainWin->m_commDebug) Pmsg1(000, "conn %i process_events\n", m_conn);
341 m_console->messagesPending(false);
342 m_console->write_dir(m_conn, ".messages", false);
351 if (mainWin->m_commDebug) Pmsg2(000, "conn %i got: %s\n", m_conn, m_sock->msg);
353 m_console->display_text("\n");
355 m_at_main_prompt = false;
358 switch (m_sock->msglen) {
359 case BNET_MSGS_PENDING :
360 if (is_notify_enabled() && m_console->hasFocus()) {
361 m_console->messagesPending(false);
362 if (mainWin->m_commDebug) Pmsg1(000, "conn %i MSGS PENDING\n", m_conn);
363 m_console->write_dir(m_conn, ".messages", false);
364 m_console->displayToPrompt(m_conn);
367 m_console->messagesPending(true);
370 if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD OK\n", m_conn);
372 m_at_main_prompt = false;
373 if (--m_in_command < 0) {
376 mainWin->set_status(_("Command completed ..."));
379 if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD BEGIN\n", m_conn);
381 m_at_main_prompt = false;
383 mainWin->set_status(_("Processing command ..."));
385 case BNET_MAIN_PROMPT:
386 if (mainWin->m_commDebug) Pmsg1(000, "conn %i MAIN PROMPT\n", m_conn);
387 if (!m_at_prompt && ! m_at_main_prompt) {
389 m_at_main_prompt = true;
390 mainWin->set_status(_("At main prompt waiting for input ..."));
393 case BNET_SUB_PROMPT:
394 if (mainWin->m_commDebug) Pmsg2(000, "conn %i SUB_PROMPT m_in_select=%d\n", m_conn, m_in_select);
396 m_at_main_prompt = false;
397 mainWin->set_status(_("At prompt waiting for input ..."));
399 case BNET_TEXT_INPUT:
400 if (mainWin->m_commDebug) Pmsg4(000, "conn %i TEXT_INPUT at_prompt=%d m_in_select=%d notify=%d\n",
401 m_conn, m_at_prompt, m_in_select, is_notify_enabled());
402 if (!m_in_select && is_notify_enabled()) {
403 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);