]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/bcomm/dircomm.cpp
kes Add first (not yet tested) cut of bcomm to the qt-console directory.
[bacula/bacula] / bacula / src / qt-console / bcomm / dircomm.cpp
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2007-2007 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 two of the GNU 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 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 John Walker.
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  *   Version $Id$
30  *
31  *  Bacula Communications class that is at a higher level than BSOCK
32  *
33  *   Kern Sibbald, May MMVII
34  *
35  */ 
36
37 #include "bat.h"
38 #include "console.h"
39 #include "restore.h"
40 #include "select.h"
41 #include "run/run.h"
42 #include "dircomm.h"
43
44 static int tls_pem_callback(char *buf, int size, const void *userdata);
45
46
47 DirComm::DirComm(Console *console)
48 {
49    m_console = console;
50    m_sock = NULL;
51    m_at_prompt = false;
52    m_at_main_prompt = false;
53    m_timer = NULL;
54 }
55
56 DirComm::~DirComm()
57 {
58    if (m_timer) {
59       stopTimer();
60    }
61 }
62
63 void DirComm::startTimer()
64 {
65    m_timer = new QTimer(m_console);
66    QWidget::connect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
67    m_timer->start(mainWin->m_checkMessagesInterval*1000);
68 }
69
70 void DirComm::stopTimer()
71 {
72    if (m_timer) {
73       QWidget::disconnect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
74       m_timer->stop();
75       delete m_timer;
76       m_timer = NULL;
77    }
78 }
79       
80 void DirComm::poll_messages()
81 {
82    m_messages_pending = true;
83    if ((m_at_main_prompt) && (mainWin->m_checkMessages)){
84       write(".messages");
85       displayToPrompt();
86    }
87 }
88
89 /* Terminate any open socket */
90 void DirComm::terminate()
91 {
92    if (m_sock) {
93       stopTimer();
94       m_sock->close();
95       m_sock = NULL;
96    }
97 }
98
99 /*
100  * Connect to Director. 
101  */
102 void DirComm::connect_dir(DIRRES *dir, CONRES *cons)
103 {
104    JCR jcr;
105    utime_t heart_beat;
106    char buf[1024];
107
108    m_dir = dir;
109    if (!m_dir) {          
110       mainWin->set_status("No Director found.");
111       return;
112    }
113    if (m_sock) {
114       mainWin->set_status("Already connected.");
115       return;
116    }
117
118    memset(&jcr, 0, sizeof(jcr));
119
120    mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
121    m_console->display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
122
123    /* Give GUI a chance */
124    app->processEvents();
125    
126    /* Initialize Console TLS context once */
127    if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
128       /* Generate passphrase prompt */
129       bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ", 
130                 cons->name());
131
132       /* Initialize TLS context:
133        * Args: CA certfile, CA certdir, Certfile, Keyfile,
134        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer   
135        */
136       cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
137          cons->tls_ca_certdir, cons->tls_certfile,
138          cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
139
140       if (!cons->tls_ctx) {
141          m_console->display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
142             m_dir->name());
143          return;
144       }
145    }
146
147    /* Initialize Director TLS context once */
148    if (!m_dir->tls_ctx && (m_dir->tls_enable || m_dir->tls_require)) {
149       /* Generate passphrase prompt */
150       bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ", 
151                 m_dir->name());
152
153       /* Initialize TLS context:
154        * Args: CA certfile, CA certdir, Certfile, Keyfile,
155        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
156       m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
157                           m_dir->tls_ca_certdir, m_dir->tls_certfile,
158                           m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
159
160       if (!m_dir->tls_ctx) {
161          m_console->display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
162             m_dir->name());
163          mainWin->set_status("Connection failed");
164          return;
165       }
166    }
167
168    if (m_dir->heartbeat_interval) {
169       heart_beat = m_dir->heartbeat_interval;
170    } else if (cons) {
171       heart_beat = cons->heartbeat_interval;
172    } else {
173       heart_beat = 0;
174    }        
175
176    m_sock = bnet_connect(NULL, 5, 15, heart_beat,
177                           _("Director daemon"), m_dir->address,
178                           NULL, m_dir->DIRport, 0);
179    if (m_sock == NULL) {
180       mainWin->set_status("Connection failed");
181       return;
182    } else {
183       /* Update page selector to green to indicate that Console is connected */
184       mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
185       QBrush greenBrush(Qt::green);
186       QTreeWidgetItem *item = mainWin->getFromHash(this);
187       item->setForeground(0, greenBrush);
188    }
189
190    jcr.dir_bsock = m_sock;
191
192    if (!authenticate_director(&jcr, m_dir, cons, buf, sizeof(buf))) {
193       m_console->display_text(buf);
194       return;
195    }
196    if (buf[0]) {
197       m_console->display_text(buf);
198    }
199
200    /* Give GUI a chance */
201    app->processEvents();
202
203    mainWin->set_status(_("Initializing ..."));
204
205    /* Set up input notifier */
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
209    mainWin->set_status(_("Connected"));
210    startTimer();                      /* start message timer */
211    return;
212 }
213
214 bool DirComm::dir_cmd(QString &cmd, QStringList &results)
215 {
216    return dir_cmd(cmd.toUtf8().data(), results);
217 }
218
219 /*
220  * Send a command to the Director, and return the
221  *  results in a QStringList.  
222  */
223 bool DirComm::dir_cmd(const char *cmd, QStringList &results)
224 {
225    int stat;
226
227    notify(false);
228    write(cmd);
229    while ((stat = read()) > 0) {
230       if (mainWin->m_displayAll) m_console->display_text(msg());
231       strip_trailing_junk(msg());
232       results << msg();
233    }
234    notify(true);
235    discardToPrompt();
236    return true;              /* ***FIXME*** return any command error */
237 }
238
239 bool DirComm::sql_cmd(QString &query, QStringList &results)
240 {
241    return sql_cmd(query.toUtf8().data(), results);
242 }
243
244 /*
245  * Send an sql query to the Director, and return the
246  *  results in a QStringList.  
247  */
248 bool DirComm::sql_cmd(const char *query, QStringList &results)
249 {
250    int stat;
251    POOL_MEM cmd(PM_MESSAGE);
252
253    if (!is_connectedGui()) {
254       return false;
255    }
256
257    notify(false);
258    
259    pm_strcpy(cmd, ".sql query=\"");
260    pm_strcat(cmd, query);
261    pm_strcat(cmd, "\"");
262    write(cmd.c_str());
263    while ((stat = read()) > 0) {
264       if (mainWin->m_displayAll) m_console->display_text(msg());
265       strip_trailing_junk(msg());
266       results << msg();
267    }
268    notify(true);
269    discardToPrompt();
270    return true;              /* ***FIXME*** return any command error */
271 }
272
273 /* 
274  * This should be moved into a bSocket class 
275  */
276 char *DirComm::msg()
277 {
278    if (m_sock) {
279       return m_sock->msg;
280    }
281    return NULL;
282 }
283
284 /* Send a command to the Director */
285 void DirComm::write_dir(const char *msg)
286 {
287    if (m_sock) {
288       mainWin->set_status(_("Processing command ..."));
289       QApplication::setOverrideCursor(Qt::WaitCursor);
290       write(msg);
291    } else {
292       mainWin->set_status(" Director not connected. Click on connect button.");
293       mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
294       QBrush redBrush(Qt::red);
295       QTreeWidgetItem *item = mainWin->getFromHash(this);
296       item->setForeground(0, redBrush);
297       m_at_prompt = false;
298       m_at_main_prompt = false;
299    }
300 }
301
302 int DirComm::write(const QString msg)
303 {
304    return write(msg.toUtf8().data());
305 }
306
307 int DirComm::write(const char *msg)
308 {
309    if (!m_sock) {
310       return -1;
311    }
312    m_sock->msglen = pm_strcpy(m_sock->msg, msg);
313    m_at_prompt = false;
314    m_at_main_prompt = false;
315    if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
316    return m_sock->send();
317
318 }
319
320 /*
321  * Get to main command prompt -- i.e. abort any suDirCommand
322  */
323 void DirComm::beginNewCommand()
324 {
325    for (int i=0; i < 3; i++) {
326       write(".\n");
327       while (read() > 0) {
328          if (mainWin->m_displayAll) m_console->display_text(msg());
329       }
330       if (m_at_main_prompt) {
331          break;
332       }
333    }
334    m_console->display_text("\n");
335 }
336
337 void DirComm::displayToPrompt()
338
339    int stat = 0;
340    if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
341    while (!m_at_prompt) {
342       if ((stat=read()) > 0) {
343          m_console->display_text(msg());
344       }
345    }
346    if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
347 }
348
349 void DirComm::discardToPrompt()
350
351    int stat = 0;
352    if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
353    while (!m_at_prompt) {
354       if ((stat=read()) > 0) {
355          if (mainWin->m_displayAll) m_console->display_text(msg());
356       }
357    }
358    if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
359 }
360
361
362 /* 
363  * Blocking read from director
364  */
365 int DirComm::read()
366 {
367    int stat = 0;
368    while (m_sock) {
369       for (;;) {
370          stat = bnet_wait_data_intr(m_sock, 1);
371          if (stat > 0) {
372             break;
373          } 
374          app->processEvents();
375          if (m_api_set && m_messages_pending) {
376             write_dir(".messages");
377             m_messages_pending = false;
378          }
379       }
380       m_sock->msg[0] = 0;
381       stat = m_sock->recv();
382       if (stat >= 0) {
383          if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
384          if (m_at_prompt) {
385             m_console->display_text("\n");
386             m_at_prompt = false;
387             m_at_main_prompt = false;
388          }
389       }
390       switch (m_sock->msglen) {
391       case BNET_MSGS_PENDING :
392          if (m_notifier->isEnabled()) {
393             if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
394             write_dir(".messages");
395             displayToPrompt();
396             m_messages_pending = false;
397          }
398          m_messages_pending = true;
399          continue;
400       case BNET_CMD_OK:
401          if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
402          m_at_prompt = false;
403          m_at_main_prompt = false;
404          mainWin->set_status(_("Command completed ..."));
405          continue;
406       case BNET_CMD_BEGIN:
407          if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
408          m_at_prompt = false;
409          m_at_main_prompt = false;
410          mainWin->set_status(_("Processing command ..."));
411          continue;
412       case BNET_MAIN_PROMPT:
413          if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
414          m_at_prompt = true;
415          m_at_main_prompt = true;
416          mainWin->set_status(_("At main prompt waiting for input ..."));
417          QApplication::restoreOverrideCursor();
418          break;
419       case BNET_PROMPT:
420          if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
421          m_at_prompt = true;
422          m_at_main_prompt = false;
423          mainWin->set_status(_("At prompt waiting for input ..."));
424          QApplication::restoreOverrideCursor();
425          break;
426       case BNET_CMD_FAILED:
427          if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
428          mainWin->set_status(_("Command failed."));
429          QApplication::restoreOverrideCursor();
430          break;
431       /* We should not get this one */
432       case BNET_EOD:
433          if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
434          mainWin->set_status_ready();
435          QApplication::restoreOverrideCursor();
436          if (!m_api_set) {
437             break;
438          }
439          continue;
440       case BNET_START_SELECT:
441          if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
442          new selectDialog(m_console);
443          break;
444       case BNET_YESNO:
445          if (mainWin->m_commDebug) Pmsg0(000, "YESNO\n");
446          new yesnoPopUp(m_console);
447          break;
448       case BNET_RUN_CMD:
449          if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
450          new runCmdPage();
451          break;
452       case BNET_ERROR_MSG:
453          if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
454          m_sock->recv();              /* get the message */
455          m_console->display_text(msg());
456          QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
457          break;
458       case BNET_WARNING_MSG:
459          if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
460          m_sock->recv();              /* get the message */
461          m_console->display_text(msg());
462          QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
463          break;
464       case BNET_INFO_MSG:
465          if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
466          m_sock->recv();              /* get the message */
467          m_console->display_text(msg());
468          mainWin->set_status(msg());
469          break;
470       }
471       if (is_bnet_stop(m_sock)) {         /* error or term request */
472          if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
473          stopTimer();
474          m_sock->close();
475          m_sock = NULL;
476          mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
477          QBrush redBrush(Qt::red);
478          QTreeWidgetItem *item = mainWin->getFromHash(this);
479          item->setForeground(0, redBrush);
480          m_notifier->setEnabled(false);
481          delete m_notifier;
482          m_notifier = NULL;
483          mainWin->set_status(_("Director disconnected."));
484          QApplication::restoreOverrideCursor();
485          stat = BNET_HARDEOF;
486       }
487       break;
488    } 
489    return stat;
490 }
491
492 /* Called by signal when the Director has output for us */
493 void DirComm::read_dir(int /* fd */)
494 {
495    if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
496    while (read() >= 0) {
497       m_console->display_text(msg());
498    }
499 }
500
501 /*
502  * When the notifier is enabled, read_dir() will automatically be
503  * called by the Qt event loop when ever there is any output 
504  * from the Directory, and read_dir() will then display it on
505  * the console.
506  *
507  * When we are in a bat dialog, we want to control *all* output
508  * from the Directory, so we set notify to off.
509  *    m_console->notifiy(false);
510  */
511 void DirComm::notify(bool enable) 
512
513    m_notifier->setEnabled(enable);   
514 }
515
516 bool DirComm::is_connectedGui()
517 {
518    if (is_connected()) {
519       return true;
520    } else {
521       QString message("Director ");
522       message += " is curerntly disconnected\n  Please reconnect!!";
523       QMessageBox::warning(this, tr("Bat"),
524          tr(message.toUtf8().data()), QMessageBox::Ok );
525       return false;
526    }
527 }
528
529 /*
530  * Call-back for reading a passphrase for an encrypted PEM file
531  * This function uses getpass(), 
532  *  which uses a static buffer and is NOT thread-safe.
533  */
534 static int tls_pem_callback(char *buf, int size, const void *userdata)
535 {
536    (void)size;
537    (void)userdata;
538 #ifdef HAVE_TLS
539    const char *prompt = (const char *)userdata;
540 # if defined(HAVE_WIN32)
541    sendit(prompt);
542    if (win32_cgets(buf, size) == NULL) {
543       buf[0] = 0;
544       return 0;
545    } else {
546       return strlen(buf);
547    }
548 # else
549    char *passwd;
550
551    passwd = getpass(prompt);
552    bstrncpy(buf, passwd, size);
553    return strlen(buf);
554 # endif
555 #else
556    buf[0] = 0;
557    return 0;
558 #endif
559 }