]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/bcomm/dircomm.cpp
Fixes to get restoration to work with multiple connections.
[bacula/bacula] / bacula / src / qt-console / bcomm / dircomm.cpp
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2007-2009 Free Software Foundation Europe e.V.
5
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 two of the GNU General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
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.
17
18    You should have received a copy of the GNU 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
21    02110-1301, USA.
22
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.
27 */
28 /*
29  *   Version $Id$
30  *
31  *  Console Class
32  *
33  *   Kern Sibbald, January MMVII
34  *
35  */ 
36
37 #include "bat.h"
38 #include "console.h"
39 #include "restore.h"
40 #include "select.h"
41 #include "run/run.h"
42
43 static int tls_pem_callback(char *buf, int size, const void *userdata);
44
45 DirComm::DirComm(Console *parent, int conn):
46 m_notifier(NULL),
47 m_api_set(false)
48 {
49    m_console = parent;
50    m_sock = NULL;
51    m_at_prompt = false;
52    m_at_main_prompt = false;
53    m_conn = conn;
54    m_in_command = 0;
55 }
56
57 DirComm::~DirComm()
58 {
59 }
60
61 /* Terminate any open socket */
62 void DirComm::terminate()
63 {
64    if (m_sock) {
65       if (m_notifier) {
66          m_notifier->setEnabled(false);
67          delete m_notifier;
68          m_notifier = NULL;
69       }
70       m_sock->close();
71       m_sock = NULL;
72    }
73 }
74
75 /*
76  * Connect to Director. 
77  */
78 bool DirComm::connect_dir()
79 {
80    JCR *jcr = new JCR;
81    utime_t heart_beat;
82    char buf[1024];
83    CONRES *cons;
84       
85    buf[0] = 0;
86
87    if (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          Pmsg1(000, "DirComm %i BAILING already connected\n", m_conn);
93       goto bail_out;
94    }
95
96    memset(jcr, 0, sizeof(JCR));
97
98    mainWin->set_statusf(_("Connecting to Director %s:%d"), m_console->m_dir->address, m_console->m_dir->DIRport);
99    m_console->display_textf(_("Connecting to Director %s:%d\n\n"), m_console->m_dir->address, m_console->m_dir->DIRport);
100
101    /* Give GUI a chance */
102    app->processEvents();
103    
104    LockRes();
105    /* If cons==NULL, default console will be used */
106    cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
107    UnlockRes();
108
109    /* Initialize Console TLS context once */
110    if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
111       /* Generate passphrase prompt */
112       bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ", 
113                 cons->name());
114
115       /* Initialize TLS context:
116        * Args: CA certfile, CA certdir, Certfile, Keyfile,
117        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer   
118        */
119       cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
120          cons->tls_ca_certdir, cons->tls_certfile,
121          cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
122
123       if (!cons->tls_ctx) {
124          m_console->display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
125             m_console->m_dir->name());
126          if (mainWin->m_connDebug)
127             Pmsg1(000, "DirComm %i BAILING Failed to initialize TLS context for Console \n", m_conn);
128          goto bail_out;
129       }
130    }
131
132    /* Initialize Director TLS context once */
133    if (!m_console->m_dir->tls_ctx && (m_console->m_dir->tls_enable || m_console->m_dir->tls_require)) {
134       /* Generate passphrase prompt */
135       bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ", 
136                 m_console->m_dir->name());
137
138       /* Initialize TLS context:
139        * Args: CA certfile, CA certdir, Certfile, Keyfile,
140        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
141       m_console->m_dir->tls_ctx = new_tls_context(m_console->m_dir->tls_ca_certfile,
142                           m_console->m_dir->tls_ca_certdir, m_console->m_dir->tls_certfile,
143                           m_console->m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
144
145       if (!m_console->m_dir->tls_ctx) {
146          m_console->display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
147             m_console->m_dir->name());
148          mainWin->set_status("Connection failed");
149          if (mainWin->m_connDebug)
150             Pmsg1(000, "DirComm %i BAILING Failed to initialize TLS context for Director \n", m_conn);
151          goto bail_out;
152       }
153    }
154
155    if (m_console->m_dir->heartbeat_interval) {
156       heart_beat = m_console->m_dir->heartbeat_interval;
157    } else if (cons) {
158       heart_beat = cons->heartbeat_interval;
159    } else {
160       heart_beat = 0;
161    }        
162
163    m_sock = bnet_connect(NULL, 5, 15, heart_beat,
164                           _("Director daemon"), m_console->m_dir->address,
165                           NULL, m_console->m_dir->DIRport, 0);
166    if (m_sock == NULL) {
167       mainWin->set_status("Connection failed");
168       if (mainWin->m_connDebug)
169          Pmsg1(000, "DirComm %i BAILING Connection failed\n", m_conn);
170       goto bail_out;
171    } else {
172       /* Update page selector to green to indicate that Console is connected */
173       mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
174       QBrush greenBrush(Qt::green);
175       QTreeWidgetItem *item = mainWin->getFromHash(m_console);
176       item->setForeground(0, greenBrush);
177    }
178
179    jcr->dir_bsock = m_sock;
180
181    if (!authenticate_director(jcr, m_console->m_dir, cons, buf, sizeof(buf))) {
182       m_console->display_text(buf);
183       if (mainWin->m_connDebug)
184          Pmsg1(000, "DirComm %i BAILING Connection failed\n", m_conn);
185       goto bail_out;
186    }
187
188    if (buf[0]) {
189       m_console->display_text(buf);
190    }
191
192    /* Give GUI a chance */
193    app->processEvents();
194
195    mainWin->set_status(_("Initializing ..."));
196
197 #ifndef HAVE_WIN32
198    /* Set up input notifier */
199    m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
200    QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
201 #endif
202
203    write(".api 1");
204    m_api_set = true;
205    m_console->displayToPrompt(m_conn);
206
207    m_console->beginNewCommand(m_conn);
208
209    mainWin->set_status(_("Connected"));
210
211    if (mainWin->m_connDebug)
212       Pmsg1(000, "Returning TRUE from DirComm->connect_dir : %i\n", m_conn);
213    return true;
214
215 bail_out:
216    if (mainWin->m_connDebug)
217       Pmsg1(000, "Returning FALSE from DirComm->connect_dir : %i\n", m_conn);
218    delete jcr;
219    return false;
220 }
221
222 /* 
223  * This should be moved into a bSocket class 
224  */
225 char *DirComm::msg()
226 {
227    if (m_sock) {
228       return m_sock->msg;
229    }
230    return NULL;
231 }
232
233 int DirComm::write(const QString msg)
234 {
235    return write(msg.toUtf8().data());
236 }
237
238 int DirComm::write(const char *msg)
239 {
240    if (!m_sock) {
241       return -1;
242    }
243    m_sock->msglen = pm_strcpy(m_sock->msg, msg);
244    m_at_prompt = false;
245    m_at_main_prompt = false;
246    if (mainWin->m_commDebug) Pmsg2(000, "conn %i send: %s\n", m_conn, msg);
247    return m_sock->send();
248 }
249
250 int DirComm::sock_read()
251 {
252    int stat;
253 #ifdef HAVE_WIN32
254    bool wasEnabled = notify(false);
255    stat = m_sock->recv();
256    notify(wasEnabled);
257 #else
258    stat = m_sock->recv();
259 #endif
260    return stat;
261 }
262
263 /* 
264  * Blocking read from director
265  */
266 int DirComm::read()
267 {
268    int stat = 0;
269    while (m_sock) {
270       for (;;) {
271          stat = m_sock->wait_data_intr(0, 50000);
272          if (stat > 0) {
273             break;
274          } 
275          app->processEvents();
276          if (m_api_set && m_console->is_messagesPending() && is_notify_enabled() && m_console->hasFocus()) {
277             m_console->write_dir(m_conn, ".messages");
278             m_console->messagesPending(false);
279          }
280       }
281       m_sock->msg[0] = 0;
282       stat = sock_read();
283       if (stat >= 0) {
284          if (mainWin->m_commDebug) Pmsg2(000, "conn %i got: %s\n", m_conn, m_sock->msg);
285          if (m_at_prompt) {
286             m_console->display_text("\n");
287             m_at_prompt = false;
288             m_at_main_prompt = false;
289          }
290       }
291       switch (m_sock->msglen) {
292       case BNET_MSGS_PENDING :
293          if (is_notify_enabled() && m_console->hasFocus()) {
294             if (mainWin->m_commDebug) Pmsg1(000, "conn %i MSGS PENDING\n", m_conn);
295             m_console->write_dir(m_conn, ".messages");
296             m_console->displayToPrompt(m_conn);
297             m_console->messagesPending(false);
298          }
299          m_console->messagesPending(true);
300          continue;
301       case BNET_CMD_OK:
302          if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD OK\n", m_conn);
303          m_at_prompt = false;
304          m_at_main_prompt = false;
305 //       Pmsg1(000, "before dec m_in_command=%d\n", m_in_command);
306          if (--m_in_command < 0) {
307 //          Pmsg0(000, "m_in_command < 0\n");
308             m_in_command = 0;
309          }
310          mainWin->set_status(_("Command completed ..."));
311          continue;
312       case BNET_CMD_BEGIN:
313          if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD BEGIN\n", m_conn);
314          m_at_prompt = false;
315          m_at_main_prompt = false;
316          m_in_command++;
317 //       Pmsg1(000, "after inc m_in_command=%d\n", m_in_command);
318          mainWin->set_status(_("Processing command ..."));
319          continue;
320       case BNET_MAIN_PROMPT:
321          if (mainWin->m_commDebug) Pmsg1(000, "conn %i MAIN PROMPT\n", m_conn);
322          m_at_prompt = true;
323          m_at_main_prompt = true;
324          mainWin->set_status(_("At main prompt waiting for input ..."));
325          QApplication::restoreOverrideCursor();
326          break;
327       case BNET_PROMPT:
328          if (mainWin->m_commDebug) Pmsg1(000, "conn %i PROMPT\n", m_conn);
329          m_at_prompt = true;
330          m_at_main_prompt = false;
331          mainWin->set_status(_("At prompt waiting for input ..."));
332          QApplication::restoreOverrideCursor();
333          break;
334       case BNET_CMD_FAILED:
335          if (mainWin->m_commDebug) Pmsg1(000, "CMD FAILED\n", m_conn);
336          if (--m_in_command < 0) {
337 //          Pmsg0(000, "m_in_command < 0\n");
338             m_in_command = 0;
339          }
340          mainWin->set_status(_("Command failed."));
341          QApplication::restoreOverrideCursor();
342          break;
343       /* We should not get this one */
344       case BNET_EOD:
345          if (mainWin->m_commDebug) Pmsg1(000, "conn %i EOD\n", m_conn);
346          mainWin->set_status_ready();
347          QApplication::restoreOverrideCursor();
348          if (!m_api_set) {
349             break;
350          }
351          continue;
352       case BNET_START_SELECT:
353          if (mainWin->m_commDebug) Pmsg1(000, "conn %i START SELECT\n", m_conn);
354          new selectDialog(m_console);
355          break;
356       case BNET_YESNO:
357          if (mainWin->m_commDebug) Pmsg1(000, "conn %i YESNO\n", m_conn);
358          new yesnoPopUp(m_console, m_conn);
359          break;
360       case BNET_RUN_CMD:
361          if (mainWin->m_commDebug) Pmsg1(000, "conn %i RUN CMD\n", m_conn);
362          new runCmdPage(m_conn);
363          break;
364       case BNET_START_RTREE:
365          if (mainWin->m_commDebug) Pmsg1(000, "conn %i START RTREE CMD\n", m_conn);
366          new restorePage(m_conn);
367          break;
368       case BNET_END_RTREE:
369          if (mainWin->m_commDebug) Pmsg1(000, "conn %i END RTREE CMD\n", m_conn);
370          break;
371       case BNET_ERROR_MSG:
372          if (mainWin->m_commDebug) Pmsg1(000, "conn %i ERROR MSG\n", m_conn);
373          stat = sock_read();          /* get the message */
374          m_console->display_text(msg());
375          QMessageBox::critical(m_console, "Error", msg(), QMessageBox::Ok);
376          break;
377       case BNET_WARNING_MSG:
378          if (mainWin->m_commDebug) Pmsg1(000, "conn %i WARNING MSG\n", m_conn);
379          stat = sock_read();          /* get the message */
380          m_console->display_text(msg());
381          QMessageBox::critical(m_console, "Warning", msg(), QMessageBox::Ok);
382          break;
383       case BNET_INFO_MSG:
384          if (mainWin->m_commDebug) Pmsg1(000, "conn %i INFO MSG\n", m_conn);
385          stat = sock_read();          /* get the message */
386          m_console->display_text(msg());
387          mainWin->set_status(msg());
388          break;
389       }
390       if (is_bnet_stop(m_sock)) {         /* error or term request */
391          if (mainWin->m_commDebug) Pmsg1(000, "conn %i BNET STOP\n", m_conn);
392          m_console->stopTimer();
393          m_sock->close();
394          m_sock = NULL;
395          mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
396          QBrush redBrush(Qt::red);
397          QTreeWidgetItem *item = mainWin->getFromHash(m_console);
398          item->setForeground(0, redBrush);
399          if (m_notifier) {
400             m_notifier->setEnabled(false);
401             delete m_notifier;
402             m_notifier = NULL;
403          }
404          mainWin->set_status(_("Director disconnected."));
405          QApplication::restoreOverrideCursor();
406          stat = BNET_HARDEOF;
407       }
408       break;
409    } 
410    return stat;
411 }
412
413 /* Called by signal when the Director has output for us */
414 void DirComm::read_dir(int /* fd */)
415 {
416    if (mainWin->m_commDebug) Pmsg1(000, "conn %i read_dir\n", m_conn);
417    while (read() >= 0) {
418       m_console->display_text(msg());
419    }
420 }
421
422 /*
423  * When the notifier is enabled, read_dir() will automatically be
424  * called by the Qt event loop when ever there is any output 
425  * from the Directory, and read_dir() will then display it on
426  * the console.
427  *
428  * When we are in a bat dialog, we want to control *all* output
429  * from the Directory, so we set notify to off.
430  *    m_console->notifiy(false);
431  */
432 bool DirComm::notify(bool enable) 
433
434    bool prev_enabled = false;
435    if (m_notifier) {
436       prev_enabled = m_notifier->isEnabled();   
437       m_notifier->setEnabled(enable);
438       if (mainWin->m_connDebug)
439          if (prev_enabled && !enable)
440             Pmsg1(000, "m_notifier Disabling notifier: %i\n", m_conn);
441          else if (!prev_enabled && enable)
442             Pmsg1(000, "m_notifier Enabling notifier: %i\n", m_conn); 
443     } else if (mainWin->m_connDebug)
444        Pmsg1(000, "m_notifier does not exist: %i\n", m_conn);
445    return prev_enabled;
446 }
447
448 bool DirComm::is_notify_enabled() const
449 {
450    bool enabled = false;
451    if (m_notifier)
452       enabled = m_notifier->isEnabled();   
453    return enabled;
454 }
455
456 /*
457  * Call-back for reading a passphrase for an encrypted PEM file
458  * This function uses getpass(), 
459  *  which uses a static buffer and is NOT thread-safe.
460  */
461 static int tls_pem_callback(char *buf, int size, const void *userdata)
462 {
463    (void)size;
464    (void)userdata;
465 #ifdef HAVE_TLS
466    const char *prompt = (const char *)userdata;
467 # if defined(HAVE_WIN32)
468    //sendit(prompt);
469    if (win32_cgets(buf, size) == NULL) {
470       buf[0] = 0;
471       return 0;
472    } else {
473       return strlen(buf);
474    }
475 # else
476    char *passwd;
477
478    passwd = getpass(prompt);
479    bstrncpy(buf, passwd, size);
480    return strlen(buf);
481 # endif
482 #else
483    buf[0] = 0;
484    return 0;
485 #endif
486 }