diff options
Diffstat (limited to 'bareos/bsock/lowlevel.py')
-rw-r--r-- | bareos/bsock/lowlevel.py | 129 |
1 files changed, 111 insertions, 18 deletions
diff --git a/bareos/bsock/lowlevel.py b/bareos/bsock/lowlevel.py index 644c3de..5e5fd38 100644 --- a/bareos/bsock/lowlevel.py +++ b/bareos/bsock/lowlevel.py @@ -14,8 +14,11 @@ from bareos.bsock.protocolmessages import ProtocolMessages import hmac import logging import random +import re +from select import select import socket import struct +import sys import time class LowLevel(object): @@ -34,12 +37,9 @@ class LowLevel(object): self.socket = None self.auth_credentials_valid = False self.connection_type = None + self.receive_buffer = b'' - - def connect(self, address="localhost", port=9101, dirname=None, type=ConnectionType.DIRECTOR): - ''' - connect to bareos-director - ''' + def connect(self, address, port, dirname, type): self.address = address self.port = port if dirname: @@ -62,17 +62,18 @@ class LowLevel(object): return True - def auth(self, password, name="*UserAgent*"): + def auth(self, name, password, auth_success_regex): ''' login to the bareos-director if the authenticate success return True else False dir: the director location - name: own name. Default is *UserAgent* + name: own name. ''' if not isinstance(password, Password): raise AuthenticationError("password must by of type bareos.Password() not %s" % (type(password))) self.password = password self.name = name + self.auth_success_regex = auth_success_regex return self.__auth() @@ -86,10 +87,15 @@ class LowLevel(object): raise AuthenticationError("failed (in response)") if not self._cram_md5_challenge(clientname=self.name, password=self.password.md5(), tls_local_need=0, compatible=True): raise AuthenticationError("failed (in challenge)") + self.recv_msg(self.auth_success_regex) self.auth_credentials_valid = True return True + def _init_connection(self): + pass + + def disconnect(self): ''' disconnect ''' # TODO @@ -100,13 +106,43 @@ class LowLevel(object): result = False if self.auth_credentials_valid: try: - if self.__connect() and self.__auth() and self._set_state_director_prompt(): + if self.__connect() and self.__auth() and self._init_connection(): result = True except socket.error: self.logger.warning("failed to reconnect") return result + def call(self, command): + ''' + call a bareos-director user agent command + ''' + if isinstance(command, list): + command = " ".join(command) + return self.__call(command, 0) + + + def __call(self, command, count): + ''' + Send a command and receive the result. + If connection is lost, try to reconnect. + ''' + result = b'' + try: + self.send(bytearray(command, 'utf-8')) + result = self.recv_msg() + except (SocketEmptyHeader, ConnectionLostError) as e: + self.logger.error("connection problem (%s): %s" % (type(e).__name__, str(e))) + if count == 0: + if self.reconnect(): + return self.__call(command, count+1) + return result + + + def send_command(self, commamd): + return self.call(command) + + def send(self, msg=None): '''use socket to send request to director''' self.__check_socket_connection() @@ -133,10 +169,9 @@ class LowLevel(object): return msg - def recv_msg(self): + def recv_msg(self, regex = b'^\d\d\d\d OK.*$', timeout = None): '''will receive data from director ''' self.__check_socket_connection() - msg = b"" try: timeouts = 0 while True: @@ -154,11 +189,22 @@ class LowLevel(object): # header is a signal self.__set_status(header) if self.is_end_of_message(header): - return msg + result = self.receive_buffer + self.receive_buffer = b'' + return result else: # header is the length of the next message length = header - msg += self.recv_submsg(length) + self.receive_buffer += self.recv_submsg(length) + # Bareos indicates end of command result by line starting with 4 digits + match = re.search(regex, self.receive_buffer, re.MULTILINE) + if match: + self.logger.debug("msg \"{0}\" matches regex \"{1}\"".format(self.receive_buffer.strip(), regex)) + result = self.receive_buffer[0:match.end()] + self.receive_buffer = self.receive_buffer[match.end()+1:] + return result + #elif re.search("^\d\d\d\d .*$", msg, re.MULTILINE): + #return msg except socket.error as e: self._handleSocketError(e) return msg @@ -166,7 +212,7 @@ class LowLevel(object): def recv_submsg(self, length): # get the message - msg = b"" + msg = b'' while length > 0: self.logger.debug(" submsg len: " + str(length)) # TODO @@ -179,9 +225,41 @@ class LowLevel(object): msg = bytearray(msg.decode('utf-8'), 'utf-8') if (type(msg) is bytes): msg = bytearray(msg) + #self.logger.debug(str(msg)) return msg + def interactive(self): + """ + Enter the interactive mode. + Exit via typing "exit" or "quit". + """ + command = "" + while command != "exit" and command != "quit" and self.is_connected(): + command = self._get_input() + resultmsg = self.call(command) + self._show_result(resultmsg) + return True + + + def _get_input(self): + # Python2: raw_input, Python3: input + try: + myinput = raw_input + except NameError: + myinput = input + data = myinput(">>") + return data + + + def _show_result(self, msg): + #print(msg.decode('utf-8')) + sys.stdout.write(msg.decode('utf-8')) + # add a linefeed, if there isn't one already + if msg[-2] != ord(b'\n'): + sys.stdout.write(b'\n') + + def __get_header(self): self.__check_socket_connection() header = self.socket.recv(4) @@ -201,11 +279,17 @@ class LowLevel(object): def is_end_of_message(self, data): - return (data == Constants.BNET_EOD or + return ((not self.is_connected()) or + data == Constants.BNET_EOD or + data == Constants.BNET_TERMINATE or data == Constants.BNET_MAIN_PROMPT or data == Constants.BNET_SUB_PROMPT) + def is_connected(self): + return (self.status != Constants.BNET_TERMINATE) + + def _cram_md5_challenge(self, clientname, password, tls_local_need=0, compatible=True): ''' client launch the challenge, @@ -246,6 +330,7 @@ class LowLevel(object): # check the response is equal to base64 return is_correct + def _cram_md5_respond(self, password, tls_remote_need=0, compatible=True): ''' client connect to dir, @@ -291,10 +376,18 @@ class LowLevel(object): self.logger.debug(str(status_text) + " (" + str(status) + ")") - def _set_state_director_prompt(self): - self.send(b".") - msg = self.recv_msg() - self.logger.debug("received message: " + str(msg)) + def has_data(self): + self.__check_socket_connection() + timeout = 0.1 + readable, writable, exceptional = select([self.socket], [], [], timeout) + return readable + + + def get_to_prompt(self): + time.sleep(0.1) + if self.has_data(): + msg = self.recv_msg() + self.logger.debug("received message: " + str(msg)) # TODO: check prompt return True |