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