]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/bcomm/dircomm.cpp
ebl Update for bat on win32
[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( tr("No Director found.") );
111       return;
112    }
113    if (m_sock) {
114       mainWin->set_status( tr("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( tr("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( tr(" 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    QString buf;
341    if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
342    while (!m_at_prompt) {
343       if ((stat=read()) > 0) {
344         buf += msg();
345         if (buf.size() >= 8196 || m_messages_pending) {
346            m_console->display_text(buf);
347            buf.clear();
348            m_messages_pending = false;
349         }
350       }
351    }
352    m_console->display_text(buf);
353    if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
354 }
355
356 void DirComm::discardToPrompt()
357
358    int stat = 0;
359    if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
360    while (!m_at_prompt) {
361       if ((stat=read()) > 0) {
362          if (mainWin->m_displayAll) m_console->display_text(msg());
363       }
364    }
365    if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
366 }
367
368
369 /* 
370  * Blocking read from director
371  */
372 int DirComm::read()
373 {
374    int stat = 0;
375    while (m_sock) {
376       for (;;) {
377          stat = bnet_wait_data_intr(m_sock, 1);
378          if (stat > 0) {
379             break;
380          } 
381          app->processEvents();
382          if (m_api_set && m_messages_pending) {
383             write_dir(".messages");
384             m_messages_pending = false;
385          }
386       }
387       m_sock->msg[0] = 0;
388       stat = m_sock->recv();
389       if (stat >= 0) {
390          if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
391          if (m_at_prompt) {
392             m_console->display_text("\n");
393             m_at_prompt = false;
394             m_at_main_prompt = false;
395          }
396       }
397       switch (m_sock->msglen) {
398       case BNET_MSGS_PENDING :
399          if (m_notifier->isEnabled()) {
400             if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
401             write_dir(".messages");
402             displayToPrompt();
403             m_messages_pending = false;
404          }
405          m_messages_pending = true;
406          continue;
407       case BNET_CMD_OK:
408          if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
409          m_at_prompt = false;
410          m_at_main_prompt = false;
411          mainWin->set_status(_("Command completed ..."));
412          continue;
413       case BNET_CMD_BEGIN:
414          if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
415          m_at_prompt = false;
416          m_at_main_prompt = false;
417          mainWin->set_status(_("Processing command ..."));
418          continue;
419       case BNET_MAIN_PROMPT:
420          if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
421          m_at_prompt = true;
422          m_at_main_prompt = true;
423          mainWin->set_status(_("At main prompt waiting for input ..."));
424          QApplication::restoreOverrideCursor();
425          break;
426       case BNET_PROMPT:
427          if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
428          m_at_prompt = true;
429          m_at_main_prompt = false;
430          mainWin->set_status(_("At prompt waiting for input ..."));
431          QApplication::restoreOverrideCursor();
432          break;
433       case BNET_CMD_FAILED:
434          if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
435          mainWin->set_status(_("Command failed."));
436          QApplication::restoreOverrideCursor();
437          break;
438       /* We should not get this one */
439       case BNET_EOD:
440          if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
441          mainWin->set_status_ready();
442          QApplication::restoreOverrideCursor();
443          if (!m_api_set) {
444             break;
445          }
446          continue;
447       case BNET_START_SELECT:
448          if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
449          new selectDialog(m_console);
450          break;
451       case BNET_YESNO:
452          if (mainWin->m_commDebug) Pmsg0(000, "YESNO\n");
453          new yesnoPopUp(m_console);
454          break;
455       case BNET_RUN_CMD:
456          if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
457          new runCmdPage();
458          break;
459       case BNET_ERROR_MSG:
460          if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
461          m_sock->recv();              /* get the message */
462          m_console->display_text(msg());
463          QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
464          break;
465       case BNET_WARNING_MSG:
466          if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
467          m_sock->recv();              /* get the message */
468          m_console->display_text(msg());
469          QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
470          break;
471       case BNET_INFO_MSG:
472          if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
473          m_sock->recv();              /* get the message */
474          m_console->display_text(msg());
475          mainWin->set_status(msg());
476          break;
477       }
478       if (is_bnet_stop(m_sock)) {         /* error or term request */
479          if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
480          stopTimer();
481          m_sock->close();
482          m_sock = NULL;
483          mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
484          QBrush redBrush(Qt::red);
485          QTreeWidgetItem *item = mainWin->getFromHash(this);
486          item->setForeground(0, redBrush);
487          m_notifier->setEnabled(false);
488          delete m_notifier;
489          m_notifier = NULL;
490          mainWin->set_status(_("Director disconnected."));
491          QApplication::restoreOverrideCursor();
492          stat = BNET_HARDEOF;
493       }
494       break;
495    } 
496    return stat;
497 }
498
499 /* Called by signal when the Director has output for us */
500 void DirComm::read_dir(int /* fd */)
501 {
502    if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
503    while (read() >= 0) {
504       m_console->display_text(msg());
505    }
506 }
507
508 /*
509  * When the notifier is enabled, read_dir() will automatically be
510  * called by the Qt event loop when ever there is any output 
511  * from the Directory, and read_dir() will then display it on
512  * the console.
513  *
514  * When we are in a bat dialog, we want to control *all* output
515  * from the Directory, so we set notify to off.
516  *    m_console->notifiy(false);
517  */
518 void DirComm::notify(bool enable) 
519
520    m_notifier->setEnabled(enable);   
521 }
522
523 bool DirComm::is_connectedGui()
524 {
525    if (is_connected()) {
526       return true;
527    } else {
528      QString message( tr("Director is currently disconnected\n  Please reconnect!"));
529      QMessageBox::warning(this, "Bat",
530          message.toUtf8().data(), QMessageBox::Ok );
531       return false;
532    }
533 }
534
535 /*
536  * Call-back for reading a passphrase for an encrypted PEM file
537  * This function uses getpass(), 
538  *  which uses a static buffer and is NOT thread-safe.
539  */
540 static int tls_pem_callback(char *buf, int size, const void *userdata)
541 {
542    (void)size;
543    (void)userdata;
544 #ifdef HAVE_TLS
545    const char *prompt = (const char *)userdata;
546 # if defined(HAVE_WIN32)
547    //sendit(prompt);
548    if (win32_cgets(buf, size) == NULL) {
549       buf[0] = 0;
550       return 0;
551    } else {
552       return strlen(buf);
553    }
554 # else
555    char *passwd;
556
557    passwd = getpass(prompt);
558    bstrncpy(buf, passwd, size);
559    return strlen(buf);
560 # endif
561 #else
562    buf[0] = 0;
563    return 0;
564 #endif
565 }