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