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