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