-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.py
executable file
·116 lines (99 loc) · 3.48 KB
/
server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#!/usr/bin/env python3
from configparser import ConfigParser
from pathlib import Path
import re
from socket import socket, AF_INET, SOCK_STREAM
from socketserver import StreamRequestHandler
import ssl
from subprocess import PIPE, Popen
from btrfs_incremental_send import (
BTRFS_RECEIVE_COMMAND,
CONTROL_PORT,
PATH_CONFIG_KEY_PATTERN,
bulk_copy,
serialize_json,
)
from ssl_socketserver import SSL_ThreadingTCPServer
def get_common_name(cert):
for field in cert['subject']:
if field[0][0] == 'commonName':
return field[0][1]
CONFIG_FILE_PATH = Path('/etc/btrfs-syncd/server.conf')
def parse_config():
config = ConfigParser()
config.read(str(CONFIG_FILE_PATH))
paths = {}
for key in config:
m = PATH_CONFIG_KEY_PATTERN.match(key)
if m:
name = m.group(1)
paths[name] = Path(config[key]['path'])
if 'key_dir' in config['keys']:
key_dir = CONFIG_FILE_PATH.parent / config['keys']['key_dir']
else:
key_dir = CONFIG_FILE_PATH.parent
key_paths = {}
for k in ['ca_cert', 'server_cert', 'server_key']:
key_paths[k] = key_dir / config['keys'][k]
return config, paths, key_paths
def get_handler_class(paths):
class BtrfsReceiveHandler(StreamRequestHandler):
def handle(self):
cn = get_common_name(self.server.client_cert)
if cn not in paths:
self.wfile.write(serialize_json({'success': False, 'reason': 'bad_hostname'}))
path = paths[cn]
print('Path:', path)
s = socket(AF_INET, SOCK_STREAM)
s.bind(('', 0))
s = ssl.wrap_socket(
s,
server_side=True,
certfile=self.server.cert_file,
keyfile=self.server.key_file,
ca_certs=self.server.ca_cert_file,
ssl_version=self.server.ssl_version,
cert_reqs=ssl.CERT_REQUIRED,
)
new_addr, new_port = s.getsockname()
print('bound new socket to {}:{}'.format(new_addr, new_port))
intermediate_data = {
'success': True,
'new_port': new_port,
}
self.wfile.write(serialize_json(intermediate_data))
s.listen()
conn, remote_addr = s.accept()
print('accepted connection from {}:{}'.format(*remote_addr))
command = [
piece.format(path=path)
for piece in BTRFS_RECEIVE_COMMAND
]
proc = Popen(
command,
stdin=PIPE,
cwd=str(path),
)
bulk_copy(conn, proc.stdin)
proc.stdin.close()
return_code = proc.wait()
print('command returned {}'.format(return_code))
conn.close()
data = {
'return_code': return_code,
'success': not return_code,
}
self.wfile.write(serialize_json(data))
return BtrfsReceiveHandler
if __name__ == '__main__':
config, paths, key_paths = parse_config()
print('Initializing btrfs sync server. Hostname -> path mapping:')
for hostname in sorted(paths):
print('{} -> {}'.format(hostname, paths[hostname]))
SSL_ThreadingTCPServer(
('0.0.0.0', CONTROL_PORT),
get_handler_class(paths),
str(key_paths['server_cert']),
str(key_paths['server_key']),
str(key_paths['ca_cert']),
).serve_forever()