[772] | 1 | #!/usr/bin/env python
|
---|
[777] | 2 | # -*- coding: utf-8 -*-
|
---|
| 3 |
|
---|
[772] | 4 | # simple illustration client/server pair; client program sends a string
|
---|
| 5 | # to server, which echoes it back to the client (in multiple copies),
|
---|
| 6 | # and the latter prints to the screen
|
---|
| 7 | # this is the client
|
---|
| 8 | import socket
|
---|
| 9 | import sys
|
---|
| 10 | import struct
|
---|
| 11 | import re
|
---|
| 12 | import time
|
---|
| 13 | import md5
|
---|
| 14 |
|
---|
| 15 | PORT = 9101
|
---|
| 16 | BUFSIZE = 1048576
|
---|
| 17 |
|
---|
| 18 | # BNET signals
|
---|
| 19 | BNET_SIGNALS = {
|
---|
| 20 | 'BNET_EOD' : -1, # End of data stream, new data may follow
|
---|
| 21 | 'BNET_EOD_POLL' : -2, # End of data and poll all in one
|
---|
| 22 | 'BNET_STATUS' : -3, # Send full status
|
---|
| 23 | 'BNET_TERMINATE' : -4, # Conversation terminated, doing close()
|
---|
| 24 | 'BNET_POLL' : -5, # Poll request, I'm hanging on a read
|
---|
| 25 | 'BNET_HEARTBEAT' : -6, # Heartbeat Response requested
|
---|
| 26 | 'BNET_HB_RESPONSE' : -7, # Only response permited to HB
|
---|
| 27 | 'BNET_PROMPT' : -8, # Prompt for subcommand
|
---|
| 28 | 'BNET_BTIME' : -9, # Send UTC btime
|
---|
| 29 | 'BNET_BREAK' : -10, # Stop current command -- ctl-c
|
---|
| 30 | 'BNET_START_SELECT' : -11, # Start of a selection list
|
---|
| 31 | 'BNET_END_SELECT' : -12, # End of a select list
|
---|
| 32 | 'BNET_INVALID_CMD' : -13, # Invalid command sent
|
---|
| 33 | 'BNET_CMD_FAILED' : -14, # Command failed
|
---|
| 34 | 'BNET_CMD_OK' : -15, # Command succeeded
|
---|
| 35 | 'BNET_CMD_BEGIN' : -16, # Start command execution
|
---|
| 36 | 'BNET_MSGS_PENDING' : -17, # Messages pending
|
---|
| 37 | 'BNET_MAIN_PROMPT' : -18, # Server ready and waiting
|
---|
| 38 | 'BNET_SELECT_INPUT' : -19, # Return selection input
|
---|
| 39 | 'BNET_WARNING_MSG' : -20, # Warning message
|
---|
| 40 | 'BNET_ERROR_MSG' : -21, # Error message -- command failed
|
---|
| 41 | 'BNET_INFO_MSG' : -22, # Info message -- status line
|
---|
| 42 | 'BNET_RUN_CMD' : -23, # Run command follows
|
---|
| 43 | 'BNET_YESNO' : -24, # Request yes no response
|
---|
| 44 | 'BNET_START_RTREE' : -25, # Start restore tree mode
|
---|
| 45 | 'BNET_END_RTREE' : -26, # End restore tree mode
|
---|
| 46 | }
|
---|
| 47 |
|
---|
[773] | 48 | import clibbac
|
---|
[772] | 49 |
|
---|
| 50 | class ConsoleError(IOError):
|
---|
| 51 | pass
|
---|
| 52 |
|
---|
| 53 | class Console:
|
---|
| 54 | """Class to connect with the bacula director as a console client
|
---|
| 55 | """
|
---|
| 56 |
|
---|
| 57 | HELLO = r"Hello %s calling"
|
---|
| 58 | HELLO_OK = "1000 OK"
|
---|
| 59 | RXP_RESP = re.compile(
|
---|
| 60 | """^auth\scram-md5(?P<compatible>c?)\s
|
---|
| 61 | (?P<challenge>\S+)\s
|
---|
| 62 | ssl=(?P<tls>\d)""",
|
---|
| 63 | re.VERBOSE)
|
---|
| 64 |
|
---|
| 65 | def __init__(self, host="bacula", port=PORT):
|
---|
| 66 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
---|
| 67 | self.socket.connect((host, port))
|
---|
| 68 | self.last_state = 0
|
---|
| 69 |
|
---|
| 70 | def __del__(self):
|
---|
| 71 | """Destructor closes the network socket"""
|
---|
| 72 | self.socket.close()
|
---|
| 73 |
|
---|
| 74 | def _send(self, data):
|
---|
| 75 | """Prepend size of data before the data string
|
---|
| 76 | and send it trough the socket"""
|
---|
| 77 | size = struct.pack("!i", len(data))
|
---|
| 78 | self.socket.send(size + data)
|
---|
| 79 |
|
---|
| 80 | def _recv(self, bufsize=BUFSIZE):
|
---|
| 81 | """Recieve data from socket and strip the prepended
|
---|
| 82 | data size"""
|
---|
| 83 | r = self.socket.recv(4)
|
---|
| 84 | size = struct.unpack("!i", r)[0]
|
---|
| 85 | if size < 0:
|
---|
| 86 | self.last_state = size
|
---|
| 87 | r = ""
|
---|
| 88 | elif size + 4 > BUFSIZE:
|
---|
| 89 | raise ConsoleError("Buffer to small: %i" %size)
|
---|
| 90 | else:
|
---|
| 91 | r = self.socket.recv(size)
|
---|
| 92 | return r
|
---|
| 93 |
|
---|
| 94 | def _recv_all(self):
|
---|
| 95 | """Gets all lines of a request"""
|
---|
| 96 | r = ""
|
---|
| 97 | s = self._recv()
|
---|
| 98 | while s:
|
---|
| 99 | r += s
|
---|
| 100 | s = self._recv()
|
---|
| 101 | return r
|
---|
| 102 |
|
---|
| 103 | def login(self, username, password):
|
---|
| 104 | pw = md5.md5(password).hexdigest()
|
---|
| 105 |
|
---|
| 106 | self._send(self.HELLO % username + "\n")
|
---|
| 107 | response = self._recv()
|
---|
| 108 |
|
---|
| 109 | # Challenge from director
|
---|
| 110 | m = self.RXP_RESP.search(response)
|
---|
| 111 | challenge = clibbac.hmac_md5(m.group("challenge"), pw)
|
---|
| 112 | self._send(clibbac.bin_to_base64(challenge[:16], False))
|
---|
| 113 | resp = self._recv()
|
---|
| 114 | if not resp.startswith(self.HELLO_OK):
|
---|
| 115 | raise ConsoleError("Challenge: %s" % resp)
|
---|
| 116 |
|
---|
| 117 | # now from client site
|
---|
| 118 | now = time.time()
|
---|
| 119 | respond = clibbac.bin_to_base64(
|
---|
| 120 | clibbac.hmac_md5("<%10.10f@bconsole>" % now, pw)[:16],
|
---|
| 121 | False)
|
---|
| 122 | self._send("auth cram-md5 <%10.10f@bconsole> ssl=0\n" % now)
|
---|
| 123 | resp = self._recv().rstrip("\x000")
|
---|
| 124 | if resp != respond:
|
---|
| 125 | print len(resp)
|
---|
| 126 | print len(respond)
|
---|
| 127 | raise ConsoleError("Respond: %s expected: %s" % (resp, respond))
|
---|
| 128 | self._send("1000 OK auth\n")
|
---|
| 129 |
|
---|
| 130 | return self._recv()
|
---|
| 131 |
|
---|
| 132 | def version(self):
|
---|
| 133 | """Returns the bacula director version string"""
|
---|
| 134 | self._send("version")
|
---|
| 135 | return self._recv_all().split("\n")[0]
|
---|
| 136 |
|
---|
| 137 | def dotstatus(self):
|
---|
| 138 | self._send(".status dir current")
|
---|
| 139 | return self._recv_all()
|
---|
| 140 |
|
---|
| 141 | if __name__ == "__main__":
|
---|
| 142 | try:
|
---|
| 143 | host = sys.argv[1]
|
---|
| 144 | port = int(sys.argv[2])
|
---|
| 145 | except:
|
---|
| 146 | host = "bacula-devel"
|
---|
| 147 | port = 9101
|
---|
| 148 |
|
---|
| 149 | print sys.path
|
---|
| 150 |
|
---|
| 151 | console = Console(host, port)
|
---|
| 152 | console.login("python", "dass-it")
|
---|
| 153 |
|
---|
| 154 | print console.version()
|
---|
| 155 | print console.dotstatus()
|
---|
| 156 |
|
---|
| 157 |
|
---|
| 158 | #....Hello python calling
|
---|
| 159 | #...7auth cram-md5 <1938954300.1244114680@bacula-dir> ssl=0
|
---|
| 160 | #....XRx1dCJU36/UJD/l7TBZZC....
|
---|
| 161 | #1000 OK auth
|
---|
| 162 | #...4auth cram-md5 <238682207.1244114679@bconsole> ssl=0
|
---|
| 163 | #....klIi6CczoW89Bl/wCA04wA....
|
---|
| 164 | #1000 OK auth
|
---|
| 165 | #...31000 OK: bacula-dir Version: 3.0.1 (30 April 2009)
|
---|