diff options
49 files changed, 2492 insertions, 1327 deletions
@@ -1,7 +1,7 @@ [main] host = https://www.transifex.com -[bitmask.bitmask] +[bitmask.bitmask_client] file_filter = data/translations/<lang>.ts source_file = data/ts/en_US.ts diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c47e531a..3c863657 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,34 @@ History 2014 ==== +0.7.0 September 26 -- the "one time download, all time updates" release: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +- Select current provider on EIP preferences. Closes #5815. +- Handle logout correctly when we stop_services to launch the + wizard. Related to #5815. +- Properly remove /tmp/bitmask.lock. Closes #5866. +- Hide EIP Start button and display correct warning on missing helpers + files. Closes #5945. +- Save default provider if changed on the combo box. Closes #5995. +- Update the EIP status on provider change. Closes #5996. +- Update and get ready to start a provider on change. Closes #5997. +- Use python2 to run bitmask-root to work fine on systems with python3 + as default. Closes #6048. +- Use python2.7 in bitmask-root shebang since is the common name for + python 2 in Ubuntu, Debian, Arch. Related to #6048. +- Remove dict comprenension in util, for 2.6 compat. +- Login shall not wait for eip to finish if eip is not able to + start. Closes #5994 +- Properly send the token for querying the EIP certificate. Fixes + #6060. +- Code cleanup and logging improvements. +- Add email firewall blocking other users to access bitmask imap & + smtp. Closes #6040 +- Remove the Advanced Key Management since we don't support stable + mail yet. Closes #6087. +- Single combined preferences window. Closes #4704, #4119, #5885. + 0.6.1 August 15 -- the "knock knock knocking on beta's door" release: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -19,9 +19,19 @@ TRANSLAT_DIR = data/translations #Project file, used for translations PROJFILE = data/bitmask.pro -#UI files to compile -UI_FILES = loggerwindow.ui mainwindow.ui wizard.ui login.ui preferences.ui eip_status.ui mail_status.ui eippreferences.ui advanced_key_management.ui -#Qt resource files to compile +# UI files to compile +UI_FILES = \ + loggerwindow.ui \ + wizard.ui \ + mainwindow.ui login.ui eip_status.ui mail_status.ui \ + preferences.ui \ + preferences_account_page.ui \ + preferences_vpn_page.ui \ + preferences_email_page.ui \ + password_change.ui \ + advanced_key_management.ui + +# Qt resource files to compile RESOURCES = icons.qrc flags.qrc locale.qrc loggerwindow.qrc #pyuic4 and pyrcc4 binaries @@ -95,5 +105,19 @@ resource_graph: ./pkg/scripts/monitor_resource.zsh `pgrep bitmask` $(RESOURCE_TIME) display bitmask-resources.png +get_wheels: + pip install --upgrade setuptools + pip install --upgrade pip + pip install wheel + +gather_wheels: + pip wheel --wheel-dir=../wheelhouse pyzmq --build-option "--zmq=bundled" + # because fuck u1db externals, that's why... + pip wheel --wheel-dir=../wheelhouse --allow-external dirspec --allow-unverified dirspec --allow-external u1db --allow-unverified u1db -r pkg/requirements.pip + +install_wheel: + # if it's the first time, you'll need to get_wheels first + pip install --pre --use-wheel --no-index --find-links=../wheelhouse -r pkg/requirements.pip + clean : $(RM) $(COMPILED_UI) $(COMPILED_RESOURCES) $(COMPILED_UI:.py=.pyc) $(COMPILED_RESOURCES:.py=.pyc) diff --git a/data/images/countries/xx.png b/data/images/countries/xx.png Binary files differnew file mode 100644 index 00000000..abd36d39 --- /dev/null +++ b/data/images/countries/xx.png diff --git a/data/resources/flags.qrc b/data/resources/flags.qrc index 8bdc9c4c..aeecc54f 100644 --- a/data/resources/flags.qrc +++ b/data/resources/flags.qrc @@ -58,6 +58,7 @@ <file>../images/countries/us.png</file> <file>../images/countries/ve.png</file> <file>../images/countries/vn.png</file> + <file>../images/countries/xx.png</file> <file>../images/countries/za.png</file> </qresource> </RCC>
\ No newline at end of file diff --git a/data/resources/locale.qrc b/data/resources/locale.qrc index ba466c36..787b0025 100644 --- a/data/resources/locale.qrc +++ b/data/resources/locale.qrc @@ -2,5 +2,6 @@ <qresource> <file>../translations/vi.qm</file> <file>../translations/en_GB.qm</file> +<file>../translations/es.qm</file> </qresource> </RCC> diff --git a/data/ts/en_US.ts b/data/ts/en_US.ts index cf74d7b6..041cdc44 100644 --- a/data/ts/en_US.ts +++ b/data/ts/en_US.ts @@ -172,7 +172,7 @@ Export canceled.</source> <context> <name>ComplainDialog</name> <message> - <location filename="../src/leap/bitmask/platform_init/initializers.py" line="419"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="414"/> <source>Ok, thanks</source> <translation type="unfinished"></translation> </message> @@ -228,22 +228,22 @@ Export canceled.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="153"/> + <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="159"/> <source>Gateway settings for provider '{0}' saved.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="230"/> + <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="236"/> <source>There was a problem with configuration files.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="122"/> + <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="125"/> <source> (uninitialized)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="245"/> + <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="251"/> <source>This is an uninitialized provider, please log in first.</source> <translation type="unfinished"></translation> </message> @@ -261,12 +261,12 @@ Export canceled.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/eip_status.ui" line="66"/> + <location filename="../src/leap/bitmask/gui/ui/eip_status.ui" line="79"/> <source>...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/eip_status.ui" line="82"/> + <location filename="../src/leap/bitmask/gui/ui/eip_status.ui" line="95"/> <source>Traffic is being routed in the clear</source> <translation type="unfinished"></translation> </message> @@ -280,149 +280,187 @@ Export canceled.</source> <source>Turn Off</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/eip_status.ui" line="259"/> + <source>Cancel</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>EIPStatusWidget</name> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="411"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="429"/> <source>Turn ON</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="521"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="569"/> <source>Authenticating...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="529"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="578"/> <source>Retrieving configuration...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="531"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="580"/> <source>Waiting to start...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="533"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="582"/> <source>Assigning IP</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="535"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="584"/> <source>Reconnecting...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="543"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="592"/> <source>Unable to start VPN, it's already running.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="304"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="320"/> <source>disabled</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="565"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="614"/> <source>{0}: OFF</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="301"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="317"/> <source>You must login to use {0}</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="570"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="619"/> <source>{0}: Starting...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="573"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="622"/> <source>{0}: ON</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="525"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="576"/> <source>Encrypted Internet is starting</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="397"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="415"/> <source>Retry</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="433"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="478"/> <source>Traffic is being routed in the clear.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="434"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="479"/> <source>Network is unreachable.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="436"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="481"/> <source>Error connecting</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="451"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="497"/> <source>Error connecting.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="454"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="500"/> <source>Bitmask is blocking unencrypted traffic.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="587"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="644"/> <source>Routing traffic through: <b>{0}</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="634"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="690"/> <source>Could not load {0} configuration.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="643"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="699"/> <source>Another openvpn instance is already running, and could not be stopped.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="653"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="709"/> <source>Another openvpn instance is already running, and could not be stopped because it was not launched by Bitmask. Please stop it and try again.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="661"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="717"/> <source>We could not find openvpn binary.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="682"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="746"/> <source>We could not find any authentication agent in your system.<br/>Make sure you have<b>polkit-gnome-authentication-agent-1</b> running andtry again.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="690"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="754"/> <source>We could not find <b>pkexec</b> in your system.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="700"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="764"/> <source>{0} cannot be started because the tuntap extension is not installed properly in your system.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/eip_status.py" line="720"/> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="784"/> <source>Network is unreachable</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="315"/> + <source><font color=red>Disabled: missing helper files</font></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="731"/> + <source>VPN Launcher error. See the logs for more info.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/eip_status.py" line="734"/> + <source>Encrypted Internet failed to start</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>Form</name> + <message> + <location filename="../src/leap/bitmask/gui/ui/logout.ui" line="14"/> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/logout.ui" line="27"/> + <source>...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/logout.ui" line="50"/> + <source>Logout</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>LoggerWindow</name> @@ -462,7 +500,7 @@ Export canceled.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="162"/> + <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="164"/> <source>Save As</source> <translation type="unfinished"></translation> </message> @@ -477,37 +515,37 @@ Export canceled.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="193"/> + <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="195"/> <source>Send to Pastebin.com</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="190"/> + <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="192"/> <source>Sending to pastebin...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="225"/> + <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="207"/> <source>Your pastebin link <a href='{0}'>{0}</a></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="230"/> + <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="212"/> <source>Pastebin OK</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="245"/> + <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="227"/> <source>Sending logs to Pastebin failed!</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="251"/> + <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="233"/> <source>Pastebin Error</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="250"/> + <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="232"/> <source>Maximum posts per day reached</source> <translation type="unfinished"></translation> </message> @@ -520,72 +558,62 @@ Export canceled.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/login.ui" line="75"/> - <source><b>Provider:</b></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/ui/login.ui" line="94"/> + <location filename="../src/leap/bitmask/gui/ui/login.ui" line="90"/> <source>Remember username and password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/login.ui" line="119"/> + <location filename="../src/leap/bitmask/gui/ui/login.ui" line="112"/> <source><b>Username:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/login.ui" line="132"/> + <location filename="../src/leap/bitmask/gui/ui/login.ui" line="125"/> <source><b>Password:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/login.py" line="246"/> + <location filename="../src/leap/bitmask/gui/login.py" line="208"/> <source>Log In</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/login.py" line="124"/> - <source>Other...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/login.py" line="241"/> + <location filename="../src/leap/bitmask/gui/login.py" line="203"/> <source>Cancel</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/login.ui" line="230"/> + <location filename="../src/leap/bitmask/gui/ui/login.ui" line="214"/> <source>...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/login.py" line="371"/> + <location filename="../src/leap/bitmask/gui/login.py" line="314"/> <source>Logout</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/login.py" line="299"/> + <location filename="../src/leap/bitmask/gui/login.py" line="240"/> <source>Please select a valid provider</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/login.py" line="304"/> + <location filename="../src/leap/bitmask/gui/login.py" line="245"/> <source>Please provide a valid username</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/login.py" line="309"/> + <location filename="../src/leap/bitmask/gui/login.py" line="250"/> <source>Please provide a valid password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/login.py" line="312"/> + <location filename="../src/leap/bitmask/gui/login.py" line="253"/> <source>Logging in...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/login.py" line="364"/> + <location filename="../src/leap/bitmask/gui/login.py" line="307"/> <source>Logging out...</source> <translation type="unfinished"></translation> </message> @@ -658,11 +686,6 @@ Export canceled.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mail_status.py" line="308"/> - <source>Looking for key for this user</source> - <translation type="unfinished"></translation> - </message> - <message> <location filename="../src/leap/bitmask/gui/mail_status.py" line="312"/> <source>Found key! Starting mail...</source> <translation type="unfinished"></translation> @@ -732,72 +755,72 @@ Export canceled.</source> <source>Starting…</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../src/leap/bitmask/gui/mail_status.py" line="308"/> + <source>Initial sync in progress, please wait...</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>MainWindow</name> <message> - <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="228"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="237"/> <source>There are new updates available, please restart.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="270"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="279"/> <source>More...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="870"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="932"/> <source>Help</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="342"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="351"/> <source>&Quit</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="352"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="361"/> <source>&Help</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="357"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="366"/> <source>&Wizard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="930"/> - <source>Hide Main Window</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="728"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="787"/> <source> The following components will be updated: %s</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="731"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="790"/> <source>Updates available</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="929"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="244"/> <source>Show Main Window</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1548"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1718"/> <source>Starting...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1563"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1742"/> <source>Not supported</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1567"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1746"/> <source>Disabled</source> <translation type="unfinished"></translation> </message> @@ -807,205 +830,255 @@ Export canceled.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="347"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="356"/> <source>About &Bitmask</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="211"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="238"/> <source>Mail is OFF</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="719"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="778"/> <source>The Bitmask app is ready to update, please restart the application.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="996"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1050"/> <source>About Bitmask - %s</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1138"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1206"/> <source>Unable to login: Problem with provider</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1219"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1385"/> <source>Log in cancelled by the user.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1584"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1764"/> <source>There was a problem with the provider</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1639"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1819"/> <source>Something went wrong with the logout.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1606"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1786"/> <source>Unable to connect: Problem with provider</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1651"/> - <source>Login</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="305"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="314"/> <source>&Bitmask</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="362"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="371"/> <source>Show &Log</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="367"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="376"/> <source>Create a new account...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="201"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="228"/> <source>File</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="121"/> - <source>Please Log In</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="332"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="341"/> <source>Account Preferences...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="337"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="346"/> <source>Internet Preferences...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="375"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="384"/> <source>Advanced Key Management</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="854"/> - <source> (offline mode)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="878"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="940"/> <source>OFF</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1010"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1064"/> <source>Version: <b>%s</b> (%s)<br><br>%sBitmask is the Desktop client application for the LEAP platform, supporting encrypted internet proxy, secure email, and secure chat (coming soon).<br><br>LEAP is a non-profit dedicated to giving all internet users access to secure communication. Our focus is on adapting encryption technology to make it easy to use and widely available. <br><br><a href='https://leap.se'>More about LEAP</a></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1038"/> - <source><strong>Instructions to use mail:</strong><br>If you use Thunderbird you can use the Bitmask extension helper. Search for 'Bitmask' in the add-on manager or download it from: {0}.<br><br>You can configure Bitmask manually with these options:<br><em> Incoming -> IMAP, port: {1}<br> Outgoing -> SMTP, port: {2}<br> Username -> your bitmask username.<br> Password -> does not matter, use any text. Just don't leave it empty and don't use your account's password.</em></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1039"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1111"/> <source>Bitmask Help</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1051"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1123"/> <source>The current client version is not supported by this provider.<br>Please update to latest version.<br><br>You can get the latest version from <a href='{0}'>{1}</a></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1052"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1124"/> <source>Update Needed</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1062"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1134"/> <source>This provider is not compatible with the client.<br><br>Error: API version incompatible.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1062"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1134"/> <source>Incompatible Provider</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="302"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="359"/> <source>Application error</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="304"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="323"/> <source>You are trying to do an operation that requires logging in first.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="362"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="398"/> <source>Unknown error.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="366"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="402"/> <source>There was a server problem with authentication.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="370"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="406"/> <source>Could not establish a connection.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="374"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="410"/> <source>Invalid username or password.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="897"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="959"/> <source>Hello!</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="898"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="960"/> <source>Bitmask has started in the tray.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1252"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1419"/> <source>Succeeded</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1501"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1672"/> <source>The server at {0} can't be found, because the DNS lookup failed. DNS is the network service that translates a website's name to its Internet address. Either your computer is having trouble connecting to the network, or you are missing some helper files that are needed to securely use DNS while {1} is active. To install these helper files, quit this application and start it again.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1504"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1674"/> <source>Connection Error</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1748"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1932"/> <source>Quitting...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1749"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1933"/> <source>Bitmask is quitting, please wait.</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="361"/> + <source>There is a problem contacting the backend, please restart Bitmask.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1078"/> + <source>bitmask.net/help</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1086"/> + <source>Email quick reference</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1090"/> + <source>For Thunderbird, you can use the Bitmask extension. Search for "Bitmask" in the add-on manager or download it from <a href='{0}'>addons.mozilla.org</a>.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1094"/> + <source>Alternately, you can manually configure your mail client to use Bitmask Email with these options:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1094"/> + <source>IMAP: localhost, port {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1095"/> + <source>SMTP: localhost, port {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1097"/> + <source>Username: your full email address</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1099"/> + <source>Password: any non-empty text</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1108"/> + <source><p><strong>{0}</strong></p><p>{1}</p><p>{2}<ul><li>&nbsp;{3}</li><li>&nbsp;{4}</li><li>&nbsp;{5}</li><li>&nbsp;{6}</li></ul></p></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1273"/> + <source>Stop services</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1273"/> + <source>Do you want to stop all services?</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1277"/> + <source>In order to change the provider, the running services needs to be stopped.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1741"/> + <source>Disabled: missing helper files</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>Preferences</name> @@ -1078,47 +1151,47 @@ Export canceled.</source> <context> <name>PreferencesWindow</name> <message> - <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="59"/> + <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="60"/> <source>Automatic</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="168"/> + <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="170"/> <source>Changing password...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="249"/> + <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="252"/> <source>Password changed successfully.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="433"/> + <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="436"/> <source>There was a problem changing the password.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="437"/> + <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="440"/> <source>You did not enter a correct current password.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="417"/> + <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="420"/> <source>Services settings for provider '{0}' saved.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="114"/> + <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="116"/> <source>You need to enable {0} in order to change the password.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="122"/> + <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="124"/> <source>You need to wait until {0} is ready in order to change the password.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="101"/> + <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="103"/> <source>In order to change your password you need to be logged in.</source> <translation type="unfinished"></translation> </message> @@ -1137,12 +1210,15 @@ Export canceled.</source> </message> </context> <context> - <name>Wizard</name> + <name>Providers</name> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="43"/> - <source>Welcome</source> + <location filename="../src/leap/bitmask/gui/providers.py" line="57"/> + <source>Other...</source> <translation type="unfinished"></translation> </message> +</context> +<context> + <name>Wizard</name> <message> <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="55"/> <source>Log In with my credentials</source> @@ -1154,22 +1230,12 @@ Export canceled.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="112"/> - <source>Provider selection</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="115"/> - <source>Please enter the domain of the provider you want to use for your connection</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="340"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="367"/> <source>Check</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="315"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="342"/> <source>https://</source> <translation type="unfinished"></translation> </message> @@ -1179,310 +1245,275 @@ Export canceled.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="159"/> - <source>Getting provider information</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="239"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="266"/> <source>Can we reach this provider?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="353"/> - <source>Provider Information</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="356"/> - <source>Description of services offered by this provider</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="365"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="392"/> <source>Name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="397"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="424"/> <source>Desc</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="407"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="434"/> <source><b>Services offered:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="417"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="444"/> <source>services</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="437"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="464"/> <source><b>Enrollment policy:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="447"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="474"/> <source>policy</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="467"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="494"/> <source><b>URL:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="477"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="504"/> <source>URL</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="484"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="511"/> <source><b>Description:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="495"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="522"/> <source>Provider setup</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="498"/> - <source>Gathering configuration options for this provider</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="520"/> - <source>We are downloading some bits that we need to establish a secure connection with the provider for the first time.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="543"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="570"/> <source>Setting up provider</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="593"/> - <source>Getting info from the Certificate Authority</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="600"/> - <source>Do we trust this Certificate Authority?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="607"/> - <source>Establishing a trust relationship with this provider</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="666"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="720"/> <source>Register new user</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="669"/> - <source>Register a new user with provider</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="684"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="738"/> <source><b>Password:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="711"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="765"/> <source><b>Re-enter password:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="721"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="775"/> <source>Register</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="767"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="821"/> <source>Remember my username and password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="791"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="845"/> <source>Service selection</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="794"/> - <source>Please select the services you would like to have</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/wizard.py" line="131"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="136"/> <source>&Next ></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/wizard.py" line="133"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="138"/> <source>Connect</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/wizard.py" line="289"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="328"/> <source>Starting registration...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/wizard.py" line="333"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="373"/> <source>User %s successfully registered.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/wizard.py" line="503"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="543"/> <source><font color='red'><b>Non-existent provider</b></font></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/wizard.py" line="522"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="562"/> <source><font color='red'><b>%s</b></font></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/wizard.py" line="551"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="590"/> <source>Unable to load provider configuration</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/wizard.py" line="557"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="596"/> <source><font color='red'><b>Not a valid provider</b></font></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/wizard.py" line="662"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="701"/> <source>Something went wrong while trying to load service %s</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="26"/> - <source>Bitmask first run</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="46"/> - <source>This is the Bitmask first run wizard</source> - <translation type="unfinished"></translation> - </message> - <message> <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="166"/> <source>Can we establish a secure connection?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="754"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="808"/> <source><b>Username:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="269"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="296"/> <source>Configure or select a provider</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="275"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="302"/> <source>Configure new provider:</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="305"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="332"/> <source>Use existing one:</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="62"/> - <source><html><head/><body><p>Now we will guide you through some configuration that is needed before you can connect for the first time.</p><p>If you ever need to modify these options again, you can find the wizard in the <span style=" font-style:italic;">'Bitmask -&gt; Create new account...'</span> menu from the main window.</p><p>Do you want to <span style=" font-weight:600;">sign up</span> for a new account, or <span style=" font-weight:600;">log in</span> with an already existing username?</p></body></html></source> + <location filename="../src/leap/bitmask/gui/wizard.py" line="400"/> + <source>Something has gone wrong. Please try again.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/wizard.py" line="360"/> - <source>Something has gone wrong. Please try again.</source> + <location filename="../src/leap/bitmask/gui/wizard.py" line="414"/> + <source>The requested username is taken, choose another.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/wizard.py" line="688"/> - <source>Gathering configuration options for {0}</source> + <location filename="../src/leap/bitmask/gui/wizard.py" line="682"/> + <source>Services by {0}</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/wizard.py" line="374"/> - <source>The requested username is taken, choose another.</source> + <location filename="../src/leap/bitmask/gui/wizard.py" line="743"/> + <source>Register a new user with {0}</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/wizard.py" line="643"/> - <source>Services by {0}</source> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="26"/> + <source>Bitmask Provider Setup</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/wizard.py" line="696"/> - <source>Description of services offered by {0}</source> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="43"/> + <source>Welcome to Bitmask</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/gui/wizard.py" line="711"/> - <source>Register a new user with {0}</source> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="112"/> + <source>Choose a provider</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="159"/> + <source>Getting provider information.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="380"/> + <source>About this provider</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="547"/> + <source>Bitmask is attempting to establish a secure connection with this provider for the first time.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="638"/> + <source>Fetching provider credentials.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="645"/> + <source>Do we trust these credentials?</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="652"/> + <source>Connecting to provider.</source> <translation type="unfinished"></translation> </message> </context> <context> <name>msg</name> <message> - <location filename="../src/leap/bitmask/platform_init/initializers.py" line="200"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="219"/> <source>TAP Driver</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/platform_init/initializers.py" line="207"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="226"/> <source>Encrypted Internet uses VPN, which needs a TAP device installed and none has been found. This will ask for administrative privileges.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/platform_init/initializers.py" line="325"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="344"/> <source>TUN Driver</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/platform_init/initializers.py" line="333"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="352"/> <source>Encrypted Internet uses VPN, which needs a kernel extension for a TUN device installed, and none has been found. This will ask for administrative privileges.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/platform_init/initializers.py" line="142"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="153"/> <source>Problem installing files</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/platform_init/initializers.py" line="143"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="154"/> <source>Some of the files could not be copied.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/platform_init/initializers.py" line="328"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="347"/> <source>Bitmask needs to install the necessary drivers for Encrypted Internet to work. Would you like to proceed?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/platform_init/initializers.py" line="92"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="102"/> <source>Missing helper files</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/platform_init/initializers.py" line="380"/> - <source>Missing resolvconf framework</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/platform_init/initializers.py" line="432"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="427"/> <source>Missing Bitmask helpers</source> <translation type="unfinished"></translation> </message> @@ -1490,25 +1521,12 @@ Export canceled.</source> <context> <name>msgstr</name> <message> - <location filename="../src/leap/bitmask/platform_init/initializers.py" line="375"/> - <source>Could not find <b>resolvconf</b> installed in your system. -Do you want to quit Bitmask now?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/platform_init/initializers.py" line="379"/> - <source>Encrypted Internet needs resolvconf installed to work properly. -Please use your package manager to install it. -</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/bitmask/platform_init/initializers.py" line="401"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="396"/> <source>Some essential helper files are missing in your system.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/platform_init/initializers.py" line="404"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="399"/> <source>Reinstall your debian packages, or make sure you place them by hand.</source> <translation type="unfinished"></translation> </message> @@ -1516,17 +1534,17 @@ Please use your package manager to install it. <context> <name>self._eip_status</name> <message> - <location filename="../src/leap/bitmask/services/eip/conductor.py" line="184"/> + <location filename="../src/leap/bitmask/services/eip/conductor.py" line="196"/> <source>{0} is restarting</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/services/eip/conductor.py" line="295"/> + <location filename="../src/leap/bitmask/services/eip/conductor.py" line="307"/> <source>{0} could not be launched because you did not authenticate properly.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/bitmask/services/eip/conductor.py" line="307"/> + <location filename="../src/leap/bitmask/services/eip/conductor.py" line="321"/> <source>{0} finished in an unexpected manner!</source> <translation type="unfinished"></translation> </message> diff --git a/docs/man/bitmask-root.1.rst b/docs/man/bitmask-root.1.rst index c18cc4d6..dde303ae 100644 --- a/docs/man/bitmask-root.1.rst +++ b/docs/man/bitmask-root.1.rst @@ -49,6 +49,19 @@ firewall **stop** Stops the firewall. +**isup** Check if the firewall is up. + + +fw-email +--------- + +**start** UID Starts the email firewall. UID is the user name or unix + id that will have access to the email. + +**stop** Stops the email firewall. + +**isup** Check if the email firewall is up. + version -------- diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index c9034b0d..ee195e3b 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python2.7 # -*- coding: utf-8 -*- # # Copyright (C) 2014 LEAP @@ -25,6 +25,8 @@ USAGE: bitmask-root firewall start [restart] GATEWAY1 GATEWAY2 ... bitmask-root openvpn stop bitmask-root openvpn start CONFIG1 CONFIG1 ... + bitmask-root fw-email stop + bitmask-root fw-email start uid All actions return exit code 0 for success, non-zero otherwise. @@ -49,12 +51,17 @@ cmdcheck = subprocess.check_output # CONSTANTS # -VERSION = "2" +VERSION = "3" SCRIPT = "bitmask-root" NAMESERVER = "10.42.0.1" BITMASK_CHAIN = "bitmask" BITMASK_CHAIN_NAT_OUT = "bitmask" BITMASK_CHAIN_NAT_POST = "bitmask_postrouting" +BITMASK_CHAIN_EMAIL = "bitmask_email" +BITMASK_CHAIN_EMAIL_OUT = "bitmask_email_output" +LOCAL_INTERFACE = "lo" +IMAP_PORT = "1984" +SMTP_PORT = "2013" IP = "/bin/ip" IPTABLES = "/sbin/iptables" @@ -101,7 +108,8 @@ PARAM_FORMATS = { "^[a-zA-Z0-9_\.\@][a-zA-Z0-9_\-\.\@]*\$?$", s), # IEEE Std 1003.1-2001 "FILE": lambda s: os.path.isfile(s), "DIR": lambda s: os.path.isdir(os.path.split(s)[0]), - "UNIXSOCKET": lambda s: s == "unix" + "UNIXSOCKET": lambda s: s == "unix", + "UID": lambda s: re.match("^[a-zA-Z0-9]+$", s) } @@ -740,6 +748,119 @@ def firewall_stop(): "Please try `firewall stop` again.") +def fw_email_start(args): + """ + Bring up the email firewall. + + :param args: the user uid of the bitmask process + :type args: list + """ + # add custom chain "bitmask_email" to front of INPUT chain + if not ipv4_chain_exists(BITMASK_CHAIN_EMAIL): + ip4tables("--new-chain", BITMASK_CHAIN_EMAIL) + if not ipv6_chain_exists(BITMASK_CHAIN_EMAIL): + ip6tables("--new-chain", BITMASK_CHAIN_EMAIL) + iptables("--insert", "INPUT", "--jump", BITMASK_CHAIN_EMAIL) + + # add custom chain "bitmask_email_output" to front of OUTPUT chain + if not ipv4_chain_exists(BITMASK_CHAIN_EMAIL_OUT): + ip4tables("--new-chain", BITMASK_CHAIN_EMAIL_OUT) + if not ipv6_chain_exists(BITMASK_CHAIN_EMAIL_OUT): + ip6tables("--new-chain", BITMASK_CHAIN_EMAIL_OUT) + iptables("--insert", "OUTPUT", "--jump", BITMASK_CHAIN_EMAIL_OUT) + + # Disable the access to imap and smtp from outside + iptables("--append", BITMASK_CHAIN_EMAIL, + "--in-interface", LOCAL_INTERFACE, "--protocol", "tcp", + "--dport", IMAP_PORT, "--jump", "ACCEPT") + iptables("--append", BITMASK_CHAIN_EMAIL, + "--in-interface", LOCAL_INTERFACE, "--protocol", "tcp", + "--dport", SMTP_PORT, "--jump", "ACCEPT") + iptables("--append", BITMASK_CHAIN_EMAIL, + "--protocol", "tcp", "--dport", IMAP_PORT, "--jump", "REJECT") + iptables("--append", BITMASK_CHAIN_EMAIL, + "--protocol", "tcp", "--dport", SMTP_PORT, "--jump", "REJECT") + + if not args or not PARAM_FORMATS["UID"](args[0]): + raise Exception("No uid given") + uid = args[0] + + # Only the unix 'uid' have access to the email imap and smtp ports + iptables("--append", BITMASK_CHAIN_EMAIL_OUT, + "--out-interface", LOCAL_INTERFACE, + "--match", "owner", "--uid-owner", uid, "--protocol", "tcp", + "--dport", IMAP_PORT, "--jump", "ACCEPT") + iptables("--append", BITMASK_CHAIN_EMAIL_OUT, + "--out-interface", LOCAL_INTERFACE, + "--match", "owner", "--uid-owner", uid, "--protocol", "tcp", + "--dport", SMTP_PORT, "--jump", "ACCEPT") + iptables("--append", BITMASK_CHAIN_EMAIL_OUT, + "--out-interface", LOCAL_INTERFACE, + "--protocol", "tcp", "--dport", IMAP_PORT, "--jump", "REJECT") + iptables("--append", BITMASK_CHAIN_EMAIL_OUT, + "--out-interface", LOCAL_INTERFACE, + "--protocol", "tcp", "--dport", SMTP_PORT, "--jump", "REJECT") + + +def fw_email_stop(): + """ + Stop the email firewall. + """ + ok = True + + try: + iptables("--delete", "INPUT", "--jump", BITMASK_CHAIN_EMAIL, + throw=True) + except subprocess.CalledProcessError as exc: + debug("INFO: not able to remove bitmask email firewall from INPUT " + "chain (maybe it is already removed?)", exc) + ok = False + + try: + iptables("--delete", "OUTPUT", "--jump", BITMASK_CHAIN_EMAIL_OUT, + throw=True) + except subprocess.CalledProcessError as exc: + debug("INFO: not able to remove bitmask email firewall from OUTPUT " + "chain (maybe it is already removed?)", exc) + ok = False + + try: + ip4tables("--flush", BITMASK_CHAIN_EMAIL, throw=True) + ip4tables("--delete-chain", BITMASK_CHAIN_EMAIL, throw=True) + except subprocess.CalledProcessError as exc: + debug("INFO: not able to flush and delete bitmask ipv4 email firewall " + "chain (maybe it is already destroyed?)", exc) + ok = False + + try: + ip6tables("--flush", BITMASK_CHAIN_EMAIL, throw=True) + ip6tables("--delete-chain", BITMASK_CHAIN_EMAIL, throw=True) + except subprocess.CalledProcessError as exc: + debug("INFO: not able to flush and delete bitmask ipv6 email firewall " + "chain (maybe it is already destroyed?)", exc) + ok = False + + try: + ip4tables("--flush", BITMASK_CHAIN_EMAIL_OUT, throw=True) + ip4tables("--delete-chain", BITMASK_CHAIN_EMAIL_OUT, throw=True) + except subprocess.CalledProcessError as exc: + debug("INFO: not able to flush and delete bitmask ipv4 email firewall " + "chain (maybe it is already destroyed?)", exc) + ok = False + + try: + ip6tables("--flush", BITMASK_CHAIN_EMAIL_OUT, throw=True) + ip6tables("--delete-chain", BITMASK_CHAIN_EMAIL_OUT, throw=True) + except subprocess.CalledProcessError as exc: + debug("INFO: not able to flush and delete bitmask ipv6 email firewall " + "chain (maybe it is already destroyed?)", exc) + ok = False + + if not (ok or ipv4_chain_exists or ipv6_chain_exists): + raise Exception("email firewall might still be left up. " + "Please try `fw-email stop` again.") + + # # MAIN # @@ -793,6 +914,26 @@ def main(): else: bail("INFO: bitmask firewall is down") + elif command == "fw-email_start": + try: + fw_email_start(args) + except Exception as ex: + if not is_restart: + fw_email_stop() + bail("ERROR: could not start email firewall", ex) + + elif command == "fw-email_stop": + try: + fw_email_stop() + except Exception as ex: + bail("ERROR: could not stop email firewall", ex) + + elif command == "fw-email_isup": + if ipv4_chain_exists(BITMASK_CHAIN_EMAIL): + log("%s: INFO: bitmask email firewall is up" % (SCRIPT,)) + else: + bail("INFO: bitmask email firewall is down") + else: bail("ERROR: No such command") else: diff --git a/pkg/postmkvenv.sh b/pkg/postmkvenv.sh index 04f8d372..7b06fa6d 100755 --- a/pkg/postmkvenv.sh +++ b/pkg/postmkvenv.sh @@ -27,7 +27,13 @@ LIB_VIRTUALENV_PATH=$(python -c "$GET_PYTHON_LIB_CMD") if [[ $platform == 'linux' ]]; then LIB_SYSTEM_PATH=$(${VAR[-1]} -c "$GET_PYTHON_LIB_CMD") elif [[ $platform == 'darwin' ]]; then - LIB_SYSTEM_PATH=$(/opt/local/bin/python2.6 -c "$GET_PYTHON_LIB_CMD") + ORIGINAL_PATH=$PATH + #change first colon of path to | because path substitution is greedy + PATH=${PATH/:/|} + #remove everything up to | from path + PATH=${PATH/*|/} + LIB_SYSTEM_PATH=$(python -c "$GET_PYTHON_LIB_CMD") + PATH=$ORIGINAL_PATH else echo "unsupported platform; not doing symlinks" fi diff --git a/pkg/requirements-dev.pip b/pkg/requirements-dev.pip index 8b5a8d85..799376d2 100644 --- a/pkg/requirements-dev.pip +++ b/pkg/requirements-dev.pip @@ -10,8 +10,12 @@ # NOTE: you have to run pip install -r pkg/requirements.pip for pip # to install it. (do it after python setup.py develop and it # will only install this) - +# +wheel sphinx +ipdb --e git+https://github.com/leapcode/leap_pycommon.git@develop#egg=leap.common --e git+https://github.com/leapcode/soledad.git@develop#egg=leap.soledad +# in case you want to install a package from a git source, you can use this: +# Useful to test pre-release branches together. +#-e git+https://github.com/leapcode/leap_pycommon.git@develop#egg=leap.common +#-e git+https://github.com/leapcode/soledad.git@develop#egg=leap.soledad diff --git a/pkg/requirements.pip b/pkg/requirements.pip index bf05aa28..9f49bf03 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -9,7 +9,10 @@ argparse requests>=1.1.0 srp>=1.0.2 pyopenssl -python-dateutil + +# This won't be needed after we refactor leap.common.events +# to use zmq. +python-dateutil==1.4 # See https://leap.se/code/issues/6099 psutil @@ -19,6 +22,8 @@ python-daemon # this should not be needed for Windows. keyring zope.proxy +# You will want to install this bundled if you don't have sodium in your system: +# pip install pyzmq --install-option="--zmq=bundled" pyzmq leap.common>=0.3.7 diff --git a/pkg/scripts/bootstrap_develop.sh b/pkg/scripts/bootstrap_develop.sh index 7027a908..68edcd43 100755 --- a/pkg/scripts/bootstrap_develop.sh +++ b/pkg/scripts/bootstrap_develop.sh @@ -159,6 +159,32 @@ update() { finish } +helpers() { + if [[ "$1" == "cleanup" ]]; then + status="removing helper files" + echo "${cc_green}Status: $status...${cc_normal}" + set -x + sudo rm -f /usr/sbin/bitmask-root + sudo rm -f /usr/share/polkit-1/actions/se.leap.bitmask.policy + set +x + else + status="installing helper files" + echo "${cc_green}Status: $status...${cc_normal}" + set -x + sudo cp bitmask_client/pkg/linux/bitmask-root /usr/sbin/ + sudo cp bitmask_client/pkg/linux/polkit/se.leap.bitmask.policy /usr/share/polkit-1/actions/ + set +x + fi +} + +install_dependencies() { + status="installing system dependencies" + echo "${cc_green}Status: $status...${cc_normal}" + set -x + sudo apt-get install -y git python-dev python-setuptools python-virtualenv python-pip libssl-dev python-openssl libsqlite3-dev g++ openvpn pyside-tools python-pyside libffi-dev + set +x +} + run() { shift # remove 'run' from arg list passthrough_args=$@ @@ -174,13 +200,17 @@ help() { echo "Bootstraps the environment to start developing the bitmask client" echo "with all the needed repositories and dependencies." echo - echo "Usage: $0 {init | update | run | help}" + echo "Usage: $0 {init | update | run | help | deps | helpers}" echo - echo " init : Initialize repositories, create virtualenv and \`python setup.py develop\` all." - echo " You can use \`init ro\` in order to use the https remotes if you don't have rw access." - echo " update : Update the repositories and install new deps (if needed)." - echo " run : Runs the client (any extra parameters will be sent to the app)." - echo " help : Show this help" + echo " init : Initialize repositories, create virtualenv and \`python setup.py develop\` all." + echo " You can use \`init ro\` in order to use the https remotes if you don't have rw access." + echo " update : Update the repositories and install new deps (if needed)." + echo " run : Runs the client (any extra parameters will be sent to the app)." + echo " help : Show this help" + echo " -- system helpers --" + echo " deps : Install the system dependencies needed for bitmask dev (Debian based Linux only)." + echo " helpers : Install the helper files needed to use bitmask (Linux only)." + echo " You can use \`helpers cleanup\` to remove those files." echo } @@ -191,6 +221,12 @@ case "$1" in update) update ;; + helpers) + helpers $2 + ;; + deps) + install_dependencies + ;; run) run "$@" ;; diff --git a/relnotes.txt b/relnotes.txt index 183d1f86..02e53ee6 100644 --- a/relnotes.txt +++ b/relnotes.txt @@ -1,8 +1,8 @@ -ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.6.1 +ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.7.0 The LEAP team is pleased to announce the immediate availability of version 0.6.1 of Bitmask, the Internet Encryption Toolkit, codename -"knock knock knocking on beta's door". +"one time download, all time updates". https://downloads.leap.se/client/ @@ -17,15 +17,6 @@ The Encrypted Internet Proxy provides circumvention, location anonymization, and traffic encryption in a hassle-free, automatically self-configuring fashion. -WARNING (LINUX ONLY): If you ever run into the situation where you -cannot access internet, open the terminal and run the following -command: - -$ pkexec /usr/local/sbin/bitmask-root firewall stop - -If for some reason that doesn't work, you will need to reboot your -computer. - Encrypted Mail offers automatic encryption and decryption for both outgoing and incoming email, adding public key cryptography to your mail without you ever having to worry about key distribution or @@ -43,17 +34,15 @@ NOT trust your life to it. WHAT CAN THIS VERSION OF BITMASK DO FOR ME? -Bitmask 0.6.1 is the new stable version of the client after the big -refactor, with a little face lift of the UI while we were at -it. Encrypted Email is still not stable though, so don't use it for -high security. Encrypted Internet is the first service we are calling -stable, although its security level is just a bit higher than plain -OpenSSL, so use accordingly. You can refer to the CHANGELOG for the -meat. +Bitmask 0.7.0 brings with tremendous joy automatic and secure updates +through The Update Framework. Right beside TUF there are some bug +fixes and a new settings panel. -Encrypted Internet on Linux now helps you don't shoot yourself in the -foot by leaking traffic outside of the secure connection it -establishes. This will be added to other platforms in the future. +You can read more about TUF in http://theupdateframework.com/ + +Encrypted Internet on Linux avoids leaking traffic outside of the +secure connection it establishes. This will be added to other +platforms in the future. The Encrypted Mail services will run local SMTP and IMAP proxies that, once you configure the mail client of your choice, will automatically @@ -89,8 +78,8 @@ repository to your apt sources: deb http://deb.leap.se/debian wheezy main -We will love to hear if you are interested in help making packages -available for any other system. +We will love to hear if you want to make packages available for any +other system. BUGS @@ -98,6 +87,16 @@ You can send the bugs our way by pointing your telnet session to port 443 on https://leap.se/code. We will do our best to make them follow our intensive bug-reeducation program. + +LINUX ONLY: If you ever run into the situation where you cannot +access internet, open the terminal and run the following command: + +$ pkexec /usr/local/sbin/bitmask-root firewall stop + +If for some reason that doesn't work, you will need to reboot your +computer. + + HACKING You can find us in the #leap channel on the freenode network. @@ -108,6 +107,6 @@ beyond any border. The LEAP team, -August 15, 2014 +Setptember 26, 2014 Somewhere in the middle of the intertubes. EOF diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index f721086b..5ef6befd 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -452,20 +452,14 @@ class EIP(object): else: logger.debug('EIP: no errors') - def _do_stop(self, shutdown=False, restart=False): + def stop(self, shutdown=False, restart=False): """ - Stop the service. This is run in a thread to avoid blocking. + Stop the service. """ self._vpn.terminate(shutdown, restart) if IS_LINUX: self._wait_for_firewall_down() - def stop(self, shutdown=False, restart=False): - """ - Stop the service. - """ - return threads.deferToThread(self._do_stop, shutdown, restart) - def _wait_for_firewall_down(self): """ Wait for the firewall to come down. @@ -665,7 +659,7 @@ class EIP(object): return False client_cert_path = eip_config.\ - get_client_cert_path(provider_config, about_to_download=False) + get_client_cert_path(provider_config, about_to_download=True) if leap_certs.should_redownload(client_cert_path): logger.error("The client should redownload the certificate," diff --git a/src/leap/bitmask/crypto/certs.py b/src/leap/bitmask/crypto/certs.py index 244decfd..c3ca4efb 100644 --- a/src/leap/bitmask/crypto/certs.py +++ b/src/leap/bitmask/crypto/certs.py @@ -46,19 +46,27 @@ def download_client_cert(provider_config, path, session): # again. srp_auth = SRPAuth(provider_config) session_id = srp_auth.get_session_id() + token = srp_auth.get_token() cookies = None - if session_id: + if session_id is not None: cookies = {"_session_id": session_id} cert_uri = "%s/%s/cert" % ( provider_config.get_api_uri(), provider_config.get_api_version()) logger.debug('getting cert from uri: %s' % cert_uri) + headers = {} + + # API v2 will only support token auth, but in v1 we can send both + if token is not None: + headers["Authorization"] = 'Token token="{0}"'.format(token) + res = session.get(cert_uri, verify=provider_config .get_ca_cert_path(), cookies=cookies, - timeout=REQUEST_TIMEOUT) + timeout=REQUEST_TIMEOUT, + headers=headers) res.raise_for_status() client_cert = res.content diff --git a/src/leap/bitmask/frontend_app.py b/src/leap/bitmask/frontend_app.py index 5ea89fc9..909005f0 100644 --- a/src/leap/bitmask/frontend_app.py +++ b/src/leap/bitmask/frontend_app.py @@ -76,15 +76,24 @@ def run_frontend(options, flags_dict, backend_pid): qApp = QtGui.QApplication(sys.argv) - # To test: - # $ LANG=es ./app.py - locale = QtCore.QLocale.system().name() - qtTranslator = QtCore.QTranslator() - if qtTranslator.load("qt_%s" % locale, ":/translations"): - qApp.installTranslator(qtTranslator) - appTranslator = QtCore.QTranslator() - if appTranslator.load("%s.qm" % locale[:2], ":/translations"): - qApp.installTranslator(appTranslator) + # To test the app in other language you can do: + # shell> LANG=es bitmask + # or in some rare case if the code above didn't work: + # shell> LC_ALL=es LANG=es bitmask + locale = QtCore.QLocale.system().name() # en_US, es_AR, ar_SA, etc + locale_short = locale[:2] # en, es, ar, etc + rtl_languages = ('ar', ) # right now tested on 'arabic' only. + + systemQtTranslator = QtCore.QTranslator() + if systemQtTranslator.load("qt_%s" % locale, ":/translations"): + qApp.installTranslator(systemQtTranslator) + + bitmaskQtTranslator = QtCore.QTranslator() + if bitmaskQtTranslator.load("%s.qm" % locale_short, ":/translations"): + qApp.installTranslator(bitmaskQtTranslator) + + if locale_short in rtl_languages: + qApp.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) # Needed for initializing qsettings it will write # .config/leap/leap.conf top level app settings in a platform diff --git a/src/leap/bitmask/gui/account.py b/src/leap/bitmask/gui/account.py new file mode 100644 index 00000000..c941c3fa --- /dev/null +++ b/src/leap/bitmask/gui/account.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +A frontend GUI object to hold the current username and domain. +""" + +from leap.bitmask.util import make_address +from leap.bitmask.config.leapsettings import LeapSettings +from leap.bitmask.services import EIP_SERVICE, MX_SERVICE + + +class Account(): + + def __init__(self, username, domain): + self._settings = LeapSettings() + self.username = username + self.domain = domain + + if self.username is not None: + self.address = make_address(self.username, self.domain) + else: + self.address = self.domain + + def services(self): + """ + returns a list of service name strings + + TODO: this should depend not just on the domain + """ + return self._settings.get_enabled_services(self.domain) + + def is_email_enabled(self): + MX_SERVICE in self.services() + + def is_eip_enabled(self): + EIP_SERVICE in self.services() diff --git a/src/leap/bitmask/gui/app.py b/src/leap/bitmask/gui/app.py new file mode 100644 index 00000000..eb1a58d5 --- /dev/null +++ b/src/leap/bitmask/gui/app.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +A single App instances holds the signals that are shared among different +frontend UI components. The App also keeps a reference to the backend object +and the signaler get signals from the backend. +""" +import logging + +from functools import partial +from PySide import QtCore, QtGui + +from leap.bitmask.config.leapsettings import LeapSettings +from leap.bitmask.backend.backend_proxy import BackendProxy +from leap.bitmask.backend.leapsignaler import LeapSignaler + +logger = logging.getLogger(__name__) + + +class App(QtGui.QWidget): + + # the user has changed which services are enabled for a particular account + # args: account (Account), active services (list of str) + service_selection_changed = QtCore.Signal(object, list) + + def __init__(self): + QtGui.QWidget.__init__(self) + + self.settings = LeapSettings() + self.backend = BackendProxy() + self.signaler = LeapSignaler() + self.signaler.start() + + # periodically check if the backend is alive + self._backend_checker = QtCore.QTimer(self) + self._backend_checker.timeout.connect(self._check_backend_status) + self._backend_checker.start(2000) + + @QtCore.Slot() + def _check_backend_status(self): + """ + TRIGGERS: + self._backend_checker.timeout + + Check that the backend is running. Otherwise show an error to the user. + """ + if not self.backend.online: + logger.critical("Backend is not online.") + QtGui.QMessageBox.critical( + self, self.tr("Application error"), + self.tr("There is a problem contacting the backend, please " + "restart Bitmask.")) + self._backend_checker.stop() diff --git a/src/leap/bitmask/gui/eip_preferenceswindow.py b/src/leap/bitmask/gui/eip_preferenceswindow.py index 0f63972f..b5788f3c 100644 --- a/src/leap/bitmask/gui/eip_preferenceswindow.py +++ b/src/leap/bitmask/gui/eip_preferenceswindow.py @@ -116,11 +116,14 @@ class EIPPreferencesWindow(QtGui.QDialog): self.ui.gbGatewaySelector.setEnabled(False) return + # block signals so the currentIndexChanged slot doesn't get triggered + self.ui.cbProvidersGateway.blockSignals(True) for provider, is_initialized in providers: label = provider if not is_initialized: label += self.tr(" (uninitialized)") self.ui.cbProvidersGateway.addItem(label, userData=provider) + self.ui.cbProvidersGateway.blockSignals(False) # Select provider by name domain = self._selected_domain diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index abd6e2c9..a5cd03d3 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -97,7 +97,7 @@ class EIPStatusWidget(QtGui.QWidget): # Action for the systray self._eip_disabled_action = QtGui.QAction( - "{0} is {1}".format(self._service_name, self.tr("disabled")), self) + u"{0} is {1}".format(self._service_name, self.tr("disabled")), self) def connect_backend_signals(self): """ @@ -303,7 +303,6 @@ class EIPStatusWidget(QtGui.QWidget): """ # XXX this name is unfortunate. "disable" is also applied to a # pushbutton being grayed out. - logger.debug('Hiding EIP start button') # you might be tempted to change this for a .setEnabled(False). # it won't work. it's under the claws of the state machine. @@ -334,7 +333,7 @@ class EIPStatusWidget(QtGui.QWidget): Triggered after a successful login. Enables the start button. """ - # logger.debug('Showing EIP start button') + logger.debug('Showing EIP start button') self.eip_button.show() self.hide_eip_cancel_button() diff --git a/src/leap/bitmask/gui/flashable.py b/src/leap/bitmask/gui/flashable.py new file mode 100644 index 00000000..a26d1ec6 --- /dev/null +++ b/src/leap/bitmask/gui/flashable.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +class Flashable(object): + + """ + An abstract super class to give a QWidget handy methods for diplaying + alert messages inline. The widget inheriting from this class must have + label named 'flash_label' available at self.ui.flash_label, or pass + the QLabel object in the constructor. + """ + + def __init__(self, widget=None): + self._setup(widget) + + def _setup(self, widget=None): + if not hasattr(self, 'widget'): + if widget: + self.widget = widget + else: + self.widget = self.ui.flash_label + self.widget.setVisible(False) + + def flash_error(self, message): + """ + Sets string for the flash message. + + :param message: the text to be displayed + :type message: str + """ + self._setup() + message = "<font color='red'><b>%s</b></font>" % (message,) + self.widget.setVisible(True) + self.widget.setText(message) + + def flash_success(self, message): + """ + Sets string for the flash message. + + :param message: the text to be displayed + :type message: str + """ + self._setup() + message = "<font color='green'><b>%s</b></font>" % (message,) + self.widget.setVisible(True) + self.widget.setText(message) + + def flash_message(self, message): + """ + Sets string for the flash message. + + :param message: the text to be displayed + :type message: str + """ + self._setup() + message = "<b>%s</b>" % (message,) + self.widget.setVisible(True) + self.widget.setText(message) + + def hide_flash(self): + self._setup() + self.widget.setVisible(False) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 8ce7f2fc..cc4ede09 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -37,7 +37,6 @@ from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement -from leap.bitmask.gui.eip_preferenceswindow import EIPPreferencesWindow from leap.bitmask.gui.eip_status import EIPStatusWidget from leap.bitmask.gui.loggerwindow import LoggerWindow from leap.bitmask.gui.login import LoginWidget @@ -46,8 +45,11 @@ from leap.bitmask.gui.preferenceswindow import PreferencesWindow from leap.bitmask.gui.systray import SysTray from leap.bitmask.gui.wizard import Wizard from leap.bitmask.gui.providers import Providers +from leap.bitmask.gui.account import Account +from leap.bitmask.gui.app import App from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX +from leap.bitmask.platform_init import locks from leap.bitmask.platform_init.initializers import init_platform from leap.bitmask.platform_init.initializers import init_signals @@ -63,10 +65,6 @@ from leap.bitmask.util import autostart, make_address from leap.bitmask.util.keyring_helpers import has_keyring from leap.bitmask.logs.leap_log_handler import LeapLogHandler -if IS_WIN: - from leap.bitmask.platform_init.locks import WindowsLock - from leap.bitmask.platform_init.locks import raise_window_ack - from leap.common.events import register from leap.common.events import events_pb2 as proto @@ -127,19 +125,10 @@ class MainWindow(QtGui.QMainWindow): self.ui.setupUi(self) self.menuBar().setNativeMenuBar(not IS_LINUX) - self._backend = BackendProxy() - - # periodically check if the backend is alive - self._backend_checker = QtCore.QTimer(self) - self._backend_checker.timeout.connect(self._check_backend_status) - self._backend_checker.start(2000) - - self._leap_signaler = LeapSignaler() - self._leap_signaler.start() - - self._settings = LeapSettings() - # gateway = self._settings.get_selected_gateway(provider) - # self._backend.settings_set_selected_gateway(provider, gateway) + self.app = App() + self._backend = self.app.backend + self._leap_signaler = self.app.signaler + self._settings = self.app.settings # Login Widget self._login_widget = LoginWidget(self._settings, self) @@ -155,6 +144,7 @@ class MainWindow(QtGui.QMainWindow): # Qt Signal Connections ##################################### # TODO separate logic from ui signals. + self.app.service_selection_changed.connect(self._update_eip_enabled_status) self._login_widget.login.connect(self._login) self._login_widget.cancel_login.connect(self._cancel_login) self._login_widget.logout.connect(self._logout) @@ -209,12 +199,13 @@ class MainWindow(QtGui.QMainWindow): self._finally_quitting = False self._system_quit = False + # Used to differentiate between a real quit and a close to tray + self._close_to_tray = True + self._backend_connected_signals = [] self._backend_connect() self.ui.action_preferences.triggered.connect(self._show_preferences) - self.ui.action_eip_preferences.triggered.connect( - self._show_eip_preferences) self.ui.action_about_leap.triggered.connect(self._about) self.ui.action_quit.triggered.connect(self.quit) self.ui.action_wizard.triggered.connect(self._launch_wizard) @@ -224,17 +215,15 @@ class MainWindow(QtGui.QMainWindow): self.ui.action_create_new_account.triggered.connect( self._on_provider_changed) - self.ui.action_advanced_key_management.triggered.connect( - self._show_AKM) + # Action item hidden since we don't provide stable mail yet. + # self.ui.action_advanced_key_management.triggered.connect( + # self._show_AKM) if IS_MAC: self.ui.menuFile.menuAction().setText(self.tr("File")) self.raise_window.connect(self._do_raise_mainwindow) - # Used to differentiate between real quits and close to tray - self._really_quit = False - self._systray = None # XXX separate actions into a different module. @@ -247,10 +236,6 @@ class MainWindow(QtGui.QMainWindow): self._action_visible = QtGui.QAction(self.tr("Show Main Window"), self) self._action_visible.triggered.connect(self._ensure_visible) - # disable buttons for now, may come back later. - # self.ui.btnPreferences.clicked.connect(self._show_preferences) - # self.ui.btnEIPPreferences.clicked.connect(self._show_eip_preferences) - self._enabled_services = [] self._ui_mx_visible = True self._ui_eip_visible = True @@ -274,9 +259,7 @@ class MainWindow(QtGui.QMainWindow): # Services signals/slots connection self.new_updates.connect(self._react_to_new_updates) - # XXX should connect to mail_conductor.start_mail_service instead - self.soledad_ready.connect(self._start_smtp_bootstrapping) - self.soledad_ready.connect(self._start_imap_service) + self.soledad_ready.connect(self._start_mail_service) # ################################ end Qt Signals connection ######## init_platform() @@ -347,23 +330,6 @@ class MainWindow(QtGui.QMainWindow): logger.error("Bad call to the backend:") logger.error(data) - @QtCore.Slot() - def _check_backend_status(self): - """ - TRIGGERS: - self._backend_checker.timeout - - Check that the backend is running. Otherwise show an error to the user. - """ - online = self._backend.online - if not online: - logger.critical("Backend is not online.") - QtGui.QMessageBox.critical( - self, self.tr("Application error"), - self.tr("There is a problem contacting the backend, please " - "restart Bitmask.")) - self._backend_checker.stop() - def _backend_connect(self, only_tracked=False): """ Connect to backend signals. @@ -413,11 +379,6 @@ class MainWindow(QtGui.QMainWindow): "Invalid username or password.")) conntrack(sig.srp_auth_bad_user_or_password, auth_bad_user_or_password) - # Logout signals - conntrack(sig.srp_logout_ok, self._logout_ok) - conntrack(sig.srp_logout_error, self._logout_error) - conntrack(sig.srp_not_logged_in_error, self._not_logged_in_error) - # EIP bootstrap signals conntrack(sig.eip_config_ready, self._eip_intermediate_stage) conntrack(sig.eip_client_certificate_ready, self._finish_eip_bootstrap) @@ -436,8 +397,12 @@ class MainWindow(QtGui.QMainWindow): sig.prov_unsupported_api.connect(self._incompatible_api) sig.prov_get_all_services.connect(self._provider_get_all_services) - # EIP start signals ============================================== + # Logout signals ================================================= + sig.srp_logout_ok.connect(self._logout_ok) + sig.srp_logout_error.connect(self._logout_error) + sig.srp_not_logged_in_error.connect(self._not_logged_in_error) + # EIP start signals ============================================== self._eip_conductor.connect_backend_signals() sig.eip_can_start.connect(self._backend_can_start_eip) sig.eip_cannot_start.connect(self._backend_cannot_start_eip) @@ -604,24 +569,16 @@ class MainWindow(QtGui.QMainWindow): Display the preferences window. """ - user = self._logged_user - domain = self._providers.get_selected_provider() - mx_provided = False - if self._provider_details is not None: - mx_provided = MX_SERVICE in self._provider_details['services'] - preferences = PreferencesWindow(self, user, domain, self._backend, - self._soledad_started, mx_provided, - self._leap_signaler) + account = Account(self._logged_user, + self._providers.get_selected_provider()) + pref_win = PreferencesWindow(self, account, self.app) + pref_win.show() - self.soledad_ready.connect(preferences.set_soledad_ready) - preferences.show() - preferences.preferences_saved.connect(self._update_eip_enabled_status) - - @QtCore.Slot() - def _update_eip_enabled_status(self): + @QtCore.Slot(object, list) + def _update_eip_enabled_status(self, account=None, services=None): """ TRIGGER: - PreferencesWindow.preferences_saved + App.service_selection_changed Enable or disable the EIP start/stop actions and stop EIP if the user disabled that service. @@ -629,24 +586,35 @@ class MainWindow(QtGui.QMainWindow): :returns: if the eip actions were enabled or disabled :rtype: bool """ - settings = self._settings - default_provider = settings.get_defaultprovider() + if account is not None: + domain = account.domain + else: + # I am not sure why, but asking for the currently selected + # provider here give you the WRONG provider + domain = self.app.settings.get_defaultprovider() - if default_provider is None: + if domain is None: logger.warning("Trying to update eip enabled status but there's no" " default provider. Disabling EIP for the time" " being...") self._backend_cannot_start_eip() return - self._trying_to_start_eip = settings.get_autostart_eip() - self._backend.eip_can_start(domain=default_provider) + if not EIP_SERVICE in self.app.settings.get_enabled_services(domain): + self._eip_conductor.terminate() + def hide(): + self.app.backend.eip_can_start(domain=domain) + QtDelayedCall(100, hide) + # ^^ VERY VERY Hacky, but with the simple state machine, + # there is no way to signal 'disconnect and then disable' + + else: + self._trying_to_start_eip = self.app.settings.get_autostart_eip() + if not self._trying_to_start_eip: + self._backend.eip_setup(provider=domain, skip_network=True) + # check if EIP can start (will trigger widget update) + self.app.backend.eip_can_start(domain=domain) - # If we don't want to start eip, we leave everything - # initialized to quickly start it - if not self._trying_to_start_eip: - self._backend.eip_setup(provider=default_provider, - skip_network=True) def _backend_can_start_eip(self): """ @@ -657,12 +625,15 @@ class MainWindow(QtGui.QMainWindow): to do so, start it. Otherwise it leaves everything in place for the user to click Turn ON. """ + if self._eip_status.missing_helpers: + self._eip_status.disable_eip_start() + return + settings = self._settings default_provider = settings.get_defaultprovider() enabled_services = [] if default_provider is not None: enabled_services = settings.get_enabled_services(default_provider) - eip_enabled = False if EIP_SERVICE in enabled_services: eip_enabled = True @@ -701,7 +672,13 @@ class MainWindow(QtGui.QMainWindow): self._eip_status.disable_eip_start() else: self._eip_status.disable_eip_start() - self._eip_status.set_eip_status(self.tr("Disabled")) + # NOTE: we shouldn't be setting the message here. + if not self._eip_status.missing_helpers: + self._eip_status.set_eip_status(self.tr("Disabled")) + + # this state flag is responsible for deferring the login + # so we must update it, otherwise we're in a deadlock. + self._trying_to_start_eip = False @QtCore.Slot() def _disable_eip_missing_helpers(self): @@ -713,20 +690,6 @@ class MainWindow(QtGui.QMainWindow): """ self._eip_status.missing_helpers = True - @QtCore.Slot() - def _show_eip_preferences(self): - """ - TRIGGERS: - self.ui.btnEIPPreferences.clicked - self.ui.action_eip_preferences (disabled for now) - - Display the EIP preferences window. - """ - domain = self._providers.get_selected_provider() - pref = EIPPreferencesWindow(self, domain, - self._backend, self._leap_signaler) - pref.show() - # # updates # @@ -809,6 +772,15 @@ class MainWindow(QtGui.QMainWindow): self._show_hide_unsupported_services() + # XXX - HACK, kind of... + # With the 1ms QTimer.singleShot call we schedule the call right after + # other signals waiting for the qt reactor to take control. + # That way, the method is called right after the EIP machines' signals. + # We need to wait until that happens because the state-machine + # controlled widget shows the 'Turn On' button and we want to do the + # changes to that button right after, not before. + QtDelayedCall(1, self._update_eip_enabled_status) + if self._wizard: possible_username = self._wizard.get_username() possible_password = self._wizard.get_password() @@ -833,8 +805,6 @@ class MainWindow(QtGui.QMainWindow): self._wizard = None self._backend_connect(only_tracked=True) else: - self._update_eip_enabled_status() - domain = self._settings.get_provider() if domain is not None: self._providers.select_provider_by_name(domain) @@ -923,7 +893,7 @@ class MainWindow(QtGui.QMainWindow): systrayMenu.addAction(self._action_visible) systrayMenu.addSeparator() - eip_status_label = "{0}: {1}".format( + eip_status_label = u"{0}: {1}".format( self._eip_conductor.eip_name, self.tr("OFF")) self._eip_menu = eip_menu = systrayMenu.addMenu(eip_status_label) eip_menu.addAction(self._action_eip_startstop) @@ -1131,7 +1101,7 @@ class MainWindow(QtGui.QMainWindow): return if QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \ - not self._really_quit: + self._close_to_tray: self._ensure_invisible() e.ignore() return @@ -1245,9 +1215,15 @@ class MainWindow(QtGui.QMainWindow): # TODO: we need to add a check for the mail status (smtp/imap/soledad) something_runing = (self._logged_user is not None or self._already_started_eip) + provider = self._providers.get_selected_provider() + if not something_runing: if wizard: self._launch_wizard() + else: + self._settings.set_provider(provider) + self._settings.set_defaultprovider(provider) + self._update_eip_enabled_status() return title = self.tr("Stop services") @@ -1265,7 +1241,11 @@ class MainWindow(QtGui.QMainWindow): res = msg.exec_() if res == QtGui.QMessageBox.Yes: + self._settings.set_provider(provider) + self._settings.set_defaultprovider(provider) + self._settings.set_autostart_eip(False) self._stop_services() + self._update_eip_enabled_status() self._eip_conductor.qtsigs.do_disconnect_signal.emit() if wizard: self._launch_wizard() @@ -1540,37 +1520,12 @@ class MainWindow(QtGui.QMainWindow): self.soledad_ready.emit() ################################################################### - # Service control methods: smtp - + # Service control methods: mail @QtCore.Slot() - def _start_smtp_bootstrapping(self): - """ - TRIGGERS: - self.soledad_ready - """ - if flags.OFFLINE is True: - logger.debug("not starting smtp in offline mode") - return - - if self._provides_mx_and_enabled(): - self._mail_conductor.start_smtp_service(download_if_needed=True) - - ################################################################### - # Service control methods: imap - - @QtCore.Slot() - def _start_imap_service(self): - """ - TRIGGERS: - self.soledad_ready - """ - # TODO in the OFFLINE mode we should also modify the rules - # in the mail state machine so it shows that imap is active - # (but not smtp since it's not yet ready for offline use) + def _start_mail_service(self): if self._provides_mx_and_enabled() or flags.OFFLINE: - self._mail_conductor.start_imap_service() - - # end service control methods (imap) + self._mail_conductor.start_mail_service(download_if_needed=True, + offline=flags.OFFLINE) ################################################################### # Service control methods: eip @@ -1839,7 +1794,7 @@ class MainWindow(QtGui.QMainWindow): Callback for the raise window event """ if IS_WIN: - raise_window_ack() + locks.raise_window_ack() self.raise_window.emit() @QtCore.Slot() @@ -1866,10 +1821,9 @@ class MainWindow(QtGui.QMainWindow): """ Stop services and cancel ongoing actions (if any). """ - logger.debug('About to quit, doing cleanup.') + logger.debug('Stopping services...') self._cancel_ongoing_defers() - self._services_being_stopped = set(('imap', 'eip')) imap_stopped = lambda: self._remove_service('imap') @@ -1880,8 +1834,7 @@ class MainWindow(QtGui.QMainWindow): self._leap_signaler.eip_stopped.connect(eip_stopped) logger.debug('Stopping mail services') - self._backend.imap_stop_service() - self._backend.smtp_stop_service() + self._mail_conductor.stop_mail_services() if self._logged_user is not None: logger.debug("Doing logout") @@ -1898,9 +1851,10 @@ class MainWindow(QtGui.QMainWindow): if self._quitting: return - autostart.set_autostart(False) - self._quitting = True + self._close_to_tray = False + logger.debug('Quitting...') + autostart.set_autostart(False) # first thing to do quitting, hide the mainwindow and show tooltip. self.hide() @@ -1919,8 +1873,6 @@ class MainWindow(QtGui.QMainWindow): # Set this in case that the app is hidden QtGui.QApplication.setQuitOnLastWindowClosed(True) - self._really_quit = True - if not self._backend.online: self.final_quit() return @@ -1996,7 +1948,6 @@ class MainWindow(QtGui.QMainWindow): # Remove lockfiles on a clean shutdown. logger.debug('Cleaning pidfiles') - if IS_WIN: - WindowsLock.release_all_locks() + locks.release_lock() self.close() diff --git a/src/leap/bitmask/gui/passwordwindow.py b/src/leap/bitmask/gui/passwordwindow.py new file mode 100644 index 00000000..f7ef079e --- /dev/null +++ b/src/leap/bitmask/gui/passwordwindow.py @@ -0,0 +1,273 @@ +# -*- coding: utf-8 -*- +# passwordwindow.py +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Change password dialog window +""" + +from PySide import QtCore, QtGui +from leap.bitmask.util.credentials import password_checks + +from leap.bitmask.gui.ui_password_change import Ui_PasswordChange +from leap.bitmask.gui.flashable import Flashable + +import logging +logger = logging.getLogger(__name__) + + +class PasswordWindow(QtGui.QDialog, Flashable): + + _current_window = None # currently visible password window + + def __init__(self, parent, account, app): + """ + :param parent: parent object of the PreferencesWindow. + :parent type: QWidget + + :param account: the user set in the login widget + :type account: Account + + :param app: App instance + :type app: App + """ + QtGui.QDialog.__init__(self, parent) + + self.account = account + self.app = app + self._backend_connect() + + self.ui = Ui_PasswordChange() + self.ui.setupUi(self) + + self.hide_flash() + self.ui.ok_button.clicked.connect(self._change_password) + self.ui.cancel_button.clicked.connect(self.close) + self.ui.username_lineedit.setText(account.address) + + if PasswordWindow._current_window is not None: + PasswordWindow._current_window.close() + PasswordWindow._current_window = self + + self._disabled = False # if set to True, never again enable widgets. + + if account.username is None: + # should not ever happen, but just in case + self._disabled = True + self._enable_password_widgets(False) + self.ui.cancel_button.setEnabled(True) + self.flash_error(self.tr("Please log in to change your password.")) + + if self.is_soledad_needed() and not self._soledad_ready: + self._enable_password_widgets(False) + self.ui.cancel_button.setEnabled(True) + self.flash_message( + self.tr("Please wait for data storage to be ready.")) + + def is_soledad_needed(self): + """ + Returns true if the current account needs to change the soledad + password as well as the SRP password. + """ + return self.account.is_email_enabled() + + # + # MANAGE WIDGETS + # + + def _enable_password_widgets(self, enabled): + """ + Enables or disables the widgets in the password change group box. + + :param enabled: True if the widgets should be enabled. + False if widgets should be disabled and + display the status label that shows that is + changing the password. + :type enabled: bool + """ + if self._disabled: + return + + if enabled: + self.hide_flash() + else: + self.flash_message(self.tr("Changing password...")) + + self.ui.current_password_lineedit.setEnabled(enabled) + self.ui.new_password_lineedit.setEnabled(enabled) + self.ui.new_password_confirmation_lineedit.setEnabled(enabled) + self.ui.ok_button.setEnabled(enabled) + self.ui.cancel_button.setEnabled(enabled) + + def _change_password_success(self): + """ + Callback used to display a successfully changed password. + """ + logger.debug("Password changed successfully.") + self._clear_password_inputs() + self._enable_password_widgets(True) + self.flash_success(self.tr("Password changed successfully.")) + + def _clear_password_inputs(self): + """ + Clear the contents of the inputs. + """ + self.ui.current_password_lineedit.setText("") + self.ui.new_password_lineedit.setText("") + self.ui.new_password_confirmation_lineedit.setText("") + + # + # SLOTS + # + + def _backend_connect(self): + """ + Helper to connect to backend signals + """ + sig = self.app.signaler + sig.srp_password_change_ok.connect(self._srp_change_password_ok) + sig.srp_password_change_error.connect(self._srp_password_change_error) + sig.srp_password_change_badpw.connect(self._srp_password_change_badpw) + sig.soledad_password_change_ok.connect( + self._soledad_change_password_ok) + sig.soledad_password_change_error.connect( + self._soledad_change_password_problem) + + self._soledad_ready = False + sig.soledad_bootstrap_finished.connect(self._on_soledad_ready) + + @QtCore.Slot() + def _change_password(self): + """ + TRIGGERS: + self.ui.buttonBox.accepted + + Changes the user's password if the inputboxes are correctly filled. + """ + current_password = self.ui.current_password_lineedit.text() + new_password = self.ui.new_password_lineedit.text() + new_password2 = self.ui.new_password_confirmation_lineedit.text() + + self._enable_password_widgets(True) + + if len(current_password) == 0: + self.flash_error(self.tr("Password is empty.")) + self.ui.current_password_lineedit.setFocus() + return + + ok, msg, field = password_checks(self.account.username, new_password, + new_password2) + if not ok: + self.flash_error(msg) + if field == 'new_password': + self.ui.new_password_lineedit.setFocus() + elif field == 'new_password_confirmation': + self.ui.new_password_confirmation_lineedit.setFocus() + return + + self._enable_password_widgets(False) + self.app.backend.user_change_password( + current_password=current_password, + new_password=new_password) + + def closeEvent(self, event=None): + """ + TRIGGERS: + cancel_button (indirectly via self.close()) + or when window is closed + + Close this dialog & delete ourselves to clean up signals. + """ + PasswordWindow._current_window = None + self.deleteLater() + + @QtCore.Slot() + def _srp_change_password_ok(self): + """ + TRIGGERS: + self._backend.signaler.srp_password_change_ok + + Callback used to display a successfully changed password. + """ + new_password = self.ui.new_password_lineedit.text() + logger.debug("SRP password changed successfully.") + + if self.is_soledad_needed(): + self._backend.soledad_change_password(new_password=new_password) + else: + self._change_password_success() + + @QtCore.Slot() + def _srp_password_change_error(self): + """ + TRIGGERS: + self._backend.signaler.srp_password_change_error + + Unknown problem changing password + """ + msg = self.tr("There was a problem changing the password.") + logger.error(msg) + self._enable_password_widgets(True) + self.flash_error(msg) + + @QtCore.Slot() + def _srp_password_change_badpw(self): + """ + TRIGGERS: + self._backend.signaler.srp_password_change_badpw + + The password the user entered was wrong. + """ + msg = self.tr("You did not enter a correct current password.") + logger.error(msg) + self._enable_password_widgets(True) + self.flash_error(msg) + self.ui.current_password_lineedit.setFocus() + + @QtCore.Slot() + def _soledad_change_password_ok(self): + """ + TRIGGERS: + Signaler.soledad_password_change_ok + + Soledad password change went OK. + """ + logger.debug("Soledad password changed successfully.") + self._change_password_success() + + @QtCore.Slot(unicode) + def _soledad_change_password_problem(self, msg): + """ + TRIGGERS: + Signaler.soledad_password_change_error + + Callback used to display an error on changing password. + + :param msg: the message to show to the user. + :type msg: unicode + """ + logger.error("Error changing soledad password: %s" % (msg,)) + self._enable_password_widgets(True) + self.flash_error(msg) + + @QtCore.Slot() + def _on_soledad_ready(self): + """ + TRIGGERS: + Signaler.soledad_bootstrap_finished + """ + self._enable_password_widgets(True) + self._soledad_ready = True diff --git a/src/leap/bitmask/gui/preferences_account_page.py b/src/leap/bitmask/gui/preferences_account_page.py new file mode 100644 index 00000000..ec6a7716 --- /dev/null +++ b/src/leap/bitmask/gui/preferences_account_page.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Widget for "account" preferences +""" +import logging + +from functools import partial + +from PySide import QtCore, QtGui +from leap.bitmask.gui.ui_preferences_account_page import Ui_PreferencesAccountPage +from leap.bitmask.gui.passwordwindow import PasswordWindow +from leap.bitmask.services import get_service_display_name + +logger = logging.getLogger(__name__) + + +class PreferencesAccountPage(QtGui.QWidget): + + def __init__(self, parent, account, app): + """ + :param parent: parent object of the PreferencesWindow. + :parent type: QWidget + + :param account: user account (user + provider or just provider) + :type account: Account + + :param app: the current App object + :type app: App + """ + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_PreferencesAccountPage() + self.ui.setupUi(self) + + self.account = account + self.app = app + + self._selected_services = set() + self.ui.change_password_label.setVisible(False) + self.ui.provider_services_label.setVisible(False) + + self.ui.change_password_button.clicked.connect( + self._show_change_password) + app.signaler.prov_get_supported_services.connect(self._load_services) + app.backend.provider_get_supported_services(domain=account.domain) + + if account.username is None: + self.ui.change_password_label.setText( + self.tr('You must be logged in to change your password.')) + self.ui.change_password_label.setVisible(True) + self.ui.change_password_button.setEnabled(False) + + @QtCore.Slot(str, int) + def _service_selection_changed(self, service, state): + """ + TRIGGERS: + service_checkbox.stateChanged + + Adds the service to the state if the state is checked, removes + it otherwise + + :param service: service to handle + :type service: str + :param state: state of the checkbox + :type state: int + """ + if state == QtCore.Qt.Checked: + self._selected_services = \ + self._selected_services.union(set([service])) + else: + self._selected_services = \ + self._selected_services.difference(set([service])) + services = list(self._selected_services) + + # We hide the maybe-visible status label after a change + self.ui.provider_services_label.setVisible(False) + + # write to config + self.app.settings.set_enabled_services(self.account.domain, services) + + # emit signal alerting change + self.app.service_selection_changed.emit(self.account, services) + + @QtCore.Slot(str) + def _load_services(self, services): + """ + TRIGGERS: + prov_get_supported_services + + Loads the services that the provider provides into the UI for + the user to enable or disable. + + :param services: list of supported service names + :type services: list of str + """ + services_conf = self.account.services() + + self._selected_services = set() + + # Remove existing checkboxes + # (the new widget is deleted when its parent is deleted. + # We need to loop backwards because removing things from the + # beginning shifts items and changes the order of items in the layout. + # Using `QObject.deleteLater` doesn't seem to work.) + layout = self.ui.provider_services_layout + for i in reversed(range(layout.count())): + layout.itemAt(i).widget().setParent(None) + + # add one checkbox per service and set the current value + # from what is saved in settings. + for service in services: + try: + checkbox = QtGui.QCheckBox( + get_service_display_name(service), self) + self.ui.provider_services_layout.addWidget(checkbox) + checkbox.stateChanged.connect( + partial(self._service_selection_changed, service)) + checkbox.setChecked(service in services_conf) + except ValueError: + logger.error("Something went wrong while trying to " + "load service %s" % (service,)) + + @QtCore.Slot() + def _show_change_password(self): + change_password_window = PasswordWindow(self, self.account, self.app) + change_password_window.show() diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py new file mode 100644 index 00000000..80e8d93e --- /dev/null +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Widget for "email" preferences +""" +import logging + +from PySide import QtCore, QtGui +from leap.bitmask.gui.ui_preferences_email_page import Ui_PreferencesEmailPage + +logger = logging.getLogger(__name__) + + +class PreferencesEmailPage(QtGui.QWidget): + + def __init__(self, parent, account, app): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_PreferencesEmailPage() + self.ui.setupUi(self) + + self.account = account + self.app = app diff --git a/src/leap/bitmask/gui/preferences_vpn_page.py b/src/leap/bitmask/gui/preferences_vpn_page.py new file mode 100644 index 00000000..901116b4 --- /dev/null +++ b/src/leap/bitmask/gui/preferences_vpn_page.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Widget for "vpn" preferences +""" + +from PySide import QtCore, QtGui +from leap.bitmask.gui.ui_preferences_vpn_page import Ui_PreferencesVpnPage + +from leap.bitmask.config.leapsettings import LeapSettings +from leap.bitmask.gui.flashable import Flashable + + +class PreferencesVpnPage(QtGui.QWidget, Flashable): + + """ + Page in the preferences window that shows VPN settings + """ + + def __init__(self, parent, account, app): + """ + :param parent: parent object of the EIPPreferencesWindow. + :type parent: QWidget + + :param account: the currently active account + :type account: Account + + :param app: shared App instance + :type app: App + """ + QtGui.QWidget.__init__(self, parent) + self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") + + self.account = account + self.app = app + + # Load UI + self.ui = Ui_PreferencesVpnPage() + self.ui.setupUi(self) + self.ui.flash_label.setVisible(False) + self.hide_flash() + + # Connections + self.ui.gateways_list.clicked.connect(self._save_selected_gateway) + sig = self.app.signaler + sig.eip_get_gateways_list.connect(self._update_gateways_list) + sig.eip_get_gateways_list_error.connect(self._gateways_list_error) + sig.eip_uninitialized_provider.connect( + self._gateways_list_uninitialized) + + # Trigger update + self.app.backend.eip_get_gateways_list(domain=self.account.domain) + + @QtCore.Slot(str) + def _save_selected_gateway(self, index): + """ + TRIGGERS: + self.ui.gateways_list.clicked + + Saves the new gateway setting to the configuration file. + + :param index: the current index of the selection. + :type current_item: QModelIndex + """ + item = self.ui.gateways_list.currentItem() + + if item.text() == self.AUTOMATIC_GATEWAY_LABEL: + gateway = self.app.settings.GATEWAY_AUTOMATIC + else: + gateway = item.data(QtCore.Qt.UserRole) + self.app.settings.set_selected_gateway(self.account.domain, gateway) + self.app.backend.settings_set_selected_gateway( + provider=self.account.domain, + gateway=gateway) + + @QtCore.Slot(list) + def _update_gateways_list(self, gateways): + """ + TRIGGERS: + Signaler.eip_get_gateways_list + + :param gateways: a list of gateways + :type gateways: list of unicode + + Add the available gateways and select the one stored in + configuration file. + """ + self.ui.gateways_list.clear() + self.ui.gateways_list.addItem(self.AUTOMATIC_GATEWAY_LABEL) + + selected_gateway = self.app.settings.get_selected_gateway( + self.account.domain) + + index = 0 + for idx, (gw_name, gw_ip, gw_country) in enumerate(gateways): + gateway_text = "{0} ({1})".format(gw_name, gw_ip) + item = QtGui.QListWidgetItem(self.ui.gateways_list) + item.setText(gateway_text) + item.setIcon(QtGui.QIcon( + ":/images/countries/%s.png" % (gw_country.lower(),))) + item.setData(QtCore.Qt.UserRole, gw_ip) + if gw_ip == selected_gateway: + index = idx + 1 + self.ui.gateways_list.setCurrentRow(index) + + @QtCore.Slot() + def _gateways_list_error(self): + """ + TRIGGERS: + Signaler.eip_get_gateways_list_error + + An error has occurred retrieving the gateway list + so we inform the user. + """ + self.flash_error( + self.tr("Error loading configuration file.")) + self.ui.gateways_list.setEnabled(False) + + @QtCore.Slot() + def _gateways_list_uninitialized(self): + """ + TRIGGERS: + Signaler.eip_uninitialized_provider + + The requested provider in not initialized yet, so we give the user an + error msg. + """ + self.flash_error( + self.tr("This is an uninitialized provider, please log in first.")) + self.ui.gateways_list.setEnabled(False) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 3c9cd5d0..f1252301 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -20,428 +20,167 @@ Preferences window """ import logging -from functools import partial - from PySide import QtCore, QtGui -from leap.bitmask.config.leapsettings import LeapSettings +from leap.bitmask.services import EIP_SERVICE, MX_SERVICE + from leap.bitmask.gui.ui_preferences import Ui_Preferences -from leap.bitmask.util.credentials import password_checks -from leap.bitmask.services import get_service_display_name, MX_SERVICE +from leap.bitmask.gui.preferences_account_page import PreferencesAccountPage +from leap.bitmask.gui.preferences_vpn_page import PreferencesVpnPage +from leap.bitmask.gui.preferences_email_page import PreferencesEmailPage logger = logging.getLogger(__name__) class PreferencesWindow(QtGui.QDialog): + """ Window that displays the preferences. """ - preferences_saved = QtCore.Signal() - def __init__(self, parent, username, domain, backend, soledad_started, mx, - leap_signaler): + _current_window = None # currently visible preferences window + + def __init__(self, parent, account, app): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget - :param username: the user set in the login widget - :type username: unicode - :param domain: the selected domain in the login widget - :type domain: unicode - :param backend: Backend being used - :type backend: Backend - :param soledad_started: whether soledad has started or not - :type soledad_started: bool - :param mx: whether the current provider provides mx or not. - :type mx: bool + + :param account: the user or provider + :type account: Account + + :param app: the current App object + :type app: App """ QtGui.QDialog.__init__(self, parent) - self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") - - self._username = username - self._domain = domain - self._leap_signaler = leap_signaler - self._backend = backend - self._soledad_started = soledad_started - self._mx_provided = mx - self._settings = LeapSettings() - self._backend_connect() + self.account = account + self.app = app - # Load UI self.ui = Ui_Preferences() self.ui.setupUi(self) - self.ui.lblPasswordChangeStatus.setVisible(False) - self.ui.lblProvidersServicesStatus.setVisible(False) - self._selected_services = set() - - # Connections - self.ui.pbChangePassword.clicked.connect(self._change_password) - self.ui.cbProvidersServices.currentIndexChanged[unicode].connect( - self._populate_services) - - if not self._settings.get_configured_providers(): - self.ui.gbEnabledServices.setEnabled(False) - else: - self._add_configured_providers() - - if self._username is None: - self._not_logged_in() - else: - self.ui.gbPasswordChange.setEnabled(True) - if self._mx_provided: - self._provides_mx() - - self._select_provider_by_name(domain) - - def _not_logged_in(self): - """ - Actions to perform if the user is not logged in. - """ - msg = self.tr( - "In order to change your password you need to be logged in.") - self._set_password_change_status(msg) - self.ui.gbPasswordChange.setEnabled(False) - - def _provides_mx(self): - """ - Actions to perform if the provider provides MX. - """ - pw_enabled = True - enabled_services = self._settings.get_enabled_services(self._domain) - mx_name = get_service_display_name(MX_SERVICE) - - if MX_SERVICE not in enabled_services: - msg = self.tr("You need to enable {0} in order to change " - "the password.".format(mx_name)) - self._set_password_change_status(msg, error=True) - pw_enabled = False - else: - # check if Soledad is bootstrapped - if not self._soledad_started: - msg = self.tr( - "You need to wait until {0} is ready in " - "order to change the password.".format(mx_name)) - self._set_password_change_status(msg) - pw_enabled = False - - self.ui.gbPasswordChange.setEnabled(pw_enabled) - - @QtCore.Slot() - def set_soledad_ready(self): + self.ui.close_button.clicked.connect(self.close) + self.ui.account_label.setText(account.address) + + self.app.service_selection_changed.connect(self._update_icons) + + self._add_icons() + self._add_pages() + self._update_icons(self.account, self.account.services()) + + # only allow a single preferences window at a time. + if PreferencesWindow._current_window is not None: + PreferencesWindow._current_window.close_window() + PreferencesWindow._current_window = self + + def _add_icons(self): + """ + Adds all the icons for the different configuration categories. + Icons are QListWidgetItems added to the nav_widget on the side + of the preferences window. + + A note on sizing of QListWidgetItems + icon_width = list_widget.width - (2 x nav_widget.spacing) - 2 + icon_height = 56 seems to look ok + """ + account_item = QtGui.QListWidgetItem(self.ui.nav_widget) + account_item.setIcon(QtGui.QIcon(":/images/black/32/user.png")) + account_item.setText(self.tr("Account")) + account_item.setTextAlignment( + QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) + account_item.setFlags( + QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + account_item.setSizeHint(QtCore.QSize(98, 56)) + self._account_item = account_item + + vpn_item = QtGui.QListWidgetItem(self.ui.nav_widget) + vpn_item.setHidden(True) + vpn_item.setIcon(QtGui.QIcon(":/images/black/32/earth.png")) + vpn_item.setText(self.tr("VPN")) + vpn_item.setTextAlignment( + QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) + vpn_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + vpn_item.setSizeHint(QtCore.QSize(98, 56)) + self._vpn_item = vpn_item + + email_item = QtGui.QListWidgetItem(self.ui.nav_widget) + email_item.setHidden(True) + email_item.setIcon(QtGui.QIcon(":/images/black/32/email.png")) + email_item.setText(self.tr("Email")) + email_item.setTextAlignment( + QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) + email_item.setFlags( + QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + email_item.setSizeHint(QtCore.QSize(98, 56)) + self._email_item = email_item + + self.ui.nav_widget.currentItemChanged.connect(self._change_page) + self.ui.nav_widget.setCurrentRow(0) + + def _add_pages(self): + """ + Adds the pages for the different configuration categories. + """ + self._account_page = PreferencesAccountPage(self, self.account, self.app) + self._vpn_page = PreferencesVpnPage(self, self.account, self.app) + self._email_page = PreferencesEmailPage(self, self.account, self.app) + + self.ui.pages_widget.addWidget(self._account_page) + self.ui.pages_widget.addWidget(self._vpn_page) + self.ui.pages_widget.addWidget(self._email_page) + + # + # Slots + # + + def closeEvent(self, e): """ TRIGGERS: - parent.soledad_ready + self.ui.close_button.clicked + (since self.close() will trigger closeEvent) + whenever the window is closed - It notifies when the soledad object as ready to use. + Close this dialog and destroy it. """ - self.ui.lblPasswordChangeStatus.setVisible(False) - self.ui.gbPasswordChange.setEnabled(True) + PreferencesWindow._current_window = None - def _set_password_change_status(self, status, error=False, success=False): - """ - Sets the status label for the password change. - - :param status: status message to display, can be HTML - :type status: str - """ - if error: - status = "<font color='red'><b>%s</b></font>" % (status,) - elif success: - status = "<font color='green'><b>%s</b></font>" % (status,) - - if not self.ui.gbPasswordChange.isEnabled(): - status = "<font color='black'>%s</font>" % (status,) - - self.ui.lblPasswordChangeStatus.setVisible(True) - self.ui.lblPasswordChangeStatus.setText(status) - - def _set_changing_password(self, disable): - """ - Enables or disables the widgets in the password change group box. - - :param disable: True if the widgets should be disabled and - it displays the status label that shows that is - changing the password. - False if they should be enabled. - :type disable: bool - """ - if disable: - self._set_password_change_status(self.tr("Changing password...")) - - self.ui.leCurrentPassword.setEnabled(not disable) - self.ui.leNewPassword.setEnabled(not disable) - self.ui.leNewPassword2.setEnabled(not disable) - self.ui.pbChangePassword.setEnabled(not disable) + # deleteLater does not seem to cascade to items in stackLayout + # (even with QtCore.Qt.WA_DeleteOnClose attribute). + # so, here we call deleteLater() explicitly: + self._account_page.deleteLater() + self._vpn_page.deleteLater() + self._email_page.deleteLater() + self.deleteLater() @QtCore.Slot() - def _change_password(self): + def _change_page(self, current, previous): """ TRIGGERS: - self.ui.pbChangePassword.clicked - - Changes the user's password if the inputboxes are correctly filled. - """ - username = self._username - current_password = self.ui.leCurrentPassword.text() - new_password = self.ui.leNewPassword.text() - new_password2 = self.ui.leNewPassword2.text() - - ok, msg = password_checks(username, new_password, new_password2) - - if not ok: - self._set_changing_password(False) - self._set_password_change_status(msg, error=True) - self.ui.leNewPassword.setFocus() - return - - self._set_changing_password(True) - self._backend.user_change_password(current_password=current_password, - new_password=new_password) - - @QtCore.Slot() - def _srp_change_password_ok(self): - """ - TRIGGERS: - self._backend.signaler.srp_password_change_ok - - Callback used to display a successfully changed password. - """ - new_password = self.ui.leNewPassword.text() - logger.debug("SRP password changed successfully.") - - if self._mx_provided: - self._backend.soledad_change_password(new_password=new_password) - else: - self._change_password_success() + self.ui.nav_widget.currentItemChanged - @QtCore.Slot(unicode) - def _srp_change_password_problem(self, msg): - """ - TRIGGERS: - self._backend.signaler.srp_password_change_error - self._backend.signaler.srp_password_change_badpw + Changes what page is displayed. - Callback used to display an error on changing password. + :param current: the currently selected item (might be None?) + :type current: PySide.QtGui.QListWidgetItem - :param msg: the message to show to the user. - :type msg: unicode + :param previous: the previously selected item (might be None) + :type previous: PySide.QtGui.QListWidgetItem """ - logger.error("Error changing password") - self._set_password_change_status(msg, error=True) - self._set_changing_password(False) + if not current: + current = previous + self.ui.pages_widget.setCurrentIndex(self.ui.nav_widget.row(current)) - @QtCore.Slot() - def _soledad_change_password_ok(self): + @QtCore.Slot(object, list) + def _update_icons(self, account, services): """ TRIGGERS: - Signaler.soledad_password_change_ok - - Soledad password change went OK. - """ - logger.debug("Soledad password changed successfully.") - self._change_password_success() + self.app.service_selection_changed - def _change_password_success(self): + Change which icons are visible. """ - Callback used to display a successfully changed password. - """ - logger.debug("Soledad password changed successfully.") - - self._set_password_change_status( - self.tr("Password changed successfully."), success=True) - self._clear_password_inputs() - self._set_changing_password(False) - - @QtCore.Slot(unicode) - def _soledad_change_password_problem(self, msg): - """ - TRIGGERS: - Signaler.soledad_password_change_error - - Callback used to display an error on changing password. - - :param msg: the message to show to the user. - :type msg: unicode - """ - logger.error("Error changing soledad password") - self._set_password_change_status(msg, error=True) - self._set_changing_password(False) - - def _clear_password_inputs(self): - """ - Clear the contents of the inputs. - """ - self.ui.leCurrentPassword.setText("") - self.ui.leNewPassword.setText("") - self.ui.leNewPassword2.setText("") - - def _set_providers_services_status(self, status, success=False): - """ - Sets the status label for the password change. - - :param status: status message to display, can be HTML - :type status: str - :param success: is set to True if we should display the - message as green - :type success: bool - """ - if success: - status = "<font color='green'><b>%s</b></font>" % (status,) - - self.ui.lblProvidersServicesStatus.setVisible(True) - self.ui.lblProvidersServicesStatus.setText(status) - - def _add_configured_providers(self): - """ - Add the client's configured providers to the providers combo boxes. - """ - self.ui.cbProvidersServices.clear() - for provider in self._settings.get_configured_providers(): - self.ui.cbProvidersServices.addItem(provider) - - def _select_provider_by_name(self, name): - """ - Given a provider name/domain, selects it in the combobox. - - :param name: name or domain for the provider - :type name: str - """ - provider_index = self.ui.cbProvidersServices.findText(name) - self.ui.cbProvidersServices.setCurrentIndex(provider_index) - - @QtCore.Slot(str, int) - def _service_selection_changed(self, service, state): - """ - TRIGGERS: - service_checkbox.stateChanged - - Adds the service to the state if the state is checked, removes - it otherwise - - :param service: service to handle - :type service: str - :param state: state of the checkbox - :type state: int - """ - if state == QtCore.Qt.Checked: - self._selected_services = \ - self._selected_services.union(set([service])) - else: - self._selected_services = \ - self._selected_services.difference(set([service])) - - # We hide the maybe-visible status label after a change - self.ui.lblProvidersServicesStatus.setVisible(False) - - @QtCore.Slot(str) - def _populate_services(self, domain): - """ - TRIGGERS: - self.ui.cbProvidersServices.currentIndexChanged[unicode] - - Fill the services list with the selected provider's services. - - :param domain: the domain of the provider to load services from. - :type domain: str - """ - # We hide the maybe-visible status label after a change - self.ui.lblProvidersServicesStatus.setVisible(False) - - if not domain: + if account != self.account: return - # set the proper connection for the 'save' button - try: - self.ui.pbSaveServices.clicked.disconnect() - except RuntimeError: - pass # Signal was not connected - - save_services = partial(self._save_enabled_services, domain) - self.ui.pbSaveServices.clicked.connect(save_services) - - self._backend.provider_get_supported_services(domain=domain) - - @QtCore.Slot(str) - def _load_services(self, services): - """ - TRIGGERS: - self.ui.cbProvidersServices.currentIndexChanged[unicode] - - Loads the services that the provider provides into the UI for - the user to enable or disable. - - :param domain: the domain of the provider to load services from. - :type domain: str - """ - domain = self.ui.cbProvidersServices.currentText() - services_conf = self._settings.get_enabled_services(domain) - - # discard changes if other provider is selected - self._selected_services = set() - - # from: http://stackoverflow.com/a/13103617/687989 - # remove existing checkboxes - layout = self.ui.vlServices - for i in reversed(range(layout.count())): - layout.itemAt(i).widget().setParent(None) - - # add one checkbox per service and set the current configured value - for service in services: - try: - checkbox = QtGui.QCheckBox(self) - service_label = get_service_display_name(service) - checkbox.setText(service_label) - - self.ui.vlServices.addWidget(checkbox) - checkbox.stateChanged.connect( - partial(self._service_selection_changed, service)) - - checkbox.setChecked(service in services_conf) - except ValueError: - logger.error("Something went wrong while trying to " - "load service %s" % (service,)) - - @QtCore.Slot(str) - def _save_enabled_services(self, provider): - """ - TRIGGERS: - self.ui.pbSaveServices.clicked - - Saves the new enabled services settings to the configuration file. - - :param provider: the provider config that we need to save. - :type provider: str - """ - services = list(self._selected_services) - self._settings.set_enabled_services(provider, services) - - msg = self.tr( - "Services settings for provider '{0}' saved.".format(provider)) - logger.debug(msg) - self._set_providers_services_status(msg, success=True) - self.preferences_saved.emit() - - def _backend_connect(self): - """ - Helper to connect to backend signals - """ - sig = self._leap_signaler - - sig.prov_get_supported_services.connect(self._load_services) - - sig.srp_password_change_ok.connect(self._srp_change_password_ok) - - pwd_change_error = lambda: self._srp_change_password_problem( - self.tr("There was a problem changing the password.")) - sig.srp_password_change_error.connect(pwd_change_error) - - pwd_change_badpw = lambda: self._srp_change_password_problem( - self.tr("You did not enter a correct current password.")) - sig.srp_password_change_badpw.connect(pwd_change_badpw) - - sig.soledad_password_change_ok.connect( - self._soledad_change_password_ok) - - sig.soledad_password_change_error.connect( - self._soledad_change_password_problem) + self._vpn_item.setHidden(not EIP_SERVICE in services) + # self._email_item.setHidden(not MX_SERVICE in services) + # ^^ disable email for now, there is nothing there yet. diff --git a/src/leap/bitmask/gui/providers.py b/src/leap/bitmask/gui/providers.py index b3eb8620..6954411f 100644 --- a/src/leap/bitmask/gui/providers.py +++ b/src/leap/bitmask/gui/providers.py @@ -109,6 +109,6 @@ class Providers(QtCore.QObject): """ self._providers_indexes.append(idx) is_wizard = idx == (self._combo.count() - 1) - self._provider_changed.emit(is_wizard) if is_wizard: self.restore_previous_provider() + self._provider_changed.emit(is_wizard) diff --git a/src/leap/bitmask/gui/ui/eippreferences.ui b/src/leap/bitmask/gui/ui/eippreferences.ui deleted file mode 100644 index 1a5fcd24..00000000 --- a/src/leap/bitmask/gui/ui/eippreferences.ui +++ /dev/null @@ -1,102 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>EIPPreferences</class> - <widget class="QDialog" name="EIPPreferences"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>435</width> - <height>144</height> - </rect> - </property> - <property name="windowTitle"> - <string>Encrypted Internet Preferences</string> - </property> - <property name="windowIcon"> - <iconset resource="../../../../../data/resources/icons.qrc"> - <normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset> - </property> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="0" column="0"> - <widget class="QGroupBox" name="gbGatewaySelector"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="title"> - <string>Select gateway for provider</string> - </property> - <property name="checkable"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <widget class="QLabel" name="lblSelectProvider"> - <property name="text"> - <string>Select &provider:</string> - </property> - <property name="buddy"> - <cstring>cbProvidersGateway</cstring> - </property> - </widget> - </item> - <item row="0" column="1" colspan="2"> - <widget class="QComboBox" name="cbProvidersGateway"> - <item> - <property name="text"> - <string><Select provider></string> - </property> - </item> - </widget> - </item> - <item row="7" column="2"> - <widget class="QPushButton" name="pbSaveGateway"> - <property name="text"> - <string>&Save this provider settings</string> - </property> - </widget> - </item> - <item row="4" column="0" colspan="3"> - <widget class="QLabel" name="lblProvidersGatewayStatus"> - <property name="text"> - <string>< Providers Gateway Status ></string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Select &gateway:</string> - </property> - <property name="buddy"> - <cstring>cbGateways</cstring> - </property> - </widget> - </item> - <item row="1" column="1" colspan="2"> - <widget class="QComboBox" name="cbGateways"> - <item> - <property name="text"> - <string>Automatic</string> - </property> - </item> - </widget> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - <tabstops> - <tabstop>cbProvidersGateway</tabstop> - <tabstop>cbGateways</tabstop> - <tabstop>pbSaveGateway</tabstop> - </tabstops> - <resources> - <include location="../../../../../data/resources/icons.qrc"/> - </resources> - <connections/> -</ui> diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index 92c13d15..b1d68c4a 100644 --- a/src/leap/bitmask/gui/ui/mainwindow.ui +++ b/src/leap/bitmask/gui/ui/mainwindow.ui @@ -75,7 +75,7 @@ <x>0</x> <y>0</y> <width>524</width> - <height>540</height> + <height>549</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout"> @@ -306,7 +306,7 @@ <x>0</x> <y>0</y> <width>524</width> - <height>25</height> + <height>21</height> </rect> </property> <widget class="QMenu" name="menuFile"> @@ -317,7 +317,6 @@ <addaction name="action_advanced_key_management"/> <addaction name="separator"/> <addaction name="action_preferences"/> - <addaction name="action_eip_preferences"/> <addaction name="separator"/> <addaction name="action_quit"/> </widget> @@ -338,12 +337,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Account Preferences...</string> - </property> - </action> - <action name="action_eip_preferences"> - <property name="text"> - <string>Internet Preferences...</string> + <string>Pr&eferences...</string> </property> </action> <action name="action_quit"> @@ -378,11 +372,14 @@ </action> <action name="action_advanced_key_management"> <property name="enabled"> - <bool>true</bool> + <bool>false</bool> </property> <property name="text"> <string>Advanced Key Management</string> </property> + <property name="visible"> + <bool>false</bool> + </property> </action> </widget> <resources> diff --git a/src/leap/bitmask/gui/ui/password_change.ui b/src/leap/bitmask/gui/ui/password_change.ui new file mode 100644 index 00000000..b7ceac38 --- /dev/null +++ b/src/leap/bitmask/gui/ui/password_change.ui @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PasswordChange</class> + <widget class="QDialog" name="PasswordChange"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>459</width> + <height>231</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Change Password</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QGridLayout" name="grid_layout"> + <item row="0" column="0"> + <widget class="QLabel" name="username_label"> + <property name="text"> + <string>Username:</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="new_password_label"> + <property name="text"> + <string>New password:</string> + </property> + <property name="buddy"> + <cstring>new_password_lineedit</cstring> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLineEdit" name="new_password_lineedit"> + <property name="echoMode"> + <enum>QLineEdit::Password</enum> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="new_password_confirmation_label"> + <property name="text"> + <string>Re-enter new password:</string> + </property> + <property name="buddy"> + <cstring>new_password_confirmation_lineedit</cstring> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="current_password_label"> + <property name="text"> + <string>Current password:</string> + </property> + <property name="buddy"> + <cstring>current_password_lineedit</cstring> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLineEdit" name="new_password_confirmation_lineedit"> + <property name="echoMode"> + <enum>QLineEdit::Password</enum> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="current_password_lineedit"> + <property name="echoMode"> + <enum>QLineEdit::Password</enum> + </property> + </widget> + </item> + <item row="2" column="1"> + <spacer name="spacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>10</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLineEdit" name="username_lineedit"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="flash_label"> + <property name="text"> + <string><flash_label></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <spacer name="spacer2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="button_layout"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="cancel_button"> + <property name="text"> + <string>Close</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="ok_button"> + <property name="text"> + <string>OK</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + <property name="default"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <tabstops> + <tabstop>username_lineedit</tabstop> + <tabstop>current_password_lineedit</tabstop> + <tabstop>new_password_lineedit</tabstop> + <tabstop>new_password_confirmation_lineedit</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/src/leap/bitmask/gui/ui/preferences.ui b/src/leap/bitmask/gui/ui/preferences.ui index cd4d3a77..5e30ea57 100644 --- a/src/leap/bitmask/gui/ui/preferences.ui +++ b/src/leap/bitmask/gui/ui/preferences.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>503</width> - <height>401</height> + <width>520</width> + <height>439</height> </rect> </property> <property name="windowTitle"> @@ -17,159 +17,97 @@ <iconset resource="../../../../../data/resources/icons.qrc"> <normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset> </property> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="4" column="0"> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> + <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1,0"> + <property name="spacing"> + <number>6</number> + </property> + <item> + <widget class="QLabel" name="account_label"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> + <property name="text"> + <string>user@example.org</string> </property> - </spacer> + </widget> </item> - <item row="0" column="0"> - <widget class="QGroupBox" name="gbPasswordChange"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="title"> - <string>Password Change</string> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> - <layout class="QFormLayout" name="formLayout"> - <property name="fieldGrowthPolicy"> - <enum>QFormLayout::ExpandingFieldsGrow</enum> - </property> - <item row="0" column="0"> - <widget class="QLabel" name="lblCurrentPassword"> - <property name="text"> - <string>&Current password:</string> - </property> - <property name="buddy"> - <cstring>leCurrentPassword</cstring> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QLineEdit" name="leCurrentPassword"> - <property name="echoMode"> - <enum>QLineEdit::Password</enum> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="lblNewPassword"> - <property name="text"> - <string>&New password:</string> - </property> - <property name="buddy"> - <cstring>leNewPassword</cstring> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLineEdit" name="leNewPassword"> - <property name="echoMode"> - <enum>QLineEdit::Password</enum> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="lblNewPassword2"> - <property name="text"> - <string>&Re-enter new password:</string> - </property> - <property name="buddy"> - <cstring>leNewPassword2</cstring> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLineEdit" name="leNewPassword2"> - <property name="echoMode"> - <enum>QLineEdit::Password</enum> - </property> - </widget> - </item> - <item row="4" column="1"> - <widget class="QPushButton" name="pbChangePassword"> - <property name="text"> - <string>Change</string> - </property> - </widget> - </item> - <item row="3" column="0" colspan="2"> - <widget class="QLabel" name="lblPasswordChangeStatus"> - <property name="text"> - <string><Password change status></string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - </layout> </widget> </item> - <item row="3" column="0"> - <widget class="QGroupBox" name="gbEnabledServices"> - <property name="title"> - <string>Enabled services</string> + <item> + <layout class="QHBoxLayout" name="horizontal_layout" stretch="0,0"> + <property name="spacing"> + <number>12</number> </property> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="3" column="1"> - <widget class="QPushButton" name="pbSaveServices"> - <property name="text"> - <string>Save this provider settings</string> - </property> - </widget> - </item> - <item row="2" column="0" colspan="2"> - <widget class="QGroupBox" name="gbServices"> - <property name="title"> - <string>Services</string> - </property> - <property name="flat"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout_4"> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="vlServices"/> - </item> - </layout> - </widget> - </item> - <item row="0" column="1"> - <widget class="QComboBox" name="cbProvidersServices"> - <item> - <property name="text"> - <string><Select provider></string> - </property> - </item> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Select provider:</string> - </property> - </widget> - </item> - <item row="1" column="0" colspan="2"> - <widget class="QLabel" name="lblProvidersServicesStatus"> - <property name="text"> - <string>< Providers Services Status ></string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - </layout> - </widget> + <item> + <widget class="QListWidget" name="nav_widget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>120</width> + <height>16777215</height> + </size> + </property> + <property name="iconSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="movement"> + <enum>QListView::Static</enum> + </property> + <property name="spacing"> + <number>10</number> + </property> + <property name="viewMode"> + <enum>QListView::IconMode</enum> + </property> + <property name="uniformItemSizes"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QStackedWidget" name="pages_widget"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="button_layout" stretch="0,0"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="close_button"> + <property name="text"> + <string>Close</string> + </property> + </widget> + </item> + </layout> </item> </layout> </widget> diff --git a/src/leap/bitmask/gui/ui/preferences_account_page.ui b/src/leap/bitmask/gui/ui/preferences_account_page.ui new file mode 100644 index 00000000..9b6d885b --- /dev/null +++ b/src/leap/bitmask/gui/ui/preferences_account_page.ui @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PreferencesAccountPage</class> + <widget class="QWidget" name="PreferencesAccountPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>462</width> + <height>371</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="provider_services_box"> + <property name="title"> + <string>Services</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="1" column="0"> + <layout class="QVBoxLayout" name="provider_services_layout"/> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="provider_services_label"> + <property name="text"> + <string><provider_services_label></string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Preferred</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>15</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Password</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QPushButton" name="change_password_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Change Password</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="change_password_label"> + <property name="text"> + <string><change_password_label></string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/leap/bitmask/gui/ui/preferences_email_page.ui b/src/leap/bitmask/gui/ui/preferences_email_page.ui new file mode 100644 index 00000000..41b3c28d --- /dev/null +++ b/src/leap/bitmask/gui/ui/preferences_email_page.ui @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PreferencesEmailPage</class> + <widget class="QWidget" name="PreferencesEmailPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <widget class="QPushButton" name="pushButton"> + <property name="geometry"> + <rect> + <x>250</x> + <y>210</y> + <width>98</width> + <height>27</height> + </rect> + </property> + <property name="text"> + <string>PushButton</string> + </property> + </widget> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/leap/bitmask/gui/ui/preferences_vpn_page.ui b/src/leap/bitmask/gui/ui/preferences_vpn_page.ui new file mode 100644 index 00000000..1bf3a060 --- /dev/null +++ b/src/leap/bitmask/gui/ui/preferences_vpn_page.ui @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PreferencesVpnPage</class> + <widget class="QWidget" name="PreferencesVpnPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>362</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="flash_label"> + <property name="text"> + <string><flash_label></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="heading_label"> + <property name="text"> + <string>Default VPN Gateway:</string> + </property> + <property name="buddy"> + <cstring>gateways_list</cstring> + </property> + </widget> + </item> + <item> + <widget class="QListWidget" name="gateways_list"> + </widget> + </item> + <item> + <widget class="QLabel" name="tip_label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>You must reconnect for changes to take effect.</string> + </property> + <property name="scaledContents"> + <bool>false</bool> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="buddy"> + <cstring>gateways_list</cstring> + </property> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="../../../../../data/resources/flags.qrc"/> + </resources> + <connections/> +</ui> diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 0223c053..ff9cae55 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -42,6 +42,7 @@ logger = logging.getLogger(__name__) class Wizard(QtGui.QWizard): + """ First run wizard to register a user and setup a provider """ @@ -96,11 +97,6 @@ class Wizard(QtGui.QWizard): # this details are set when the provider download is complete. self._provider_details = None - # We will store a reference to the defers for eventual use - # (eg, to cancel them) but not doing anything with them right now. - self._provider_select_defer = None - self._provider_setup_defer = None - self._connect_and_track(self.currentIdChanged, self._current_id_changed) @@ -322,7 +318,8 @@ class Wizard(QtGui.QWizard): user_ok, msg = username_checks(username) if user_ok: - pass_ok, msg = password_checks(username, password, password2) + pass_ok, msg, field = password_checks( + username, password, password2) if user_ok and pass_ok: self._set_register_status(self.tr("Starting registration...")) @@ -479,8 +476,7 @@ class Wizard(QtGui.QWizard): self.button(QtGui.QWizard.BackButton).clearFocus() self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) - self._provider_select_defer = self._backend.\ - provider_setup(provider=self._domain) + self._backend.provider_setup(provider=self._domain) @QtCore.Slot(bool) def _skip_provider_checks(self, skip): @@ -678,8 +674,9 @@ class Wizard(QtGui.QWizard): Loads the services that the provider provides into the UI for the user to enable or disable. """ - self.ui.grpServices.setTitle( - self.tr("Services by {0}").format(self._provider_details['domain'])) + title = self.tr("Services by {0}").format( + self._provider_details['domain']) + self.ui.grpServices.setTitle(title) services = get_supported(self._provider_details['services']) @@ -724,8 +721,7 @@ class Wizard(QtGui.QWizard): if not self._provider_setup_ok: self._reset_provider_setup() self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON) - self._provider_setup_defer = self._backend.\ - provider_bootstrap(provider=self._domain) + self._backend.provider_bootstrap(provider=self._domain) if pageId == self.PRESENT_PROVIDER_PAGE: details = self._provider_details diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index f56b9330..1d6bb1d0 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -29,9 +29,9 @@ from PySide import QtGui, QtCore from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.services.eip import get_vpn_launcher -from leap.bitmask.services.eip.linuxvpnlauncher import LinuxVPNLauncher from leap.bitmask.services.eip.darwinvpnlauncher import DarwinVPNLauncher from leap.bitmask.util import first +from leap.bitmask.util.privilege_policies import LinuxPolicyChecker logger = logging.getLogger(__name__) @@ -127,12 +127,15 @@ def check_missing(): complain_missing = True launcher = get_vpn_launcher() - missing_scripts = launcher.missing_updown_scripts - missing_other = launcher.missing_other_files + missing_scripts = launcher.missing_updown_scripts() + missing_other = launcher.missing_other_files() - logger.debug("MISSING OTHER: %s" % (str(missing_other()))) + if missing_scripts: + logger.warning("Missing scripts: %s" % (missing_scripts)) + if missing_other: + logger.warning("Missing other files: %s" % (missing_other)) - missing_some = missing_scripts() or missing_other() + missing_some = missing_scripts or missing_other if alert_missing and missing_some: msg = get_missing_helpers_dialog() ret = msg.exec_() @@ -164,13 +167,20 @@ def check_missing(): logger.debug( "Setting alert_missing_scripts to False, we will not " "ask again") + init_signals.eip_missing_helpers.emit() config.set_alert_missing_scripts(False) if complain_missing and missing_some: - missing = missing_scripts() + missing_other() + missing = missing_scripts + missing_other msg = _get_missing_complain_dialog(missing) ret = msg.exec_() + # If there is some missing file and we don't want to complain, we emit the + # 'missing helpers' signal so the eip status can show that some files are + # missing. + if missing_some and not alert_missing and not complain_missing: + init_signals.eip_missing_helpers.emit() + # # windows initializers # @@ -435,7 +445,6 @@ def _linux_install_missing_scripts(badexec, notfound): success = False installer_path = os.path.abspath( os.path.join(os.getcwd(), "apps", "eip", "files")) - launcher = LinuxVPNLauncher install_helper = "leap-install-helper.sh" install_helper_path = os.path.join(installer_path, install_helper) @@ -446,7 +455,8 @@ def _linux_install_missing_scripts(badexec, notfound): if os.path.isdir(installer_path): try: - pkexec = first(launcher.maybe_pkexec()) + policyChecker = LinuxPolicyChecker() + pkexec = first(policyChecker.maybe_pkexec()) cmdline = ["%s %s %s" % ( pkexec, install_helper_path, install_opts)] diff --git a/src/leap/bitmask/platform_init/locks.py b/src/leap/bitmask/platform_init/locks.py index 78ebf4cd..ac45a5ce 100644 --- a/src/leap/bitmask/platform_init/locks.py +++ b/src/leap/bitmask/platform_init/locks.py @@ -22,11 +22,11 @@ import errno import os import platform -from leap.bitmask import platform_init +from leap.bitmask.platform_init import IS_WIN, IS_UNIX from leap.common.events import signal as signal_event from leap.common.events import events_pb2 as proto -if platform_init.IS_UNIX: +if IS_UNIX: from fcntl import flock, LOCK_EX, LOCK_NB else: # WINDOWS import datetime @@ -40,7 +40,7 @@ else: # WINDOWS logger = logging.getLogger(__name__) -if platform_init.IS_UNIX: +if IS_UNIX: class UnixLock(object): """ @@ -48,14 +48,13 @@ if platform_init.IS_UNIX: See man 2 flock """ - def __init__(self, path): + _LOCK_FILE = '/tmp/bitmask.lock' + + def __init__(self): """ - iniializes t he UnixLock with the path of the - desired lockfile + Initialize the UnixLock. """ - self._fd = None - self.path = path def get_lock(self): """ @@ -77,7 +76,7 @@ if platform_init.IS_UNIX: :rtype: bool """ - self._fd = os.open(self.path, os.O_CREAT | os.O_RDWR) + self._fd = os.open(self._LOCK_FILE, os.O_CREAT | os.O_RDWR) try: flock(self._fd, LOCK_EX | LOCK_NB) @@ -102,6 +101,21 @@ if platform_init.IS_UNIX: gotit, pid = self._get_lock_and_pid() return pid == os.getpid() + @classmethod + def release_lock(self): + """ + Release the lock. + + :return: True if the lock was released, False otherwise + :rtype: bool + """ + try: + os.remove(self._LOCK_FILE) + return True + except Exception as e: + logger.debug("Problem removing lock, {0!r}".format(e)) + return False + def _get_lock_and_pid(self): """ Tries to get a lock over the file. @@ -109,7 +123,6 @@ if platform_init.IS_UNIX: :rtype: tuple """ - if self._get_lock(): self._write_to_pidfile() return True, None @@ -121,9 +134,7 @@ if platform_init.IS_UNIX: Tries to read pid from the pidfile, returns False if no content found. """ - - pidfile = os.read( - self._fd, 16) + pidfile = os.read(self._fd, 16) if not pidfile: return False @@ -144,7 +155,7 @@ if platform_init.IS_UNIX: os.fsync(fd) -if platform_init.IS_WIN: +if IS_WIN: # Time to wait (in secs) before assuming a raise window signal has not been # ack-ed. @@ -348,17 +359,15 @@ def we_are_the_one_and_only(): :rtype: bool """ - _sys = platform.system() - - if _sys in ("Linux", "Darwin"): - locker = UnixLock('/tmp/bitmask.lock') + if IS_UNIX: + locker = UnixLock() locker.get_lock() we_are_the_one = locker.locked_by_us if not we_are_the_one: signal_event(proto.RAISE_WINDOW) return we_are_the_one - elif _sys == "Windows": + elif IS_WIN: locker = WindowsLock() locker.get_lock() we_are_the_one = locker.locked_by_us @@ -398,6 +407,16 @@ def we_are_the_one_and_only(): else: logger.warning("Multi-instance checker " - "not implemented for %s" % (_sys)) + "not implemented for %s" % (platform.system())) # lies, lies, lies... return True + + +def release_lock(): + """ + Release the acquired lock. + """ + if IS_WIN: + WindowsLock.release_all_locks() + elif IS_UNIX: + UnixLock.release_lock() diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py index 0ee56628..01dd7449 100644 --- a/src/leap/bitmask/services/eip/conductor.py +++ b/src/leap/bitmask/services/eip/conductor.py @@ -124,6 +124,12 @@ class EIPConductor(object): """ self._backend.tear_fw_down() + def terminate(self): + """ + Turn off VPN + """ + self.qtsigs.do_disconnect_signal.emit() + @QtCore.Slot() def _start_eip(self): """ diff --git a/src/leap/bitmask/services/eip/eipconfig.py b/src/leap/bitmask/services/eip/eipconfig.py index 37c0c8ae..5b51d12e 100644 --- a/src/leap/bitmask/services/eip/eipconfig.py +++ b/src/leap/bitmask/services/eip/eipconfig.py @@ -113,39 +113,38 @@ class VPNGatewaySelector(object): """ Return the existing gateways, sorted by timezone proximity. - :rtype: list of tuples (location, ip) - (str, IPv4Address or IPv6Address object) + :rtype: list of tuples (label, ip, country_code) + (str, IPv4Address or IPv6Address object, str) """ gateways_timezones = [] locations = self._eipconfig.get_locations() gateways = self._eipconfig.get_gateways() for idx, gateway in enumerate(gateways): - gateway_location = gateway.get('location') - gateway_distance = 99 # if hasn't location -> should go last - - if gateway_location is not None: - timezone = locations[gateway['location']]['timezone'] - gateway_name = locations[gateway['location']].get('name', None) - if gateway_name is not None: - gateway_location = gateway_name - - gw_offset = int(timezone) - if gw_offset in self.equivalent_timezones: - gw_offset = self.equivalent_timezones[gw_offset] - - gateway_distance = self._get_timezone_distance(gw_offset) + distance = 99 # if hasn't location -> should go last + location = locations.get(gateway.get('location')) + label = gateway.get('location', 'Unknown') + country = 'XX' + if location is not None: + country = location.get('country_code', 'XX') + label = location.get('name', label) + timezone = location.get('timezone') + if timezone is not None: + offset = int(timezone) + if offset in self.equivalent_timezones: + offset = self.equivalent_timezones[offset] + distance = self._get_timezone_distance(offset) ip = self._eipconfig.get_gateway_ip(idx) - gateways_timezones.append((ip, gateway_distance, gateway_location)) + gateways_timezones.append((ip, distance, label, country)) gateways_timezones = sorted(gateways_timezones, key=lambda gw: gw[1]) - gateways = [] - for ip, distance, location in gateways_timezones: - gateways.append((location, ip)) + result = [] + for ip, distance, label, country in gateways_timezones: + result.append((label, ip, country)) - return gateways + return result def get_gateways(self): """ @@ -153,7 +152,7 @@ class VPNGatewaySelector(object): :rtype: list of IPv4Address or IPv6Address object. """ - gateways = [ip for location, ip in self.get_gateways_list()][:4] + gateways = [gateway[1] for gateway in self.get_gateways_list()][:4] return gateways def get_gateways_country_code(self): diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index b6e47f25..a3ab408b 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -20,17 +20,15 @@ Linux VPN launcher implementation. import commands import logging import os -import subprocess import sys -import time from leap.bitmask.config import flags from leap.bitmask.util.privilege_policies import LinuxPolicyChecker -from leap.common.files import which +from leap.bitmask.util.privilege_policies import NoPolkitAuthAgentAvailable +from leap.bitmask.util.privilege_policies import NoPkexecAvailable from leap.bitmask.services.eip.vpnlauncher import VPNLauncher from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException from leap.bitmask.util import get_path_prefix, force_eval -from leap.common.check import leap_assert from leap.bitmask.util import first logger = logging.getLogger(__name__) @@ -46,66 +44,11 @@ class EIPNoPkexecAvailable(VPNLauncherException): pass -def _is_pkexec_in_system(): - """ - Checks the existence of the pkexec binary in system. - """ - pkexec_path = which('pkexec') - if len(pkexec_path) == 0: - return False - return True - - -def _is_auth_agent_running(): - """ - Checks if a polkit daemon is running. - - :return: True if it's running, False if it's not. - :rtype: boolean - """ - # Note that gnome-shell does not uses a separate process for the - # polkit-agent, it uses a polkit-agent within its own process so we can't - # ps-grep a polkit process, we can ps-grep gnome-shell itself. - - # the [x] thing is to avoid grep match itself - polkit_options = [ - 'ps aux | grep "polkit-[g]nome-authentication-agent-1"', - 'ps aux | grep "polkit-[k]de-authentication-agent-1"', - 'ps aux | grep "polkit-[m]ate-authentication-agent-1"', - 'ps aux | grep "[l]xpolkit"', - 'ps aux | grep "[g]nome-shell"', - 'ps aux | grep "[f]ingerprint-polkit-agent"', - ] - is_running = [commands.getoutput(cmd) for cmd in polkit_options] - - return any(is_running) - - -def _try_to_launch_agent(): - """ - Tries to launch a polkit daemon. - """ - env = None - if flags.STANDALONE: - env = {"PYTHONPATH": os.path.abspath('../../../../lib/')} - try: - # We need to quote the command because subprocess call - # will do "sh -c 'foo'", so if we do not quoute it we'll end - # up with a invocation to the python interpreter. And that - # is bad. - logger.debug("Trying to launch polkit agent") - subprocess.call(["python -m leap.bitmask.util.polkit_agent"], - shell=True, env=env) - except Exception as exc: - logger.exception(exc) - - SYSTEM_CONFIG = "/etc/leap" leapfile = lambda f: "%s/%s" % (SYSTEM_CONFIG, f) class LinuxVPNLauncher(VPNLauncher): - PKEXEC_BIN = 'pkexec' # The following classes depend on force_eval to be called against # the classes, to get the evaluation of the standalone flag on runtine. @@ -130,36 +73,6 @@ class LinuxVPNLauncher(VPNLauncher): OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT, OPENVPN_BIN_PATH) @classmethod - def maybe_pkexec(kls): - """ - Checks whether pkexec is available in the system, and - returns the path if found. - - Might raise: - EIPNoPkexecAvailable, - EIPNoPolkitAuthAgentAvailable. - - :returns: a list of the paths where pkexec is to be found - :rtype: list - """ - if _is_pkexec_in_system(): - if not _is_auth_agent_running(): - _try_to_launch_agent() - time.sleep(2) - if _is_auth_agent_running(): - pkexec_possibilities = which(kls.PKEXEC_BIN) - leap_assert(len(pkexec_possibilities) > 0, - "We couldn't find pkexec") - return pkexec_possibilities - else: - logger.warning("No polkit auth agent found. pkexec " + - "will use its own auth agent.") - raise EIPNoPolkitAuthAgentAvailable() - else: - logger.warning("System has no pkexec") - raise EIPNoPkexecAvailable() - - @classmethod def get_vpn_command(kls, eipconfig, providerconfig, socket_host, socket_port="unix", openvpn_verb=1): """ @@ -194,7 +107,13 @@ class LinuxVPNLauncher(VPNLauncher): command.insert(1, "openvpn") command.insert(2, "start") - pkexec = kls.maybe_pkexec() + policyChecker = LinuxPolicyChecker() + try: + pkexec = policyChecker.maybe_pkexec() + except NoPolkitAuthAgentAvailable: + raise EIPNoPolkitAuthAgentAvailable() + except NoPkexecAvailable: + raise EIPNoPkexecAvailable() if pkexec: command.insert(0, first(pkexec)) diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index c7159a93..8dc6021f 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -50,8 +50,7 @@ from leap.common.check import leap_assert, leap_assert_type logger = logging.getLogger(__name__) vpnlog = logging.getLogger('leap.openvpn') -from twisted.internet import protocol -from twisted.internet import defer +from twisted.internet import defer, protocol, reactor from twisted.internet import error as internet_error from twisted.internet.task import LoopingCall @@ -157,10 +156,8 @@ class VPN(object): of a QObject containing the QSignals that we will pass along to the VPNManager. """ - from twisted.internet import reactor self._vpnproc = None self._pollers = [] - self._reactor = reactor self._signaler = kwargs['signaler'] self._openvpn_verb = flags.OPENVPN_VERBOSITY @@ -217,14 +214,15 @@ class VPN(object): # and abstract us away from anything else. try: cmd = vpnproc.getCommand() - except Exception: - logger.error("Error while getting vpn command...") + except Exception as e: + logger.error("Error while getting vpn command... {0!r}".format(e)) raise + env = os.environ for key, val in vpnproc.vpn_env.items(): env[key] = val - self._reactor.spawnProcess(vpnproc, cmd[0], cmd, env) + reactor.spawnProcess(vpnproc, cmd[0], cmd, env) self._vpnproc = vpnproc # add pollers for status and state @@ -300,7 +298,6 @@ class VPN(object): :param tries: counter of tries, used in recursion :type tries: int """ - from twisted.internet import reactor while tries < self.TERMINATE_MAXTRIES: if self._vpnproc.transport.pid is None: logger.debug("Process has been happily terminated.") @@ -351,7 +348,6 @@ class VPN(object): :param restart: whether this stop is part of a hard restart. :type restart: bool """ - from twisted.internet import reactor self._stop_pollers() # First we try to be polite and send a SIGTERM... @@ -375,6 +371,8 @@ class VPN(object): logger.debug("Firewall down") else: logger.warning("Could not tear firewall down") + else: + logger.debug("VPN is not running.") def _start_pollers(self): """ @@ -424,8 +422,6 @@ class VPNManager(object): backend :type signaler: backend.Signaler """ - from twisted.internet import reactor - self._reactor = reactor self._tn = None self._signaler = signaler self._aborted = False @@ -602,7 +598,7 @@ class VPNManager(object): logger.debug('trying to connect to management') if not self.aborted and not self.is_connected(): self.connect_to_management(self._socket_host, self._socket_port) - self._reactor.callLater( + reactor.callLater( self.CONNECTION_RETRY_TIME, self.try_to_connect_to_management, retry + 1) diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 5e85368f..416aff34 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -22,6 +22,7 @@ import logging from leap.bitmask.config import flags from leap.bitmask.gui import statemachines from leap.bitmask.services.mail import connection as mail_connection +from leap.bitmask.services.mail.emailfirewall import get_email_firewall from leap.common.events import events_pb2 as leap_events from leap.common.events import register as leap_register @@ -211,6 +212,11 @@ class MailConductor(IMAPControl, SMTPControl): self._mail_connection = mail_connection.MailConnection() self._userid = None + try: + self._firewall = get_email_firewall() + except NotImplementedError: + self._firewall = None + logger.info("Email firewall is not implemented in this platform") @property def userid(self): @@ -247,12 +253,25 @@ class MailConductor(IMAPControl, SMTPControl): self._smtp_machine = smtp self._smtp_machine.start() + def start_mail_service(self, download_if_needed=False, offline=False): + """ + Start the IMAP and SMTP servcies. + """ + if self._firewall is not None: + self._firewall.start() + if not offline: + logger.debug("not starting smtp in offline mode") + self.start_smtp_service(download_if_needed=download_if_needed) + self.start_imap_service() + def stop_mail_services(self): """ Stop the IMAP and SMTP services. """ self.stop_imap_service() self.stop_smtp_service() + if self._firewall is not None: + self._firewall.stop() def connect_mail_signals(self, widget): """ diff --git a/src/leap/bitmask/services/mail/emailfirewall.py b/src/leap/bitmask/services/mail/emailfirewall.py new file mode 100644 index 00000000..2cd2ec31 --- /dev/null +++ b/src/leap/bitmask/services/mail/emailfirewall.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +# emailfirewall.py +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Email firewall implementation. +""" + +import os +import subprocess + +from abc import ABCMeta, abstractmethod + +from leap.bitmask.config import flags +from leap.bitmask.platform_init import IS_LINUX +from leap.bitmask.util import first, force_eval +from leap.bitmask.util.privilege_policies import LinuxPolicyChecker +from leap.common.check import leap_assert + + +def get_email_firewall(): + """ + Return the email firewall handler for the current platform. + """ + if not (IS_LINUX): + error_msg = "Email firewall not implemented for this platform." + raise NotImplementedError(error_msg) + + firewall = None + if IS_LINUX: + firewall = LinuxEmailFirewall + + leap_assert(firewall is not None) + + return firewall() + + +class EmailFirewall(object): + """ + Abstract email firwall class + """ + __metaclass__ = ABCMeta + + @abstractmethod + def start(self): + """ + Start email firewall + """ + return False + + @abstractmethod + def stop(self): + """ + Stop email firewall + """ + return False + + +class EmailFirewallException(Exception): + pass + + +class LinuxEmailFirewall(EmailFirewall): + + class BITMASK_ROOT(object): + def __call__(self): + return ("/usr/local/sbin/bitmask-root" if flags.STANDALONE else + "/usr/sbin/bitmask-root") + + def start(self): + uid = str(os.getuid()) + return True if self._run(["start", uid]) is 0 else False + + def stop(self): + return True if self._run(["stop"]) is 0 else False + + def _run(self, cmd): + """ + Run an email firewall command with bitmask-root + + Might raise: + NoPkexecAvailable, + NoPolkitAuthAgentAvailable, + + :param cmd: command to send to bitmask-root fw-email + :type cmd: [str] + :returns: exit code of bitmask-root + :rtype: int + """ + command = [] + + policyChecker = LinuxPolicyChecker() + pkexec = policyChecker.maybe_pkexec() + if pkexec: + command.append(first(pkexec)) + + command.append(force_eval(self.BITMASK_ROOT)) + command.append("fw-email") + command += cmd + + # XXX: will be nice to use twisted ProcessProtocol instead of + # subprocess to avoid blocking until it finish + return subprocess.call(command) diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py index 3ef755e8..9dd61488 100644 --- a/src/leap/bitmask/services/mail/smtpbootstrapper.py +++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py @@ -173,3 +173,5 @@ class SMTPBootstrapper(AbstractBootstrapper): logger.debug('Stopping SMTP service.') self._smtp_port.stopListening() self._smtp_service.doStop() + else: + logger.debug('SMTP service not running.') diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py index caa94ec7..e8eddd64 100644 --- a/src/leap/bitmask/util/__init__.py +++ b/src/leap/bitmask/util/__init__.py @@ -151,6 +151,6 @@ def flags_to_dict(): :rtype: dict. """ items = [i for i in dir(flags) if i[0] != '_'] - values = {i: getattr(flags, i) for i in items} + values = dict((i, getattr(flags, i)) for i in items) return values diff --git a/src/leap/bitmask/util/credentials.py b/src/leap/bitmask/util/credentials.py index 757ce10c..dfc78a09 100644 --- a/src/leap/bitmask/util/credentials.py +++ b/src/leap/bitmask/util/credentials.py @@ -38,7 +38,7 @@ def username_checks(username): valid = USERNAME_VALIDATOR.validate(username, 0) valid_username = valid[0] == QtGui.QValidator.State.Acceptable if message is None and not valid_username: - message = _tr("Invalid username") + message = _tr("That username is not allowed. Try another.") return message is None, message @@ -54,28 +54,34 @@ def password_checks(username, password, password2): :param password2: second password from the registration form :type password: str - :returns: True and empty message if all the checks pass, - False and an error message otherwise - :rtype: tuple(bool, str) + :returns: (True, None, None) if all the checks pass, + (False, message, field name) otherwise + :rtype: tuple(bool, str, str) """ # translation helper _tr = QtCore.QObject().tr message = None + field = None if message is None and password != password2: message = _tr("Passwords don't match") + field = 'new_password_confirmation' if message is None and not password: - message = _tr("You can't use an empty password") + message = _tr("Password is empty") + field = 'new_password' if message is None and len(password) < 8: - message = _tr("Password too short") + message = _tr("Password is too short") + field = 'new_password' if message is None and password in WEAK_PASSWORDS: - message = _tr("Password too easy") + message = _tr("Password is too easy") + field = 'new_password' if message is None and username == password: - message = _tr("Password equal to username") + message = _tr("Password can't be the same as username") + field = 'new_password' - return message is None, message + return message is None, message, field diff --git a/src/leap/bitmask/util/privilege_policies.py b/src/leap/bitmask/util/privilege_policies.py index f894d73b..2016e67b 100644 --- a/src/leap/bitmask/util/privilege_policies.py +++ b/src/leap/bitmask/util/privilege_policies.py @@ -18,17 +18,30 @@ Helpers to determine if the needed policies for privilege escalation are operative under this client run. """ +import commands import logging import os +import subprocess import platform +import time from abc import ABCMeta, abstractmethod from leap.bitmask.config import flags +from leap.common.check import leap_assert +from leap.common.files import which logger = logging.getLogger(__name__) +class NoPolkitAuthAgentAvailable(Exception): + pass + + +class NoPkexecAvailable(Exception): + pass + + def is_missing_policy_permissions(): """ Returns True if we do not have implemented a policy checker for this @@ -75,6 +88,7 @@ class LinuxPolicyChecker(PolicyChecker): "se.leap.bitmask.policy") LINUX_POLKIT_FILE_BUNDLE = ("/usr/share/polkit-1/actions/" "se.leap.bitmask.bundle.policy") + PKEXEC_BIN = 'pkexec' @classmethod def get_polkit_path(self): @@ -97,3 +111,87 @@ class LinuxPolicyChecker(PolicyChecker): """ path = self.get_polkit_path() return not os.path.isfile(path) + + @classmethod + def maybe_pkexec(self): + """ + Checks whether pkexec is available in the system, and + returns the path if found. + + Might raise: + NoPkexecAvailable, + NoPolkitAuthAgentAvailable. + + :returns: a list of the paths where pkexec is to be found + :rtype: list + """ + if self._is_pkexec_in_system(): + if not self.is_up(): + self.launch() + time.sleep(2) + if self.is_up(): + pkexec_possibilities = which(self.PKEXEC_BIN) + leap_assert(len(pkexec_possibilities) > 0, + "We couldn't find pkexec") + return pkexec_possibilities + else: + logger.warning("No polkit auth agent found. pkexec " + + "will use its own auth agent.") + raise NoPolkitAuthAgentAvailable() + else: + logger.warning("System has no pkexec") + raise NoPkexecAvailable() + + @classmethod + def launch(self): + """ + Tries to launch policykit + """ + env = None + if flags.STANDALONE: + env = {"PYTHONPATH": os.path.abspath('../../../../lib/')} + try: + # We need to quote the command because subprocess call + # will do "sh -c 'foo'", so if we do not quoute it we'll end + # up with a invocation to the python interpreter. And that + # is bad. + logger.debug("Trying to launch polkit agent") + subprocess.call(["python -m leap.bitmask.util.polkit_agent"], + shell=True, env=env) + except Exception as exc: + logger.exception(exc) + + @classmethod + def is_up(self): + """ + Checks if a polkit daemon is running. + + :return: True if it's running, False if it's not. + :rtype: boolean + """ + # Note that gnome-shell does not uses a separate process for the + # polkit-agent, it uses a polkit-agent within its own process so we + # can't ps-grep a polkit process, we can ps-grep gnome-shell itself. + + # the [x] thing is to avoid grep match itself + polkit_options = [ + 'ps aux | grep "polkit-[g]nome-authentication-agent-1"', + 'ps aux | grep "polkit-[k]de-authentication-agent-1"', + 'ps aux | grep "polkit-[m]ate-authentication-agent-1"', + 'ps aux | grep "[l]xpolkit"', + 'ps aux | grep "[g]nome-shell"', + 'ps aux | grep "[f]ingerprint-polkit-agent"', + ] + is_running = [commands.getoutput(cmd) for cmd in polkit_options] + + return any(is_running) + + @classmethod + def _is_pkexec_in_system(self): + """ + Checks the existence of the pkexec binary in system. + """ + pkexec_path = which('pkexec') + if len(pkexec_path) == 0: + return False + return True |