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