Merge pull request #223 from albertosottile/master
Opportunistic TLS support
This commit is contained in:
commit
14af13c526
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,6 +5,7 @@ venv
|
|||||||
|
|
||||||
/SyncPlay.egg-info
|
/SyncPlay.egg-info
|
||||||
/build
|
/build
|
||||||
|
/cert
|
||||||
/dist
|
/dist
|
||||||
/syncplay v*
|
/syncplay v*
|
||||||
/syncplay_v*
|
/syncplay_v*
|
||||||
@ -13,4 +14,4 @@ dist.7z
|
|||||||
.*
|
.*
|
||||||
!.travis.yml
|
!.travis.yml
|
||||||
!.appveyor.yml
|
!.appveyor.yml
|
||||||
__pycache__
|
__pycache__
|
||||||
|
|||||||
@ -23,7 +23,7 @@ before_install:
|
|||||||
- python3 -c "from py2app.recipes import pyside2"
|
- python3 -c "from py2app.recipes import pyside2"
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip3 install twisted appnope requests
|
- pip3 install twisted[tls] appnope requests certifi
|
||||||
|
|
||||||
before_deploy:
|
before_deploy:
|
||||||
- pip3 install dmgbuild
|
- pip3 install dmgbuild
|
||||||
|
|||||||
@ -12,11 +12,12 @@ import syncplay
|
|||||||
APP = ['syncplayClient.py']
|
APP = ['syncplayClient.py']
|
||||||
DATA_FILES = [
|
DATA_FILES = [
|
||||||
('resources', glob('resources/*.png') + glob('resources/*.rtf') + glob('resources/*.lua')),
|
('resources', glob('resources/*.png') + glob('resources/*.rtf') + glob('resources/*.lua')),
|
||||||
|
('resources/lua/intf', glob('resources/lua/intf/*.lua'))
|
||||||
]
|
]
|
||||||
OPTIONS = {
|
OPTIONS = {
|
||||||
'iconfile': 'resources/icon.icns',
|
'iconfile': 'resources/icon.icns',
|
||||||
'extra_scripts': 'syncplayServer.py',
|
'extra_scripts': 'syncplayServer.py',
|
||||||
'includes': {'PySide2.QtCore', 'PySide2.QtUiTools', 'PySide2.QtGui', 'PySide2.QtWidgets', 'certifi'},
|
'includes': {'PySide2.QtCore', 'PySide2.QtUiTools', 'PySide2.QtGui', 'PySide2.QtWidgets', 'certifi', 'cffi'},
|
||||||
'excludes': {'PySide', 'PySide.QtCore', 'PySide.QtUiTools', 'PySide.QtGui'},
|
'excludes': {'PySide', 'PySide.QtCore', 'PySide.QtUiTools', 'PySide.QtGui'},
|
||||||
'qt_plugins': [
|
'qt_plugins': [
|
||||||
'platforms/libqcocoa.dylib',
|
'platforms/libqcocoa.dylib',
|
||||||
|
|||||||
@ -658,6 +658,7 @@ guiIcons = [
|
|||||||
'resources/mpv.png', 'resources/vlc.png', 'resources/house.png', 'resources/film_link.png',
|
'resources/mpv.png', 'resources/vlc.png', 'resources/house.png', 'resources/film_link.png',
|
||||||
'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/chevrons_right.png',
|
'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/chevrons_right.png',
|
||||||
'resources/user_key.png', 'resources/lock.png', 'resources/key_go.png', 'resources/page_white_key.png',
|
'resources/user_key.png', 'resources/lock.png', 'resources/key_go.png', 'resources/page_white_key.png',
|
||||||
|
'resources/lock_green.png', 'resources/lock_green_dialog.png',
|
||||||
'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png',
|
'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png',
|
||||||
'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', 'resources/arrow_switch.png',
|
'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', 'resources/arrow_switch.png',
|
||||||
'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', 'resources/bullet_right_grey.png',
|
'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', 'resources/bullet_right_grey.png',
|
||||||
|
|||||||
BIN
resources/lock_green.png
Normal file
BIN
resources/lock_green.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 791 B |
BIN
resources/lock_green_dialog.png
Normal file
BIN
resources/lock_green_dialog.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 862 B |
@ -1,7 +1,8 @@
|
|||||||
{\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf830
|
{\rtf1\ansi\ansicpg1252\cocoartf1561\cocoasubrtf600
|
||||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||||
{\colortbl;\red255\green255\blue255;}
|
{\colortbl;\red255\green255\blue255;}
|
||||||
{\*\expandedcolortbl;;}
|
{\*\expandedcolortbl;;}
|
||||||
|
\vieww13920\viewh8980\viewkind0
|
||||||
\deftab529
|
\deftab529
|
||||||
\pard\tx529\pardeftab529\pardirnatural\partightenfactor0
|
\pard\tx529\pardeftab529\pardirnatural\partightenfactor0
|
||||||
|
|
||||||
@ -24,25 +25,6 @@ The above copyright notice and this permission notice shall be included in all\
|
|||||||
copies or substantial portions of the Software.\
|
copies or substantial portions of the Software.\
|
||||||
\
|
\
|
||||||
|
|
||||||
\b PySide\
|
|
||||||
|
|
||||||
\b0 \
|
|
||||||
Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).\
|
|
||||||
Contact: PySide team <contact@pyside.org>\
|
|
||||||
\
|
|
||||||
This library is free software; you can redistribute it and/or\
|
|
||||||
modify it under the terms of the GNU Lesser General Public\
|
|
||||||
License as published by the Free Software Foundation; either\
|
|
||||||
version 2.1 of the License, or (at your option) any later version.\
|
|
||||||
This library is distributed in the hope that it will be useful,\
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of\
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\
|
|
||||||
Lesser General Public License for more details.\
|
|
||||||
\
|
|
||||||
You should have received a copy of the GNU Lesser General Public License\
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>\
|
|
||||||
\
|
|
||||||
|
|
||||||
\b Qt for Python\
|
\b Qt for Python\
|
||||||
|
|
||||||
\b0 \
|
\b0 \
|
||||||
@ -318,4 +300,143 @@ http://www.apache.org/licenses/LICENSE-2.0\
|
|||||||
Unless required by applicable law or agreed to in writing, software distributed under the \
|
Unless required by applicable law or agreed to in writing, software distributed under the \
|
||||||
License is distributed on an \'93AS IS\'94 BASIS, WITHOUT WARRANTIES OR CONDI-\
|
License is distributed on an \'93AS IS\'94 BASIS, WITHOUT WARRANTIES OR CONDI-\
|
||||||
TIONS OF ANY KIND, either express or implied. See the License for the specific lang-\
|
TIONS OF ANY KIND, either express or implied. See the License for the specific lang-\
|
||||||
uage governing permissions and limitations under the License.}
|
uage governing permissions and limitations under the License.\
|
||||||
|
\
|
||||||
|
|
||||||
|
\b python-certifi
|
||||||
|
\b0 \
|
||||||
|
\
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public License,\
|
||||||
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain\
|
||||||
|
one at http://mozilla.org/MPL/2.0/.\
|
||||||
|
\
|
||||||
|
|
||||||
|
\b cffi
|
||||||
|
\b0 \
|
||||||
|
\
|
||||||
|
\pard\pardeftab720\partightenfactor0
|
||||||
|
\cf0 This package has been mostly done by Armin Rigo with help from\
|
||||||
|
Maciej Fija\uc0\u322 kowski. The idea is heavily based (although not directly\
|
||||||
|
copied) from LuaJIT ffi by Mike Pall.\
|
||||||
|
\
|
||||||
|
Other contributors:\
|
||||||
|
\
|
||||||
|
Google Inc.\
|
||||||
|
\pard\tx529\pardeftab529\pardirnatural\partightenfactor0
|
||||||
|
\cf0 \
|
||||||
|
The MIT License\
|
||||||
|
\
|
||||||
|
Permission is hereby granted, free of charge, to any person \
|
||||||
|
obtaining a copy of this software and associated documentation \
|
||||||
|
files (the "Software"), to deal in the Software without \
|
||||||
|
restriction, including without limitation the rights to use, \
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or \
|
||||||
|
sell copies of the Software, and to permit persons to whom the \
|
||||||
|
Software is furnished to do so, subject to the following conditions:\
|
||||||
|
\
|
||||||
|
The above copyright notice and this permission notice shall be included \
|
||||||
|
in all copies or substantial portions of the Software.\
|
||||||
|
\
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS \
|
||||||
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL \
|
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING \
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER \
|
||||||
|
DEALINGS IN THE SOFTWARE.\
|
||||||
|
\
|
||||||
|
|
||||||
|
\b service-identity
|
||||||
|
\b0 \
|
||||||
|
\
|
||||||
|
Copyright (c) 2014 Hynek Schlawack\
|
||||||
|
\
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of\
|
||||||
|
this software and associated documentation files (the "Software"), to deal in\
|
||||||
|
the Software without restriction, including without limitation the rights to\
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do\
|
||||||
|
so, subject to the following conditions:\
|
||||||
|
\
|
||||||
|
The above copyright notice and this permission notice shall be included in all\
|
||||||
|
copies or substantial portions of the Software.\
|
||||||
|
\
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\
|
||||||
|
SOFTWARE.\
|
||||||
|
\
|
||||||
|
|
||||||
|
\b pyopenssl
|
||||||
|
\b0 \
|
||||||
|
\
|
||||||
|
Licensed under the Apache License, Version 2.0 (the \'93License\'94); you may not use this file\
|
||||||
|
except in compliance with the License. You may obtain a copy of the License at\
|
||||||
|
\
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0\
|
||||||
|
\
|
||||||
|
Unless required by applicable law or agreed to in writing, software distributed under the \
|
||||||
|
License is distributed on an \'93AS IS\'94 BASIS, WITHOUT WARRANTIES OR CONDI-\
|
||||||
|
TIONS OF ANY KIND, either express or implied. See the License for the specific lang-\
|
||||||
|
uage governing permissions and limitations under the License.\
|
||||||
|
\
|
||||||
|
|
||||||
|
\b cryptography
|
||||||
|
\b0 \
|
||||||
|
\
|
||||||
|
Authors listed here: https://github.com/pyca/cryptography/blob/master/AUTHORS.rst\
|
||||||
|
\
|
||||||
|
Licensed under the Apache License, Version 2.0 (the \'93License\'94); you may not use this file\
|
||||||
|
except in compliance with the License. You may obtain a copy of the License at\
|
||||||
|
\
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0\
|
||||||
|
\
|
||||||
|
Unless required by applicable law or agreed to in writing, software distributed under the \
|
||||||
|
License is distributed on an \'93AS IS\'94 BASIS, WITHOUT WARRANTIES OR CONDI-\
|
||||||
|
TIONS OF ANY KIND, either express or implied. See the License for the specific lang-\
|
||||||
|
uage governing permissions and limitations under the License.\
|
||||||
|
\
|
||||||
|
|
||||||
|
\b Icons\
|
||||||
|
\
|
||||||
|
|
||||||
|
\b0 Syncplay uses the following icons and images:\
|
||||||
|
\
|
||||||
|
- Silk icon set 1.3\
|
||||||
|
_________________________________________\
|
||||||
|
Mark James\
|
||||||
|
http://www.famfamfam.com/lab/icons/silk/\
|
||||||
|
_________________________________________\
|
||||||
|
\
|
||||||
|
This work is licensed under a\
|
||||||
|
Creative Commons Attribution 2.5 License.\
|
||||||
|
[ http://creativecommons.org/licenses/by/2.5/ ]\
|
||||||
|
\
|
||||||
|
This means you may use it for any purpose,\
|
||||||
|
and make any changes you like.\
|
||||||
|
All I ask is that you include a link back\
|
||||||
|
to this page in your credits.\
|
||||||
|
\
|
||||||
|
Are you using this icon set? Send me an email\
|
||||||
|
(including a link or picture if available) to\
|
||||||
|
mjames@gmail.com\
|
||||||
|
\
|
||||||
|
Any other questions about this icon set please\
|
||||||
|
contact mjames@gmail.com\
|
||||||
|
\
|
||||||
|
- Silk Companion 1\
|
||||||
|
\
|
||||||
|
\pard\pardeftab720\partightenfactor0
|
||||||
|
\cf0 Copyright Damien Guard - CC-BY 3.0\
|
||||||
|
https://damieng.com/creative/icons/silk-companion-1-icons\
|
||||||
|
\
|
||||||
|
- Padlock free icon\
|
||||||
|
CC-BY 3.0\
|
||||||
|
Icon made by Maxim Basinski from https://www.flaticon.com/free-icon/padlock_291248\
|
||||||
|
\
|
||||||
|
\pard\tx529\pardeftab529\pardirnatural\partightenfactor0
|
||||||
|
\cf0 \
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
version = '1.6.3'
|
version = '1.6.3'
|
||||||
revision = ' beta'
|
revision = ' beta'
|
||||||
milestone = 'Yoitsu'
|
milestone = 'Yoitsu'
|
||||||
release_number = '72'
|
release_number = '73'
|
||||||
projectURL = 'https://syncplay.pl/'
|
projectURL = 'https://syncplay.pl/'
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
import ast
|
import ast
|
||||||
import collections
|
import collections
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
@ -16,6 +17,24 @@ from twisted.internet.protocol import ClientFactory
|
|||||||
from twisted.internet import reactor, task, defer, threads
|
from twisted.internet import reactor, task, defer, threads
|
||||||
from twisted.application.internet import ClientService
|
from twisted.application.internet import ClientService
|
||||||
|
|
||||||
|
try:
|
||||||
|
import certifi
|
||||||
|
from twisted.internet.ssl import Certificate, optionsForClientTLS
|
||||||
|
certPath = certifi.where()
|
||||||
|
if os.path.exists(certPath):
|
||||||
|
os.environ['SSL_CERT_FILE'] = certPath
|
||||||
|
elif 'zip' in certPath:
|
||||||
|
import tempfile
|
||||||
|
import zipfile
|
||||||
|
zipPath, memberPath = certPath.split('.zip/')
|
||||||
|
zipPath += '.zip'
|
||||||
|
archive = zipfile.ZipFile(zipPath, 'r')
|
||||||
|
tmpDir = tempfile.gettempdir()
|
||||||
|
extractedPath = archive.extract(memberPath, tmpDir)
|
||||||
|
os.environ['SSL_CERT_FILE'] = extractedPath
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
from syncplay import utils, constants, version
|
from syncplay import utils, constants, version
|
||||||
from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \
|
from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \
|
||||||
PRIVACY_HIDDENFILENAME
|
PRIVACY_HIDDENFILENAME
|
||||||
@ -108,6 +127,8 @@ class SyncplayClient(object):
|
|||||||
self.fileSwitch = FileSwitchManager(self)
|
self.fileSwitch = FileSwitchManager(self)
|
||||||
self.playlist = SyncplayPlaylist(self)
|
self.playlist = SyncplayPlaylist(self)
|
||||||
|
|
||||||
|
self._serverSupportsTLS = True
|
||||||
|
|
||||||
if constants.LIST_RELATIVE_CONFIGS and 'loadedRelativePaths' in self._config and self._config['loadedRelativePaths']:
|
if constants.LIST_RELATIVE_CONFIGS and 'loadedRelativePaths' in self._config and self._config['loadedRelativePaths']:
|
||||||
paths = "; ".join(self._config['loadedRelativePaths'])
|
paths = "; ".join(self._config['loadedRelativePaths'])
|
||||||
self.ui.showMessage(getMessage("relative-config-notification").format(paths), noPlayer=True, noTimestamp=True)
|
self.ui.showMessage(getMessage("relative-config-notification").format(paths), noPlayer=True, noTimestamp=True)
|
||||||
@ -388,6 +409,7 @@ class SyncplayClient(object):
|
|||||||
self.ui.showMessage(getMessage("current-offset-notification").format(self._userOffset))
|
self.ui.showMessage(getMessage("current-offset-notification").format(self._userOffset))
|
||||||
|
|
||||||
def onDisconnect(self):
|
def onDisconnect(self):
|
||||||
|
self.ui.setSSLMode(False)
|
||||||
if self._config['pauseOnLeave']:
|
if self._config['pauseOnLeave']:
|
||||||
self.setPaused(True)
|
self.setPaused(True)
|
||||||
self.lastPausedOnLeaveTime = time.time()
|
self.lastPausedOnLeaveTime = time.time()
|
||||||
@ -704,9 +726,20 @@ class SyncplayClient(object):
|
|||||||
host = host.strip('[]')
|
host = host.strip('[]')
|
||||||
port = int(port)
|
port = int(port)
|
||||||
self._endpoint = HostnameEndpoint(reactor, host, port)
|
self._endpoint = HostnameEndpoint(reactor, host, port)
|
||||||
|
try:
|
||||||
|
caCertFP = open(os.environ['SSL_CERT_FILE'])
|
||||||
|
caCertTwisted = Certificate.loadPEM(caCertFP.read())
|
||||||
|
caCertFP.close()
|
||||||
|
self.protocolFactory.options = optionsForClientTLS(hostname=host)
|
||||||
|
self._clientSupportsTLS = True
|
||||||
|
except Exception as e:
|
||||||
|
self.ui.showDebugMessage(str(e))
|
||||||
|
self.protocolFactory.options = None
|
||||||
|
self._clientSupportsTLS = False
|
||||||
|
|
||||||
def retry(retries):
|
def retry(retries):
|
||||||
self._lastGlobalUpdate = None
|
self._lastGlobalUpdate = None
|
||||||
|
self.ui.setSSLMode(False)
|
||||||
if retries == 0:
|
if retries == 0:
|
||||||
self.onDisconnect()
|
self.onDisconnect()
|
||||||
if retries > constants.RECONNECT_RETRIES:
|
if retries > constants.RECONNECT_RETRIES:
|
||||||
@ -719,7 +752,7 @@ class SyncplayClient(object):
|
|||||||
self.reconnecting = True
|
self.reconnecting = True
|
||||||
return(0.1 * (2 ** min(retries, 5)))
|
return(0.1 * (2 ** min(retries, 5)))
|
||||||
|
|
||||||
self._reconnectingService = ClientService(self._endpoint, self.protocolFactory , retryPolicy=retry)
|
self._reconnectingService = ClientService(self._endpoint, self.protocolFactory, retryPolicy=retry)
|
||||||
waitForConnection = self._reconnectingService.whenConnected(failAfterFailures=1)
|
waitForConnection = self._reconnectingService.whenConnected(failAfterFailures=1)
|
||||||
self._reconnectingService.startService()
|
self._reconnectingService.startService()
|
||||||
|
|
||||||
@ -1456,6 +1489,9 @@ class UiManager(object):
|
|||||||
self.showOSDMessage(messageString, duration=constants.OSD_DURATION)
|
self.showOSDMessage(messageString, duration=constants.OSD_DURATION)
|
||||||
self.__ui.showMessage(messageString)
|
self.__ui.showMessage(messageString)
|
||||||
|
|
||||||
|
def setSSLMode(self, sslMode, sslInformation=""):
|
||||||
|
self.__ui.setSSLMode(sslMode, sslInformation)
|
||||||
|
|
||||||
def showMessage(self, message, noPlayer=False, noTimestamp=False, OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL):
|
def showMessage(self, message, noPlayer=False, noTimestamp=False, OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL):
|
||||||
if not noPlayer:
|
if not noPlayer:
|
||||||
self.showOSDMessage(message, duration=constants.OSD_DURATION, OSDType=OSDType, mood=mood)
|
self.showOSDMessage(message, duration=constants.OSD_DURATION, OSDType=OSDType, mood=mood)
|
||||||
|
|||||||
@ -310,6 +310,20 @@ de = {
|
|||||||
"userguide-menu-label": "&Benutzerhandbuch öffnen",
|
"userguide-menu-label": "&Benutzerhandbuch öffnen",
|
||||||
"update-menu-label": "auf &Aktualisierung prüfen",
|
"update-menu-label": "auf &Aktualisierung prüfen",
|
||||||
|
|
||||||
|
# startTLS messages - TODO: Translate
|
||||||
|
"startTLS-initiated": "Attempting secure connection",
|
||||||
|
"startTLS-secure-connection-ok": "Secure connection established ({})",
|
||||||
|
"startTLS-server-certificate-invalid": 'Secure connection failed. The server uses an invalid security certificate. This communication could be intercepted by a third party. For further details and troubleshooting see <a href="https://syncplay.pl/trouble">here</a>.',
|
||||||
|
"startTLS-not-supported-client": "This client does not support TLS",
|
||||||
|
"startTLS-not-supported-server": "This server does not support TLS",
|
||||||
|
|
||||||
|
# TLS certificate dialog - TODO: Translate
|
||||||
|
"tls-information-title": "Certificate Details",
|
||||||
|
"tls-dialog-status-label": "<strong>Syncplay is using an encrypted connection to {}.</strong>",
|
||||||
|
"tls-dialog-desc-label": "Encryption with a digital certificate keeps information private as it is sent to or from the<br/>server {}.",
|
||||||
|
"tls-dialog-connection-label": "Information encrypted using Transport Layer Security (TLS), version {} with the cipher<br/>suite: {}.",
|
||||||
|
"tls-dialog-certificate-label": "Certificate issued by {} valid until {}.",
|
||||||
|
|
||||||
# About dialog - TODO: Translate
|
# About dialog - TODO: Translate
|
||||||
"about-menu-label": "&About Syncplay",
|
"about-menu-label": "&About Syncplay",
|
||||||
"about-dialog-title": "About Syncplay",
|
"about-dialog-title": "About Syncplay",
|
||||||
@ -403,6 +417,8 @@ de = {
|
|||||||
"reset-tooltip": "Alle Einstellungen auf Standardwerte zurücksetzen.",
|
"reset-tooltip": "Alle Einstellungen auf Standardwerte zurücksetzen.",
|
||||||
"update-server-list-tooltip": "Mit syncplay.pl verbinden um die Liste öffentlicher Server zu aktualisieren.",
|
"update-server-list-tooltip": "Mit syncplay.pl verbinden um die Liste öffentlicher Server zu aktualisieren.",
|
||||||
|
|
||||||
|
"sslconnection-tooltip": "Securely connected to server. Click for certificate details.", # TODO: Translate
|
||||||
|
|
||||||
"joinroom-tooltip": "Den aktuellen Raum verlassen und stattdessen den angegebenen betreten.",
|
"joinroom-tooltip": "Den aktuellen Raum verlassen und stattdessen den angegebenen betreten.",
|
||||||
"seektime-msgbox-label": "Springe zur angegebenen Zeit (in Sekunden oder min:sek). Verwende +/- zum relativen Springen.",
|
"seektime-msgbox-label": "Springe zur angegebenen Zeit (in Sekunden oder min:sek). Verwende +/- zum relativen Springen.",
|
||||||
"ready-tooltip": "Zeigt an, ob du bereit zum anschauen bist",
|
"ready-tooltip": "Zeigt an, ob du bereit zum anschauen bist",
|
||||||
@ -438,6 +454,7 @@ de = {
|
|||||||
"server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # TODO: Translate
|
"server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # TODO: Translate
|
||||||
"server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})", # TODO: Translate
|
"server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})", # TODO: Translate
|
||||||
"server-stats-db-file-argument": "Enable server stats using the SQLite db file provided", # TODO: Translate
|
"server-stats-db-file-argument": "Enable server stats using the SQLite db file provided", # TODO: Translate
|
||||||
|
"server-startTLS-argument": "Enable TLS connections using the certificate files in the path provided", # TODO: Translate
|
||||||
"server-messed-up-motd-unescaped-placeholders": "Die Nachricht des Tages hat unmaskierte Platzhalter. Alle $-Zeichen sollten verdoppelt werden ($$).",
|
"server-messed-up-motd-unescaped-placeholders": "Die Nachricht des Tages hat unmaskierte Platzhalter. Alle $-Zeichen sollten verdoppelt werden ($$).",
|
||||||
"server-messed-up-motd-too-long": "Die Nachricht des Tages ist zu lang - Maximal {} Zeichen, aktuell {}.",
|
"server-messed-up-motd-too-long": "Die Nachricht des Tages ist zu lang - Maximal {} Zeichen, aktuell {}.",
|
||||||
|
|
||||||
|
|||||||
@ -267,7 +267,7 @@ en = {
|
|||||||
"run-label": "Run Syncplay",
|
"run-label": "Run Syncplay",
|
||||||
"storeandrun-label": "Store configuration and run Syncplay",
|
"storeandrun-label": "Store configuration and run Syncplay",
|
||||||
|
|
||||||
"contact-label": "Feel free to e-mail <a href=\"mailto:dev@syncplay.pl\"><nobr>dev@syncplay.pl</nobr></a>, chat via the <a href=\"https://webchat.freenode.net/?channels=#syncplay\"><nobr>#Syncplay IRC channel</nobr></a> on irc.freenode.net, <a href=\"https://github.com/Uriziel/syncplay/issues\"><nobr>raise an issue</nobr></a> via GitHub, <a href=\"https://www.facebook.com/SyncplaySoftware\"><nobr>like us on Facebook</nobr></a>, <a href=\"https://twitter.com/Syncplay/\"><nobr>follow us on Twitter</nobr></a>, or visit <a href=\"https://syncplay.pl/\"><nobr>https://syncplay.pl/</nobr></a>. NOTE: Chat messages are not encrypted so do not use Syncplay to send sensitive information.",
|
"contact-label": "Feel free to e-mail <a href=\"mailto:dev@syncplay.pl\"><nobr>dev@syncplay.pl</nobr></a>, chat via the <a href=\"https://webchat.freenode.net/?channels=#syncplay\"><nobr>#Syncplay IRC channel</nobr></a> on irc.freenode.net, <a href=\"https://github.com/Uriziel/syncplay/issues\"><nobr>raise an issue</nobr></a> via GitHub, <a href=\"https://www.facebook.com/SyncplaySoftware\"><nobr>like us on Facebook</nobr></a>, <a href=\"https://twitter.com/Syncplay/\"><nobr>follow us on Twitter</nobr></a>, or visit <a href=\"https://syncplay.pl/\"><nobr>https://syncplay.pl/</nobr></a>. Do not use Syncplay to send sensitive information.",
|
||||||
|
|
||||||
"joinroom-label": "Join room",
|
"joinroom-label": "Join room",
|
||||||
"joinroom-menu-label": "Join room {}",
|
"joinroom-menu-label": "Join room {}",
|
||||||
@ -312,6 +312,19 @@ en = {
|
|||||||
"userguide-menu-label": "Open user &guide",
|
"userguide-menu-label": "Open user &guide",
|
||||||
"update-menu-label": "Check for &update",
|
"update-menu-label": "Check for &update",
|
||||||
|
|
||||||
|
"startTLS-initiated": "Attempting secure connection",
|
||||||
|
"startTLS-secure-connection-ok": "Secure connection established ({})",
|
||||||
|
"startTLS-server-certificate-invalid": 'Secure connection failed. The server uses an invalid security certificate. This communication could be intercepted by a third party. For further details and troubleshooting see <a href="https://syncplay.pl/trouble">here</a>.',
|
||||||
|
"startTLS-not-supported-client": "This client does not support TLS",
|
||||||
|
"startTLS-not-supported-server": "This server does not support TLS",
|
||||||
|
|
||||||
|
# TLS certificate dialog
|
||||||
|
"tls-information-title": "Certificate Details",
|
||||||
|
"tls-dialog-status-label": "<strong>Syncplay is using an encrypted connection to {}.</strong>",
|
||||||
|
"tls-dialog-desc-label": "Encryption with a digital certificate keeps information private as it is sent to or from the<br/>server {}.",
|
||||||
|
"tls-dialog-connection-label": "Information encrypted using Transport Layer Security (TLS), version {} with the cipher<br/>suite: {}.",
|
||||||
|
"tls-dialog-certificate-label": "Certificate issued by {} valid until {}.",
|
||||||
|
|
||||||
# About dialog
|
# About dialog
|
||||||
"about-menu-label": "&About Syncplay",
|
"about-menu-label": "&About Syncplay",
|
||||||
"about-dialog-title": "About Syncplay",
|
"about-dialog-title": "About Syncplay",
|
||||||
@ -403,6 +416,8 @@ en = {
|
|||||||
"reset-tooltip": "Reset all settings to the default configuration.",
|
"reset-tooltip": "Reset all settings to the default configuration.",
|
||||||
"update-server-list-tooltip": "Connect to syncplay.pl to update list of public servers.",
|
"update-server-list-tooltip": "Connect to syncplay.pl to update list of public servers.",
|
||||||
|
|
||||||
|
"sslconnection-tooltip": "Securely connected to server. Click for certificate details.",
|
||||||
|
|
||||||
"joinroom-tooltip": "Leave current room and joins specified room.",
|
"joinroom-tooltip": "Leave current room and joins specified room.",
|
||||||
"seektime-msgbox-label": "Jump to specified time (in seconds / min:sec). Use +/- for relative seek.",
|
"seektime-msgbox-label": "Jump to specified time (in seconds / min:sec). Use +/- for relative seek.",
|
||||||
"ready-tooltip": "Indicates whether you are ready to watch.",
|
"ready-tooltip": "Indicates whether you are ready to watch.",
|
||||||
@ -439,6 +454,7 @@ en = {
|
|||||||
"server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # Default number of characters
|
"server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # Default number of characters
|
||||||
"server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})",
|
"server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})",
|
||||||
"server-stats-db-file-argument": "Enable server stats using the SQLite db file provided",
|
"server-stats-db-file-argument": "Enable server stats using the SQLite db file provided",
|
||||||
|
"server-startTLS-argument": "Enable TLS connections using the certificate files in the path provided",
|
||||||
"server-messed-up-motd-unescaped-placeholders": "Message of the Day has unescaped placeholders. All $ signs should be doubled ($$).",
|
"server-messed-up-motd-unescaped-placeholders": "Message of the Day has unescaped placeholders. All $ signs should be doubled ($$).",
|
||||||
"server-messed-up-motd-too-long": "Message of the Day is too long - maximum of {} chars, {} given.",
|
"server-messed-up-motd-too-long": "Message of the Day is too long - maximum of {} chars, {} given.",
|
||||||
|
|
||||||
|
|||||||
@ -267,7 +267,7 @@ it = {
|
|||||||
"run-label": "Avvia Syncplay",
|
"run-label": "Avvia Syncplay",
|
||||||
"storeandrun-label": "Salva la configurazione e avvia Syncplay",
|
"storeandrun-label": "Salva la configurazione e avvia Syncplay",
|
||||||
|
|
||||||
"contact-label": "Sentiti libero di inviare un'e-mail a <a href=\"mailto:dev@syncplay.pl\"><nobr>dev@syncplay.pl</nobr></a>, chattare tramite il <a href=\"https://webchat.freenode.net/?channels=#syncplay\"><nobr>canale IRC #Syncplay</nobr></a> su irc.freenode.net, <a href=\"https://github.com/Uriziel/syncplay/issues\"><nobr>segnalare un problema</nobr></a> su GitHub, <a href=\"https://www.facebook.com/SyncplaySoftware\"><nobr>lasciare un like sulla nostra pagina Facebook</nobr></a>, <a href=\"https://twitter.com/Syncplay/\"><nobr>seguirci su Twitter</nobr></a>, o visitare <a href=\"https://syncplay.pl/\"><nobr>https://syncplay.pl/</nobr></a>. NOTA: i messaggi di chat non sono cifrati, quindi non usare Syncplay per inviare dati sensibili.",
|
"contact-label": "Sentiti libero di inviare un'e-mail a <a href=\"mailto:dev@syncplay.pl\"><nobr>dev@syncplay.pl</nobr></a>, chattare tramite il <a href=\"https://webchat.freenode.net/?channels=#syncplay\"><nobr>canale IRC #Syncplay</nobr></a> su irc.freenode.net, <a href=\"https://github.com/Uriziel/syncplay/issues\"><nobr>segnalare un problema</nobr></a> su GitHub, <a href=\"https://www.facebook.com/SyncplaySoftware\"><nobr>lasciare un like sulla nostra pagina Facebook</nobr></a>, <a href=\"https://twitter.com/Syncplay/\"><nobr>seguirci su Twitter</nobr></a>, o visitare <a href=\"https://syncplay.pl/\"><nobr>https://syncplay.pl/</nobr></a>. Non usare Syncplay per inviare dati sensibili.", # TODO: Check translation
|
||||||
|
|
||||||
"joinroom-label": "Entra nella stanza",
|
"joinroom-label": "Entra nella stanza",
|
||||||
"joinroom-menu-label": "Entra nella stanza {}",
|
"joinroom-menu-label": "Entra nella stanza {}",
|
||||||
@ -312,6 +312,19 @@ it = {
|
|||||||
"userguide-menu-label": "Apri guida &utente",
|
"userguide-menu-label": "Apri guida &utente",
|
||||||
"update-menu-label": "Controlla la presenza di &aggiornamenti",
|
"update-menu-label": "Controlla la presenza di &aggiornamenti",
|
||||||
|
|
||||||
|
"startTLS-initiated": "Tentativo di connessione sicura in corso",
|
||||||
|
"startTLS-secure-connection-ok": "Connessione sicura stabilita ({})",
|
||||||
|
"startTLS-server-certificate-invalid": 'Connessione sicura non riuscita. Il certificato di sicurezza di questo server non è valido. La comunicazione potrebbe essere intercettata da una terza parte. Per ulteriori dettagli e informazioni sulla risoluzione del problema, clicca <a href="https://syncplay.pl/trouble">qui</a>.',
|
||||||
|
"startTLS-not-supported-client": "Questo client non supporta TLS",
|
||||||
|
"startTLS-not-supported-server": "Questo server non supporta TLS",
|
||||||
|
|
||||||
|
# TLS certificate dialog
|
||||||
|
"tls-information-title": "Informazioni sul certificato",
|
||||||
|
"tls-dialog-status-label": "<strong>Syncplay è connesso a {} tramite una connessione codificata.</strong>",
|
||||||
|
"tls-dialog-desc-label": "La codifica con un certificato digitale mantiene private le informazioni quando vengono<br/>inviate dal/al server {}.",
|
||||||
|
"tls-dialog-connection-label": "Informazioni codificate usando Transport Layer Security (TLS), versione {} usando gli<br/>algoritmi di cifratura: {}.",
|
||||||
|
"tls-dialog-certificate-label": "Certificato rilasciato da {} valido fino al {}.",
|
||||||
|
|
||||||
# About dialog
|
# About dialog
|
||||||
"about-menu-label": "&Informazioni su Syncplay",
|
"about-menu-label": "&Informazioni su Syncplay",
|
||||||
"about-dialog-title": "Informazioni su Syncplay",
|
"about-dialog-title": "Informazioni su Syncplay",
|
||||||
@ -403,6 +416,8 @@ it = {
|
|||||||
"reset-tooltip": "Ripristina le impostazioni iniziali di Syncplay.",
|
"reset-tooltip": "Ripristina le impostazioni iniziali di Syncplay.",
|
||||||
"update-server-list-tooltip": "Scarica la lista dei server pubblici da syncplay.pl.",
|
"update-server-list-tooltip": "Scarica la lista dei server pubblici da syncplay.pl.",
|
||||||
|
|
||||||
|
"sslconnection-tooltip": "Connessione sicura al server. Clicca per informazioni sul certificato.",
|
||||||
|
|
||||||
"joinroom-tooltip": "Lascia la stanza attuale e entra in quella specificata.",
|
"joinroom-tooltip": "Lascia la stanza attuale e entra in quella specificata.",
|
||||||
"seektime-msgbox-label": "Salta all'istante di tempo specificato (in secondi / min:sec). Usa +/- per una ricerca relativa.",
|
"seektime-msgbox-label": "Salta all'istante di tempo specificato (in secondi / min:sec). Usa +/- per una ricerca relativa.",
|
||||||
"ready-tooltip": "Indica quando sei pronto a guardare.",
|
"ready-tooltip": "Indica quando sei pronto a guardare.",
|
||||||
@ -439,6 +454,7 @@ it = {
|
|||||||
"server-chat-maxchars-argument": "Numero massimo di caratteri in un messaggio di chat (default è {})", # Default number of characters
|
"server-chat-maxchars-argument": "Numero massimo di caratteri in un messaggio di chat (default è {})", # Default number of characters
|
||||||
"server-maxusernamelength-argument": "Numero massimo di caratteri in un nome utente (default è {})",
|
"server-maxusernamelength-argument": "Numero massimo di caratteri in un nome utente (default è {})",
|
||||||
"server-stats-db-file-argument": "Abilita la raccolta dei dati statistici nel file SQLite indicato",
|
"server-stats-db-file-argument": "Abilita la raccolta dei dati statistici nel file SQLite indicato",
|
||||||
|
"server-startTLS-argument": "Abilita il protocollo TLS usando i certificati contenuti nel percorso indicato",
|
||||||
"server-messed-up-motd-unescaped-placeholders": "Il messaggio del giorno ha dei caratteri non 'escaped'. Tutti i simboli $ devono essere doppi ($$).",
|
"server-messed-up-motd-unescaped-placeholders": "Il messaggio del giorno ha dei caratteri non 'escaped'. Tutti i simboli $ devono essere doppi ($$).",
|
||||||
"server-messed-up-motd-too-long": "Il messaggio del giorno è troppo lungo - numero massimo di caratteri è {}, {} trovati.",
|
"server-messed-up-motd-too-long": "Il messaggio del giorno è troppo lungo - numero massimo di caratteri è {}, {} trovati.",
|
||||||
|
|
||||||
|
|||||||
@ -269,7 +269,7 @@ ru = {
|
|||||||
"run-label": "Запустить",
|
"run-label": "Запустить",
|
||||||
"storeandrun-label": "Сохранить и запустить",
|
"storeandrun-label": "Сохранить и запустить",
|
||||||
|
|
||||||
"contact-label": "Есть идея, нашли ошибку или хотите оставить отзыв? Пишите на <a href=\"mailto:dev@syncplay.pl\">dev@syncplay.pl</a>, в <a href=\"https://webchat.freenode.net/?channels=#syncplay\">IRC канал #Syncplay</a> на irc.freenode.net или <a href=\"https://github.com/Uriziel/syncplay/issues\">задавайте вопросы через GitHub</a>. Кроме того, заходите на <a href=\"https://syncplay.pl/\">www.syncplay.pl</a> за инорфмацией, помощью и обновлениями! NOTE: Chat messages are not encrypted so do not use Syncplay to send sensitive information.", # TODO: Translate last sentence
|
"contact-label": "Есть идея, нашли ошибку или хотите оставить отзыв? Пишите на <a href=\"mailto:dev@syncplay.pl\">dev@syncplay.pl</a>, в <a href=\"https://webchat.freenode.net/?channels=#syncplay\">IRC канал #Syncplay</a> на irc.freenode.net или <a href=\"https://github.com/Uriziel/syncplay/issues\">задавайте вопросы через GitHub</a>. Кроме того, заходите на <a href=\"https://syncplay.pl/\">www.syncplay.pl</a> за инорфмацией, помощью и обновлениями! Do not use Syncplay to send sensitive information.", # TODO: Translate last sentence
|
||||||
|
|
||||||
"joinroom-label": "Зайти в комнату",
|
"joinroom-label": "Зайти в комнату",
|
||||||
"joinroom-menu-label": "Зайти в комнату {}",
|
"joinroom-menu-label": "Зайти в комнату {}",
|
||||||
@ -313,6 +313,20 @@ ru = {
|
|||||||
"userguide-menu-label": "&Руководство пользователя",
|
"userguide-menu-label": "&Руководство пользователя",
|
||||||
"update-menu-label": "Проверить &обновления",
|
"update-menu-label": "Проверить &обновления",
|
||||||
|
|
||||||
|
# startTLS messages - TODO: Translate
|
||||||
|
"startTLS-initiated": "Attempting secure connection",
|
||||||
|
"startTLS-secure-connection-ok": "Secure connection established ({})",
|
||||||
|
"startTLS-server-certificate-invalid": 'Secure connection failed. The server uses an invalid security certificate. This communication could be intercepted by a third party. For further details and troubleshooting see <a href="https://syncplay.pl/trouble">here</a>.',
|
||||||
|
"startTLS-not-supported-client": "This client does not support TLS",
|
||||||
|
"startTLS-not-supported-server": "This server does not support TLS",
|
||||||
|
|
||||||
|
# TLS certificate dialog - TODO: Translate
|
||||||
|
"tls-information-title": "Certificate Details",
|
||||||
|
"tls-dialog-status-label": "<strong>Syncplay is using an encrypted connection to {}.</strong>",
|
||||||
|
"tls-dialog-desc-label": "Encryption with a digital certificate keeps information private as it is sent to or from the<br/>server {}.",
|
||||||
|
"tls-dialog-connection-label": "Information encrypted using Transport Layer Security (TLS), version {} with the cipher<br/>suite: {}.",
|
||||||
|
"tls-dialog-certificate-label": "Certificate issued by {} valid until {}.",
|
||||||
|
|
||||||
# About dialog - TODO: Translate
|
# About dialog - TODO: Translate
|
||||||
"about-menu-label": "&About Syncplay",
|
"about-menu-label": "&About Syncplay",
|
||||||
"about-dialog-title": "About Syncplay",
|
"about-dialog-title": "About Syncplay",
|
||||||
@ -404,6 +418,8 @@ ru = {
|
|||||||
"reset-tooltip": "Сбрасывает все настройки Syncplay в начальное состояние.",
|
"reset-tooltip": "Сбрасывает все настройки Syncplay в начальное состояние.",
|
||||||
"update-server-list-tooltip": "Обновить список публичных серверов от syncplay.pl.",
|
"update-server-list-tooltip": "Обновить список публичных серверов от syncplay.pl.",
|
||||||
|
|
||||||
|
"sslconnection-tooltip": "Securely connected to server. Click for certificate details.", # TODO: Translate
|
||||||
|
|
||||||
"joinroom-tooltip": "Покинуть комнату и зайти в другую, указанную комнату.",
|
"joinroom-tooltip": "Покинуть комнату и зайти в другую, указанную комнату.",
|
||||||
"seektime-msgbox-label": "Перемотать к определенному моменту времени (указывать в секундах или мин:сек). Используйте +/-, чтобы перемотать вперед/назад относительно настоящего момента.",
|
"seektime-msgbox-label": "Перемотать к определенному моменту времени (указывать в секундах или мин:сек). Используйте +/-, чтобы перемотать вперед/назад относительно настоящего момента.",
|
||||||
"ready-tooltip": "Показывает, готовы ли Вы к просмотру или нет.",
|
"ready-tooltip": "Показывает, готовы ли Вы к просмотру или нет.",
|
||||||
@ -439,6 +455,7 @@ ru = {
|
|||||||
"server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # TODO: Translate
|
"server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # TODO: Translate
|
||||||
"server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})", # TODO: Translate
|
"server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})", # TODO: Translate
|
||||||
"server-stats-db-file-argument": "Enable server stats using the SQLite db file provided", # TODO: Translate
|
"server-stats-db-file-argument": "Enable server stats using the SQLite db file provided", # TODO: Translate
|
||||||
|
"server-startTLS-argument": "Enable TLS connections using the certificate files in the path provided", # TODO: Translate
|
||||||
"server-messed-up-motd-unescaped-placeholders" : "MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).",
|
"server-messed-up-motd-unescaped-placeholders" : "MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).",
|
||||||
"server-messed-up-motd-too-long" : "MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).",
|
"server-messed-up-motd-too-long" : "MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).",
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import time
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from twisted.protocols.basic import LineReceiver
|
from twisted.protocols.basic import LineReceiver
|
||||||
|
from twisted.internet.interfaces import IHandshakeListener
|
||||||
|
from zope.interface.declarations import implementer
|
||||||
|
|
||||||
import syncplay
|
import syncplay
|
||||||
from syncplay.constants import PING_MOVING_AVERAGE_WEIGHT, CONTROLLED_ROOMS_MIN_VERSION, USER_READY_MIN_VERSION, SHARED_PLAYLIST_MIN_VERSION, CHAT_MIN_VERSION
|
from syncplay.constants import PING_MOVING_AVERAGE_WEIGHT, CONTROLLED_ROOMS_MIN_VERSION, USER_READY_MIN_VERSION, SHARED_PLAYLIST_MIN_VERSION, CHAT_MIN_VERSION
|
||||||
@ -27,6 +29,8 @@ class JSONCommandProtocol(LineReceiver):
|
|||||||
self.handleError(message[1])
|
self.handleError(message[1])
|
||||||
elif command == "Chat":
|
elif command == "Chat":
|
||||||
self.handleChat(message[1])
|
self.handleChat(message[1])
|
||||||
|
elif command == "TLS":
|
||||||
|
self.handleTLS(message[1])
|
||||||
else:
|
else:
|
||||||
self.dropWithError(getMessage("unknown-command-server-error").format(message[1])) # TODO: log, not drop
|
self.dropWithError(getMessage("unknown-command-server-error").format(message[1])) # TODO: log, not drop
|
||||||
|
|
||||||
@ -59,6 +63,7 @@ class JSONCommandProtocol(LineReceiver):
|
|||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
@implementer(IHandshakeListener)
|
||||||
class SyncClientProtocol(JSONCommandProtocol):
|
class SyncClientProtocol(JSONCommandProtocol):
|
||||||
def __init__(self, client):
|
def __init__(self, client):
|
||||||
self._client = client
|
self._client = client
|
||||||
@ -72,9 +77,27 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
|
|
||||||
def connectionMade(self):
|
def connectionMade(self):
|
||||||
self._client.initProtocol(self)
|
self._client.initProtocol(self)
|
||||||
self.sendHello()
|
if self._client._clientSupportsTLS:
|
||||||
|
if self._client._serverSupportsTLS:
|
||||||
|
self.sendTLS({"startTLS": "send"})
|
||||||
|
self._client.ui.showMessage(getMessage("startTLS-initiated"))
|
||||||
|
else:
|
||||||
|
self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-server"))
|
||||||
|
self.sendHello()
|
||||||
|
else:
|
||||||
|
self._client.ui.showMessage(getMessage("startTLS-not-supported-client"))
|
||||||
|
self.sendHello()
|
||||||
|
|
||||||
def connectionLost(self, reason):
|
def connectionLost(self, reason):
|
||||||
|
try:
|
||||||
|
if "Invalid DNS-ID" in str(reason.value):
|
||||||
|
self._client._serverSupportsTLS = False
|
||||||
|
elif "tlsv1 alert protocol version" in str(reason.value):
|
||||||
|
self._client._clientSupportsTLS = False
|
||||||
|
elif "certificate verify failed" in str(reason.value):
|
||||||
|
self.dropWithError(getMessage("startTLS-server-certificate-invalid"))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
self._client.destroyProtocol()
|
self._client.destroyProtocol()
|
||||||
|
|
||||||
def dropWithError(self, error):
|
def dropWithError(self, error):
|
||||||
@ -296,11 +319,49 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
})
|
})
|
||||||
|
|
||||||
def handleError(self, error):
|
def handleError(self, error):
|
||||||
self.dropWithError(error["message"])
|
if "startTLS" in error["message"] and not self.logged:
|
||||||
|
self._client._serverSupportsTLS = False
|
||||||
|
else:
|
||||||
|
self.dropWithError(error["message"])
|
||||||
|
|
||||||
def sendError(self, message):
|
def sendError(self, message):
|
||||||
self.sendMessage({"Error": {"message": message}})
|
self.sendMessage({"Error": {"message": message}})
|
||||||
|
|
||||||
|
def sendTLS(self, message):
|
||||||
|
self.sendMessage({"TLS": message})
|
||||||
|
|
||||||
|
def handleTLS(self, message):
|
||||||
|
answer = message["startTLS"] if "startTLS" in message else None
|
||||||
|
if "true" in answer and not self.logged and self._client.protocolFactory.options is not None:
|
||||||
|
self.transport.startTLS(self._client.protocolFactory.options)
|
||||||
|
elif "false" in answer:
|
||||||
|
self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-server"))
|
||||||
|
self.sendHello()
|
||||||
|
|
||||||
|
def handshakeCompleted(self):
|
||||||
|
from datetime import datetime
|
||||||
|
self._serverCertificateTLS = self.transport.getPeerCertificate()
|
||||||
|
self._subjectTLS = self._serverCertificateTLS.get_subject().CN
|
||||||
|
self._issuerTLS = self._serverCertificateTLS.get_issuer().CN
|
||||||
|
self._expiredTLS =self._serverCertificateTLS.has_expired()
|
||||||
|
self._expireDateTLS = datetime.strptime(self._serverCertificateTLS.get_notAfter().decode('ascii'), '%Y%m%d%H%M%SZ')
|
||||||
|
|
||||||
|
self._encryptedConnectionTLS = self.transport.protocol._tlsConnection
|
||||||
|
self._connVersionNumberTLS = self._encryptedConnectionTLS.get_protocol_version()
|
||||||
|
self._connVersionStringTLS = self._encryptedConnectionTLS.get_protocol_version_name()
|
||||||
|
self._cipherNameTLS = self._encryptedConnectionTLS.get_cipher_name()
|
||||||
|
|
||||||
|
if self._connVersionNumberTLS == 771:
|
||||||
|
self._connVersionNumberTLS = '1.2'
|
||||||
|
elif self._connVersionNumberTLS == 772:
|
||||||
|
self._connVersionNumberTLS = '1.3'
|
||||||
|
|
||||||
|
self._client.ui.showMessage(getMessage("startTLS-secure-connection-ok").format(self._connVersionStringTLS))
|
||||||
|
self._client.ui.setSSLMode( True,
|
||||||
|
{'subject': self._subjectTLS, 'issuer': self._issuerTLS, 'expires': self._expireDateTLS,
|
||||||
|
'protocolString': self._connVersionStringTLS, 'protocolVersion': self._connVersionNumberTLS,
|
||||||
|
'cipher': self._cipherNameTLS})
|
||||||
|
|
||||||
|
|
||||||
class SyncServerProtocol(JSONCommandProtocol):
|
class SyncServerProtocol(JSONCommandProtocol):
|
||||||
def __init__(self, factory):
|
def __init__(self, factory):
|
||||||
@ -602,6 +663,18 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
def sendError(self, message):
|
def sendError(self, message):
|
||||||
self.sendMessage({"Error": {"message": message}})
|
self.sendMessage({"Error": {"message": message}})
|
||||||
|
|
||||||
|
def sendTLS(self, message):
|
||||||
|
self.sendMessage({"TLS": message})
|
||||||
|
|
||||||
|
def handleTLS(self, message):
|
||||||
|
inquiry = message["startTLS"] if "startTLS" in message else None
|
||||||
|
if "send" in inquiry:
|
||||||
|
if not self.isLogged() and self._factory.options is not None:
|
||||||
|
self.sendTLS({"startTLS": "true"})
|
||||||
|
self.transport.startTLS(self._factory.options)
|
||||||
|
else:
|
||||||
|
self.sendTLS({"startTLS": "false"})
|
||||||
|
|
||||||
|
|
||||||
class PingService(object):
|
class PingService(object):
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,12 @@ from twisted.enterprise import adbapi
|
|||||||
from twisted.internet import task, reactor
|
from twisted.internet import task, reactor
|
||||||
from twisted.internet.protocol import Factory
|
from twisted.internet.protocol import Factory
|
||||||
|
|
||||||
|
try:
|
||||||
|
from OpenSSL import crypto
|
||||||
|
from twisted.internet import ssl
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
import syncplay
|
import syncplay
|
||||||
from syncplay import constants
|
from syncplay import constants
|
||||||
from syncplay.messages import getMessage
|
from syncplay.messages import getMessage
|
||||||
@ -20,7 +26,7 @@ from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomString
|
|||||||
class SyncFactory(Factory):
|
class SyncFactory(Factory):
|
||||||
def __init__(self, port='', password='', motdFilePath=None, isolateRooms=False, salt=None,
|
def __init__(self, port='', password='', motdFilePath=None, isolateRooms=False, salt=None,
|
||||||
disableReady=False, disableChat=False, maxChatMessageLength=constants.MAX_CHAT_MESSAGE_LENGTH,
|
disableReady=False, disableChat=False, maxChatMessageLength=constants.MAX_CHAT_MESSAGE_LENGTH,
|
||||||
maxUsernameLength=constants.MAX_USERNAME_LENGTH, statsDbFile=None):
|
maxUsernameLength=constants.MAX_USERNAME_LENGTH, statsDbFile=None, tlsCertPath=None):
|
||||||
self.isolateRooms = isolateRooms
|
self.isolateRooms = isolateRooms
|
||||||
print(getMessage("welcome-server-notification").format(syncplay.version))
|
print(getMessage("welcome-server-notification").format(syncplay.version))
|
||||||
self.port = port
|
self.port = port
|
||||||
@ -48,6 +54,9 @@ class SyncFactory(Factory):
|
|||||||
self._statsRecorder.startRecorder(statsDelay)
|
self._statsRecorder.startRecorder(statsDelay)
|
||||||
else:
|
else:
|
||||||
self._statsDbHandle = None
|
self._statsDbHandle = None
|
||||||
|
self.options = None
|
||||||
|
if tlsCertPath is not None:
|
||||||
|
self._allowTLSconnections(tlsCertPath)
|
||||||
|
|
||||||
def buildProtocol(self, addr):
|
def buildProtocol(self, addr):
|
||||||
return SyncServerProtocol(self)
|
return SyncServerProtocol(self)
|
||||||
@ -194,6 +203,30 @@ class SyncFactory(Factory):
|
|||||||
else:
|
else:
|
||||||
watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex())
|
watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex())
|
||||||
|
|
||||||
|
def _allowTLSconnections(self, path):
|
||||||
|
try:
|
||||||
|
privKey = open(path+'/privkey.pem', 'rt').read()
|
||||||
|
certif = open(path+'/cert.pem', 'rt').read()
|
||||||
|
chain = open(path+'/chain.pem', 'rt').read()
|
||||||
|
|
||||||
|
privKeyPySSL = crypto.load_privatekey(crypto.FILETYPE_PEM, privKey)
|
||||||
|
certifPySSL = crypto.load_certificate(crypto.FILETYPE_PEM, certif)
|
||||||
|
chainPySSL = [crypto.load_certificate(crypto.FILETYPE_PEM, chain)]
|
||||||
|
|
||||||
|
cipherListString = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:"\
|
||||||
|
"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:"\
|
||||||
|
"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384"
|
||||||
|
accCiphers = ssl.AcceptableCiphers.fromOpenSSLCipherString(cipherListString)
|
||||||
|
|
||||||
|
contextFactory = ssl.CertificateOptions(privateKey=privKeyPySSL, certificate=certifPySSL,
|
||||||
|
extraCertChain=chainPySSL, acceptableCiphers=accCiphers,
|
||||||
|
raiseMinimumTo=ssl.TLSVersion.TLSv1_2)
|
||||||
|
self.options = contextFactory
|
||||||
|
except Exception as e:
|
||||||
|
self.options = None
|
||||||
|
print(e)
|
||||||
|
print("TLS support is not enabled.")
|
||||||
|
|
||||||
|
|
||||||
class StatsRecorder(object):
|
class StatsRecorder(object):
|
||||||
def __init__(self, dbHandle, roomManager):
|
def __init__(self, dbHandle, roomManager):
|
||||||
@ -624,3 +657,4 @@ class ConfigurationGetter(object):
|
|||||||
self._argparser.add_argument('--max-chat-message-length', metavar='maxChatMessageLength', type=int, nargs='?', help=getMessage("server-chat-maxchars-argument").format(constants.MAX_CHAT_MESSAGE_LENGTH))
|
self._argparser.add_argument('--max-chat-message-length', metavar='maxChatMessageLength', type=int, nargs='?', help=getMessage("server-chat-maxchars-argument").format(constants.MAX_CHAT_MESSAGE_LENGTH))
|
||||||
self._argparser.add_argument('--max-username-length', metavar='maxUsernameLength', type=int, nargs='?', help=getMessage("server-maxusernamelength-argument").format(constants.MAX_USERNAME_LENGTH))
|
self._argparser.add_argument('--max-username-length', metavar='maxUsernameLength', type=int, nargs='?', help=getMessage("server-maxusernamelength-argument").format(constants.MAX_USERNAME_LENGTH))
|
||||||
self._argparser.add_argument('--stats-db-file', metavar='file', type=str, nargs='?', help=getMessage("server-stats-db-file-argument"))
|
self._argparser.add_argument('--stats-db-file', metavar='file', type=str, nargs='?', help=getMessage("server-stats-db-file-argument"))
|
||||||
|
self._argparser.add_argument('--tls', metavar='path', type=str, nargs='?', help=getMessage("server-startTLS-argument"))
|
||||||
|
|||||||
@ -108,6 +108,9 @@ class ConsoleUI(threading.Thread):
|
|||||||
def showErrorMessage(self, message, criticalerror=False):
|
def showErrorMessage(self, message, criticalerror=False):
|
||||||
print("ERROR:\t" + message)
|
print("ERROR:\t" + message)
|
||||||
|
|
||||||
|
def setSSLMode(self, sslMode, sslInformation):
|
||||||
|
pass
|
||||||
|
|
||||||
def _extractSign(self, m):
|
def _extractSign(self, m):
|
||||||
if m:
|
if m:
|
||||||
if m == "-":
|
if m == "-":
|
||||||
|
|||||||
@ -124,6 +124,7 @@ class AboutDialog(QtWidgets.QDialog):
|
|||||||
self.setWindowTitle(getMessage("about-dialog-title"))
|
self.setWindowTitle(getMessage("about-dialog-title"))
|
||||||
if isWindows():
|
if isWindows():
|
||||||
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
||||||
|
self.setWindowIcon(QtGui.QPixmap(resourcespath + 'syncplay.png'))
|
||||||
nameLabel = QtWidgets.QLabel("<center><strong>Syncplay</strong></center>")
|
nameLabel = QtWidgets.QLabel("<center><strong>Syncplay</strong></center>")
|
||||||
nameLabel.setFont(QtGui.QFont("Helvetica", 18))
|
nameLabel.setFont(QtGui.QFont("Helvetica", 18))
|
||||||
linkLabel = QtWidgets.QLabel("<center><a href=\"https://syncplay.pl\">syncplay.pl</a></center>")
|
linkLabel = QtWidgets.QLabel("<center><a href=\"https://syncplay.pl\">syncplay.pl</a></center>")
|
||||||
@ -171,11 +172,56 @@ class AboutDialog(QtWidgets.QDialog):
|
|||||||
QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + "third-party-notices.rtf"))
|
QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + "third-party-notices.rtf"))
|
||||||
|
|
||||||
|
|
||||||
|
class CertificateDialog(QtWidgets.QDialog):
|
||||||
|
def __init__(self, tlsData, parent=None):
|
||||||
|
super(CertificateDialog, self).__init__(parent)
|
||||||
|
if isMacOS():
|
||||||
|
self.setWindowTitle("")
|
||||||
|
self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | Qt.WindowCloseButtonHint | Qt.CustomizeWindowHint)
|
||||||
|
else:
|
||||||
|
self.setWindowTitle(getMessage("tls-information-title"))
|
||||||
|
if isWindows():
|
||||||
|
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
||||||
|
self.setWindowIcon(QtGui.QPixmap(resourcespath + 'syncplay.png'))
|
||||||
|
statusLabel = QtWidgets.QLabel(getMessage("tls-dialog-status-label").format(tlsData["subject"]))
|
||||||
|
descLabel = QtWidgets.QLabel(getMessage("tls-dialog-desc-label").format(tlsData["subject"]))
|
||||||
|
connDataLabel = QtWidgets.QLabel(getMessage("tls-dialog-connection-label").format(tlsData["protocolVersion"], tlsData["cipher"]))
|
||||||
|
certDataLabel = QtWidgets.QLabel(getMessage("tls-dialog-certificate-label").format(tlsData["issuer"], tlsData["expires"]))
|
||||||
|
if isMacOS():
|
||||||
|
statusLabel.setFont(QtGui.QFont("Helvetica", 12))
|
||||||
|
descLabel.setFont(QtGui.QFont("Helvetica", 12))
|
||||||
|
connDataLabel.setFont(QtGui.QFont("Helvetica", 12))
|
||||||
|
certDataLabel.setFont(QtGui.QFont("Helvetica", 12))
|
||||||
|
lockIconPixmap = QtGui.QPixmap(resourcespath + "lock_green_dialog.png")
|
||||||
|
lockIconLabel = QtWidgets.QLabel()
|
||||||
|
lockIconLabel.setPixmap(lockIconPixmap.scaled(64, 64, Qt.KeepAspectRatio))
|
||||||
|
certLayout = QtWidgets.QGridLayout()
|
||||||
|
certLayout.addWidget(lockIconLabel, 1, 0, 3, 1, Qt.AlignLeft | Qt.AlignTop)
|
||||||
|
certLayout.addWidget(statusLabel, 0, 1, 1, 3)
|
||||||
|
certLayout.addWidget(descLabel, 1, 1, 1, 3)
|
||||||
|
certLayout.addWidget(connDataLabel, 2, 1, 1, 3)
|
||||||
|
certLayout.addWidget(certDataLabel, 3, 1, 1, 3)
|
||||||
|
closeButton = QtWidgets.QPushButton("Close")
|
||||||
|
closeButton.setFixedWidth(100)
|
||||||
|
closeButton.setAutoDefault(False)
|
||||||
|
closeButton.clicked.connect(self.closeDialog)
|
||||||
|
certLayout.addWidget(closeButton, 4, 3, 1, 1)
|
||||||
|
certLayout.setVerticalSpacing(10)
|
||||||
|
certLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
|
||||||
|
self.setSizeGripEnabled(False)
|
||||||
|
self.setLayout(certLayout)
|
||||||
|
|
||||||
|
def closeDialog(self):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QtWidgets.QMainWindow):
|
class MainWindow(QtWidgets.QMainWindow):
|
||||||
insertPosition = None
|
insertPosition = None
|
||||||
playlistState = []
|
playlistState = []
|
||||||
updatingPlaylist = False
|
updatingPlaylist = False
|
||||||
playlistIndex = None
|
playlistIndex = None
|
||||||
|
sslInformation = "N/A"
|
||||||
|
sslMode = False
|
||||||
|
|
||||||
def setPlaylistInsertPosition(self, newPosition):
|
def setPlaylistInsertPosition(self, newPosition):
|
||||||
if not self.playlist.isEnabled():
|
if not self.playlist.isEnabled():
|
||||||
@ -431,6 +477,14 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.chatInput.setMaxLength(constants.MAX_CHAT_MESSAGE_LENGTH)
|
self.chatInput.setMaxLength(constants.MAX_CHAT_MESSAGE_LENGTH)
|
||||||
self.roomInput.setMaxLength(constants.MAX_ROOM_NAME_LENGTH)
|
self.roomInput.setMaxLength(constants.MAX_ROOM_NAME_LENGTH)
|
||||||
|
|
||||||
|
def setSSLMode(self, sslMode, sslInformation):
|
||||||
|
self.sslMode = sslMode
|
||||||
|
self.sslInformation = sslInformation
|
||||||
|
self.sslButton.setVisible(sslMode)
|
||||||
|
|
||||||
|
def getSSLInformation(self):
|
||||||
|
return self.sslInformation
|
||||||
|
|
||||||
def showMessage(self, message, noTimestamp=False):
|
def showMessage(self, message, noTimestamp=False):
|
||||||
message = str(message)
|
message = str(message)
|
||||||
username = None
|
username = None
|
||||||
@ -783,6 +837,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
if criticalerror:
|
if criticalerror:
|
||||||
QtWidgets.QMessageBox.critical(self, "Syncplay", message)
|
QtWidgets.QMessageBox.critical(self, "Syncplay", message)
|
||||||
message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">")
|
message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">")
|
||||||
|
message = message.replace("<a href="https://syncplay.pl/trouble">", '<a href="https://syncplay.pl/trouble">').replace("</a>", "</a>")
|
||||||
message = message.replace("\n", "<br />")
|
message = message.replace("\n", "<br />")
|
||||||
message = "<span style=\"{}\">".format(constants.STYLE_ERRORNOTIFICATION) + message + "</span>"
|
message = "<span style=\"{}\">".format(constants.STYLE_ERRORNOTIFICATION) + message + "</span>"
|
||||||
self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "<br />")
|
self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "<br />")
|
||||||
@ -1204,6 +1259,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
window.outputbox.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
|
window.outputbox.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
|
||||||
|
|
||||||
window.outputlabel = QtWidgets.QLabel(getMessage("notifications-heading-label"))
|
window.outputlabel = QtWidgets.QLabel(getMessage("notifications-heading-label"))
|
||||||
|
window.outputlabel.setMinimumHeight(27)
|
||||||
window.chatInput = QtWidgets.QLineEdit()
|
window.chatInput = QtWidgets.QLineEdit()
|
||||||
window.chatInput.setMaxLength(constants.MAX_CHAT_MESSAGE_LENGTH)
|
window.chatInput.setMaxLength(constants.MAX_CHAT_MESSAGE_LENGTH)
|
||||||
window.chatInput.returnPressed.connect(self.sendChatMessage)
|
window.chatInput.returnPressed.connect(self.sendChatMessage)
|
||||||
@ -1240,21 +1296,37 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.listTreeView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
self.listTreeView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||||
self.listTreeView.customContextMenuRequested.connect(self.openRoomMenu)
|
self.listTreeView.customContextMenuRequested.connect(self.openRoomMenu)
|
||||||
window.listlabel = QtWidgets.QLabel(getMessage("userlist-heading-label"))
|
window.listlabel = QtWidgets.QLabel(getMessage("userlist-heading-label"))
|
||||||
|
window.listlabel.setMinimumHeight(27)
|
||||||
|
if isMacOS:
|
||||||
|
window.sslButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'lock_green.png').scaled(14, 14),"")
|
||||||
|
window.sslButton.setVisible(False)
|
||||||
|
window.sslButton.setFixedHeight(21)
|
||||||
|
window.sslButton.setFixedWidth(21)
|
||||||
|
window.sslButton.setMinimumSize(21, 21)
|
||||||
|
window.sslButton.setStyleSheet("QPushButton:!hover{border: 1px solid gray;} QPushButton:hover{border:2px solid black;}")
|
||||||
|
else:
|
||||||
|
window.sslButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'lock_green.png'),"")
|
||||||
|
window.sslButton.setVisible(False)
|
||||||
|
window.sslButton.setFixedHeight(27)
|
||||||
|
window.sslButton.setFixedWidth(27)
|
||||||
|
window.sslButton.pressed.connect(self.openSSLDetails)
|
||||||
|
window.sslButton.setToolTip(getMessage("sslconnection-tooltip"))
|
||||||
window.listFrame = QtWidgets.QFrame()
|
window.listFrame = QtWidgets.QFrame()
|
||||||
window.listFrame.setLineWidth(0)
|
window.listFrame.setLineWidth(0)
|
||||||
window.listFrame.setMidLineWidth(0)
|
window.listFrame.setMidLineWidth(0)
|
||||||
window.listFrame.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
window.listFrame.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
||||||
window.listLayout.setContentsMargins(0, 0, 0, 0)
|
window.listLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
window.userlistLayout = QtWidgets.QVBoxLayout()
|
window.userlistLayout = QtWidgets.QGridLayout()
|
||||||
window.userlistFrame = QtWidgets.QFrame()
|
window.userlistFrame = QtWidgets.QFrame()
|
||||||
window.userlistFrame.setLineWidth(0)
|
window.userlistFrame.setLineWidth(0)
|
||||||
window.userlistFrame.setMidLineWidth(0)
|
window.userlistFrame.setMidLineWidth(0)
|
||||||
window.userlistFrame.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
window.userlistFrame.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
||||||
window.userlistLayout.setContentsMargins(0, 0, 0, 0)
|
window.userlistLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
window.userlistFrame.setLayout(window.userlistLayout)
|
window.userlistFrame.setLayout(window.userlistLayout)
|
||||||
window.userlistLayout.addWidget(window.listlabel)
|
window.userlistLayout.addWidget(window.listlabel, 0, 0, Qt.AlignLeft)
|
||||||
window.userlistLayout.addWidget(window.listTreeView)
|
window.userlistLayout.addWidget(window.sslButton, 0, 2, Qt.AlignRight)
|
||||||
|
window.userlistLayout.addWidget(window.listTreeView, 1, 0, 1, 3)
|
||||||
|
|
||||||
window.listSplit = QtWidgets.QSplitter(Qt.Vertical, self)
|
window.listSplit = QtWidgets.QSplitter(Qt.Vertical, self)
|
||||||
window.listSplit.addWidget(window.userlistFrame)
|
window.listSplit.addWidget(window.userlistFrame)
|
||||||
@ -1513,6 +1585,12 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
if not isMacOS():
|
if not isMacOS():
|
||||||
window.mainLayout.setMenuBar(window.menuBar)
|
window.mainLayout.setMenuBar(window.menuBar)
|
||||||
|
|
||||||
|
@needsClient
|
||||||
|
def openSSLDetails(self):
|
||||||
|
sslDetailsBox = CertificateDialog(self.getSSLInformation())
|
||||||
|
sslDetailsBox.exec_()
|
||||||
|
self.sslButton.setDown(False)
|
||||||
|
|
||||||
def openAbout(self):
|
def openAbout(self):
|
||||||
aboutMsgBox = AboutDialog()
|
aboutMsgBox = AboutDialog()
|
||||||
aboutMsgBox.exec_()
|
aboutMsgBox.exec_()
|
||||||
|
|||||||
@ -13,38 +13,56 @@ except AttributeError:
|
|||||||
import warnings
|
import warnings
|
||||||
warnings.warn("You must run Syncplay with Python 3.4 or newer!")
|
warnings.warn("You must run Syncplay with Python 3.4 or newer!")
|
||||||
|
|
||||||
from twisted.internet import reactor, tcp
|
from twisted.internet import reactor
|
||||||
|
from twisted.internet.endpoints import TCP4ServerEndpoint, TCP6ServerEndpoint
|
||||||
|
from twisted.internet.error import CannotListenError
|
||||||
|
|
||||||
from syncplay.server import SyncFactory, ConfigurationGetter
|
from syncplay.server import SyncFactory, ConfigurationGetter
|
||||||
|
|
||||||
class DualStackPort(tcp.Port):
|
class ServerStatus: pass
|
||||||
|
|
||||||
def __init__(self, port, factory, backlog=50, interface='', reactor=None):
|
def isListening6(f):
|
||||||
tcp.Port.__init__(self, port, factory, backlog, interface, reactor)
|
ServerStatus.listening6 = True
|
||||||
|
|
||||||
|
def isListening4(f):
|
||||||
|
ServerStatus.listening4 = True
|
||||||
|
|
||||||
|
def failed6(f):
|
||||||
|
ServerStatus.listening6 = False
|
||||||
|
print(f.value)
|
||||||
|
print("IPv6 listening failed.")
|
||||||
|
|
||||||
|
def failed4(f):
|
||||||
|
ServerStatus.listening4 = False
|
||||||
|
if f.type is CannotListenError and ServerStatus.listening6:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
print(f.value)
|
||||||
|
print("IPv4 listening failed.")
|
||||||
|
|
||||||
def createInternetSocket(self):
|
|
||||||
s = tcp.Port.createInternetSocket(self)
|
|
||||||
try:
|
|
||||||
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return s
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
argsGetter = ConfigurationGetter()
|
argsGetter = ConfigurationGetter()
|
||||||
args = argsGetter.getConfiguration()
|
args = argsGetter.getConfiguration()
|
||||||
dsp = DualStackPort(int(args.port),
|
factory = SyncFactory(
|
||||||
SyncFactory(
|
args.port,
|
||||||
args.port,
|
args.password,
|
||||||
args.password,
|
args.motd_file,
|
||||||
args.motd_file,
|
args.isolate_rooms,
|
||||||
args.isolate_rooms,
|
args.salt,
|
||||||
args.salt,
|
args.disable_ready,
|
||||||
args.disable_ready,
|
args.disable_chat,
|
||||||
args.disable_chat,
|
args.max_chat_message_length,
|
||||||
args.max_chat_message_length,
|
args.max_username_length,
|
||||||
args.max_username_length,
|
args.stats_db_file,
|
||||||
args.stats_db_file),
|
args.tls
|
||||||
interface='::')
|
)
|
||||||
dsp.startListening()
|
endpoint6 = TCP6ServerEndpoint(reactor, int(args.port))
|
||||||
reactor.run()
|
endpoint6.listen(factory).addCallbacks(isListening6, failed6)
|
||||||
|
endpoint4 = TCP4ServerEndpoint(reactor, int(args.port))
|
||||||
|
endpoint4.listen(factory).addCallbacks(isListening4, failed4)
|
||||||
|
if ServerStatus.listening6 or ServerStatus.listening4:
|
||||||
|
reactor.run()
|
||||||
|
else:
|
||||||
|
print("Unable to listen using either IPv4 and IPv6 protocols. Quitting the server now.")
|
||||||
|
sys.exit()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user