simpleauth.py 3.53 KB
Newer Older
1
from ..util.sockio import read_line
2
import socket
3
import logging
4
from sbws.globals import WIRE_VERSION
5
6

MAGIC_BYTES = b'SBWS'
Matt Traudt's avatar
Matt Traudt committed
7
SUCCESS_BYTES = b'.'
8
9
PW_LEN = 64

10
log = logging.getLogger(__name__)
11

12

Matt Traudt's avatar
Matt Traudt committed
13
14
15
def authenticate_scanner(sock, conf_section):
    ''' Use this on the server side to read bytes from the scanner and properly
    authenticate them. Return the name of the scanner who has authenticated if
16
    they provided a good password, otherwise None.
Matt Traudt's avatar
Matt Traudt committed
17
18

    :param socket.socket sock: The open and blocking socket to use to
Matt Traudt's avatar
Matt Traudt committed
19
        communicate with the scanner
Matt Traudt's avatar
Matt Traudt committed
20
21
    :param configparser.SectionProxy conf_section: The ``[server.passwords]``
        section from the sbws config file
Matt Traudt's avatar
Matt Traudt committed
22
    :returns: The name of the scanner that successfully authenticated as a str,
Matt Traudt's avatar
Matt Traudt committed
23
        as pulled from the ``[server.passwords]`` section of the config. If
Matt Traudt's avatar
Matt Traudt committed
24
        the scanner couldn't authenticate, returns None
25
26
    '''
    assert sock.fileno() > 0
27
    assert len(conf_section) > 0
28
29
30
    try:
        magic = sock.recv(len(MAGIC_BYTES))
    except socket.timeout as e:
31
        log.warning(e)
32
        return None
33
    if magic != MAGIC_BYTES:
34
        log.warning('Magic string doesn\'t match')
35
        return None
36

37
    line = read_line(sock, max_len=4)
38
    if line != str(WIRE_VERSION):
Matt Traudt's avatar
Matt Traudt committed
39
        log.warning('Scanner gave protocol version %s but we support %d', line,
40
                    WIRE_VERSION)
41
        return None
42

43
44
45
    try:
        pw = str(sock.recv(PW_LEN), 'utf-8')
    except UnicodeDecodeError:
46
        log.warning('Non-unicode password string received')
47
        return None
48
    except socket.timeout as e:
49
        log.warning(e)
50
        return None
51

Matt Traudt's avatar
Matt Traudt committed
52
53
    scanner_name = _is_valid_password(pw, conf_section)
    if not scanner_name:
54
        log.warning('Invalid password')
55
        return None
56
57
58

    try:
        sock.send(SUCCESS_BYTES)
59
    except (socket.timeout, ConnectionResetError, BrokenPipeError) as e:
60
        log.warning(e)
61
        return None
Matt Traudt's avatar
Matt Traudt committed
62
    return scanner_name
63
64


65
def authenticate_to_server(sock, pw):
Matt Traudt's avatar
Matt Traudt committed
66
    '''
Matt Traudt's avatar
Matt Traudt committed
67
    Use this on the scanner side to send bytes to the server and properly
Matt Traudt's avatar
Matt Traudt committed
68
69
70
71
72
73
74
75
76
    authenticate to them.

    :param socket.socket sock: The open and blocking socket to use to
        communicate with the server
    :param str pw: 64 character password string to give to the server for
        identification
    :returns: True if we authenticated to the server successfully, False
        otherwise. On False, the caller should close the socket
    '''
77
    assert sock.fileno() > 0
78
79
    assert isinstance(pw, str)
    assert len(pw) == PW_LEN
80
81
    try:
        sock.send(MAGIC_BYTES)
82
        sock.send(bytes('{}\n'.format(WIRE_VERSION), 'utf-8'))
83
84
        sock.send(bytes(pw, 'utf-8'))
        msg = sock.recv(len(SUCCESS_BYTES))
85
    except (socket.timeout, ConnectionResetError, BrokenPipeError) as e:
86
        log.warning(e)
87
88
        return False
    if msg != SUCCESS_BYTES:
Matt Traudt's avatar
Matt Traudt committed
89
90
91
        log.warning('Didn\'t get success code from server. Most likely the '
                    'password we are giving it is wrong. It\'s also possible '
                    'the server doesn\'t support wire protocol version %d.',
92
                    WIRE_VERSION)
93
94
95
96
        return False
    return True


97
98
def _is_valid_password(pw, conf_section):
    ''' Returns the key in the [server.passwords] section of the config for the
Matt Traudt's avatar
Matt Traudt committed
99
    password the scanner provided (AKA: if the scanner provided a valid
100
101
102
103
104
105
106
    password).  Otherwise return None '''
    assert len(conf_section) > 0
    if len(pw) != PW_LEN:
        return None
    for key in conf_section.keys():
        if pw == conf_section[key]:
            return key
107
    return False