camera_config['webcam_maxrate'] = camera_config.pop('stream_maxrate')
if 'stream_localhost' in camera_config:
camera_config['webcam_localhost'] = camera_config.pop('stream_localhost')
+ if 'stream_auth_method' in camera_config:
+ camera_config.pop('stream_auth_method')
+ if 'stream_authentication' in camera_config:
+ camera_config.pop('stream_authentication')
if 'event_gap' in camera_config:
camera_config['gap'] = camera_config.pop('event_gap')
def camera_ui_to_dict(ui):
+ main_config = get_main() # needed for surveillance password
+
data = {
# device
'@name': ui['name'],
'@webcam_resolution': max(1, int(ui['streaming_resolution'])),
'@webcam_server_resize': ui['streaming_server_resize'],
'stream_motion': ui['streaming_motion'],
-
+ 'stream_auth_method': 2 if main_config['@normal_password'] else 0,
+ 'stream_authentication': (main_config['@normal_username'] + ':' + main_config['@normal_password']) if main_config['@normal_password'] else '',
+
# still images
'output_pictures': False,
'emulate_motion': False,
data.setdefault('stream_maxrate', 5)
data.setdefault('stream_quality', 85)
data.setdefault('stream_motion', False)
+ data.setdefault('stream_auth_method', 0)
data.setdefault('@webcam_resolution', 100)
data.setdefault('@webcam_server_resize', False)
old_main_config = config.get_main()
old_admin_credentials = '%s:%s' % (old_main_config.get('@admin_username', ''), old_main_config.get('@admin_password', ''))
-
+ old_normal_credentials = '%s:%s' % (old_main_config.get('@normal_username', ''), old_main_config.get('@normal_password', ''))
+
main_config = config.main_ui_to_dict(ui_config)
main_config.setdefault('thread', old_main_config.get('thread', []))
admin_credentials = '%s:%s' % (main_config.get('@admin_username', ''), main_config.get('@admin_password', ''))
+ normal_credentials = '%s:%s' % (main_config.get('@normal_username', ''), main_config.get('@normal_password', ''))
additional_configs = config.get_additional_structure(camera=False)[1]
reboot_config_names = [('@_' + c['name']) for c in additional_configs.values() if c.get('reboot')]
config.set_main(main_config)
reload = False
+ restart = False
if admin_credentials != old_admin_credentials:
logging.debug('admin credentials changed, reload needed')
reload = True
+ if normal_credentials != old_normal_credentials:
+ logging.debug('surveillance credentials changed, all camera configs must be updated')
+
+ for camera_id in config.get_camera_ids():
+ local_config = config.get_camera(camera_id)
+ if not utils.local_camera(local_config):
+ continue
+
+ # this will update the stream authentication options
+ ui_config = config.camera_dict_to_ui(local_config)
+ local_config = config.camera_ui_to_dict(ui_config)
+ config.set_camera(camera_id, local_config)
+
+ restart = True
+
if reboot and settings.ENABLE_REBOOT:
logging.debug('system settings changed, reboot needed')
-
- reboot = True
+
+ else:
+ reboot = False
- return {'reload': reload, 'reboot': reboot}
+ return {'reload': reload, 'reboot': reboot, 'restart': restart}
reload = False # indicates that browser should reload the page
reboot = [False] # indicates that the server will reboot immediately
if so_far[0] >= len(ui_config): # finished
finish()
-
- for key, cfg in ui_config.items():
+
+ # make sure main config is handled first
+ items = ui_config.items()
+ items.sort(key=lambda (key, cfg): key != 'main')
+
+ for key, cfg in items:
if key == 'main':
result = set_main_config(cfg)
reload = result['reload'] or reload
reboot[0] = result['reboot'] or reboot[0]
+ restart[0] = result['restart'] or restart[0]
check_finished(None, reload)
else:
result = set_main_config(ui_config)
reload = result['reload']
reboot[0] = result['reboot']
+ restart[0] = result['restart']
@BaseHandler.auth(admin=True)
def set_preview(self, camera_id):
def post(self):
self.set_header('Content-Type', 'text/html')
- if not self.current_user:
- self.set_status(403)
self.finish()
last_jpg_moment = {} # dictionary of moments of the last received jpeg indexed by camera id
last_access = {} # dictionary of access moments indexed by camera id
- def __init__(self, camera_id, port):
+ def __init__(self, camera_id, port, username, password):
self._camera_id = camera_id
self._port = port
+ self._username = (username or '').encode('utf8')
+ self._password = (password or '').encode('utf8')
+ self._auth_digest_state = {}
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
iostream.IOStream.__init__(self, s)
logging.debug('mjpg client for camera %(camera_id)s connected on port %(port)s' % {
'port': self._port, 'camera_id': self._camera_id})
- self.write(b"GET / HTTP/1.0\r\n\r\n")
- self._seek_content_length()
+ self.write('GET / HTTP/1.0\r\n\r\n')
+ self._seek_http()
+
+ def _seek_http(self):
+ if self._check_error():
+ return
+
+ self.read_until_regex('HTTP/1.\d \d+ ', self._on_http)
+
+ def _on_http(self, data):
+ if data.endswith('401 '):
+ self._seek_www_authenticate()
+
+ else: # no authorization required, skip to content length
+ self._seek_content_length()
+
+ def _seek_www_authenticate(self):
+ if self._check_error():
+ return
+
+ self.read_until('WWW-Authenticate:', self._on_before_www_authenticate)
+
+ def _on_before_www_authenticate(self, data):
+ if self._check_error():
+ return
+ self.read_until('\r\n', self._on_www_authenticate)
+
+ def _on_www_authenticate(self, data):
+ if self._check_error():
+ return
+
+ m = re.match('Digest\s*realm="([a-zA-Z0-9\-\s]+)",\s*nonce="([a-zA-Z0-9]+)"', data.strip())
+ if not m:
+ logging.error('mjpgclient: unknown authentication header: "%s"' % data)
+ return self._seek_content_length()
+
+ realm, nonce = m.groups()
+ self._auth_digest_state['realm'] = realm
+ self._auth_digest_state['nonce'] = nonce
+
+ auth_header = utils.build_digest_header('GET', '/', self._username, self._password, self._auth_digest_state)
+ self.write('GET / HTTP/1.0\r\n\r\nAuthorization: %s\r\n\r\n' % auth_header)
+ self._seek_http()
+
def _seek_content_length(self):
if self._check_error():
return
return None
port = camera_config['stream_port']
- client = MjpgClient(camera_id, port)
+ username, password = None, None
+ if camera_config.get('stream_auth_method') == 2:
+ username, password = camera_config.get('stream_authentication', ':').split(':')
+
+ client = MjpgClient(camera_id, port, username, password)
client.connect()
MjpgClient.last_access[camera_id] = datetime.datetime.utcnow()
import hashlib
import logging
import os
+import time
import urllib
import urlparse
uri = urlparse.urlunsplit(parts)
return hashlib.sha1('%s:%s:%s:%s' % (method, uri, body or '', key)).hexdigest().lower()
+
+
+def build_digest_header(method, url, username, password, state):
+ realm = state['realm']
+ nonce = state['nonce']
+ last_nonce = state.get('last_nonce', '')
+ nonce_count = state.get('nonce_count', 0)
+ qop = state.get('qop')
+ algorithm = state.get('algorithm')
+ opaque = state.get('opaque')
+
+ if algorithm is None:
+ _algorithm = 'MD5'
+
+ else:
+ _algorithm = algorithm.upper()
+
+ if _algorithm == 'MD5' or _algorithm == 'MD5-SESS':
+ def md5_utf8(x):
+ if isinstance(x, str):
+ x = x.encode('utf-8')
+ return hashlib.md5(x).hexdigest()
+ hash_utf8 = md5_utf8
+
+ elif _algorithm == 'SHA':
+ def sha_utf8(x):
+ if isinstance(x, str):
+ x = x.encode('utf-8')
+ return hashlib.sha1(x).hexdigest()
+ hash_utf8 = sha_utf8
+
+ KD = lambda s, d: hash_utf8("%s:%s" % (s, d))
+
+ if hash_utf8 is None:
+ return None
+
+ entdig = None
+ p_parsed = urlparse.urlparse(url)
+ path = p_parsed.path
+ if p_parsed.query:
+ path += '?' + p_parsed.query
+
+ A1 = '%s:%s:%s' % (username, realm, password)
+ A2 = '%s:%s' % (method, path)
+
+ HA1 = hash_utf8(A1)
+ HA2 = hash_utf8(A2)
+
+ if nonce == last_nonce:
+ nonce_count += 1
+
+ else:
+ nonce_count = 1
+
+ ncvalue = '%08x' % nonce_count
+ s = str(nonce_count).encode('utf-8')
+ s += nonce.encode('utf-8')
+ s += time.ctime().encode('utf-8')
+ s += os.urandom(8)
+
+ cnonce = (hashlib.sha1(s).hexdigest()[:16])
+ if _algorithm == 'MD5-SESS':
+ HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce))
+
+ if qop is None:
+ respdig = KD(HA1, "%s:%s" % (nonce, HA2))
+
+ elif qop == 'auth' or 'auth' in qop.split(','):
+ noncebit = "%s:%s:%s:%s:%s" % (
+ nonce, ncvalue, cnonce, 'auth', HA2
+ )
+ respdig = KD(HA1, noncebit)
+
+ else:
+ return None
+
+ last_nonce = nonce
+
+ base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
+ 'response="%s"' % (username, realm, nonce, path, respdig)
+ if opaque:
+ base += ', opaque="%s"' % opaque
+ if algorithm:
+ base += ', algorithm="%s"' % algorithm
+ if entdig:
+ base += ', digest="%s"' % entdig
+ if qop:
+ base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce)
+
+ state['last_nonce'] = last_nonce
+ state['nonce_count'] = nonce_count
+
+ return 'Digest %s' % (base)