]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/bcomm/dircomm.cpp
Tweak typo
[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 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  *  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       item->setForeground(0, greenBrush);
183    }
184
185    jcr->dir_bsock = m_sock;
186
187    if (!authenticate_director(jcr, m_console->m_dir, cons, buf, sizeof(buf))) {
188       m_console->display_text(buf);
189       if (mainWin->m_connDebug)
190          Pmsg2(000, "DirComm %i BAILING Connection failed %s\n", m_conn, m_console->m_dir->name());
191       goto bail_out;
192    }
193
194    if (buf[0]) {
195       m_console->display_text(buf);
196    }
197
198    /* Give GUI a chance */
199    app->processEvents();
200
201    mainWin->set_status(_("Initializing ..."));
202
203    /* 
204     * Set up input notifier
205     */
206    m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
207    QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
208    m_notifier->setEnabled(false);
209
210    write(".api 1");
211    m_api_set = true;
212    m_console->displayToPrompt(m_conn);
213
214    m_console->beginNewCommand(m_conn);
215
216    mainWin->set_status(_("Connected"));
217
218    if (mainWin->m_connDebug) {
219       Pmsg2(000, "Returning TRUE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
220    }
221    return true;
222
223 bail_out:
224    if (mainWin->m_connDebug) {
225       Pmsg2(000, "Returning FALSE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
226    }
227    delete jcr;
228    return false;
229 }
230
231 /* 
232  * This should be moved into a bSocket class 
233  */
234 char *DirComm::msg()
235 {
236    if (m_sock) {
237       return m_sock->msg;
238    }
239    return NULL;
240 }
241
242 int DirComm::write(const QString msg)
243 {
244    return write(msg.toUtf8().data());
245 }
246
247 int DirComm::write(const char *msg)
248 {
249    if (!m_sock) {
250       return -1;
251    }
252    m_sock->msglen = pm_strcpy(m_sock->msg, msg);
253    m_at_prompt = false;
254    m_at_main_prompt = false;
255    if (mainWin->m_commDebug) Pmsg2(000, "conn %i send: %s\n", m_conn, msg);
256    return m_sock->send();
257 }
258
259 int DirComm::sock_read()
260 {
261    int stat;
262 #ifdef HAVE_WIN32
263    bool wasEnabled = notify(false);
264    stat = m_sock->recv();
265    notify(wasEnabled);
266 #else
267    stat = m_sock->recv();
268 #endif
269    return stat;
270 }
271
272 /* 
273  * Blocking read from director
274  */
275 int DirComm::read()
276 {
277    int stat = -1;
278
279    if (!m_sock) {
280       return -1;
281    }
282    while (m_sock) {
283       for (;;) {
284          if (!m_sock) break;
285          stat = m_sock->wait_data_intr(0, 50000);
286          if (stat > 0) {
287             break;
288          } 
289          app->processEvents();
290          if (m_api_set && m_console->is_messagesPending() && is_notify_enabled() && m_console->hasFocus()) {
291             m_console->write_dir(m_conn, ".messages", false);
292             m_console->messagesPending(false);
293          }
294       }
295       if (!m_sock) {
296          return -1;
297       }
298       m_sock->msg[0] = 0;
299       stat = sock_read();
300       if (stat >= 0) {
301          if (mainWin->m_commDebug) Pmsg2(000, "conn %i got: %s\n", m_conn, m_sock->msg);
302          if (m_at_prompt) {
303             m_console->display_text("\n");
304             m_at_prompt = false;
305             m_at_main_prompt = false;
306          }
307       }
308       switch (m_sock->msglen) {
309       case BNET_MSGS_PENDING :
310          if (is_notify_enabled() && m_console->hasFocus()) {
311             if (mainWin->m_commDebug) Pmsg1(000, "conn %i MSGS PENDING\n", m_conn);
312             m_console->write_dir(m_conn, ".messages", false);
313             m_console->displayToPrompt(m_conn);
314             m_console->messagesPending(false);
315          }
316          m_console->messagesPending(true);
317          continue;
318       case BNET_CMD_OK:
319          if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD OK\n", m_conn);
320          m_at_prompt = false;
321          m_at_main_prompt = false;
322          if (--m_in_command < 0) {
323             m_in_command = 0;
324          }
325          mainWin->set_status(_("Command completed ..."));
326          continue;
327       case BNET_CMD_BEGIN:
328          if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD BEGIN\n", m_conn);
329          m_at_prompt = false;
330          m_at_main_prompt = false;
331          m_in_command++;
332          mainWin->set_status(_("Processing command ..."));
333          continue;
334       case BNET_MAIN_PROMPT:
335          if (mainWin->m_commDebug) Pmsg1(000, "conn %i MAIN PROMPT\n", m_conn);
336          m_at_prompt = true;
337          m_at_main_prompt = true;
338          mainWin->set_status(_("At main prompt waiting for input ..."));
339          break;
340       case BNET_PROMPT:
341          if (mainWin->m_commDebug) Pmsg2(000, "conn %i PROMPT m_in_select %i\n", m_conn, m_in_select);
342          m_at_prompt = true;
343          m_at_main_prompt = false;
344          mainWin->set_status(_("At prompt waiting for input ..."));
345          /***** FIXME *****/
346          /* commented out until the prompt communication issue with the director is resolved 
347           * This is where I call a new text input dialog class to prevent the connection issues
348           * when a text input is requited.
349           *   if (!m_in_select) {
350           *      new textInputDialog(m_console, m_conn);
351           *   }
352           */
353          break;
354       case BNET_CMD_FAILED:
355          if (mainWin->m_commDebug) Pmsg1(000, "CMD FAILED\n", m_conn);
356          if (--m_in_command < 0) {
357             m_in_command = 0;
358          }
359          mainWin->set_status(_("Command failed."));
360          break;
361       /* We should not get this one */
362       case BNET_EOD:
363          if (mainWin->m_commDebug) Pmsg1(000, "conn %i EOD\n", m_conn);
364          mainWin->set_status_ready();
365          if (!m_api_set) {
366             break;
367          }
368          continue;
369       case BNET_START_SELECT:
370          if (mainWin->m_commDebug) Pmsg1(000, "conn %i START SELECT\n", m_conn);
371          m_in_select = true;
372          new selectDialog(m_console, m_conn);
373          m_in_select = false;
374          break;
375       case BNET_YESNO:
376          if (mainWin->m_commDebug) Pmsg1(000, "conn %i YESNO\n", m_conn);
377          new yesnoPopUp(m_console, m_conn);
378          break;
379       case BNET_RUN_CMD:
380          if (mainWin->m_commDebug) Pmsg1(000, "conn %i RUN CMD\n", m_conn);
381          new runCmdPage(m_conn);
382          break;
383       case BNET_START_RTREE:
384          if (mainWin->m_commDebug) Pmsg1(000, "conn %i START RTREE CMD\n", m_conn);
385          new restorePage(m_conn);
386          break;
387       case BNET_END_RTREE:
388          if (mainWin->m_commDebug) Pmsg1(000, "conn %i END RTREE CMD\n", m_conn);
389          break;
390       case BNET_ERROR_MSG:
391          if (mainWin->m_commDebug) Pmsg1(000, "conn %i ERROR MSG\n", m_conn);
392          stat = sock_read();          /* get the message */
393          m_console->display_text(msg());
394          QMessageBox::critical(m_console, "Error", msg(), QMessageBox::Ok);
395          break;
396       case BNET_WARNING_MSG:
397          if (mainWin->m_commDebug) Pmsg1(000, "conn %i WARNING MSG\n", m_conn);
398          stat = sock_read();          /* get the message */
399          if (!m_console->m_warningPrevent) {
400             QMessageBox::critical(m_console, "Warning", msg(), QMessageBox::Ok);
401          }
402          break;
403       case BNET_INFO_MSG:
404          if (mainWin->m_commDebug) Pmsg1(000, "conn %i INFO MSG\n", m_conn);
405          stat = sock_read();          /* get the message */
406          m_console->display_text(msg());
407          mainWin->set_status(msg());
408          break;
409       }
410       if (is_bnet_stop(m_sock)) {         /* error or term request */
411          if (mainWin->m_commDebug) Pmsg1(000, "conn %i BNET STOP\n", m_conn);
412          m_console->stopTimer();
413          m_sock->close();
414          m_sock = NULL;
415          mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
416          QBrush redBrush(Qt::red);
417          QTreeWidgetItem *item = mainWin->getFromHash(m_console);
418          item->setForeground(0, redBrush);
419          if (m_notifier) {
420             m_notifier->setEnabled(false);
421             delete m_notifier;
422             m_notifier = NULL;
423          }
424          mainWin->set_status(_("Director disconnected."));
425          stat = BNET_HARDEOF;
426       }
427       break;
428    } 
429    return stat;
430 }
431
432 /* Called by signal when the Director has output for us */
433 void DirComm::read_dir(int /* fd */)
434 {
435    int stat;
436    if (mainWin->m_commDebug) Pmsg1(000, "enter read_dir conn %i read_dir\n", m_conn);
437    stat = m_sock->wait_data(0, 5000);
438    if (stat > 0) {
439       if (mainWin->m_commDebug) Pmsg2(000, "read_dir conn %i stat=%d\n", m_conn, stat);
440       while (read() >= 0) {
441          m_console->display_text(msg());
442       }
443    }
444    if (mainWin->m_commDebug) Pmsg2(000, "exit read_dir conn %i stat=%d\n", m_conn, stat);
445 }
446
447 /*
448  * When the notifier is enabled, read_dir() will automatically be
449  * called by the Qt event loop when ever there is any output 
450  * from the Director, and read_dir() will then display it on
451  * the console.
452  *
453  * When we are in a bat dialog, we want to control *all* output
454  * from the Directory, so we set notify to off.
455  *    m_console->notifiy(false);
456  */
457 bool DirComm::notify(bool enable) 
458
459    bool prev_enabled = false;
460    if (m_notifier) {
461       prev_enabled = m_notifier->isEnabled();   
462       m_notifier->setEnabled(enable);
463       if (mainWin->m_connDebug) {
464          Pmsg3(000, "conn=%i notify=%d prev=%d\n", m_conn, enable, prev_enabled);
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 }