]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/bcomm/dircomm.cpp
4b81699d4ca7aed909bf8a0dae0166f0476bb5e9
[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 many
7    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    Bacula® is a registered trademark of Kern Sibbald.
15 */
16 /*
17  *  DirComm, Director communications,class
18  *
19  *   Kern Sibbald, January MMVII
20  *
21  */
22
23 #include "bat.h"
24 #include "console.h"
25 #include "restore.h"
26 #include "select.h"
27 #include "textinput.h"
28 #include "run/run.h"
29
30 static int tls_pem_callback(char *buf, int size, const void *userdata);
31
32 DirComm::DirComm(Console *parent, int conn):  m_notifier(NULL),  m_api_set(false)
33 {
34    m_console = parent;
35    m_sock = NULL;
36    m_at_prompt = false;
37    m_at_main_prompt = false;
38    m_sent_blank = false;
39    m_conn = conn;
40    m_in_command = 0;
41    m_in_select = false;
42    m_notify = false;
43 }
44
45 DirComm::~DirComm()
46 {
47 }
48
49 /* Terminate any open socket */
50 void DirComm::terminate()
51 {
52    if (m_sock) {
53       if (m_notifier) {
54          m_notifier->setEnabled(false);
55          delete m_notifier;
56          m_notifier = NULL;
57          m_notify = false;
58       }
59       if (mainWin->m_connDebug)
60          Pmsg2(000, "DirComm %i terminating connections %s\n", m_conn, m_console->m_dir->name());
61       free_bsock(m_sock);
62    }
63 }
64
65 /*
66  * Connect to Director.
67  */
68 bool DirComm::connect_dir()
69 {
70    JCR *jcr = new JCR;
71    utime_t heart_beat;
72    char buf[1024];
73    CONRES *cons;
74    int numcon = 0;
75    int i = 0;
76
77    buf[0] = 0;
78
79    foreach_res(cons, R_CONSOLE) {
80       numcon++;
81    }
82
83    if (m_sock && !is_bsock_open(m_sock)) {
84       mainWin->set_status( tr("Already connected."));
85       m_console->display_textf(_("Already connected\"%s\".\n"),
86             m_console->m_dir->name());
87       if (mainWin->m_connDebug) {
88          Pmsg2(000, "DirComm %i BAILING already connected %s\n", m_conn, m_console->m_dir->name());
89       }
90       goto bail_out;
91    }
92
93    if (mainWin->m_connDebug)Pmsg2(000, "DirComm %i connecting %s\n", m_conn, m_console->m_dir->name());
94    memset(jcr, 0, sizeof(JCR));
95
96    mainWin->set_statusf(_("Connecting to Director %s:%d"), m_console->m_dir->address, m_console->m_dir->DIRport);
97    if (m_conn == 0) {
98       m_console->display_textf(_("Connecting to Director %s:%d\n\n"), m_console->m_dir->address, m_console->m_dir->DIRport);
99    }
100
101    /* Give GUI a chance */
102    app->processEvents();
103
104    LockRes();
105    /* If cons==NULL, default console will be used */
106    for (i=0; i<numcon; i++) {
107       cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)cons);
108       if (cons->director && strcasecmp(cons->director, m_console->m_dir->name()) == 0) {
109          break;
110       }
111       if (i == (numcon - 1)) {
112          cons = NULL;
113       }
114    }
115
116    /* Look for the first non-linked console */
117    if (cons == NULL) {
118       for (i=0; i<numcon; i++) {
119          cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)cons);
120          if (cons->director == NULL) {
121             break;
122          }
123          if (i == (numcon - 1)) {
124             cons = NULL;
125          }
126       }
127    }
128
129    /* If no console, take first one */
130    if (!cons) {
131       cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
132    }
133    UnlockRes();
134
135    /* Initialize Console TLS context once */
136    if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
137       /* Generate passphrase prompt */
138       bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
139                 cons->name());
140
141       /* Initialize TLS context:
142        * Args: CA certfile, CA certdir, Certfile, Keyfile,
143        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
144        */
145       cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
146          cons->tls_ca_certdir, cons->tls_certfile,
147          cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
148
149       if (!cons->tls_ctx) {
150          m_console->display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
151             m_console->m_dir->name());
152          if (mainWin->m_connDebug) {
153             Pmsg2(000, "DirComm %i BAILING Failed to initialize TLS context for Console %s\n", m_conn, m_console->m_dir->name());
154          }
155          goto bail_out;
156       }
157    }
158
159    /* Initialize Director TLS context once */
160    if (!m_console->m_dir->tls_ctx && (m_console->m_dir->tls_enable || m_console->m_dir->tls_require)) {
161       /* Generate passphrase prompt */
162       bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
163                 m_console->m_dir->name());
164
165       /* Initialize TLS context:
166        * Args: CA certfile, CA certdir, Certfile, Keyfile,
167        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
168       m_console->m_dir->tls_ctx = new_tls_context(m_console->m_dir->tls_ca_certfile,
169                           m_console->m_dir->tls_ca_certdir, m_console->m_dir->tls_certfile,
170                           m_console->m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
171
172       if (!m_console->m_dir->tls_ctx) {
173          m_console->display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
174             m_console->m_dir->name());
175          mainWin->set_status("Connection failed");
176          if (mainWin->m_connDebug) {
177             Pmsg2(000, "DirComm %i BAILING Failed to initialize TLS context for Director %s\n", m_conn, m_console->m_dir->name());
178          }
179          goto bail_out;
180       }
181    }
182
183    if (m_console->m_dir->heartbeat_interval) {
184       heart_beat = m_console->m_dir->heartbeat_interval;
185    } else if (cons) {
186       heart_beat = cons->heartbeat_interval;
187    } else {
188       heart_beat = 0;
189    }
190
191    if (!m_sock) {
192       m_sock = new_bsock();
193    }
194    if (!m_sock->connect(NULL, 5, 15, heart_beat,
195                           _("Director daemon"), m_console->m_dir->address,
196                           NULL, m_console->m_dir->DIRport, 0)) {
197       m_sock->destroy();
198       m_sock = NULL;
199    }
200    if (m_sock == NULL) {
201       mainWin->set_status("Connection failed");
202       if (mainWin->m_connDebug) {
203          Pmsg2(000, "DirComm %i BAILING Connection failed %s\n", m_conn, m_console->m_dir->name());
204       }
205       goto bail_out;
206    } else {
207       /* Update page selector to green to indicate that Console is connected */
208       mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
209       QBrush greenBrush(Qt::green);
210       QTreeWidgetItem *item = mainWin->getFromHash(m_console);
211       if (item) {
212          item->setForeground(0, greenBrush);
213       }
214    }
215
216    jcr->dir_bsock = m_sock;
217
218    if (!authenticate_director(jcr, m_console->m_dir, cons, buf, sizeof(buf))) {
219       m_console->display_text(buf);
220       if (mainWin->m_connDebug) {
221          Pmsg2(000, "DirComm %i BAILING Connection failed %s\n", m_conn, m_console->m_dir->name());
222       }
223       goto bail_out;
224    }
225
226    if (buf[0]) {
227       m_console->display_text(buf);
228    }
229
230    /* Give GUI a chance */
231    app->processEvents();
232
233    mainWin->set_status(_("Initializing ..."));
234
235    /*
236     * Set up input notifier
237     */
238    m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
239    QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(notify_read_dir(int)));
240    m_notifier->setEnabled(true);
241    m_notify = true;
242
243    write(".api 1");
244    m_api_set = true;
245    m_console->displayToPrompt(m_conn);
246
247    m_console->beginNewCommand(m_conn);
248
249    mainWin->set_status(_("Connected"));
250
251    if (mainWin->m_connDebug) {
252       Pmsg2(000, "Returning TRUE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
253    }
254    return true;
255
256 bail_out:
257    if (mainWin->m_connDebug) {
258       Pmsg2(000, "Returning FALSE from DirComm->connect_dir : %i %s\n", m_conn, m_console->m_dir->name());
259    }
260    delete jcr;
261    return false;
262 }
263
264 /*
265  * This should be moved into a bSocket class
266  */
267 char *DirComm::msg()
268 {
269    if (m_sock) {
270       return m_sock->msg;
271    }
272    return NULL;
273 }
274
275 int DirComm::write(const QString msg)
276 {
277    return write(msg.toUtf8().data());
278 }
279
280 int DirComm::write(const char *msg)
281 {
282    if (!m_sock) {
283       return -1;
284    }
285    m_sock->msglen = pm_strcpy(m_sock->msg, msg);
286    m_at_prompt = false;
287    m_at_main_prompt = false;
288    if (mainWin->m_commDebug) Pmsg2(000, "conn %i send: %s\n", m_conn, msg);
289    /*
290     * Ensure we send only one blank line.  Multiple blank lines are
291     *  simply discarded, it keeps the console output looking nicer.
292     */
293    if (m_sock->msglen == 0 || (m_sock->msglen == 1 && *m_sock->msg == '\n')) {
294       if (!m_sent_blank) {
295          m_sent_blank = true;
296          return m_sock->send();
297       } else {
298          return -1;             /* discard multiple blanks */
299       }
300    }
301    m_sent_blank = false;        /* clear flag */
302    return m_sock->send();
303 }
304
305 int DirComm::sock_read()
306 {
307    int stat;
308 #ifdef HAVE_WIN32
309    bool wasEnabled = notify(false);
310    stat = m_sock->recv();
311    notify(wasEnabled);
312 #else
313    stat = m_sock->recv();
314 #endif
315    return stat;
316 }
317
318 /*
319  * Blocking read from director
320  */
321 int DirComm::read()
322 {
323    int stat = -1;
324
325    if (!m_sock) {
326       return -1;
327    }
328    while (m_sock) {
329       for (;;) {
330          if (!m_sock) break;
331          stat = m_sock->wait_data_intr(0, 50000);
332          if (stat > 0) {
333             break;
334          }
335          app->processEvents();
336          if (m_api_set && m_console->is_messagesPending() && is_notify_enabled() && m_console->hasFocus()) {
337             if (mainWin->m_commDebug) Pmsg1(000, "conn %i process_events\n", m_conn);
338             m_console->messagesPending(false);
339             m_console->write_dir(m_conn, ".messages", false);
340          }
341       }
342       if (!m_sock) {
343          return -1;
344       }
345       m_sock->msg[0] = 0;
346       stat = sock_read();
347       if (stat >= 0) {
348          if (mainWin->m_commDebug) Pmsg2(000, "conn %i got: %s\n", m_conn, m_sock->msg);
349          if (m_at_prompt) {
350             m_console->display_text("\n");
351             m_at_prompt = false;
352             m_at_main_prompt = false;
353          }
354       }
355       switch (m_sock->msglen) {
356       case BNET_MSGS_PENDING :
357          if (is_notify_enabled() && m_console->hasFocus()) {
358             m_console->messagesPending(false);
359             if (mainWin->m_commDebug) Pmsg1(000, "conn %i MSGS PENDING\n", m_conn);
360             m_console->write_dir(m_conn, ".messages", false);
361             m_console->displayToPrompt(m_conn);
362             continue;
363          }
364          m_console->messagesPending(true);
365          continue;
366       case BNET_CMD_OK:
367          if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD OK\n", m_conn);
368          m_at_prompt = false;
369          m_at_main_prompt = false;
370          if (--m_in_command < 0) {
371             m_in_command = 0;
372          }
373          mainWin->set_status(_("Command completed ..."));
374          continue;
375       case BNET_CMD_BEGIN:
376          if (mainWin->m_commDebug) Pmsg1(000, "conn %i CMD BEGIN\n", m_conn);
377          m_at_prompt = false;
378          m_at_main_prompt = false;
379          m_in_command++;
380          mainWin->set_status(_("Processing command ..."));
381          continue;
382       case BNET_MAIN_PROMPT:
383          if (mainWin->m_commDebug) Pmsg1(000, "conn %i MAIN PROMPT\n", m_conn);
384          if (!m_at_prompt && ! m_at_main_prompt) {
385             m_at_prompt = true;
386             m_at_main_prompt = true;
387             mainWin->set_status(_("At main prompt waiting for input ..."));
388          }
389          break;
390       case BNET_SUB_PROMPT:
391          if (mainWin->m_commDebug) Pmsg2(000, "conn %i SUB_PROMPT m_in_select=%d\n", m_conn, m_in_select);
392          m_at_prompt = true;
393          m_at_main_prompt = false;
394          mainWin->set_status(_("At prompt waiting for input ..."));
395          break;
396       case BNET_TEXT_INPUT:
397          if (mainWin->m_commDebug) Pmsg4(000, "conn %i TEXT_INPUT at_prompt=%d  m_in_select=%d notify=%d\n",
398                m_conn, m_at_prompt, m_in_select, is_notify_enabled());
399          //if (!m_in_select && is_notify_enabled()) {
400          if (!m_in_select) {
401             mainWin->waitExit();
402             new textInputDialog(m_console, m_conn);
403          } else {
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 }