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