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