]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/bcomm/dircomm.cpp
Make bat sock handling a bit more robust
[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):  m_notifier(NULL),  m_api_set(false)
46 {
47    m_console = parent;
48    m_sock = NULL;
49    m_at_prompt = false;
50    m_at_main_prompt = false;
51    m_conn = conn;
52    m_in_command = 0;
53 }
54
55 DirComm::~DirComm()
56 {
57 }
58
59 /* Terminate any open socket */
60 void DirComm::terminate()
61 {
62    if (m_sock) {
63       if (m_notifier) {
64          m_notifier->setEnabled(false);
65          delete m_notifier;
66          m_notifier = NULL;
67       }
68       if (mainWin->m_connDebug)
69          Pmsg2(000, "DirComm %i terminating connections %s\n", m_conn, m_console->m_dir->name());
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          Pmsg2(000, "DirComm %i BAILING already connected %s\n", m_conn, m_console->m_dir->name());
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             Pmsg2(000, "DirComm %i BAILING Failed to initialize TLS context for Console %s\n", m_conn, m_console->m_dir->name());
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             Pmsg2(000, "DirComm %i BAILING Failed to initialize TLS context for Director %s\n", m_conn, m_console->m_dir->name());
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          Pmsg2(000, "DirComm %i BAILING Connection failed %s\n", m_conn, m_console->m_dir->name());
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          Pmsg2(000, "DirComm %i BAILING Connection failed %s\n", m_conn, m_console->m_dir->name());
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       Pmsg2(000, "Returning TRUE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
213    return true;
214
215 bail_out:
216    if (mainWin->m_connDebug)
217       Pmsg2(000, "Returning FALSE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
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 = -1;
269
270    if (!m_sock) {
271       return -1;
272    }
273    while (m_sock) {
274       for (;;) {
275          if (!m_sock) break;
276          stat = m_sock->wait_data_intr(0, 50000);
277          if (stat > 0) {
278             break;
279          } 
280          app->processEvents();
281          if (m_api_set && m_console->is_messagesPending() && is_notify_enabled() && m_console->hasFocus()) {
282             m_console->write_dir(m_conn, ".messages");
283             m_console->messagesPending(false);
284          }
285       }
286       if (!m_sock) {
287          return -1;
288       }
289       m_sock->msg[0] = 0;
290       stat = sock_read();
291       if (stat >= 0) {
292          if (mainWin->m_commDebug) Pmsg2(000, "conn %i got: %s\n", m_conn, m_sock->msg);
293          if (m_at_prompt) {
294             m_console->display_text("\n");
295             m_at_prompt = false;
296             m_at_main_prompt = false;
297          }
298       }
299       switch (m_sock->msglen) {
300       case BNET_MSGS_PENDING :
301          if (is_notify_enabled() && m_console->hasFocus()) {
302             if (mainWin->m_commDebug) Pmsg1(000, "conn %i MSGS PENDING\n", m_conn);
303             m_console->write_dir(m_conn, ".messages");
304             m_console->displayToPrompt(m_conn);
305             m_console->messagesPending(false);
306          }
307          m_console->messagesPending(true);
308          continue;
309       case BNET_CMD_OK:
310          if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD OK\n", m_conn);
311          m_at_prompt = false;
312          m_at_main_prompt = false;
313 //       Pmsg1(000, "before dec m_in_command=%d\n", m_in_command);
314          if (--m_in_command < 0) {
315 //          Pmsg0(000, "m_in_command < 0\n");
316             m_in_command = 0;
317          }
318          mainWin->set_status(_("Command completed ..."));
319          continue;
320       case BNET_CMD_BEGIN:
321          if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD BEGIN\n", m_conn);
322          m_at_prompt = false;
323          m_at_main_prompt = false;
324          m_in_command++;
325 //       Pmsg1(000, "after inc m_in_command=%d\n", m_in_command);
326          mainWin->set_status(_("Processing command ..."));
327          continue;
328       case BNET_MAIN_PROMPT:
329          if (mainWin->m_commDebug) Pmsg1(000, "conn %i MAIN PROMPT\n", m_conn);
330          m_at_prompt = true;
331          m_at_main_prompt = true;
332          mainWin->set_status(_("At main prompt waiting for input ..."));
333          break;
334       case BNET_PROMPT:
335          if (mainWin->m_commDebug) Pmsg1(000, "conn %i PROMPT\n", m_conn);
336          m_at_prompt = true;
337          m_at_main_prompt = false;
338          mainWin->set_status(_("At prompt waiting for input ..."));
339          break;
340       case BNET_CMD_FAILED:
341          if (mainWin->m_commDebug) Pmsg1(000, "CMD FAILED\n", m_conn);
342          if (--m_in_command < 0) {
343             m_in_command = 0;
344          }
345          mainWin->set_status(_("Command failed."));
346          break;
347       /* We should not get this one */
348       case BNET_EOD:
349          if (mainWin->m_commDebug) Pmsg1(000, "conn %i EOD\n", m_conn);
350          mainWin->set_status_ready();
351          if (!m_api_set) {
352             break;
353          }
354          continue;
355       case BNET_START_SELECT:
356          if (mainWin->m_commDebug) Pmsg1(000, "conn %i START SELECT\n", m_conn);
357          new selectDialog(m_console);
358          break;
359       case BNET_YESNO:
360          if (mainWin->m_commDebug) Pmsg1(000, "conn %i YESNO\n", m_conn);
361          new yesnoPopUp(m_console, m_conn);
362          break;
363       case BNET_RUN_CMD:
364          if (mainWin->m_commDebug) Pmsg1(000, "conn %i RUN CMD\n", m_conn);
365          new runCmdPage(m_conn);
366          break;
367       case BNET_START_RTREE:
368          if (mainWin->m_commDebug) Pmsg1(000, "conn %i START RTREE CMD\n", m_conn);
369          new restorePage(m_conn);
370          break;
371       case BNET_END_RTREE:
372          if (mainWin->m_commDebug) Pmsg1(000, "conn %i END RTREE CMD\n", m_conn);
373          break;
374       case BNET_ERROR_MSG:
375          if (mainWin->m_commDebug) Pmsg1(000, "conn %i ERROR MSG\n", m_conn);
376          stat = sock_read();          /* get the message */
377          m_console->display_text(msg());
378          QMessageBox::critical(m_console, "Error", msg(), QMessageBox::Ok);
379          break;
380       case BNET_WARNING_MSG:
381          if (mainWin->m_commDebug) Pmsg1(000, "conn %i WARNING MSG\n", m_conn);
382          stat = sock_read();          /* get the message */
383          m_console->display_text(msg());
384          QMessageBox::critical(m_console, "Warning", msg(), QMessageBox::Ok);
385          break;
386       case BNET_INFO_MSG:
387          if (mainWin->m_commDebug) Pmsg1(000, "conn %i INFO MSG\n", m_conn);
388          stat = sock_read();          /* get the message */
389          m_console->display_text(msg());
390          mainWin->set_status(msg());
391          break;
392       }
393       if (is_bnet_stop(m_sock)) {         /* error or term request */
394          if (mainWin->m_commDebug) Pmsg1(000, "conn %i BNET STOP\n", m_conn);
395          m_console->stopTimer();
396          m_sock->close();
397          m_sock = NULL;
398          mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
399          QBrush redBrush(Qt::red);
400          QTreeWidgetItem *item = mainWin->getFromHash(m_console);
401          item->setForeground(0, redBrush);
402          if (m_notifier) {
403             m_notifier->setEnabled(false);
404             delete m_notifier;
405             m_notifier = NULL;
406          }
407          mainWin->set_status(_("Director disconnected."));
408 //         QApplication::restoreOverrideCursor();
409          stat = BNET_HARDEOF;
410       }
411       break;
412    } 
413    return stat;
414 }
415
416 /* Called by signal when the Director has output for us */
417 void DirComm::read_dir(int /* fd */)
418 {
419    if (mainWin->m_commDebug) Pmsg1(000, "conn %i read_dir\n", m_conn);
420    while (read() >= 0) {
421       m_console->display_text(msg());
422    }
423 }
424
425 /*
426  * When the notifier is enabled, read_dir() will automatically be
427  * called by the Qt event loop when ever there is any output 
428  * from the Directory, and read_dir() will then display it on
429  * the console.
430  *
431  * When we are in a bat dialog, we want to control *all* output
432  * from the Directory, so we set notify to off.
433  *    m_console->notifiy(false);
434  */
435 bool DirComm::notify(bool enable) 
436
437    bool prev_enabled = false;
438    if (m_notifier) {
439       prev_enabled = m_notifier->isEnabled();   
440       m_notifier->setEnabled(enable);
441       if (mainWin->m_connDebug) {
442          if (prev_enabled && !enable)
443             Pmsg2(000, "m_notifier Disabling notifier: %i %s\n", m_conn, m_console->m_dir->name());
444          else if (!prev_enabled && enable)
445             Pmsg2(000, "m_notifier Enabling notifier: %i %s\n", m_conn, m_console->m_dir->name());
446       }
447    } else if (mainWin->m_connDebug)
448       Pmsg2(000, "m_notifier does not exist: %i %s\n", m_conn, m_console->m_dir->name());
449    return prev_enabled;
450 }
451
452 bool DirComm::is_notify_enabled() const
453 {
454    bool enabled = false;
455    if (m_notifier)
456       enabled = m_notifier->isEnabled();   
457    return enabled;
458 }
459
460 /*
461  * Call-back for reading a passphrase for an encrypted PEM file
462  * This function uses getpass(), 
463  *  which uses a static buffer and is NOT thread-safe.
464  */
465 static int tls_pem_callback(char *buf, int size, const void *userdata)
466 {
467    (void)size;
468    (void)userdata;
469 #ifdef HAVE_TLS
470    const char *prompt = (const char *)userdata;
471 # if defined(HAVE_WIN32)
472    //sendit(prompt);
473    if (win32_cgets(buf, size) == NULL) {
474       buf[0] = 0;
475       return 0;
476    } else {
477       return strlen(buf);
478    }
479 # else
480    char *passwd;
481
482    passwd = getpass(prompt);
483    bstrncpy(buf, passwd, size);
484    return strlen(buf);
485 # endif
486 #else
487    buf[0] = 0;
488    return 0;
489 #endif
490 }