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