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