no_convert=['@admin_username', '@admin_password', '@normal_username', '@normal_password'])
_get_additional_config(main_config, camera=False)
- _set_default_motion(main_config, old_motion=_is_old_motion())
+ _set_default_motion(main_config, old_motion=is_old_motion())
_main_config_cache = main_config
global _main_config_cache
main_config = dict(main_config)
- _set_default_motion(main_config, old_motion=_is_old_motion())
+ _set_default_motion(main_config, old_motion=is_old_motion())
_main_config_cache = main_config
main_config = dict(main_config)
camera_config['@enabled'] = _CAMERA_CONFIG_FILE_NAME % {'id': camera_id} in threads
camera_config['@id'] = camera_id
- old_motion = _is_old_motion()
+ old_motion = is_old_motion()
# adapt directives from old configuration, if needed
if old_motion:
camera_config = dict(camera_config)
if utils.local_camera(camera_config):
- old_motion = _is_old_motion()
+ old_motion = is_old_motion()
# adapt directives to old configuration, if needed
if old_motion:
'@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 '',
+ 'stream_auth_method': {'disabled': 0, 'basic': 1, 'digest': 2}.get(ui['streaming_auth_mode'], 0),
+ 'stream_authentication': main_config['@normal_username'] + ':' + main_config['@normal_password'],
# still images
'output_pictures': False,
'streaming_resolution': int(data['@webcam_resolution']),
'streaming_server_resize': data['@webcam_server_resize'],
'streaming_port': int(data['stream_port']),
+ 'streaming_auth_mode': {0: 'disabled', 1: 'basic', 2: 'digest'}.get(data.get('stream_auth_method'), 'disabled'),
'streaming_motion': int(data['stream_motion']),
# still images
return None
+def is_old_motion():
+ try:
+ binary, version = motionctl.find_motion() # @UnusedVariable
+
+ if version.startswith('trunkREV'): # e.g. trunkREV599
+ version = int(version[8:])
+ return version < _LAST_OLD_CONFIG_VERSIONS[0]
+
+ else: # stable release, should be in the format x.y.z
+ return update.compare_versions(version, _LAST_OLD_CONFIG_VERSIONS[1]) <= 0
+
+ except:
+ return False
+
+
def _value_to_python(value):
value_lower = value.lower()
if value_lower == 'off':
return lines
-def _is_old_motion():
- try:
- binary, version = motionctl.find_motion() # @UnusedVariable
-
- if version.startswith('trunkREV'): # e.g. trunkREV599
- version = int(version[8:])
- return version < _LAST_OLD_CONFIG_VERSIONS[0]
-
- else: # stable release, should be in the format x.y.z
- return update.compare_versions(version, _LAST_OLD_CONFIG_VERSIONS[1]) <= 0
-
- except:
- return False
-
-
def _set_default_motion(data, old_motion):
data.setdefault('@enabled', True)
main_sections=main_sections,
camera_sections=camera_sections,
hostname=socket.gethostname(),
- admin_username=config.get_main().get('@admin_username'))
+ admin_username=config.get_main().get('@admin_username'),
+ old_motion=config.is_old_motion())
class ConfigHandler(BaseHandler):
logging.debug('mjpg client for camera %(camera_id)s connected on port %(port)s' % {
'port': self._port, 'camera_id': self._camera_id})
- self.write('GET / HTTP/1.0\r\n\r\n')
+ if self._username:
+ auth_header = utils.build_basic_header(self._username, self._password)
+ self.write('GET / HTTP/1.0\r\n\r\nAuthorization: %s\r\n\r\n' % auth_header)
+
+ else:
+ self.write('GET / HTTP/1.0\r\n\r\n')
+
self._seek_http()
-
+
def _seek_http(self):
if self._check_error():
return
if self._check_error():
return
+ m = re.match('Basic\s*realm="([a-zA-Z0-9\-\s]+)"', data.strip())
+ if m:
+ logging.debug('mjpgclient: using basic authentication')
+
+ auth_header = utils.build_basic_header(self._username, self._password)
+ self.write('GET / HTTP/1.0\r\n\r\nAuthorization: %s\r\n\r\n' % auth_header)
+ self._seek_http()
+
+ 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()
+ if m:
+ logging.debug('mjpgclient: using digest authentication')
- realm, nonce = m.groups()
- self._auth_digest_state['realm'] = realm
- self._auth_digest_state['nonce'] = nonce
+ 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()
+
+ return
- 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()
+ logging.error('mjpgclient: unknown authentication header: "%s"' % data)
+ self._seek_content_length()
def _seek_content_length(self):
if self._check_error():
port = camera_config['stream_port']
username, password = None, None
- if camera_config.get('stream_auth_method') == 2:
+ if camera_config.get('stream_auth_method') > 0:
username, password = camera_config.get('stream_authentication', ':').split(':')
client = MjpgClient(camera_id, port, username, password)
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import base64
import datetime
import hashlib
import logging
return hashlib.sha1('%s:%s:%s:%s' % (method, uri, body or '', key)).hexdigest().lower()
+def build_basic_header(username, password):
+ return 'Basic ' + base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
+
+
def build_digest_header(method, url, username, password, state):
realm = state['realm']
nonce = state['nonce']
'streaming_resolution': $('#streamingResolutionSlider').val(),
'streaming_server_resize': $('#streamingServerResizeSwitch')[0].checked,
'streaming_port': $('#streamingPortEntry').val(),
+ 'streaming_auth_mode': $('#streamingAuthModeSelect').val() || 'disabled', /* compatibility with old motion */
'streaming_motion': $('#streamingMotion')[0].checked,
/* still images */
$('#streamingResolutionSlider').val(dict['streaming_resolution']);
$('#streamingServerResizeSwitch')[0].checked = dict['streaming_server_resize'];
$('#streamingPortEntry').val(dict['streaming_port']);
+ $('#streamingAuthModeSelect').val(dict['streaming_auth_mode']);
$('#streamingMotion')[0].checked = dict['streaming_motion'];
var cameraUrl = location.protocol + '//' + location.host + '/picture/' + dict.id + '/';
<td class="settings-item-value"><input type="text" class="styled streaming camera-config" id="streamingPortEntry"></td>
<td><span class="help-mark" title="sets the TCP port on which the webcam streaming server listens">?</span></td>
</tr>
+ {% if not old_motion %}
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Authentication Mode</span></td>
+ <td class="settings-item-value">
+ <select class="styled streaming camera-config" id="streamingAuthModeSelect">
+ <option value="disabled">Disabled</option>
+ <option value="basic">Basic</option>
+ <option value="digest">Digest</option>
+ </select>
+ </td>
+ <td><span class="help-mark" title="the authentication mode to use when accessing the stream (use Basic instead of Digest if you encounter issues with third party apps)">?</span></td>
+ </tr>
+ {% endif %}
<tr class="settings-item advanced-setting">
<td class="settings-item-label"><span class="settings-item-label">Motion Optimization</span></td>
<td class="settings-item-value"><input type="checkbox" class="styled streaming camera-config" id="streamingMotion"></td>
<td><span class="help-mark" title="enable this if you want a lower frame rate for the live streaming when no motion is detected">?</span></td>
</tr>
+ <tr class="settings-item advanced-setting">
+ <td colspan="100"><div class="settings-item-separator"></div></td>
+ </tr>
<tr class="settings-item advanced-setting local-streaming">
<td class="settings-item-label"><span class="settings-item-label">Snapshot URL</span></td>
<td class="settings-item-value"><input type="text" class="styled streaming camera-config" id="streamingSnapshotUrlEntry" readonly="readonly"></td>