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