2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2015 Kern Sibbald
5 Copyright (C) 2007-2011 Free Software Foundation Europe e.V.
7 The original author of Bacula is Kern Sibbald, with contributions
8 from many others, a complete list can be found in the file AUTHORS.
10 You may use this file and others of this release according to the
11 license defined in the LICENSE file, which includes the Affero General
12 Public License, v3.0 ("AGPLv3") and some additional permissions and
13 terms pursuant to its AGPLv3 Section 7.
15 This notice must be preserved when any source code is
16 conveyed and/or propagated.
18 Bacula(R) is a registered trademark of Kern Sibbald.
21 * DirComm, Director communications,class
23 * Kern Sibbald, January MMVII
31 #include "textinput.h"
34 static int tls_pem_callback(char *buf, int size, const void *userdata);
36 DirComm::DirComm(Console *parent, int conn): m_notifier(NULL), m_api_set(false)
41 m_at_main_prompt = false;
53 /* Terminate any open socket */
54 void DirComm::terminate()
58 m_notifier->setEnabled(false);
63 if (mainWin->m_connDebug)
64 Pmsg2(000, "DirComm %i terminating connections %s\n", m_conn, m_console->m_dir->name());
70 * Connect to Director.
72 bool DirComm::connect_dir()
83 foreach_res(cons, R_CONSOLE) {
87 if (m_sock && !is_bsock_open(m_sock)) {
88 mainWin->set_status( tr("Already connected."));
89 m_console->display_textf(_("Already connected\"%s\".\n"),
90 m_console->m_dir->name());
91 if (mainWin->m_connDebug) {
92 Pmsg2(000, "DirComm %i BAILING already connected %s\n", m_conn, m_console->m_dir->name());
97 if (mainWin->m_connDebug)Pmsg2(000, "DirComm %i connecting %s\n", m_conn, m_console->m_dir->name());
98 memset(jcr, 0, sizeof(JCR));
100 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_console->m_dir->address, m_console->m_dir->DIRport);
102 m_console->display_textf(_("Connecting to Director %s:%d\n\n"), m_console->m_dir->address, m_console->m_dir->DIRport);
105 /* Give GUI a chance */
106 app->processEvents();
109 /* If cons==NULL, default console will be used */
110 for (i=0; i<numcon; i++) {
111 cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)cons);
112 if (cons->director && strcasecmp(cons->director, m_console->m_dir->name()) == 0) {
115 if (i == (numcon - 1)) {
120 /* Look for the first non-linked console */
122 for (i=0; i<numcon; i++) {
123 cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)cons);
124 if (cons->director == NULL) {
127 if (i == (numcon - 1)) {
133 /* If no console, take first one */
135 cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
139 /* Initialize Console TLS context once */
140 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
141 /* Generate passphrase prompt */
142 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
145 /* Initialize TLS context:
146 * Args: CA certfile, CA certdir, Certfile, Keyfile,
147 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
149 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
150 cons->tls_ca_certdir, cons->tls_certfile,
151 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
153 if (!cons->tls_ctx) {
154 m_console->display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
155 m_console->m_dir->name());
156 if (mainWin->m_connDebug) {
157 Pmsg2(000, "DirComm %i BAILING Failed to initialize TLS context for Console %s\n", m_conn, m_console->m_dir->name());
163 /* Initialize Director TLS context once */
164 if (!m_console->m_dir->tls_ctx && (m_console->m_dir->tls_enable || m_console->m_dir->tls_require)) {
165 /* Generate passphrase prompt */
166 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
167 m_console->m_dir->name());
169 /* Initialize TLS context:
170 * Args: CA certfile, CA certdir, Certfile, Keyfile,
171 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
172 m_console->m_dir->tls_ctx = new_tls_context(m_console->m_dir->tls_ca_certfile,
173 m_console->m_dir->tls_ca_certdir, m_console->m_dir->tls_certfile,
174 m_console->m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
176 if (!m_console->m_dir->tls_ctx) {
177 m_console->display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
178 m_console->m_dir->name());
179 mainWin->set_status("Connection failed");
180 if (mainWin->m_connDebug) {
181 Pmsg2(000, "DirComm %i BAILING Failed to initialize TLS context for Director %s\n", m_conn, m_console->m_dir->name());
187 if (m_console->m_dir->heartbeat_interval) {
188 heart_beat = m_console->m_dir->heartbeat_interval;
190 heart_beat = cons->heartbeat_interval;
196 m_sock = new_bsock();
198 if (!m_sock->connect(NULL, 5, 15, heart_beat,
199 _("Director daemon"), m_console->m_dir->address,
200 NULL, m_console->m_dir->DIRport, 0)) {
204 if (m_sock == NULL) {
205 mainWin->set_status("Connection failed");
206 if (mainWin->m_connDebug) {
207 Pmsg2(000, "DirComm %i BAILING Connection failed %s\n", m_conn, m_console->m_dir->name());
211 /* Update page selector to green to indicate that Console is connected */
212 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
213 QBrush greenBrush(Qt::green);
214 QTreeWidgetItem *item = mainWin->getFromHash(m_console);
216 item->setForeground(0, greenBrush);
220 jcr->dir_bsock = m_sock;
222 if (!authenticate_director(jcr, m_console->m_dir, cons, buf, sizeof(buf))) {
223 m_console->display_text(buf);
224 if (mainWin->m_connDebug) {
225 Pmsg2(000, "DirComm %i BAILING Connection failed %s\n", m_conn, m_console->m_dir->name());
231 m_console->display_text(buf);
234 /* Give GUI a chance */
235 app->processEvents();
237 mainWin->set_status(_("Initializing ..."));
240 * Set up input notifier
242 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
243 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(notify_read_dir(int)));
244 m_notifier->setEnabled(true);
249 m_console->displayToPrompt(m_conn);
251 m_console->beginNewCommand(m_conn);
253 mainWin->set_status(_("Connected"));
255 if (mainWin->m_connDebug) {
256 Pmsg2(000, "Returning TRUE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
261 if (mainWin->m_connDebug) {
262 Pmsg2(000, "Returning FALSE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
269 * This should be moved into a bSocket class
279 int DirComm::write(const QString msg)
281 return write(msg.toUtf8().data());
284 int DirComm::write(const char *msg)
289 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
291 m_at_main_prompt = false;
292 if (mainWin->m_commDebug) Pmsg2(000, "conn %i send: %s\n", m_conn, msg);
294 * Ensure we send only one blank line. Multiple blank lines are
295 * simply discarded, it keeps the console output looking nicer.
297 if (m_sock->msglen == 0 || (m_sock->msglen == 1 && *m_sock->msg == '\n')) {
300 return m_sock->send();
302 return -1; /* discard multiple blanks */
305 m_sent_blank = false; /* clear flag */
306 return m_sock->send();
309 int DirComm::sock_read()
313 bool wasEnabled = notify(false);
314 stat = m_sock->recv();
317 stat = m_sock->recv();
323 * Blocking read from director
335 stat = m_sock->wait_data_intr(0, 50000);
339 app->processEvents();
340 if (m_api_set && m_console->is_messagesPending() && is_notify_enabled() && m_console->hasFocus()) {
341 if (mainWin->m_commDebug) Pmsg1(000, "conn %i process_events\n", m_conn);
342 m_console->messagesPending(false);
343 m_console->write_dir(m_conn, ".messages", false);
352 if (mainWin->m_commDebug) Pmsg2(000, "conn %i got: %s\n", m_conn, m_sock->msg);
354 m_console->display_text("\n");
356 m_at_main_prompt = false;
359 switch (m_sock->msglen) {
360 case BNET_MSGS_PENDING :
361 if (is_notify_enabled() && m_console->hasFocus()) {
362 m_console->messagesPending(false);
363 if (mainWin->m_commDebug) Pmsg1(000, "conn %i MSGS PENDING\n", m_conn);
364 m_console->write_dir(m_conn, ".messages", false);
365 m_console->displayToPrompt(m_conn);
368 m_console->messagesPending(true);
371 if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD OK\n", m_conn);
373 m_at_main_prompt = false;
374 if (--m_in_command < 0) {
377 mainWin->set_status(_("Command completed ..."));
380 if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD BEGIN\n", m_conn);
382 m_at_main_prompt = false;
384 mainWin->set_status(_("Processing command ..."));
386 case BNET_MAIN_PROMPT:
387 if (mainWin->m_commDebug) Pmsg1(000, "conn %i MAIN PROMPT\n", m_conn);
388 if (!m_at_prompt && ! m_at_main_prompt) {
390 m_at_main_prompt = true;
391 mainWin->set_status(_("At main prompt waiting for input ..."));
394 case BNET_SUB_PROMPT:
395 if (mainWin->m_commDebug) Pmsg2(000, "conn %i SUB_PROMPT m_in_select=%d\n", m_conn, m_in_select);
397 m_at_main_prompt = false;
398 mainWin->set_status(_("At prompt waiting for input ..."));
400 case BNET_TEXT_INPUT:
401 if (mainWin->m_commDebug) Pmsg4(000, "conn %i TEXT_INPUT at_prompt=%d m_in_select=%d notify=%d\n",
402 m_conn, m_at_prompt, m_in_select, is_notify_enabled());
403 if (!m_in_select && is_notify_enabled()) {
404 new textInputDialog(m_console, m_conn);
405 if (mainWin->m_commDebug) Pmsg0(000, "!m_in_select && is_notify_enabled\n");
407 m_at_main_prompt = false;
408 mainWin->set_status(_("At prompt waiting for input ..."));
411 case BNET_CMD_FAILED:
412 if (mainWin->m_commDebug) Pmsg1(000, "CMD FAILED\n", m_conn);
413 if (--m_in_command < 0) {
416 mainWin->set_status(_("Command failed."));
418 /* We should not get this one */
420 if (mainWin->m_commDebug) Pmsg1(000, "conn %i EOD\n", m_conn);
421 mainWin->set_status_ready();
426 case BNET_START_SELECT:
427 if (mainWin->m_commDebug) Pmsg1(000, "conn %i START SELECT\n", m_conn);
429 new selectDialog(m_console, m_conn);
433 if (mainWin->m_commDebug) Pmsg1(000, "conn %i YESNO\n", m_conn);
434 new yesnoPopUp(m_console, m_conn);
437 if (mainWin->m_commDebug) Pmsg1(000, "conn %i RUN CMD\n", m_conn);
438 new runCmdPage(m_conn);
440 case BNET_START_RTREE:
441 if (mainWin->m_commDebug) Pmsg1(000, "conn %i START RTREE CMD\n", m_conn);
442 new restorePage(m_conn);
445 if (mainWin->m_commDebug) Pmsg1(000, "conn %i END RTREE CMD\n", m_conn);
448 if (mainWin->m_commDebug) Pmsg1(000, "conn %i ERROR MSG\n", m_conn);
449 stat = sock_read(); /* get the message */
450 m_console->display_text(msg());
451 QMessageBox::critical(m_console, "Error", msg(), QMessageBox::Ok);
452 m_console->beginNewCommand(m_conn);
455 case BNET_WARNING_MSG:
456 if (mainWin->m_commDebug) Pmsg1(000, "conn %i WARNING MSG\n", m_conn);
457 stat = sock_read(); /* get the message */
458 if (!m_console->m_warningPrevent) {
459 QMessageBox::critical(m_console, "Warning", msg(), QMessageBox::Ok);
463 if (mainWin->m_commDebug) Pmsg1(000, "conn %i INFO MSG\n", m_conn);
464 stat = sock_read(); /* get the message */
465 m_console->display_text(msg());
466 mainWin->set_status(msg());
474 if (m_sock->is_stop()) { /* error or term request */
475 if (mainWin->m_commDebug) Pmsg1(000, "conn %i BNET STOP\n", m_conn);
476 m_console->stopTimer();
478 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
479 QBrush redBrush(Qt::red);
480 QTreeWidgetItem *item = mainWin->getFromHash(m_console);
481 item->setForeground(0, redBrush);
483 m_notifier->setEnabled(false);
488 mainWin->set_status(_("Director disconnected."));
496 /* Called by signal when the Director has output for us */
497 void DirComm::notify_read_dir(int /* fd */)
500 if (!mainWin->m_notify) {
503 if (mainWin->m_commDebug) Pmsg1(000, "enter read_dir conn %i read_dir\n", m_conn);
504 stat = m_sock->wait_data(0, 5000);
506 if (mainWin->m_commDebug) Pmsg2(000, "read_dir conn %i stat=%d\n", m_conn, stat);
507 while (read() >= 0) {
508 m_console->display_text(msg());
511 if (mainWin->m_commDebug) Pmsg2(000, "exit read_dir conn %i stat=%d\n", m_conn, stat);
515 * When the notifier is enabled, read_dir() will automatically be
516 * called by the Qt event loop when ever there is any output
517 * from the Director, and read_dir() will then display it on
520 * When we are in a bat dialog, we want to control *all* output
521 * from the Directory, so we set notify to off.
522 * m_console->notify(false);
524 bool DirComm::notify(bool enable)
526 bool prev_enabled = false;
527 /* Set global flag */
528 mainWin->m_notify = enable;
530 prev_enabled = m_notifier->isEnabled();
531 m_notifier->setEnabled(enable);
533 if (mainWin->m_connDebug) Pmsg3(000, "conn=%i set_notify=%d prev=%d\n", m_conn, enable, prev_enabled);
534 } else if (mainWin->m_connDebug) {
535 Pmsg2(000, "m_notifier does not exist: %i %s\n", m_conn, m_console->m_dir->name());
540 bool DirComm::is_notify_enabled() const
546 * Call-back for reading a passphrase for an encrypted PEM file
547 * This function uses getpass(),
548 * which uses a static buffer and is NOT thread-safe.
550 static int tls_pem_callback(char *buf, int size, const void *userdata)
555 # if defined(HAVE_WIN32)
557 if (win32_cgets(buf, size) == NULL) {
564 const char *prompt = (const char *)userdata;
567 passwd = getpass(prompt);
568 bstrncpy(buf, passwd, size);