]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/bcomm/dircomm.cpp
Fix text input in bat. Fixe bug #1965
[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          if (!m_in_select) {
376             mainWin->waitExit();
377             new textInputDialog(m_console, m_conn);
378          } else {
379             if (mainWin->m_commDebug) Pmsg0(000, "!m_in_select && is_notify_enabled\n");
380             m_at_prompt = true;
381             m_at_main_prompt = false;
382             mainWin->set_status(_("At prompt waiting for input ..."));
383          }
384          break;
385       case BNET_CMD_FAILED:
386          if (mainWin->m_commDebug) Pmsg1(000, "CMD FAILED\n", m_conn);
387          if (--m_in_command < 0) {
388             m_in_command = 0;
389          }
390          mainWin->set_status(_("Command failed."));
391          break;
392       /* We should not get this one */
393       case BNET_EOD:
394          if (mainWin->m_commDebug) Pmsg1(000, "conn %i EOD\n", m_conn);
395          mainWin->set_status_ready();
396          if (!m_api_set) {
397             break;
398          }
399          continue;
400       case BNET_START_SELECT:
401          if (mainWin->m_commDebug) Pmsg1(000, "conn %i START SELECT\n", m_conn);
402          m_in_select = true;
403          new selectDialog(m_console, m_conn);
404          m_in_select = false;
405          break;
406       case BNET_YESNO:
407          if (mainWin->m_commDebug) Pmsg1(000, "conn %i YESNO\n", m_conn);
408          new yesnoPopUp(m_console, m_conn);
409          break;
410       case BNET_RUN_CMD:
411          if (mainWin->m_commDebug) Pmsg1(000, "conn %i RUN CMD\n", m_conn);
412          new runCmdPage(m_conn);
413          break;
414       case BNET_START_RTREE:
415          if (mainWin->m_commDebug) Pmsg1(000, "conn %i START RTREE CMD\n", m_conn);
416          new restorePage(m_conn);
417          break;
418       case BNET_END_RTREE:
419          if (mainWin->m_commDebug) Pmsg1(000, "conn %i END RTREE CMD\n", m_conn);
420          break;
421       case BNET_ERROR_MSG:
422          if (mainWin->m_commDebug) Pmsg1(000, "conn %i ERROR MSG\n", m_conn);
423          stat = sock_read();          /* get the message */
424          m_console->display_text(msg());
425          QMessageBox::critical(m_console, "Error", msg(), QMessageBox::Ok);
426          m_console->beginNewCommand(m_conn);
427          mainWin->waitExit();
428          break;
429       case BNET_WARNING_MSG:
430          if (mainWin->m_commDebug) Pmsg1(000, "conn %i WARNING MSG\n", m_conn);
431          stat = sock_read();          /* get the message */
432          if (!m_console->m_warningPrevent) {
433             QMessageBox::critical(m_console, "Warning", msg(), QMessageBox::Ok);
434          }
435          break;
436       case BNET_INFO_MSG:
437          if (mainWin->m_commDebug) Pmsg1(000, "conn %i INFO MSG\n", m_conn);
438          stat = sock_read();          /* get the message */
439          m_console->display_text(msg());
440          mainWin->set_status(msg());
441          break;
442       }
443
444       if (!m_sock) {
445          stat = BNET_HARDEOF;
446          return stat;
447       }
448       if (is_bnet_stop(m_sock)) {         /* error or term request */
449          if (mainWin->m_commDebug) Pmsg1(000, "conn %i BNET STOP\n", m_conn);
450          m_console->stopTimer();
451          m_sock->close();
452          m_sock = NULL;
453          mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
454          QBrush redBrush(Qt::red);
455          QTreeWidgetItem *item = mainWin->getFromHash(m_console);
456          item->setForeground(0, redBrush);
457          if (m_notifier) {
458             m_notifier->setEnabled(false);
459             delete m_notifier;
460             m_notifier = NULL;
461             m_notify = false;
462          }
463          mainWin->set_status(_("Director disconnected."));
464          stat = BNET_HARDEOF;
465       }
466       break;
467    } 
468    return stat;
469 }
470
471 /* Called by signal when the Director has output for us */
472 void DirComm::notify_read_dir(int /* fd */)
473 {
474    int stat;
475    if (!mainWin->m_notify) {
476       return;
477    }
478    if (mainWin->m_commDebug) Pmsg1(000, "enter read_dir conn %i read_dir\n", m_conn);
479    stat = m_sock->wait_data(0, 5000);
480    if (stat > 0) {
481       if (mainWin->m_commDebug) Pmsg2(000, "read_dir conn %i stat=%d\n", m_conn, stat);
482       while (read() >= 0) {
483          m_console->display_text(msg());
484       }
485    }
486    if (mainWin->m_commDebug) Pmsg2(000, "exit read_dir conn %i stat=%d\n", m_conn, stat);
487 }
488
489 /*
490  * When the notifier is enabled, read_dir() will automatically be
491  * called by the Qt event loop when ever there is any output 
492  * from the Director, and read_dir() will then display it on
493  * the console.
494  *
495  * When we are in a bat dialog, we want to control *all* output
496  * from the Directory, so we set notify to off.
497  *    m_console->notify(false);
498  */
499 bool DirComm::notify(bool enable) 
500
501    bool prev_enabled = false;
502    /* Set global flag */
503    mainWin->m_notify = enable;
504    if (m_notifier) {
505       prev_enabled = m_notifier->isEnabled();   
506       m_notifier->setEnabled(enable);
507       m_notify = enable;
508       if (mainWin->m_connDebug) Pmsg3(000, "conn=%i set_notify=%d prev=%d\n", m_conn, enable, prev_enabled);
509    } else if (mainWin->m_connDebug) {
510       Pmsg2(000, "m_notifier does not exist: %i %s\n", m_conn, m_console->m_dir->name());
511    }
512    return prev_enabled;
513 }
514
515 bool DirComm::is_notify_enabled() const
516 {
517    return m_notify;
518 }
519
520 /*
521  * Call-back for reading a passphrase for an encrypted PEM file
522  * This function uses getpass(), 
523  *  which uses a static buffer and is NOT thread-safe.
524  */
525 static int tls_pem_callback(char *buf, int size, const void *userdata)
526 {
527    (void)size;
528    (void)userdata;
529 #ifdef HAVE_TLS
530 # if defined(HAVE_WIN32)
531    //sendit(prompt);
532    if (win32_cgets(buf, size) == NULL) {
533       buf[0] = 0;
534       return 0;
535    } else {
536       return strlen(buf);
537    }
538 # else
539    const char *prompt = (const char *)userdata;
540    char *passwd;
541
542    passwd = getpass(prompt);
543    bstrncpy(buf, passwd, size);
544    return strlen(buf);
545 # endif
546 #else
547    buf[0] = 0;
548    return 0;
549 #endif
550 }