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