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