]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/bcomm/dircomm.cpp
This is the fix to http://bugs.bacula.org/view.php?id=1276. The select
[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", false);
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", false);
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          notify(false);
357          if (mainWin->m_commDebug) Pmsg1(000, "conn %i START SELECT\n", m_conn);
358          new selectDialog(m_console, m_conn);
359          break;
360       case BNET_YESNO:
361          if (mainWin->m_commDebug) Pmsg1(000, "conn %i YESNO\n", m_conn);
362          new yesnoPopUp(m_console, m_conn);
363          break;
364       case BNET_RUN_CMD:
365          if (mainWin->m_commDebug) Pmsg1(000, "conn %i RUN CMD\n", m_conn);
366          new runCmdPage(m_conn);
367          break;
368       case BNET_START_RTREE:
369          if (mainWin->m_commDebug) Pmsg1(000, "conn %i START RTREE CMD\n", m_conn);
370          new restorePage(m_conn);
371          break;
372       case BNET_END_RTREE:
373          if (mainWin->m_commDebug) Pmsg1(000, "conn %i END RTREE CMD\n", m_conn);
374          break;
375       case BNET_ERROR_MSG:
376          if (mainWin->m_commDebug) Pmsg1(000, "conn %i ERROR MSG\n", m_conn);
377          stat = sock_read();          /* get the message */
378          m_console->display_text(msg());
379          QMessageBox::critical(m_console, "Error", msg(), QMessageBox::Ok);
380          break;
381       case BNET_WARNING_MSG:
382          if (mainWin->m_commDebug) Pmsg1(000, "conn %i WARNING MSG\n", m_conn);
383          stat = sock_read();          /* get the message */
384          m_console->display_text(msg());
385          QMessageBox::critical(m_console, "Warning", msg(), QMessageBox::Ok);
386          break;
387       case BNET_INFO_MSG:
388          if (mainWin->m_commDebug) Pmsg1(000, "conn %i INFO MSG\n", m_conn);
389          stat = sock_read();          /* get the message */
390          m_console->display_text(msg());
391          mainWin->set_status(msg());
392          break;
393       }
394       if (is_bnet_stop(m_sock)) {         /* error or term request */
395          if (mainWin->m_commDebug) Pmsg1(000, "conn %i BNET STOP\n", m_conn);
396          m_console->stopTimer();
397          m_sock->close();
398          m_sock = NULL;
399          mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
400          QBrush redBrush(Qt::red);
401          QTreeWidgetItem *item = mainWin->getFromHash(m_console);
402          item->setForeground(0, redBrush);
403          if (m_notifier) {
404             m_notifier->setEnabled(false);
405             delete m_notifier;
406             m_notifier = NULL;
407          }
408          mainWin->set_status(_("Director disconnected."));
409 //         QApplication::restoreOverrideCursor();
410          stat = BNET_HARDEOF;
411       }
412       break;
413    } 
414    return stat;
415 }
416
417 /* Called by signal when the Director has output for us */
418 void DirComm::read_dir(int /* fd */)
419 {
420    if (mainWin->m_commDebug) Pmsg1(000, "conn %i read_dir\n", m_conn);
421    while (read() >= 0) {
422       m_console->display_text(msg());
423    }
424 }
425
426 /*
427  * When the notifier is enabled, read_dir() will automatically be
428  * called by the Qt event loop when ever there is any output 
429  * from the Directory, and read_dir() will then display it on
430  * the console.
431  *
432  * When we are in a bat dialog, we want to control *all* output
433  * from the Directory, so we set notify to off.
434  *    m_console->notifiy(false);
435  */
436 bool DirComm::notify(bool enable) 
437
438    bool prev_enabled = false;
439    if (m_notifier) {
440       prev_enabled = m_notifier->isEnabled();   
441       m_notifier->setEnabled(enable);
442       if (mainWin->m_connDebug) {
443          if (prev_enabled && !enable)
444             Pmsg2(000, "m_notifier Disabling notifier: %i %s\n", m_conn, m_console->m_dir->name());
445          else if (!prev_enabled && enable)
446             Pmsg2(000, "m_notifier Enabling notifier: %i %s\n", m_conn, m_console->m_dir->name());
447       }
448    } else if (mainWin->m_connDebug)
449       Pmsg2(000, "m_notifier does not exist: %i %s\n", m_conn, m_console->m_dir->name());
450    return prev_enabled;
451 }
452
453 bool DirComm::is_notify_enabled() const
454 {
455    bool enabled = false;
456    if (m_notifier)
457       enabled = m_notifier->isEnabled();   
458    return enabled;
459 }
460
461 /*
462  * Call-back for reading a passphrase for an encrypted PEM file
463  * This function uses getpass(), 
464  *  which uses a static buffer and is NOT thread-safe.
465  */
466 static int tls_pem_callback(char *buf, int size, const void *userdata)
467 {
468    (void)size;
469    (void)userdata;
470 #ifdef HAVE_TLS
471    const char *prompt = (const char *)userdata;
472 # if defined(HAVE_WIN32)
473    //sendit(prompt);
474    if (win32_cgets(buf, size) == NULL) {
475       buf[0] = 0;
476       return 0;
477    } else {
478       return strlen(buf);
479    }
480 # else
481    char *passwd;
482
483    passwd = getpass(prompt);
484    bstrncpy(buf, passwd, size);
485    return strlen(buf);
486 # endif
487 #else
488    buf[0] = 0;
489    return 0;
490 #endif
491 }