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
7 many others, a complete list can be found in the file AUTHORS.
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version three of the GNU Affero General Public
10 License as published by the Free Software Foundation and included
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU Affero General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 Bacula® is a registered trademark of Kern Sibbald.
24 The licensor of Bacula is the Free Software Foundation Europe
25 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26 Switzerland, email:ftf@fsfeurope.org.
29 * DirComm, Director communications,class
31 * Kern Sibbald, January MMVII
39 #include "textinput.h"
42 static int tls_pem_callback(char *buf, int size, const void *userdata);
44 DirComm::DirComm(Console *parent, int conn): m_notifier(NULL), m_api_set(false)
49 m_at_main_prompt = false;
60 /* Terminate any open socket */
61 void DirComm::terminate()
65 m_notifier->setEnabled(false);
69 if (mainWin->m_connDebug)
70 Pmsg2(000, "DirComm %i terminating connections %s\n", m_conn, m_console->m_dir->name());
77 * Connect to Director.
79 bool DirComm::connect_dir()
89 mainWin->set_status( tr("Already connected."));
90 m_console->display_textf(_("Already connected\"%s\".\n"),
91 m_console->m_dir->name());
92 if (mainWin->m_connDebug) {
93 Pmsg2(000, "DirComm %i BAILING already connected %s\n", m_conn, m_console->m_dir->name());
98 if (mainWin->m_connDebug)Pmsg2(000, "DirComm %i connecting %s\n", m_conn, m_console->m_dir->name());
99 memset(jcr, 0, sizeof(JCR));
101 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_console->m_dir->address, m_console->m_dir->DIRport);
103 m_console->display_textf(_("Connecting to Director %s:%d\n\n"), m_console->m_dir->address, m_console->m_dir->DIRport);
106 /* Give GUI a chance */
107 app->processEvents();
110 /* If cons==NULL, default console will be used */
111 cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
114 /* Initialize Console TLS context once */
115 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
116 /* Generate passphrase prompt */
117 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
120 /* Initialize TLS context:
121 * Args: CA certfile, CA certdir, Certfile, Keyfile,
122 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
124 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
125 cons->tls_ca_certdir, cons->tls_certfile,
126 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
128 if (!cons->tls_ctx) {
129 m_console->display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
130 m_console->m_dir->name());
131 if (mainWin->m_connDebug) {
132 Pmsg2(000, "DirComm %i BAILING Failed to initialize TLS context for Console %s\n", m_conn, m_console->m_dir->name());
138 /* Initialize Director TLS context once */
139 if (!m_console->m_dir->tls_ctx && (m_console->m_dir->tls_enable || m_console->m_dir->tls_require)) {
140 /* Generate passphrase prompt */
141 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
142 m_console->m_dir->name());
144 /* Initialize TLS context:
145 * Args: CA certfile, CA certdir, Certfile, Keyfile,
146 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
147 m_console->m_dir->tls_ctx = new_tls_context(m_console->m_dir->tls_ca_certfile,
148 m_console->m_dir->tls_ca_certdir, m_console->m_dir->tls_certfile,
149 m_console->m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
151 if (!m_console->m_dir->tls_ctx) {
152 m_console->display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
153 m_console->m_dir->name());
154 mainWin->set_status("Connection failed");
155 if (mainWin->m_connDebug) {
156 Pmsg2(000, "DirComm %i BAILING Failed to initialize TLS context for Director %s\n", m_conn, m_console->m_dir->name());
162 if (m_console->m_dir->heartbeat_interval) {
163 heart_beat = m_console->m_dir->heartbeat_interval;
165 heart_beat = cons->heartbeat_interval;
170 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
171 _("Director daemon"), m_console->m_dir->address,
172 NULL, m_console->m_dir->DIRport, 0);
173 if (m_sock == NULL) {
174 mainWin->set_status("Connection failed");
175 if (mainWin->m_connDebug) {
176 Pmsg2(000, "DirComm %i BAILING Connection failed %s\n", m_conn, m_console->m_dir->name());
180 /* Update page selector to green to indicate that Console is connected */
181 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
182 QBrush greenBrush(Qt::green);
183 QTreeWidgetItem *item = mainWin->getFromHash(m_console);
185 item->setForeground(0, greenBrush);
189 jcr->dir_bsock = m_sock;
191 if (!authenticate_director(jcr, m_console->m_dir, cons, buf, sizeof(buf))) {
192 m_console->display_text(buf);
193 if (mainWin->m_connDebug) {
194 Pmsg2(000, "DirComm %i BAILING Connection failed %s\n", m_conn, m_console->m_dir->name());
200 m_console->display_text(buf);
203 /* Give GUI a chance */
204 app->processEvents();
206 mainWin->set_status(_("Initializing ..."));
209 * Set up input notifier
211 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
212 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(notify_read_dir(int)));
213 m_notifier->setEnabled(true);
217 m_console->displayToPrompt(m_conn);
219 m_console->beginNewCommand(m_conn);
221 mainWin->set_status(_("Connected"));
223 if (mainWin->m_connDebug) {
224 Pmsg2(000, "Returning TRUE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
229 if (mainWin->m_connDebug) {
230 Pmsg2(000, "Returning FALSE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
237 * This should be moved into a bSocket class
247 int DirComm::write(const QString msg)
249 return write(msg.toUtf8().data());
252 int DirComm::write(const char *msg)
257 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
259 m_at_main_prompt = false;
260 if (mainWin->m_commDebug) Pmsg2(000, "conn %i send: %s\n", m_conn, msg);
262 * Ensure we send only one blank line. Multiple blank lines are
263 * simply discarded, it keeps the console output looking nicer.
265 if (m_sock->msglen == 0 || (m_sock->msglen == 1 && *m_sock->msg == '\n')) {
268 return m_sock->send();
270 return -1; /* discard multiple blanks */
273 m_sent_blank = false; /* clear flag */
274 return m_sock->send();
277 int DirComm::sock_read()
281 bool wasEnabled = notify(false);
282 stat = m_sock->recv();
285 stat = m_sock->recv();
291 * Blocking read from director
303 stat = m_sock->wait_data_intr(0, 50000);
307 app->processEvents();
308 if (m_api_set && m_console->is_messagesPending() && is_notify_enabled() && m_console->hasFocus()) {
309 if (mainWin->m_commDebug) Pmsg1(000, "conn %i process_events\n", m_conn);
310 m_console->messagesPending(false);
311 m_console->write_dir(m_conn, ".messages", false);
320 if (mainWin->m_commDebug) Pmsg2(000, "conn %i got: %s\n", m_conn, m_sock->msg);
322 m_console->display_text("\n");
324 m_at_main_prompt = false;
327 switch (m_sock->msglen) {
328 case BNET_MSGS_PENDING :
329 if (is_notify_enabled() && m_console->hasFocus()) {
330 m_console->messagesPending(false);
331 if (mainWin->m_commDebug) Pmsg1(000, "conn %i MSGS PENDING\n", m_conn);
332 m_console->write_dir(m_conn, ".messages", false);
333 m_console->displayToPrompt(m_conn);
336 m_console->messagesPending(true);
339 if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD OK\n", m_conn);
341 m_at_main_prompt = false;
342 if (--m_in_command < 0) {
345 mainWin->set_status(_("Command completed ..."));
348 if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD BEGIN\n", m_conn);
350 m_at_main_prompt = false;
352 mainWin->set_status(_("Processing command ..."));
354 case BNET_MAIN_PROMPT:
355 if (mainWin->m_commDebug) Pmsg1(000, "conn %i MAIN PROMPT\n", m_conn);
356 if (!m_at_prompt && ! m_at_main_prompt) {
358 m_at_main_prompt = true;
359 mainWin->set_status(_("At main prompt waiting for input ..."));
362 case BNET_SUB_PROMPT:
363 if (mainWin->m_commDebug) Pmsg2(000, "conn %i SUB_PROMPT m_in_select=%d\n", m_conn, m_in_select);
365 m_at_main_prompt = false;
366 mainWin->set_status(_("At prompt waiting for input ..."));
368 case BNET_TEXT_INPUT:
369 if (mainWin->m_commDebug) Pmsg4(000, "conn %i TEXT_INPUT at_prompt=%d m_in_select=%d notify=%d\n",
370 m_conn, m_at_prompt, m_in_select, is_notify_enabled());
371 if (!m_in_select && is_notify_enabled()) {
373 new textInputDialog(m_console, m_conn);
375 if (mainWin->m_commDebug) Pmsg0(000, "!m_in_select && is_notify_enabled\n");
377 m_at_main_prompt = false;
378 mainWin->set_status(_("At prompt waiting for input ..."));
381 case BNET_CMD_FAILED:
382 if (mainWin->m_commDebug) Pmsg1(000, "CMD FAILED\n", m_conn);
383 if (--m_in_command < 0) {
386 mainWin->set_status(_("Command failed."));
388 /* We should not get this one */
390 if (mainWin->m_commDebug) Pmsg1(000, "conn %i EOD\n", m_conn);
391 mainWin->set_status_ready();
396 case BNET_START_SELECT:
397 if (mainWin->m_commDebug) Pmsg1(000, "conn %i START SELECT\n", m_conn);
399 new selectDialog(m_console, m_conn);
403 if (mainWin->m_commDebug) Pmsg1(000, "conn %i YESNO\n", m_conn);
404 new yesnoPopUp(m_console, m_conn);
407 if (mainWin->m_commDebug) Pmsg1(000, "conn %i RUN CMD\n", m_conn);
408 new runCmdPage(m_conn);
410 case BNET_START_RTREE:
411 if (mainWin->m_commDebug) Pmsg1(000, "conn %i START RTREE CMD\n", m_conn);
412 new restorePage(m_conn);
415 if (mainWin->m_commDebug) Pmsg1(000, "conn %i END RTREE CMD\n", m_conn);
418 if (mainWin->m_commDebug) Pmsg1(000, "conn %i ERROR MSG\n", m_conn);
419 stat = sock_read(); /* get the message */
420 m_console->display_text(msg());
421 QMessageBox::critical(m_console, "Error", msg(), QMessageBox::Ok);
422 m_console->beginNewCommand(m_conn);
425 case BNET_WARNING_MSG:
426 if (mainWin->m_commDebug) Pmsg1(000, "conn %i WARNING MSG\n", m_conn);
427 stat = sock_read(); /* get the message */
428 if (!m_console->m_warningPrevent) {
429 QMessageBox::critical(m_console, "Warning", msg(), QMessageBox::Ok);
433 if (mainWin->m_commDebug) Pmsg1(000, "conn %i INFO MSG\n", m_conn);
434 stat = sock_read(); /* get the message */
435 m_console->display_text(msg());
436 mainWin->set_status(msg());
444 if (is_bnet_stop(m_sock)) { /* error or term request */
445 if (mainWin->m_commDebug) Pmsg1(000, "conn %i BNET STOP\n", m_conn);
446 m_console->stopTimer();
449 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
450 QBrush redBrush(Qt::red);
451 QTreeWidgetItem *item = mainWin->getFromHash(m_console);
452 item->setForeground(0, redBrush);
454 m_notifier->setEnabled(false);
458 mainWin->set_status(_("Director disconnected."));
466 /* Called by signal when the Director has output for us */
467 void DirComm::notify_read_dir(int /* fd */)
470 if (!mainWin->m_notify) {
473 if (mainWin->m_commDebug) Pmsg1(000, "enter read_dir conn %i read_dir\n", m_conn);
474 stat = m_sock->wait_data(0, 5000);
476 if (mainWin->m_commDebug) Pmsg2(000, "read_dir conn %i stat=%d\n", m_conn, stat);
477 while (read() >= 0) {
478 m_console->display_text(msg());
481 if (mainWin->m_commDebug) Pmsg2(000, "exit read_dir conn %i stat=%d\n", m_conn, stat);
485 * When the notifier is enabled, read_dir() will automatically be
486 * called by the Qt event loop when ever there is any output
487 * from the Director, and read_dir() will then display it on
490 * When we are in a bat dialog, we want to control *all* output
491 * from the Directory, so we set notify to off.
492 * m_console->notifiy(false);
494 bool DirComm::notify(bool enable)
496 bool prev_enabled = false;
497 /* Set global flag */
498 mainWin->m_notify = enable;
500 prev_enabled = m_notifier->isEnabled();
501 if (prev_enabled != enable) {
502 m_notifier->setEnabled(enable);
504 if (mainWin->m_connDebug) Pmsg3(000, "conn=%i notify=%d prev=%d\n", m_conn, enable, prev_enabled);
505 } else if (mainWin->m_connDebug) {
506 Pmsg2(000, "m_notifier does not exist: %i %s\n", m_conn, m_console->m_dir->name());
511 bool DirComm::is_notify_enabled() const
513 return mainWin->m_notify;
517 * Call-back for reading a passphrase for an encrypted PEM file
518 * This function uses getpass(),
519 * which uses a static buffer and is NOT thread-safe.
521 static int tls_pem_callback(char *buf, int size, const void *userdata)
526 # if defined(HAVE_WIN32)
528 if (win32_cgets(buf, size) == NULL) {
535 const char *prompt = (const char *)userdata;
538 passwd = getpass(prompt);
539 bstrncpy(buf, passwd, size);