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