urbackup-server-python-web-.../urbackup_api/__init__.py
Yvan E. Watchman fa0b759a10 fix crlf
2019-11-09 12:59:58 +01:00

471 lines
12 KiB
Python

import json
import requests
from base64 import b64encode
import hashlib
import socket
import shutil
import os
import binascii
import logging
logger = logging.getLogger('urbackup-server-python-api-wrapper')
"""Backwards compatible imports"""
try:
import urllib
except ImportError:
import urllib.parse
class urbackup_server:
#If you have basic authentication via .htpasswd
server_basic_username = ''
server_basic_password = ''
action_incr_file = 1
action_full_file = 2
action_incr_image = 3
action_full_image = 4
action_resumed_incr_file = 5
action_resumed_full_file = 6
action_file_restore = 8
action_image_restore = 9
action_client_update = 10
action_check_db_integrity = 11
action_backup_db = 12
action_recalc_stats = 13
_session = ''
_logged_in = False
_lastlogid = 0
def __init__(self, url, username, password):
self._server_url = url
self._server_username = username
self._server_password = password
def _get_response(self, action, params, method='POST'):
headers = {
'Accept': 'application/json',
'Content-Type': 'application/json; charset=UTF-8'
}
if('server_basic_username' in globals() and len(self.server_basic_username)>0):
userAndPass = b64encode(
str.encode('%s:%s' % (self.server_basic_username, self.server_basic_password))
).decode('ascii')
headers['Authorization'] = 'Basic %s' % userAndPass
try:
action_url = '%s?%s' % (self._server_url, urllib.urlencode({'a': action}))
except AttributeError:
action_url = '%s?%s' % (self._server_url, urllib.parse.urlencode({'a': action}))
if(len(self._session)>0):
params['ses'] = self._session
if method == 'POST':
r = requests.post(
action_url,
data=params
)
elif method == 'GET':
r = requests.get(
action_url,
params=params
)
else:
raise Exception('Request with method \'%s\' has not been implemented yet.' % method)
return r
def _get_json(self, action, params = {}):
tries = 50
while tries>0:
response = self._get_response(action, params)
if(response.status_code == 200):
break
tries -= 1
if tries <= 0:
return None
else:
logger.error('API call failed. Retrying...')
return response.json()
def _download_file(self, action, outputfn, params):
req = self._get_response(action, params, 'GET');
if req.status_code != 200:
return False
with open(outputfn, 'wb') as stream:
for chunk in req.iter_content(chunk_size=128):
stream.write(chunk)
return True
def _md5(self, s):
return hashlib.md5(s.encode()).hexdigest()
def login(self):
if not self._logged_in:
logger.debug('Trying anonymous login...')
login = self._get_json('login', {});
if not login or 'success' not in login or not login['success'] :
logger.debug('Logging in...')
salt = self._get_json('salt', {'username': self._server_username})
if not salt or not ('ses' in salt):
logger.warning('Username does not exist')
return False
self._session = salt['ses'];
if 'salt' in salt:
password_md5_bin = hashlib.md5((salt['salt']+self._server_password).encode()).digest()
password_md5 = binascii.hexlify(password_md5_bin).decode()
if 'pbkdf2_rounds' in salt:
pbkdf2_rounds = int(salt['pbkdf2_rounds'])
if pbkdf2_rounds>0:
password_md5 = binascii.hexlify(hashlib.pbkdf2_hmac('sha256', password_md5_bin,
salt['salt'].encode(), pbkdf2_rounds)).decode()
password_md5 = self._md5(salt['rnd']+password_md5)
login = self._get_json('login', {
'username': self._server_username,
'password': password_md5 })
if not login or 'success' not in login or not login['success']:
logger.warning('Error during login. Password wrong?')
return False
else:
self._logged_in = True
return True
else:
return False
else:
self._logged_in = True
self._session = login['session'];
return True
else:
return True
def get_client_status(self, clientname):
if not self.login():
return None
status = self._get_json('status')
if not status:
return None
if not 'status' in status:
return None
for client in status['status']:
if client['name'] == clientname:
return client;
logger.warning('Could not find client status. No permission?')
return None
def download_installer(self, installer_fn, new_clientname, os='linux'):
"""Download installer for os, defaults to linux"""
if not os.lower() in ['linux', 'osx', 'mac', 'windows']:
raise Exception('OS not supported')
if not self.login():
return False
new_client = self._get_json('add_client', {'clientname': new_clientname})
if 'already_exists' in new_client:
status = self.get_client_status(new_clientname)
if status == None:
return False
return self._download_file('download_client', installer_fn, {'clientid': status['id'] })
if not 'new_authkey' in new_client:
return False
return self._download_file('download_client', installer_fn,
{
'clientid': new_client['new_clientid'],
'authkey': new_client['new_authkey'],
'os': os
})
def add_client(self, clientname):
if not self.login():
return None
ret = self._get_json('add_client', { 'clientname': clientname})
if ret==None or 'already_exists' in ret:
return None
return ret
def get_global_settings(self):
if not self.login():
return None
settings = self._get_json('settings', {'sa': 'general'} )
if not settings or not 'settings' in settings:
return None
return settings['settings']
def set_global_setting(self, key, new_value):
if not self.login():
return False
settings = self._get_json('settings', {'sa': 'general'} )
if not settings or not 'settings' in settings:
return False
settings['settings'][key] = new_value
settings['settings']['sa'] = 'general_save'
ret = self._get_json('settings', settings['settings'])
return ret != None and 'saved_ok' in ret
def get_client_settings(self, clientname):
if not self.login():
return None
client = self.get_client_status(clientname)
if client == None:
return None
clientid = client['id'];
settings = self._get_json('settings', {'sa': 'clientsettings',
't_clientid': clientid})
if not settings or not 'settings' in settings:
return None
return settings['settings']
def change_client_setting(self, clientname, key, new_value):
if not self.login():
return False
client = self.get_client_status(clientname)
if client == None:
return False
clientid = client['id'];
settings = self._get_json('settings', {'sa': 'clientsettings',
't_clientid': clientid})
if not settings or not 'settings' in settings:
return False
settings['settings'][key] = new_value
settings['settings']['overwrite'] = 'true'
settings['settings']['sa'] = 'clientsettings_save'
settings['settings']['t_clientid'] = clientid
ret = self._get_json('settings', settings['settings'])
return ret != None and 'saved_ok' in ret
def get_client_authkey(self, clientname):
if not self.login():
return None
settings = self.get_client_settings(clientname)
if settings:
return settings['internet_authkey']
return None
def get_server_identity(self):
if not self.login():
return None
status = self._get_json('status')
if not status:
return None
if not 'server_identity' in status:
return None
return status['server_identity']
def get_status(self):
if not self.login():
return None
status = self._get_json('status')
if not status:
return None
if not 'status' in status:
return None
return status['status']
def get_livelog(self, clientid = 0):
if not self.login():
return None
log = self._get_json('livelog', {'clientid': clientid, 'lastid': self._lastlogid})
if not log:
return None
if not 'logdata' in log:
return None
self._lastlogid = log['logdata'][-1]['id']
return log['logdata']
def get_usage(self):
if not self.login():
return None
usage = self._get_json('usage')
if not usage:
return None
if not 'usage' in usage:
return None
return usage['usage']
def get_extra_clients(self):
if not self.login():
return None
status = self._get_json('status')
if not status:
return None
if not 'extra_clients' in status:
return None
return status['extra_clients']
def _start_backup(self, clientname, backup_type):
client_info = self.get_client_status(clientname)
if not client_info:
return False
ret = self._get_json('start_backup', {'start_client': client_info['id'],
'start_type': backup_type } );
if ( ret == None
or 'result' not in ret
or len(ret['result'])!=1
or 'start_ok' not in ret['result'][0]
or not ret['result'][0]['start_ok']):
return False
return True
def start_incr_file_backup(self, clientname):
return self._start_backup(clientname, 'incr_file');
def start_full_file_backup(self, clientname):
return self._start_backup(clientname, 'full_file');
def start_incr_image_backup(self, clientname):
return self._start_backup(clientname, 'incr_image');
def start_full_image_backup(self, clientname):
return self._start_backup(clientname, 'full_image');
def add_extra_client(self, addr):
if not self.login():
return None
ret = self._get_json('status', {'hostname': addr } )
if not ret:
return False
return True
def remove_extra_client(self, ecid):
if not self.login():
return None
ret = self._get_json('status', {'hostname': ecid,
'remove': 'true' })
if not ret:
return False
return True
def get_actions(self):
if not self.login():
return None
ret = self._get_json('progress')
if not ret or not 'progress' in ret:
return None
return ret['progress']
def stop_action(self, action):
if (not 'clientid' in action
or not 'id' in action):
return False
if not self.login():
return None
ret = self._get_json('progress',
{'stop_clientid': action['clientid'],
'stop_id': action['id']})
if not ret or not 'progress' in ret:
return False
return True