]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/bcomm/dircomm.cpp
Implement .bvfs_versions command to display all file versions for a client
[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             m_console->write_dir(m_conn, ".messages", false);
296             m_console->messagesPending(false);
297          }
298       }
299       if (!m_sock) {
300          return -1;
301       }
302       m_sock->msg[0] = 0;
303       stat = sock_read();
304       if (stat >= 0) {
305          if (mainWin->m_commDebug) Pmsg2(000, "conn %i got: %s\n", m_conn, m_sock->msg);
306          if (m_at_prompt) {
307             m_console->display_text("\n");
308             m_at_prompt = false;
309             m_at_main_prompt = false;
310          }
311       }
312       switch (m_sock->msglen) {
313       case BNET_MSGS_PENDING :
314          if (is_notify_enabled() && m_console->hasFocus()) {
315             if (mainWin->m_commDebug) Pmsg1(000, "conn %i MSGS PENDING\n", m_conn);
316             m_console->write_dir(m_conn, ".messages", false);
317             m_console->displayToPrompt(m_conn);
318             m_console->messagesPending(false);
319          }
320          m_console->messagesPending(true);
321          continue;
322       case BNET_CMD_OK:
323          if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD OK\n", m_conn);
324          m_at_prompt = false;
325          m_at_main_prompt = false;
326          if (--m_in_command < 0) {
327             m_in_command = 0;
328          }
329          mainWin->set_status(_("Command completed ..."));
330          continue;
331       case BNET_CMD_BEGIN:
332          if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD BEGIN\n", m_conn);
333          m_at_prompt = false;
334          m_at_main_prompt = false;
335          m_in_command++;
336          mainWin->set_status(_("Processing command ..."));
337          continue;
338       case BNET_MAIN_PROMPT:
339          if (mainWin->m_commDebug) Pmsg1(000, "conn %i MAIN PROMPT\n", m_conn);
340          if (!m_at_prompt && ! m_at_main_prompt) {
341             m_at_prompt = true;
342             m_at_main_prompt = true;
343             mainWin->set_status(_("At main prompt waiting for input ..."));
344          }
345          break;
346       case BNET_SUB_PROMPT:
347          if (mainWin->m_commDebug) Pmsg2(000, "conn %i SUB_PROMPT m_in_select=%d\n", m_conn, m_in_select);
348          m_at_prompt = true;
349          m_at_main_prompt = false;
350          mainWin->set_status(_("At prompt waiting for input ..."));
351          break;
352       case BNET_TEXT_INPUT:
353          if (mainWin->m_commDebug) Pmsg4(000, "conn %i TEXT_INPUT at_prompt=%d  m_in_select=%d notify=%d\n", 
354                m_conn, m_at_prompt, m_in_select, is_notify_enabled());
355          if (!m_in_select && is_notify_enabled()) {
356             mainWin->waitExit();
357             new textInputDialog(m_console, m_conn);
358          } else {
359             if (mainWin->m_commDebug) Pmsg0(000, "!m_in_select && is_notify_enabled\n");
360             m_at_prompt = true;
361             m_at_main_prompt = false;
362             mainWin->set_status(_("At prompt waiting for input ..."));
363          }
364          break;
365       case BNET_CMD_FAILED:
366          if (mainWin->m_commDebug) Pmsg1(000, "CMD FAILED\n", m_conn);
367          if (--m_in_command < 0) {
368             m_in_command = 0;
369          }
370          mainWin->set_status(_("Command failed."));
371          break;
372       /* We should not get this one */
373       case BNET_EOD:
374          if (mainWin->m_commDebug) Pmsg1(000, "conn %i EOD\n", m_conn);
375          mainWin->set_status_ready();
376          if (!m_api_set) {
377             break;
378          }
379          continue;
380       case BNET_START_SELECT:
381          if (mainWin->m_commDebug) Pmsg1(000, "conn %i START SELECT\n", m_conn);
382          m_in_select = true;
383          new selectDialog(m_console, m_conn);
384          m_in_select = false;
385          break;
386       case BNET_YESNO:
387          if (mainWin->m_commDebug) Pmsg1(000, "conn %i YESNO\n", m_conn);
388          new yesnoPopUp(m_console, m_conn);
389          break;
390       case BNET_RUN_CMD:
391          if (mainWin->m_commDebug) Pmsg1(000, "conn %i RUN CMD\n", m_conn);
392          new runCmdPage(m_conn);
393          break;
394       case BNET_START_RTREE:
395          if (mainWin->m_commDebug) Pmsg1(000, "conn %i START RTREE CMD\n", m_conn);
396          new restorePage(m_conn);
397          break;
398       case BNET_END_RTREE:
399          if (mainWin->m_commDebug) Pmsg1(000, "conn %i END RTREE CMD\n", m_conn);
400          break;
401       case BNET_ERROR_MSG:
402          if (mainWin->m_commDebug) Pmsg1(000, "conn %i ERROR MSG\n", m_conn);
403          stat = sock_read();          /* get the message */
404          m_console->display_text(msg());
405          QMessageBox::critical(m_console, "Error", msg(), QMessageBox::Ok);
406          m_console->beginNewCommand(m_conn);
407          mainWin->waitExit();
408          break;
409       case BNET_WARNING_MSG:
410          if (mainWin->m_commDebug) Pmsg1(000, "conn %i WARNING MSG\n", m_conn);
411          stat = sock_read();          /* get the message */
412          if (!m_console->m_warningPrevent) {
413             QMessageBox::critical(m_console, "Warning", msg(), QMessageBox::Ok);
414          }
415          break;
416       case BNET_INFO_MSG:
417          if (mainWin->m_commDebug) Pmsg1(000, "conn %i INFO MSG\n", m_conn);
418          stat = sock_read();          /* get the message */
419          m_console->display_text(msg());
420          mainWin->set_status(msg());
421          break;
422       }
423
424       if (!m_sock) {
425          stat = BNET_HARDEOF;
426          return stat;
427       }
428       if (is_bnet_stop(m_sock)) {         /* error or term request */
429          if (mainWin->m_commDebug) Pmsg1(000, "conn %i BNET STOP\n", m_conn);
430          m_console->stopTimer();
431          m_sock->close();
432          m_sock = NULL;
433          mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
434          QBrush redBrush(Qt::red);
435          QTreeWidgetItem *item = mainWin->getFromHash(m_console);
436          item->setForeground(0, redBrush);
437          if (m_notifier) {
438             m_notifier->setEnabled(false);
439             delete m_notifier;
440             m_notifier = NULL;
441          }
442          mainWin->set_status(_("Director disconnected."));
443          stat = BNET_HARDEOF;
444       }
445       break;
446    } 
447    return stat;
448 }
449
450 /* Called by signal when the Director has output for us */
451 void DirComm::notify_read_dir(int /* fd */)
452 {
453    int stat;
454    if (!mainWin->m_notify) {
455       return;
456    }
457    if (mainWin->m_commDebug) Pmsg1(000, "enter read_dir conn %i read_dir\n", m_conn);
458    stat = m_sock->wait_data(0, 5000);
459    if (stat > 0) {
460       if (mainWin->m_commDebug) Pmsg2(000, "read_dir conn %i stat=%d\n", m_conn, stat);
461       while (read() >= 0) {
462          m_console->display_text(msg());
463       }
464    }
465    if (mainWin->m_commDebug) Pmsg2(000, "exit read_dir conn %i stat=%d\n", m_conn, stat);
466 }
467
468 /*
469  * When the notifier is enabled, read_dir() will automatically be
470  * called by the Qt event loop when ever there is any output 
471  * from the Director, and read_dir() will then display it on
472  * the console.
473  *
474  * When we are in a bat dialog, we want to control *all* output
475  * from the Directory, so we set notify to off.
476  *    m_console->notifiy(false);
477  */
478 bool DirComm::notify(bool enable) 
479
480    bool prev_enabled = false;
481    /* Set global flag */
482    mainWin->m_notify = enable;
483    if (m_notifier) {
484       prev_enabled = m_notifier->isEnabled();   
485       if (prev_enabled != enable) {
486          m_notifier->setEnabled(enable);
487       }
488       if (mainWin->m_connDebug) Pmsg3(000, "conn=%i notify=%d prev=%d\n", m_conn, enable, prev_enabled);
489    } else if (mainWin->m_connDebug) {
490       Pmsg2(000, "m_notifier does not exist: %i %s\n", m_conn, m_console->m_dir->name());
491    }
492    return prev_enabled;
493 }
494
495 bool DirComm::is_notify_enabled() const
496 {
497    return mainWin->m_notify;
498 }
499
500 /*
501  * Call-back for reading a passphrase for an encrypted PEM file
502  * This function uses getpass(), 
503  *  which uses a static buffer and is NOT thread-safe.
504  */
505 static int tls_pem_callback(char *buf, int size, const void *userdata)
506 {
507    (void)size;
508    (void)userdata;
509 #ifdef HAVE_TLS
510 # if defined(HAVE_WIN32)
511    //sendit(prompt);
512    if (win32_cgets(buf, size) == NULL) {
513       buf[0] = 0;
514       return 0;
515    } else {
516       return strlen(buf);
517    }
518 # else
519    const char *prompt = (const char *)userdata;
520    char *passwd;
521
522    passwd = getpass(prompt);
523    bstrncpy(buf, passwd, size);
524    return strlen(buf);
525 # endif
526 #else
527    buf[0] = 0;
528    return 0;
529 #endif
530 }