]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/bcomm/dircomm.cpp
52ca833c6b26d10c5c352058dcbf15263e81cb47
[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    /* 
202     * Set up input notifier
203     */
204    m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
205    QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
206    m_notifier->setEnabled(false);
207
208    write(".api 1");
209    m_api_set = true;
210    m_console->displayToPrompt(m_conn);
211
212    m_console->beginNewCommand(m_conn);
213
214    mainWin->set_status(_("Connected"));
215
216    if (mainWin->m_connDebug)
217       Pmsg2(000, "Returning TRUE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
218    return true;
219
220 bail_out:
221    if (mainWin->m_connDebug)
222       Pmsg2(000, "Returning FALSE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
223    delete jcr;
224    return false;
225 }
226
227 /* 
228  * This should be moved into a bSocket class 
229  */
230 char *DirComm::msg()
231 {
232    if (m_sock) {
233       return m_sock->msg;
234    }
235    return NULL;
236 }
237
238 int DirComm::write(const QString msg)
239 {
240    return write(msg.toUtf8().data());
241 }
242
243 int DirComm::write(const char *msg)
244 {
245    if (!m_sock) {
246       return -1;
247    }
248    m_sock->msglen = pm_strcpy(m_sock->msg, msg);
249    m_at_prompt = false;
250    m_at_main_prompt = false;
251    if (mainWin->m_commDebug) Pmsg2(000, "conn %i send: %s\n", m_conn, msg);
252    return m_sock->send();
253 }
254
255 int DirComm::sock_read()
256 {
257    int stat;
258 #ifdef HAVE_WIN32
259    bool wasEnabled = notify(false);
260    stat = m_sock->recv();
261    notify(wasEnabled);
262 #else
263    stat = m_sock->recv();
264 #endif
265    return stat;
266 }
267
268 /* 
269  * Blocking read from director
270  */
271 int DirComm::read()
272 {
273    int stat = -1;
274
275    if (!m_sock) {
276       return -1;
277    }
278    while (m_sock) {
279       for (;;) {
280          if (!m_sock) break;
281          stat = m_sock->wait_data_intr(0, 50000);
282          if (stat > 0) {
283             break;
284          } 
285          app->processEvents();
286          if (m_api_set && m_console->is_messagesPending() && is_notify_enabled() && m_console->hasFocus()) {
287             m_console->write_dir(m_conn, ".messages", false);
288             m_console->messagesPending(false);
289          }
290       }
291       if (!m_sock) {
292          return -1;
293       }
294       m_sock->msg[0] = 0;
295       stat = sock_read();
296       if (stat >= 0) {
297          if (mainWin->m_commDebug) Pmsg2(000, "conn %i got: %s\n", m_conn, m_sock->msg);
298          if (m_at_prompt) {
299             m_console->display_text("\n");
300             m_at_prompt = false;
301             m_at_main_prompt = false;
302          }
303       }
304       switch (m_sock->msglen) {
305       case BNET_MSGS_PENDING :
306          if (is_notify_enabled() && m_console->hasFocus()) {
307             if (mainWin->m_commDebug) Pmsg1(000, "conn %i MSGS PENDING\n", m_conn);
308             m_console->write_dir(m_conn, ".messages", false);
309             m_console->displayToPrompt(m_conn);
310             m_console->messagesPending(false);
311          }
312          m_console->messagesPending(true);
313          continue;
314       case BNET_CMD_OK:
315          if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD OK\n", m_conn);
316          m_at_prompt = false;
317          m_at_main_prompt = false;
318          if (--m_in_command < 0) {
319             m_in_command = 0;
320          }
321          mainWin->set_status(_("Command completed ..."));
322          continue;
323       case BNET_CMD_BEGIN:
324          if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD BEGIN\n", m_conn);
325          m_at_prompt = false;
326          m_at_main_prompt = false;
327          m_in_command++;
328          mainWin->set_status(_("Processing command ..."));
329          continue;
330       case BNET_MAIN_PROMPT:
331          if (mainWin->m_commDebug) Pmsg1(000, "conn %i MAIN PROMPT\n", m_conn);
332          m_at_prompt = true;
333          m_at_main_prompt = true;
334          mainWin->set_status(_("At main prompt waiting for input ..."));
335          break;
336       case BNET_PROMPT:
337          if (mainWin->m_commDebug) Pmsg2(000, "conn %i PROMPT m_in_select %i\n", m_conn, m_in_select);
338          m_at_prompt = true;
339          m_at_main_prompt = false;
340          mainWin->set_status(_("At prompt waiting for input ..."));
341          /***** FIXME *****/
342          /* commented out until the prompt communication issue with the director is resolved 
343           * This is where I call a new text input dialog class to prevent the connection issues
344           * when a text input is requited.
345           *   if (!m_in_select) {
346           *      new textInputDialog(m_console, m_conn);
347           *   }
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          stat = BNET_HARDEOF;
423       }
424       break;
425    } 
426    return stat;
427 }
428
429 /* Called by signal when the Director has output for us */
430 void DirComm::read_dir(int /* fd */)
431 {
432    int stat;
433    if (mainWin->m_commDebug) Pmsg1(000, "enter read_dir conn %i read_dir\n", m_conn);
434    stat = m_sock->wait_data(0, 5000);
435    if (stat > 0) {
436       if (mainWin->m_commDebug) Pmsg2(000, "read_dir conn %i stat=%d\n", m_conn, stat);
437       while (read() >= 0) {
438          m_console->display_text(msg());
439       }
440    }
441    if (mainWin->m_commDebug) Pmsg2(000, "exit read_dir conn %i stat=%d\n", m_conn, stat);
442 }
443
444 /*
445  * When the notifier is enabled, read_dir() will automatically be
446  * called by the Qt event loop when ever there is any output 
447  * from the Directory, and read_dir() will then display it on
448  * the console.
449  *
450  * When we are in a bat dialog, we want to control *all* output
451  * from the Directory, so we set notify to off.
452  *    m_console->notifiy(false);
453  */
454 bool DirComm::notify(bool enable) 
455
456    bool prev_enabled = false;
457    if (m_notifier) {
458       prev_enabled = m_notifier->isEnabled();   
459       m_notifier->setEnabled(enable);
460       if (mainWin->m_connDebug) {
461          if (prev_enabled && !enable)
462             Pmsg2(000, "m_notifier Disabling notifier: %i %s\n", m_conn, m_console->m_dir->name());
463          else if (!prev_enabled && enable)
464             Pmsg2(000, "m_notifier Enabling notifier: %i %s\n", m_conn, m_console->m_dir->name());
465       }
466    } else if (mainWin->m_connDebug)
467       Pmsg2(000, "m_notifier does not exist: %i %s\n", m_conn, m_console->m_dir->name());
468    return prev_enabled;
469 }
470
471 bool DirComm::is_notify_enabled() const
472 {
473    bool enabled = false;
474    if (m_notifier) {
475       enabled = m_notifier->isEnabled();   
476    }
477    return enabled;
478 }
479
480 /*
481  * Call-back for reading a passphrase for an encrypted PEM file
482  * This function uses getpass(), 
483  *  which uses a static buffer and is NOT thread-safe.
484  */
485 static int tls_pem_callback(char *buf, int size, const void *userdata)
486 {
487    (void)size;
488    (void)userdata;
489 #ifdef HAVE_TLS
490 # if defined(HAVE_WIN32)
491    //sendit(prompt);
492    if (win32_cgets(buf, size) == NULL) {
493       buf[0] = 0;
494       return 0;
495    } else {
496       return strlen(buf);
497    }
498 # else
499    const char *prompt = (const char *)userdata;
500    char *passwd;
501
502    passwd = getpass(prompt);
503    bstrncpy(buf, passwd, size);
504    return strlen(buf);
505 # endif
506 #else
507    buf[0] = 0;
508    return 0;
509 #endif
510 }