diff options
39 files changed, 2235 insertions, 943 deletions
diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index 25e90051..00000000 --- a/CHANGELOG +++ /dev/null @@ -1,369 +0,0 @@ -0.3.5 Oct 18 -- the "I can stand on one foot" release: - o In case of Soledad failure, display to the user that there was a - problem. Closes #4025. - o Widget squashing problem in wizard checking a new provider. Closes - #4058. - o Remember last domain used to login. Closes #4116. - o Display first run wizard, regardless of pinned providers. Closes - #4143. - o Show EIP status 'ON' in the systray tooltip when is - connected. Related to #3998. - o Catch u1db errors during soledad initialization. - o Disable --danger flag on release versions. Closes #4124. - o Display mail status in the tray icon as an enabled item. Fixes - #4036. - o Only show N unread Emails when N > 0. Fixes #4098. - o Hide login error message when the user interacts with the widgets - to fix the potential problem. Fixes #4022. - o Add call to `make` to the bootstrap script. - o Improve GUI based on QA rounds. Fixes #4041 and #4042. - o Increase the amount of retries for the authentication request - session. Fixes #4037. - o Rename EIP to Encrypted Internet in its preference panel. Fixes - #4057. - o Disable stdout redirection on Windows for the time being since it - breaks the bundle. - o Default UP_SCRIPT and DOWN_SCRIPT to None and only add that - parameter to the vpn command if not None. - o Look for gpg on windows with the .exe extension. - o Change the Util menu to be named File in OSX. Fixes #4039. - o Show more context information in the logs. Closes #3923. - o Automate internationalization process, create project file - dynamically on make. Closes #3925. - o Add support for running lxde polkit agent. Closes #4028. - o Added Vietnamese and English (United Kingdom) translations. - o Implements openvpn observer. Closes: #3901 - o Reconnect EIP if network down. Closes #3790 - o Reconnect if tls-restart. Closes: #3262 - -0.3.4 Oct 4 -- the "look at my new makeup" release: - o Fixes a bug where you cannot login to a different provider once - you logged in to another one. Fixes #3695. - o Resets the session for every login attempt. Related to #3695. - o Avoid error message if --version flag is used. Closes #3914. - o Fix a bug in which failing to authenticate properly left - connection in an unconsistent state. Closes: #3926 - o Avoids errors due to the EIP switch button and action being - enabled when we do not have a configured provider. Closes: #3927 - o Add more verbose error handling during key generation and syncing. - Helps diagnose: #3985; Addresses in part: #3965 - o Choose one gnupg binary path that is also not a symlink. Closes - #3999. - o Refactor vpn launchers, reuse code, improve implementations, - update documentation. Closes #2858. - o Add preferences option to enable/disable the automatic start of - EIP and selection of the EIP provider to auto start. Closes #3631. - o Force cleanlooks style for kde only if the app is running from - bundle. Closes #3981. - o Add a dropdown for known providers in the wizard. Closes #3995. - o Separate pinned providers from user configures ones. Closes #3996. - o Improve error handling during soledad bootstrap. Closes: #3965. - Affects: #3619, #3867, #3966 - o Implement new UI design. Closes #3973. - o Make the initial provider cert verifications against our modified - CA-bundle (includes ca-cert certificates, for now). Closes: #3850 - o Use token header for authenticated requests. Closes #3910. - o Do not distinguish between different possible authentication - errors. Fixes #3859. - o Do not start Soledad if Mail is not enabled. Fixes #3989. - o Allow window minization on OSX. Fixes #3932. - o Properly stop the smtp daemon. Fixes #3873. - -0.3.3 Sep 20 -- "the calm after the tempest" release: - o Remove execution bits in text files in bundle. Closes #3617. - o Use generic bad username/password message instead of specific ones when - the user uses incorrect data during login. Closes #3656. - o Fix LoggerWindow saving more than one line return per line in the logs - file. Closes #3714. - o Fix keyring imports so we do not get import errors. Closes: #3759 - o Catch logout problem, display a user message and allow log back in after a - successful logout if there was a logout error before. Closes #3774. - o Fix path prefix helper for the bundle and add regresion tests. Closes #3778. - o Prevent dialogs closing the app when it has been minimized to the tray. Closes #3791. - o Do not try to install resolv-update globally. Closes: #3803 - o Inconsistent hide/show main window from tray action. Closes #3821. - o Allow SMTP to start even when provider does not offer EIP. Closes: #3847 - o Fix username case problem at register/login. Closes #3857. - o Catch IndexError on `first` utility. - o Update git repo name in docs. Closes: #3417 - o Move STANDALONE flag to a module and unify get_path_prefix queries. - Closes #3636. - o Display the Encrypted Internet and Encrypted Email status in the systray - tooltip. Closes #3758. - o Tasktray menu changes, closes #3792. - - Remove the provider domain item (e.g. bitmask.net). - - Rename the EIP status menu items to be more descriptive. - - Change the EIP status menu items from disabled menu items - to submenus with children. - - Move the EIP action menu items under the EIP status submenu tree. - o Adds --version flag. Closes: #3816 - o Refactors EIPConnection to use LEAPConnection state machine. Closes: #3900 - o Include resource files and ui in the distrubution tarball. Closes: #3825 - -0.3.2 Sep 6 -- the "no crashes or anything" release: - o Fix up script in non-bundle linuces. Closes: #3450 - o Logout stops imap and smtp services. Closes: #3553 - o Properly daemonize polkit-gnome-authentication-agent. Closes: #3554 - o Set appropiate error on login cancel. Closes #3582. - o Fix gateway selection problem. Closes 3595. - o Fix typo in wizard: stablish -> establish. Closes #3615. - o Display Encrypted Mail instead of mx in wizard. Closes #3657. - o Fix save logs to file dialog freezing. Closes #3675. - o Complain if setup.py is run with python3. Closes: #3711 - o Enable preferences option in systray. Closes #3717. - o Make soledad emit failed signal for all kinds of socket error. - o Allow to selectively silence logs from different leap components. Closes: #3504 - o Add option to select gateway manually in the preferences panel. Closes #3505. - o Add preferences option to select the enabled services of a provider. Closes #3534. - o Refactor basic password checks. Closes #3552. - o Use dirspec instead of plain xdg. Closes #3574. - o Remove last page from wizard. Closes #3616. - o Display encrypted mail status in the tray. Closes #3659. - -0.3.1 Aug 23: - o Replace wizard images with the rainbow mask. Closes #3425. - o Update leap.common minimum version needed. - o Set the standalone flag before it's being used. Fixes #3426. - o Stop the twisted reactor adding the stop call to the call chain - instead of stopping it directly. Fixes #3406. - o Allow soledad initialization to retry if it times out. Closes: - #3413 - o Activate window when setting it visible. Also display Hide/Show - message in the tray icon taking into account the window - activation. Fixes #3433. - o Do not start IMAP daemon if mail was not selected among the - services. Fixes #3435. - o Reword RECONNECTING state of openvpn. Fixes #3429. - o Improve OpenVPN detection by searching for a specific leap-only - string in the command line. This makes it possible to run other - VPN instances while also using EIP. Fixes #3268 and #3364. - o OSX: Check for the tun.kext existence in /Library/Extensions - instead of /System/Library/Extensions. Fixes #3271. - o Use DELETE /1/logout to properly logout. Fixes #3510. - o Make the poll interval bigger to improve openvpn's internal - behavior. If it gets queried too many times per second, it's - behavior won't be good. Fixes #3430. - o Transforms usernames to lower case before they are used in the - registration and authentication. Closes #3541. - o Add filter option to the logger window. Closes #3407. - o Add a preference panel that lets you change your password. Closes - #3500 #2798 #3533. - o Move all client code into its own namespace - (leap.bitmask). Closes: #2959 - o Make mail fetch interval in imap service configurable via - environment variable. Closes: #3409 - o Update to new soledad package scheme (common, client and - server). Closes #3487. - o Fetch incoming mail when mail client logs in. Closes: #3525 - o Add first draft of the UI for Encrypted Mail. Closes #3499. - -0.3.0 Aug 9: - o Add missing scripts does not stop if a command fails, also warns - the user if there was an error. Closes #3294. - o Replace 'Sign Out' with 'Log Out' and 'User' with - 'Username'. Closes #3319. - o Verify cacert existence before using it. Closes bug #3362. - o Properly handle login failures. Closes bug #3401. - o Bugfix, avoid getting negative rates. Closes #3274. - o Raise window when setting it as visible. Fixes #3374 - o Fail gracefully when the events port 8090 is in use by something - else. Fixes #3276. - o Validate the username in the login form against the same regexp as - the wizard registration form. Fixes #3214. - o Update text from the tray menu based on the visibility of the - window. Fixes #3400. - o Add check for outdated polkit file. Closes #3209. - o Add support for multiple schemas so we can support multiples api - versions. Closes #3310. - o Rebrand the client to be named Bitmask. Feature #3313. - o Add cancel button to login. Closes #3318. - o Add multiple schema support for SMTP. Closes #3403. - o Add multiple schema support for Soledad. Closes #3404. - o Update Transifex project name and translators' - documentation. Closes #3418. - o Add check for tuntap kext before launching openvpn. Closes: #2906 - o Accept flag for changing openvpn verbosity in logs. Closes: #3305 - o Add imap service to the client. Closes: #2579 - o Add pyside-uic support inside the virtualenv. This way it won't - fail to 'make' if the virtualenv is activated. Closes #3411. - o Reintegrate SMTP relay module. Closes #3375 - o Reintegrate Soledad into the client. Closes #3307. - o Support bundled gpg. Related to #3397. - o Set the default port for SMTP to be 2013. - o Display a more generic error message in the main window, and leave - the detailed one for the log. Closes #3373. - -0.2.4 Jul 26: - o Use the provider CA cert for every request once we have it - bootstrapped (TOFU). Closes #3227. - o Make calls to leap.common.events asynchronous. Closes #2937. - o Always logout when closing the app if the user previously signed - in. Fixes #3245. - o Make sure the domain field in provider.json is escaped to avoid - potential problems. Fixes #3244. - o Fix incorrect handling of locks in Windows so that stalled locks - do not avoid raising the first instance of the app. Closes: #2910 - o Use traffic rates instead of totals. Closes #2913 - o Allow to alternate between rates and total throughput for the - virtual interface. Closes: #3232 - o Reset rates/totals when terminating connection. Closes #3249 - o Fix a bug in the displayed magnitude for the up/down traffic rates - and totals. - o Force Cleanlooks style if we are running in a KDE environment, so - that it doesn't load potentially incompatible Qt libs. Fixes - #3194. - o Wrap long login status messages to 40 characters. Fixes #3124 - o Workaround a segmentation fault when emitting a signal with its - last parameter being None. Fixes #3083. - o Added IS_RELEASE_VERSION flag that allows us to use code only in - develop versions. Closes #3224. - o Try to terminate already running openvpn instances. Closes #2916 - o Linux: Dynamically generate policy file for polkit. Closes #3208 - o Workaround some OpenVPN problems with priviledge dropping and - routing. Fixes #3178 #3135 #3207 #3203 - -0.2.3 Jul 12: - o Adapt code to Soledad 0.2.1 api. - o Fix Main Window briefly display before the wizard on first - start. Closes Bug #2954. - o Bugfix: Remember should not be automatically set to - checked. Closes #2955. - o Bugfix: reload config if switching to a different provider. Closes - #3067. - o Bugfix: logger window's toggle button reflects window - state. Closes #3152. - o Set timeout for requests to 10 seconds globally, configurable from - leap.util.constants. Fixes #2878. - o Bugfix: display error message on registration problem. Closes - #3039. - o Make wizard use the main event loop, ensuring clean termination. - o Use cocoasudo for installing missing updown scripts. - o Bugfix: Systray Turn ON action fails because is not correctly - enabled/disabled. Closes #3125. - o Bugfix: wrong systray icon on startup. Closes #3147. - o Bugfix: parse line return in the logger window. Closes #3151. - o Do not log user data on registration. Fixes #3168. - o Add --log-append eip.log to windows EIP launcher options to save - the logs in case of any problems. Fixes #2054. - o OSX: Make the install_path relative to the launcher path instead - of absolute. - o OSX: Fix icon display in cocoasudo. - o OSX: Raise window when showing if running on OSX. - o Bugfix: EIP status button moved to status panel. - o Check if there is no gateway to use and display correct - message. Close #2921. - o Reorder tray icons according new design. Closes #2919. - o Redirect stdout/stderr and twisted log to the logger. Closes - #3134. - o Improve LoggerWindow colors for easier debugging. - o Move the key manager to its own repository/package. - -0.2.2 Jun 28: - o Add support for the kde polkit daemon - o Handle 'Incorrect Password' exception (keyring) - o Select the configured domain in the providers combo box. Closes - #2693. - o Remember provider along with the username and password. Closes - #2755. - o Close the app on rejected wizard. Closes bug #2905. - o Only use the Keyring when it's using a known good backend. Closes - #2960 - o Update implementation and semantics of the supported and available - services by a provider. Closes bug #3032. - o Only show the question mark for a check being done if the previous - one passed. Fixes #2569. - o Fix main client window not restoring after minimized into - systray. Closes #2574 - o Set EIP different status icons depending on OS. Closes #2643. - o Reimplement openvpn invocation to use twisted ProcessProtocol - o Add runtime requirements checker, verifies that the requirements - are installed and in its correct versions. Closes #2563 - o Add centraliced logging facility, log history in a window. Closes - #2566 - o Improve wizard, hide registration widgets (labels, inputs, button) - and only display a message. Closes #2694 - o Clarify labels through the app (use of EIP) - o Check if the provider api version is supported. Closes feature - #2774. - o Autoselect VPN gateway based on timezone. Closes #2790. - o Disable vpn disconnect on logout. Closes #2795. - o Improve gateway selector based on timezone. It allows to use - multiple gateways in openvpn for redundancy. Closes #2894. - o Use cocoasudo in place of osascript for osx privilege escalation - during openvpn launch. - o Clicking in the tray icon will always show the context menu - instead of activating the window under certain - circumstances. Closes #2788 - o Autostart EIP whenever possible. Closes #2815 - o Update test suite, run_scripts and requirements to run smoothly - with buildbot. - o Add a copy of the processed requirements to util/ - o Display the default provider configured in the systray menu. Close - #2813 - o Make the login steps be a chain of defers in order to be able to - have more cancel points for the whole procedure. Closes #2571 - o Linux: check for up/down scripts and policy files and ask user for - permission to install them in a root-writeable location. Used from - within bundle or for broken installations. - o Integrate SMTP-Relay into the client. - o Integrate Soledad and KeyManager. - o Move the KeyManager from leap.common to leap-client. - o Only use one systray icon, repesenting the status for EIP. Closes - #2762 - o Properly set the binary manifest to the windows openvpn - binary. Closes #203 - o OSX: Add dialog with suggestion to install up/down scripts if - these not found. Closes: #1264, #2759, #2249 - o Workaround for PySide breaking with multiple inheritance. Closes - #2827 - o Refactor login to its own widget and remove Utils menu. Closes - #2789 - o Refactor the status bits out of the MainWindow to its own - StatusPanelWidget. Closes #2792 - o Save the default provider to be used for autostart EIP as - DefaultProvider in leap.conf. Closes #2793 - o Cleanly terminate openvpn process, sending SIGTERM and SIGKILL - after a while. Closes #2753 - o Use twisted's deferToThread and Deferreds to handle parallel tasks - o Use a qt4 reactor for twisted, for launching leap twisted - services. - -0.2.1 May 15: - o Rewrite most of the client based on the insight gained so far. - o Deselecting the remember checkbox makes the app not populate - user/password values on the login widget. Closes #2059 - o Rewording of setup steps in wizard, to make them more meaningful - to the non-technical user. Closes #2061 - o Fix typo in wizard. - o Fix multiple drawing of services if going back. - o Make registration errors show in red. - o Add a warning if EIP service needs admin password. Addresses part - of #2062 - o Make traffic indicators display fixed precision. Closes #2114 - o Do not hide the application if the user right clicked the system - tray icon. - o Sanitize network-fetched content that is used to build openvpn - command. - o Avoids multiple instances of leap-client. Each new one just raises - the existing instance and quits. - o Use dark eip icons os osx. Closes #2130 - o Moves BaseConfig to leap.common.config. Closes #2164 - o Add handling for ASSIGN_IP state from OpenVPN in the mainwindow. - o Emit events notifying of the session_id and uid after - authentication so other services can make use of it. Closes #1957 - o Working packaging workflow with rewritten client, using - pyinstaller and platypus. - o Remove network checks temporarily until we find a good way of - doing it, and a good way to deal with them. - o Saves the token to allow token authenticated queries. - o Turn "leap" into namespace package, move common files to - leap_common package that can be shared by other LEAP projects. - o Support standalone configurations for distribution in thumbdrives - and the like. - o Add support for requests < 1.0.0 - o Tests infrastructure, and tests for crypto/srpauth and crypto/srpregister. - o Documentation updated for 0.2.1 release. - o Docstrings style changed to fit sphinx autodoc format. - o Add a simple UI to notify of pending updates. - o Add Windows support. - o Try to install TAP driver on Windows if no tap device is preset. diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 00000000..09ea992e --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,417 @@ +.. :changelog:: + +History +------- + +0.3.6 Nov 1 -- the "bạn có thể đọc này?" release: +- Fix problem changing a non-ascii password. Closes #4003. +- Enable password change in the client only if it has started the + correct services. Closes #4093. +- Select the current logged in provider in the preferences + window. Closes #4117. +- Fix problem with non-ascii paths. Closes #4189. +- Capture soledad boostrap errors after latest soledad changes. +- Refactor keyring handling and make it properly save user and + password. Fixes #4190. +- Properly stop the imap daemon at logout. Fixes #4199. +- Align left the speed and transferred displays for EIP. Fixes #4204. +- Remove autostart eip option from settings panel, rely on last used + setting. Closes #4132. +- Add support for requests 1.1.0 (raring). Closes: #4308 +- Refactor mail connections to use state machine. Closes: #4059 +- Add a command to setup.py to freeze the versions reported under + debian branches. Closes: #4315 +- Use coloredlogs handler if present (for development, not a + requirement). +- Hide the GUI for services that are not supported on the set of + configured providers. Closes #4170. + +0.3.5 Oct 18 -- the "I can stand on one foot" release: +++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +- In case of Soledad failure, display to the user that there was a + problem. Closes #4025. +- Widget squashing problem in wizard checking a new provider. Closes + #4058. +- Remember last domain used to login. Closes #4116. +- Display first run wizard, regardless of pinned providers. Closes + #4143. +- Show EIP status 'ON' in the systray tooltip when is + connected. Related to #3998. +- Catch u1db errors during soledad initialization. +- Disable --danger flag on release versions. Closes #4124. +- Display mail status in the tray icon as an enabled item. Fixes + #4036. +- Only show N unread Emails when N > 0. Fixes #4098. +- Hide login error message when the user interacts with the widgets + to fix the potential problem. Fixes #4022. +- Add call to `make` to the bootstrap script. +- Improve GUI based on QA rounds. Fixes #4041 and #4042. +- Increase the amount of retries for the authentication request + session. Fixes #4037. +- Rename EIP to Encrypted Internet in its preference panel. Fixes + #4057. +- Disable stdout redirection on Windows for the time being since it + breaks the bundle. +- Default UP_SCRIPT and DOWN_SCRIPT to None and only add that + parameter to the vpn command if not None. +- Look for gpg on windows with the .exe extension. +- Change the Util menu to be named File in OSX. Fixes #4039. +- Show more context information in the logs. Closes #3923. +- Automate internationalization process, create project file + dynamically on make. Closes #3925. +- Add support for running lxde polkit agent. Closes #4028. +- Added Vietnamese and English (United Kingdom) translations. +- Implements openvpn observer. Closes: #3901 +- Reconnect EIP if network down. Closes #3790 +- Reconnect if tls-restart. Closes: #3262 + +0.3.4 Oct 4 -- the "look at my new makeup" release: ++++++++++++++++++++++++++++++++++++++++++++++++++++ + +- Fixes a bug where you cannot login to a different provider once + you logged in to another one. Fixes #3695. +- Resets the session for every login attempt. Related to #3695. +- Avoid error message if --version flag is used. Closes #3914. +- Fix a bug in which failing to authenticate properly left + connection in an unconsistent state. Closes: #3926 +- Avoids errors due to the EIP switch button and action being + enabled when we do not have a configured provider. Closes: #3927 +- Add more verbose error handling during key generation and syncing. + Helps diagnose: #3985; Addresses in part: #3965 +- Choose one gnupg binary path that is also not a symlink. Closes + #3999. +- Refactor vpn launchers, reuse code, improve implementations, + update documentation. Closes #2858. +- Add preferences option to enable/disable the automatic start of + EIP and selection of the EIP provider to auto start. Closes #3631. +- Force cleanlooks style for kde only if the app is running from + bundle. Closes #3981. +- Add a dropdown for known providers in the wizard. Closes #3995. +- Separate pinned providers from user configures ones. Closes #3996. +- Improve error handling during soledad bootstrap. Closes: #3965. + Affects: #3619, #3867, #3966 +- Implement new UI design. Closes #3973. +- Make the initial provider cert verifications against our modified + CA-bundle (includes ca-cert certificates, for now). Closes: #3850 +- Use token header for authenticated requests. Closes #3910. +- Do not distinguish between different possible authentication + errors. Fixes #3859. +- Do not start Soledad if Mail is not enabled. Fixes #3989. +- Allow window minization on OSX. Fixes #3932. +- Properly stop the smtp daemon. Fixes #3873. + +0.3.3 Sep 20 -- "the calm after the tempest" release: ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +- Remove execution bits in text files in bundle. Closes #3617. +- Use generic bad username/password message instead of specific ones when + the user uses incorrect data during login. Closes #3656. +- Fix LoggerWindow saving more than one line return per line in the logs + file. Closes #3714. +- Fix keyring imports so we do not get import errors. Closes: #3759 +- Catch logout problem, display a user message and allow log back in after a + successful logout if there was a logout error before. Closes #3774. +- Fix path prefix helper for the bundle and add regresion tests. Closes #3778. +- Prevent dialogs closing the app when it has been minimized to the tray. Closes #3791. +- Do not try to install resolv-update globally. Closes: #3803 +- Inconsistent hide/show main window from tray action. Closes #3821. +- Allow SMTP to start even when provider does not offer EIP. Closes: #3847 +- Fix username case problem at register/login. Closes #3857. +- Catch IndexError on `first` utility. +- Update git repo name in docs. Closes: #3417 +- Move STANDALONE flag to a module and unify get_path_prefix queries. + Closes #3636. +- Display the Encrypted Internet and Encrypted Email status in the systray + tooltip. Closes #3758. +- Tasktray menu changes, closes #3792. +- Remove the provider domain item (e.g. bitmask.net). +- Rename the EIP status menu items to be more descriptive. +- Change the EIP status menu items from disabled menu items + to submenus with children. +- Move the EIP action menu items under the EIP status submenu tree. +- Adds ``--version`` flag. Closes: #3816 +- Refactors EIPConnection to use LEAPConnection state machine. Closes: #3900 +- Include resource files and ui in the distrubution tarball. Closes: #3825 + +0.3.2 Sep 6 -- the "no crashes or anything" release: +++++++++++++++++++++++++++++++++++++++++++++++++++++ + +- Fix up script in non-bundle linuces. Closes: #3450 +- Logout stops imap and smtp services. Closes: #3553 +- Properly daemonize polkit-gnome-authentication-agent. Closes: #3554 +- Set appropiate error on login cancel. Closes #3582. +- Fix gateway selection problem. Closes 3595. +- Fix typo in wizard: stablish -> establish. Closes #3615. +- Display Encrypted Mail instead of mx in wizard. Closes #3657. +- Fix save logs to file dialog freezing. Closes #3675. +- Complain if setup.py is run with python3. Closes: #3711 +- Enable preferences option in systray. Closes #3717. +- Make soledad emit failed signal for all kinds of socket error. +- Allow to selectively silence logs from different leap components. Closes: #3504 +- Add option to select gateway manually in the preferences panel. Closes #3505. +- Add preferences option to select the enabled services of a provider. Closes #3534. +- Refactor basic password checks. Closes #3552. +- Use dirspec instead of plain xdg. Closes #3574. +- Remove last page from wizard. Closes #3616. +- Display encrypted mail status in the tray. Closes #3659. + +0.3.1 Aug 23: ++++++++++++++ + +- Replace wizard images with the rainbow mask. Closes #3425. +- Update leap.common minimum version needed. +- Set the standalone flag before it's being used. Fixes #3426. +- Stop the twisted reactor adding the stop call to the call chain + instead of stopping it directly. Fixes #3406. +- Allow soledad initialization to retry if it times out. Closes: + #3413 +- Activate window when setting it visible. Also display Hide/Show + message in the tray icon taking into account the window + activation. Fixes #3433. +- Do not start IMAP daemon if mail was not selected among the + services. Fixes #3435. +- Reword RECONNECTING state of openvpn. Fixes #3429. +- Improve OpenVPN detection by searching for a specific leap-only + string in the command line. This makes it possible to run other + VPN instances while also using EIP. Fixes #3268 and #3364. +- OSX: Check for the tun.kext existence in /Library/Extensions + instead of /System/Library/Extensions. Fixes #3271. +- Use DELETE /1/logout to properly logout. Fixes #3510. +- Make the poll interval bigger to improve openvpn's internal + behavior. If it gets queried too many times per second, it's + behavior won't be good. Fixes #3430. +- Transforms usernames to lower case before they are used in the + registration and authentication. Closes #3541. +- Add filter option to the logger window. Closes #3407. +- Add a preference panel that lets you change your password. Closes + #3500 #2798 #3533. +- Move all client code into its own namespace + (leap.bitmask). Closes: #2959 +- Make mail fetch interval in imap service configurable via + environment variable. Closes: #3409 +- Update to new soledad package scheme (common, client and + server). Closes #3487. +- Fetch incoming mail when mail client logs in. Closes: #3525 +- Add first draft of the UI for Encrypted Mail. Closes #3499. + +0.3.0 Aug 9: +++++++++++++ + +- Add missing scripts does not stop if a command fails, also warns + the user if there was an error. Closes #3294. +- Replace 'Sign Out' with 'Log Out' and 'User' with + 'Username'. Closes #3319. +- Verify cacert existence before using it. Closes bug #3362. +- Properly handle login failures. Closes bug #3401. +- Bugfix, avoid getting negative rates. Closes #3274. +- Raise window when setting it as visible. Fixes #3374 +- Fail gracefully when the events port 8090 is in use by something + else. Fixes #3276. +- Validate the username in the login form against the same regexp as + the wizard registration form. Fixes #3214. +- Update text from the tray menu based on the visibility of the + window. Fixes #3400. +- Add check for outdated polkit file. Closes #3209. +- Add support for multiple schemas so we can support multiples api + versions. Closes #3310. +- Rebrand the client to be named Bitmask. Feature #3313. +- Add cancel button to login. Closes #3318. +- Add multiple schema support for SMTP. Closes #3403. +- Add multiple schema support for Soledad. Closes #3404. +- Update Transifex project name and translators' + documentation. Closes #3418. +- Add check for tuntap kext before launching openvpn. Closes: #2906 +- Accept flag for changing openvpn verbosity in logs. Closes: #3305 +- Add imap service to the client. Closes: #2579 +- Add pyside-uic support inside the virtualenv. This way it won't + fail to 'make' if the virtualenv is activated. Closes #3411. +- Reintegrate SMTP relay module. Closes #3375 +- Reintegrate Soledad into the client. Closes #3307. +- Support bundled gpg. Related to #3397. +- Set the default port for SMTP to be 2013. +- Display a more generic error message in the main window, and leave + the detailed one for the log. Closes #3373. + +0.2.4 Jul 26: ++++++++++++++ + +- Use the provider CA cert for every request once we have it + bootstrapped (TOFU). Closes #3227. +- Make calls to leap.common.events asynchronous. Closes #2937. +- Always logout when closing the app if the user previously signed + in. Fixes #3245. +- Make sure the domain field in provider.json is escaped to avoid + potential problems. Fixes #3244. +- Fix incorrect handling of locks in Windows so that stalled locks + do not avoid raising the first instance of the app. Closes: #2910 +- Use traffic rates instead of totals. Closes #2913 +- Allow to alternate between rates and total throughput for the + virtual interface. Closes: #3232 +- Reset rates/totals when terminating connection. Closes #3249 +- Fix a bug in the displayed magnitude for the up/down traffic rates + and totals. +- Force Cleanlooks style if we are running in a KDE environment, so + that it doesn't load potentially incompatible Qt libs. Fixes + #3194. +- Wrap long login status messages to 40 characters. Fixes #3124 +- Workaround a segmentation fault when emitting a signal with its + last parameter being None. Fixes #3083. +- Added IS_RELEASE_VERSION flag that allows us to use code only in + develop versions. Closes #3224. +- Try to terminate already running openvpn instances. Closes #2916 +- Linux: Dynamically generate policy file for polkit. Closes #3208 +- Workaround some OpenVPN problems with priviledge dropping and + routing. Fixes #3178 #3135 #3207 #3203 + +0.2.3 Jul 12: ++++++++++++++ + +- Adapt code to Soledad 0.2.1 api. +- Fix Main Window briefly display before the wizard on first + start. Closes Bug #2954. +- Bugfix: Remember should not be automatically set to + checked. Closes #2955. +- Bugfix: reload config if switching to a different provider. Closes + #3067. +- Bugfix: logger window's toggle button reflects window + state. Closes #3152. +- Set timeout for requests to 10 seconds globally, configurable from + leap.util.constants. Fixes #2878. +- Bugfix: display error message on registration problem. Closes + #3039. +- Make wizard use the main event loop, ensuring clean termination. +- Use cocoasudo for installing missing updown scripts. +- Bugfix: Systray Turn ON action fails because is not correctly + enabled/disabled. Closes #3125. +- Bugfix: wrong systray icon on startup. Closes #3147. +- Bugfix: parse line return in the logger window. Closes #3151. +- Do not log user data on registration. Fixes #3168. +- Add --log-append eip.log to windows EIP launcher options to save + the logs in case of any problems. Fixes #2054. +- OSX: Make the install_path relative to the launcher path instead + -f absolute. +- OSX: Fix icon display in cocoasudo. +- OSX: Raise window when showing if running on OSX. +- Bugfix: EIP status button moved to status panel. +- Check if there is no gateway to use and display correct + message. Close #2921. +- Reorder tray icons according new design. Closes #2919. +- Redirect stdout/stderr and twisted log to the logger. Closes + #3134. +- Improve LoggerWindow colors for easier debugging. +- Move the key manager to its own repository/package. + +0.2.2 Jun 28: ++++++++++++++ + +- Add support for the kde polkit daemon +- Handle 'Incorrect Password' exception (keyring) +- Select the configured domain in the providers combo box. Closes + #2693. +- Remember provider along with the username and password. Closes + #2755. +- Close the app on rejected wizard. Closes bug #2905. +- Only use the Keyring when it's using a known good backend. Closes + #2960 +- Update implementation and semantics of the supported and available + services by a provider. Closes bug #3032. +- Only show the question mark for a check being done if the previous + -ne passed. Fixes #2569. +- Fix main client window not restoring after minimized into + systray. Closes #2574 +- Set EIP different status icons depending on OS. Closes #2643. +- Reimplement openvpn invocation to use twisted ProcessProtocol +- Add runtime requirements checker, verifies that the requirements + are installed and in its correct versions. Closes #2563 +- Add centraliced logging facility, log history in a window. Closes + #2566 +- Improve wizard, hide registration widgets (labels, inputs, button) + and only display a message. Closes #2694 +- Clarify labels through the app (use of EIP) +- Check if the provider api version is supported. Closes feature + #2774. +- Autoselect VPN gateway based on timezone. Closes #2790. +- Disable vpn disconnect on logout. Closes #2795. +- Improve gateway selector based on timezone. It allows to use + multiple gateways in openvpn for redundancy. Closes #2894. +- Use cocoasudo in place of osascript for osx privilege escalation + during openvpn launch. +- Clicking in the tray icon will always show the context menu + instead of activating the window under certain + circumstances. Closes #2788 +- Autostart EIP whenever possible. Closes #2815 +- Update test suite, run_scripts and requirements to run smoothly + with buildbot. +- Add a copy of the processed requirements to util/ +- Display the default provider configured in the systray menu. Close + #2813 +- Make the login steps be a chain of defers in order to be able to + have more cancel points for the whole procedure. Closes #2571 +- Linux: check for up/down scripts and policy files and ask user for + permission to install them in a root-writeable location. Used from + within bundle or for broken installations. +- Integrate SMTP-Relay into the client. +- Integrate Soledad and KeyManager. +- Move the KeyManager from leap.common to leap-client. +- Only use one systray icon, repesenting the status for EIP. Closes + #2762 +- Properly set the binary manifest to the windows openvpn + binary. Closes #203 +- OSX: Add dialog with suggestion to install up/down scripts if + these not found. Closes: #1264, #2759, #2249 +- Workaround for PySide breaking with multiple inheritance. Closes + #2827 +- Refactor login to its own widget and remove Utils menu. Closes + #2789 +- Refactor the status bits out of the MainWindow to its own + StatusPanelWidget. Closes #2792 +- Save the default provider to be used for autostart EIP as + DefaultProvider in leap.conf. Closes #2793 +- Cleanly terminate openvpn process, sending SIGTERM and SIGKILL + after a while. Closes #2753 +- Use twisted's deferToThread and Deferreds to handle parallel tasks +- Use a qt4 reactor for twisted, for launching leap twisted + services. + +0.2.1 May 15: ++++++++++++++ + +- Rewrite most of the client based on the insight gained so far. +- Deselecting the remember checkbox makes the app not populate + user/password values on the login widget. Closes #2059 +- Rewording of setup steps in wizard, to make them more meaningful + to the non-technical user. Closes #2061 +- Fix typo in wizard. +- Fix multiple drawing of services if going back. +- Make registration errors show in red. +- Add a warning if EIP service needs admin password. Addresses part + -f #2062 +- Make traffic indicators display fixed precision. Closes #2114 +- Do not hide the application if the user right clicked the system + tray icon. +- Sanitize network-fetched content that is used to build openvpn + command. +- Avoids multiple instances of leap-client. Each new one just raises + the existing instance and quits. +- Use dark eip icons os osx. Closes #2130 +- Moves BaseConfig to leap.common.config. Closes #2164 +- Add handling for ASSIGN_IP state from OpenVPN in the mainwindow. +- Emit events notifying of the session_id and uid after + authentication so other services can make use of it. Closes #1957 +- Working packaging workflow with rewritten client, using + pyinstaller and platypus. +- Remove network checks temporarily until we find a good way of + doing it, and a good way to deal with them. +- Saves the token to allow token authenticated queries. +- Turn "leap" into namespace package, move common files to + leap_common package that can be shared by other LEAP projects. +- Support standalone configurations for distribution in thumbdrives + and the like. +- Add support for requests < 1.0.0 +- Tests infrastructure, and tests for crypto/srpauth and crypto/srpregister. +- Documentation updated for 0.2.1 release. +- Docstrings style changed to fit sphinx autodoc format. +- Add a simple UI to notify of pending updates. +- Add Windows support. +- Try to install TAP driver on Windows if no tap device is preset. diff --git a/MANIFEST.in b/MANIFEST.in index 876393da..73355b99 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,7 +6,7 @@ prune docs/covhtml include versioneer.py include LICENSE include README.rst -include CHANGELOG +include CHANGELOG.rst include src/leap/bitmask/util/reqs.txt include src/leap/bitmask/crypto/tests/wrongcert.pem @@ -1,5 +1,6 @@ Bitmask ======= + *your internet encryption toolkit* .. image:: https://pypip.in/v/leap.bitmask/badge.png @@ -7,12 +8,13 @@ Bitmask **Bitmask** is the multiplatform desktop client for the services offered by `the LEAP Platform`_. + It is written in python using `PySide`_ and licensed under the GPL3. -Currently we distribute pre-compiled bundles for Linux and OSX, with Windows -bundles following soon. +Currently we distribute pre-compiled `bundles`_ for Linux, OSX and Windows. .. _`PySide`: http://qt-project.org/wiki/PySide .. _`the LEAP Platform`: https://github.com/leapcode/leap_platform +.. _`bundles`: https://downloads.leap.se/client/ Read the Docs! @@ -35,19 +37,26 @@ Bitmask depends on these libraries: Python packages are listed in ``pkg/requirements.pip`` and ``pkg/test-requirements.pip`` Getting dependencies under debian -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +++++++++++++++++++++++++++++++++++ With a Debian based system, to be able to run Bitmask you need to run the following command:: - $ sudo apt-get install openvpn python-pyside pyside-tools python-setuptools python-all-dev python-pip python-dev python-openssl + $ sudo apt-get install git python-dev python-setuptools + python-virtualenv python-pip python-openssl libsqlite3-dev g++ openvpn + pyside-tools python-pyside Installing ----------- -After getting the source and installing all the dependencies, proceed to install ``bitmask`` package:: +Quick install, from the cheese shop:: + + $ sudo pip install leap.bitmask + +If you prefer to install from sources:: + + $ make + $ sudo python2 setup.py install - $ make - $ sudo python2 setup.py install Running ------- @@ -100,13 +109,6 @@ Run Bitmask:: (bitmask)$ bitmask --debug - -If you are testing a new provider that doesn't have the proper certificates yet, you can use --danger flag, but **DO NOT use it on a regular basis**. - -**WARNING**: If you use the --danger flag you may be victim to a MITM_ attack without noticing. Use at your own risk. - -.. _MITM: http://en.wikipedia.org/wiki/Man-in-the-middle_attack - Testing ======= diff --git a/changes/log_eip_status b/changes/log_eip_status new file mode 100644 index 00000000..32ac2b42 --- /dev/null +++ b/changes/log_eip_status @@ -0,0 +1 @@ +- Properly log EIP status changes. diff --git a/docs/dev/environment.rst b/docs/dev/environment.rst index e942b1cb..0f6366ef 100644 --- a/docs/dev/environment.rst +++ b/docs/dev/environment.rst @@ -3,7 +3,11 @@ Setting up a development environment ==================================== -This document covers how to get an enviroment ready to contribute code to Bitmask. +This document covers how to get an enviroment ready to contribute code to +Bitmask, with some explanations of what are we doing in each step along the way. +For just the meat, check the :ref:`quickstart <quickstart>` section. If you only +want to do a a quick fetch of the latest code for casual testing, you can use +the :ref:`bootstrap script <fetchinglatest>` instead. Cloning the repo ---------------- @@ -13,14 +17,15 @@ Cloning the repo :: - git clone git://leap.se/bitmask_client + git clone https://leap.se/git/?p=bitmask_client.git bitmask + cd bitmask git checkout develop .. XXX change this when repo changes. Base Dependencies ------------------ -Bitmask depends on these libraries: +Bitmask depends on these base libraries: * `python 2.6 or 2.7` * `qt4` libraries (see also :ref:`Troubleshooting PySide install <pysidevirtualenv>` about how to install inside your virtualenv) @@ -29,13 +34,11 @@ Bitmask depends on these libraries: Debian ^^^^^^ -In debian-based systems:: +In debian-based systems, you can get everything you need: - $ apt-get install openvpn python-pyside python-openssl - -To install the software from sources:: - - $ apt-get install python-pip python-dev +.. include:: quickstart.rst + :start-after: begin-debian-deps + :end-before: end-debian-deps .. _virtualenv: @@ -60,10 +63,17 @@ Read more about it in the `project documentation page <http://pypi.python.org/py Create and activate your dev environment ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -:: - $ virtualenv </path/to/new/environment> - $ source </path/to/new/environment>/bin/activate +You first create a virtualenv in any directory that you like:: + + $ mkdir ~/Virtualenvs + $ virtualenv ~/Virtualenvs/bitmask + $ source ~/Virtualenvs/bitmask/bin/activate + (bitmask)$ + +Note the change in the prompt. + +.. TODO use virtualenvwrapper + isis non-sudo recipe here .. _pysidevirtualenv: @@ -74,16 +84,12 @@ If you attempt to install PySide inside a virtualenv as part of the rest of the As a workaround, you can run the following script after creating your virtualenv. It will symlink to your global PySide installation (*this is the recommended way if you are running a debian-based system*):: - $ pkg/postmkvenv.sh + (bitmask)$ pkg/postmkvenv.sh -A second option if that does not work for you would be to install PySide globally and pass the ``--site-packages`` option when you are creating your virtualenv:: +A second option if that does not work for you would be to install PySide globally and pass the ``--system-site-packages`` option when you are creating your virtualenv:: - $ apt-get install python-pyside - $ virtualenv --site-packages . - -After that, you must export ``LEAP_VENV_SKIP_PYSIDE`` to skip the isntallation:: - - $ export LEAP_VENV_SKIP_PYSIDE=1 + $ sudo apt-get install python-pyside + $ virtualenv --system-site-packages . And now you are ready to proceed with the next section. @@ -92,10 +98,50 @@ And now you are ready to proceed with the next section. Install python dependencies ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -You can install python dependencies with ``pip``. If you do it inside your working environment, they will be installed avoiding the need for administrative permissions:: +You can install python dependencies with ``pip``. If you do it inside your +working environment, they will be installed avoiding the need for administrative +permissions:: + + (bitmask)$ pip install -r pkg/requirements.pip + +This step is not strictly needed, since the ``setup.py develop`` in the next +paragraph with also fetch the needed dependencies. But you need to know abou it: +when you or any person in the development team will be adding a new dependency, +you will have to repeat this command so that the new dependencies are installed +inside your virtualenv. + +.. _installbitmaskdevelop: - $ pip install -r pkg/requirements.pip +Install Bitmask +--------------- +Normally we would install the ``leap.bitmask`` package as any other package +inside the virtualenv. +But, instead, we will be using setuptools **development mode**. The difference +is that, instead of installing the package in a permanent location in your +regular installed packages path, it will create a link from the local +site-packages to your working directory. In this way, your changes will always +be in the installation path without need to install the package you are working +on.:: + + (bitmask)$ python2 setup.py develop + +After this step, your Bitmask launcher will be located at +``~/Virtualenvs/bitmask/bin/bitmask``, and it will be in the path as long as you +have sourced your virtualenv. + +.. _makeresources: + +Make resources +-------------- + +We also need to compile the resource files:: + + (bitmask)$ make resources + +You need to repeat this step each time you change a ``.ui`` file. + +.. TODO need to make translations too? .. _copyscriptfiles: @@ -122,6 +168,7 @@ If you *only* are running bitmask from inside a virtualenv, you will need to cop Missing Authentication agent ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. TODO I think we could be safely removing this section by now. If you are using linux and running a desktop other than unity or gnome, you might get an error saying that you are not running the authentication agent. For systems with gnome libraries installed you can launch it like this:: @@ -130,3 +177,12 @@ If you are using linux and running a desktop other than unity or gnome, you migh or if you are a kde user:: /usr/lib/kde4/libexec/polkit-kde-authentication-agent-1 & + +Running! +-------- + +If everything went well, you should be able to run your client by invoking +``bitmask``. If it does not get launched, or you just want to see more verbose +output, try the debug mode:: + + (bitmask)$ bitmask --debug diff --git a/docs/dev/quickstart.rst b/docs/dev/quickstart.rst new file mode 100644 index 00000000..8ef7dfb8 --- /dev/null +++ b/docs/dev/quickstart.rst @@ -0,0 +1,82 @@ +.. _quickstart: + +Quickstart +========== + +**Assumptions:** These instructions were made on a clean Ubuntu 12.04.3 +system. + +**Goal:** With minimal effort or reading install the necessary packages +to build the latest development code for ``bitmask_client`` + +**Outcome:** At the end of these instructions, you should be able to run +the latest development branch for bitmask client, getting the GUI in debug +mode and connect to a LEAP provider (bitmask.net) + +If you want to know what each step is for, check +:ref:`this other section <environment>`. + + +Prerequisites +------------- + +.. begin-debian-deps +:: + + $ sudo apt-get install git python-dev python-setuptools + python-virtualenv python-pip python-openssl libsqlite3-dev g++ openvpn + pyside-tools python-pyside + +.. python-qt4 ??? (for translations) +.. TODO I'm pretty sure python-qt4 shoudln't be there... + Nor libsqlite-dev, that's a bug in python-sqlcipher/soledad. + + +.. XXX any change HERE ^^^^ should be reflected also in README.rst. + From any other place in the documentation, it should be just included. + +.. end-debian-deps + +Clone the repo into your working directory, and checkout development branch:: + + $ git clone https://github.com/leapcode/bitmask_client bitmask + $ cd bitmask + $ git checkout develop + + +Create and activate the virtualenv, and symlink to your gloabal PySide install:: + + $ virtualenv . + $ source bin/activate + (bitmask)$ pkg/postmkvenv.sh + + +Python libraries +---------------- + +Install the bitmask package in development mode inside the virtualenv. This will +also install the needed dependencies:: + + (bitmask)$ python2 setup.py develop + +Compile the resource files:: + + (bitmask)$ make resources + +Copy necessary files into system folders, with root privileges:: + + (bitmask)$ sudo mkdir -p /etc/leap + (bitmask)$ sudo cp pkg/linux/resolv-update /etc/leap + (bitmask)$ sudo cp pkg/linux/polkit/net.openvpn.gui.leap.policy /usr/share/polkit-1/actions/ + + +Running +-------- + +Run ``bitmask_client`` in debug mode:: + + (bitmask)$ bitmask --debug + +You should see the ``bitmask_client`` window prompting to connect to an +existing node or add a new one. If not, something went wrong, maybe ask +on #leap-dev at irc.freenode.net diff --git a/docs/dev/workflow.rst b/docs/dev/workflow.rst index abd228c1..f217df24 100644 --- a/docs/dev/workflow.rst +++ b/docs/dev/workflow.rst @@ -90,3 +90,8 @@ Other methods ------------- Feel free to use any other methods like format-patch and mail or whatever method you prefer, although we recommend you follow the same workflow as we do. + +Contributors +------------ + +Please, add yourself to ``dev/authors.rst`` if you contribute code to Bitmask. diff --git a/docs/index.rst b/docs/index.rst index d0b0ff22..f210be8c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -44,6 +44,7 @@ If you want to contribute to the project, we wrote this for you. .. toctree:: :maxdepth: 1 + dev/quickstart dev/environment dev/tests dev/workflow diff --git a/docs/testers/howto.rst b/docs/testers/howto.rst index 1e276f7d..d9536632 100644 --- a/docs/testers/howto.rst +++ b/docs/testers/howto.rst @@ -108,16 +108,11 @@ compact way suitable (ahem) also for non developers. Install dependencies ^^^^^^^^^^^^^^^^^^^^ -First, install all the base dependencies plus git, virtualenv and development -files needed to compile several extensions:: +First, install all the development files and dependencies needed to compile: - apt-get install openvpn git-core python-dev python-pyside python-setuptools python-virtualenv - -.. TODO Should review these dependencies. I think python-sqlite is missing, we - have an issue for that^^ - -.. TODO we really should keep the dependencies in a single file that we are able to - include, to avoid phasing out. +.. include:: ../dev/quickstart.rst + :start-after: begin-debian-deps + :end-before: end-debian-deps Bootstrap script @@ -132,7 +127,7 @@ Download and source the following script in the parent folder where you want you .. code-block:: bash - cd /tmp + cd /tmp wget https://raw.github.com/leapcode/bitmask_client/develop/pkg/scripts/bitmask_bootstrap.sh source bitmask_bootstrap.sh diff --git a/pkg/requirements.pip b/pkg/requirements.pip index 458db39c..885b19f8 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -6,7 +6,7 @@ # Use LEAP_VENV_SKIP_PYSIDE to avoid installing it! argparse -requests +requests>=1.1.0 srp>=1.0.2 pyopenssl python-dateutil @@ -16,11 +16,12 @@ twisted qt4reactor python-daemon # this should not be needed for Windows. keyring +zope.proxy leap.common>=0.3.4 -leap.soledad.client>=0.4.0 +leap.soledad.client>=0.4.2 leap.keymanager>=0.3.4 -leap.mail>=0.3.5 +leap.mail>=0.3.6 # Remove this when u1db fixes its dependency on oauth oauth diff --git a/pkg/scripts/bootstrap_develop.sh b/pkg/scripts/bootstrap_develop.sh new file mode 100755 index 00000000..4660c499 --- /dev/null +++ b/pkg/scripts/bootstrap_develop.sh @@ -0,0 +1,191 @@ +#!/bin/bash +###################################################################### +# boostrap_develop.sh +# Copyright (C) 2013 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/>. +###################################################################### +set -e # Exit immediately if a command exits with a non-zero status. +REPOSITORIES="bitmask_client leap_pycommon soledad keymanager leap_mail" +PACKAGES="leap_pycommon keymanager soledad/common soledad/client soledad/server leap_mail bitmask_client" +REPOS_ROOT=`pwd` # Root path for all the needed repositories + +PS4=">> " # for debugging + +# Escape code +esc=`echo -en "\033"` + +# Set colors +cc_green="${esc}[0;32m" +cc_yellow="${esc}[0;33m" +cc_blue="${esc}[0;34m" +cc_red="${esc}[0;31m" +cc_normal=`echo -en "${esc}[m\017"` + +clone_repos() { + status="cloning repositories" + echo "${cc_green}Status: $status...${cc_normal}" + set -x # show commands + + # clone the leap.se repos + git clone ssh://gitolite@leap.se/bitmask_client + git clone ssh://gitolite@leap.se/leap_pycommon + git clone ssh://gitolite@leap.se/soledad + git clone ssh://gitolite@leap.se/keymanager + git clone ssh://gitolite@leap.se/leap_mail + + set +x + echo "${cc_green}Status: $status done!${cc_normal}" +} + +checkout_develop(){ + status="checkout develop" + echo "${cc_green}Status: $status...${cc_normal}" + set -x # show commands + + # get the latest develop in every repo + for repo in $REPOSITORIES; do + cd $REPOS_ROOT/$repo + git checkout -f develop + done + + set +x + echo "${cc_green}Status: $status done!${cc_normal}" +} + +update_repos() { + status="updating repositories" + echo "${cc_green}Status: $status...${cc_normal}" + set -x # show commands + + # get the latest develop in every repo + for repo in $REPOSITORIES; do + cd $REPOS_ROOT/$repo + git checkout -f develop + git fetch origin; git fetch --tags origin + git reset --hard origin/develop + done + + set +x + echo "${cc_green}Status: $status done!${cc_normal}" +} + +create_venv() { + status="creating virtualenv" + echo "${cc_green}Status: $status...${cc_normal}" + set -x # show commands + + # create and activate the virtualenv + cd $REPOS_ROOT + virtualenv bitmask.venv && source ./bitmask.venv/bin/activate + + # symlink PySide to the venv + cd $REPOS_ROOT/bitmask_client + ./pkg/postmkvenv.sh + + set +x + echo "${cc_green}Status: $status done.${cc_normal}" +} + +setup_develop() { + status="installing packages" + echo "${cc_green}Status: $status...${cc_normal}" + set -x # show commands + + # do a setup develop in every package + for package in $PACKAGES; do + cd $REPOS_ROOT/$package + python setup.py develop + done + + # hack to solve gnupg version problem + pip uninstall -y gnupg && pip install gnupg + + set +x + echo "${cc_green}Status: $status done.${cc_normal}" +} + +finish(){ + echo "${cc_green}Status: process completed.${cc_normal}" + echo "You can run the client with the following command:" + echo -n "${cc_yellow}" + echo " shell> source bitmask.venv/bin/activate" + echo " shell> python bitmask_client/src/leap/bitmask/app.py -d" + echo "${cc_normal}" + echo "or with this script using:" + echo "${cc_yellow} shell> $0 run${cc_normal}" + echo +} + +initialize() { + clone_repos + checkout_develop + create_venv + setup_develop + + # make: compile ui and resources in client + make + + finish +} + +update() { + update_repos + + source $REPOS_ROOT/bitmask.venv/bin/activate + + setup_develop + + # make: compile ui and resources in client + make + + finish +} + +run() { + echo "${cc_green}Status: running client...${cc_normal}" + source bitmask.venv/bin/activate + set -x + python bitmask_client/src/leap/bitmask/app.py -d $* + set +x +} + +help() { + echo ">> LEAP developer helper" + echo "Bootstraps the environment to start developing the bitmask client" + echo "with all the needed repositories and dependencies." + echo + echo "Usage: $0 {init | update | help}" + echo + echo " init : Initialize repositories, create virtualenv and \`python setup.py develop\` all." + 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 +} + +case "$1" in + init) + initialize + ;; + update) + update + ;; + run) + run + ;; + *) + help + ;; +esac @@ -44,7 +44,6 @@ versioneer.versionfile_build = 'leap/bitmask/_version.py' versioneer.tag_prefix = '' # tags are like 1.2.0 versioneer.parentdir_prefix = 'leap.bitmask-' -#from setuptools import Command # The following import avoids the premature unloading of the `util` submodule # when running tests, which would cause an error when nose finishes tests and @@ -74,10 +73,69 @@ trove_classifiers = [ "Topic :: Utilities", ] +DOWNLOAD_BASE = ('https://github.com/leapcode/bitmask_client/' + 'archive/%s.tar.gz') +_versions = versioneer.get_versions() +VERSION = _versions['version'] +VERSION_FULL = _versions['full'] +DOWNLOAD_URL = "" -parsed_reqs = utils.parse_requirements() +# get the short version for the download url +_version_short = re.findall('\d+\.\d+\.\d+', VERSION) +if len(_version_short) > 0: + VERSION_SHORT = _version_short[0] + DOWNLOAD_URL = DOWNLOAD_BASE % VERSION_SHORT cmdclass = versioneer.get_cmdclass() + + +from setuptools import Command + + +class freeze_debianver(Command): + """ + Freezes the version in a debian branch. + To be used after merging the development branch onto the debian one. + """ + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + proceed = str(raw_input( + "This will overwrite the file _version.py. Continue? [y/N] ")) + if proceed != "y": + print("He. You scared. Aborting.") + return + template = r""" +# This file was generated by the `freeze_debianver` command in setup.py +# Using 'versioneer.py' (0.7+) from +# revision-control system data, or from the parent directory name of an +# unpacked source archive. Distribution tarballs contain a pre-generated copy +# of this file. + +version_version = '{version}' +version_full = '{version_full}' +""" + templatefun = r""" + +def get_versions(default={}, verbose=False): + return {'version': version_version, 'full': version_full} +""" + subst_template = template.format( + version=VERSION_SHORT, + version_full=VERSION_FULL) + templatefun + with open(versioneer.versionfile_source, 'w') as f: + f.write(subst_template) + + +cmdclass["freeze_debianver"] = freeze_debianver +parsed_reqs = utils.parse_requirements() + leap_launcher = 'bitmask=leap.bitmask.app:main' from setuptools.command.develop import develop as _develop @@ -150,16 +208,6 @@ if IS_LINUX: ["pkg/linux/resolv-update"]), ] -DOWNLOAD_BASE = ('https://github.com/leapcode/bitmask_client/' - 'archive/%s.tar.gz') -VERSION = versioneer.get_version() -DOWNLOAD_URL = "" - -# get the short version for the download url -short_ver = re.findall('\d+\.\d+\.\d+', VERSION) -if len(short_ver) > 0: - DOWNLOAD_URL = DOWNLOAD_BASE % short_ver[0] - setup( name="leap.bitmask", @@ -168,25 +216,8 @@ setup( cmdclass=cmdclass, description=("The Internet Encryption Toolkit: " "Encrypted Internet Proxy and Encrypted Mail."), - # XXX unify the long_description on a file of its own, so we - # can reuse it easily. - long_description=( - "Bitmask is the multiplatform desktop client for the LEAP Platform." - "\n" - "The LEAP Encryption Access Project develops " - "a multi-year plan to secure everyday communication.\n " - "The Encrypted Internet Proxy (EIP) provides circumvention, location " - "anonymization, and traffic encryption in a hassle-free, " - "automatically self-configuring fashion.\n" - "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 signature verification. \n" - "The Encrypted Mail services will run local SMTP and IMAP proxies " - "that, once you configure the mail client of your choice, will " - "automatically encrypt and decrypt your email using GPG encryption " - "under the hood." - ), + long_description=open('README.rst').read() + '\n\n\n' + + open('CHANGELOG.rst').read(), classifiers=trove_classifiers, install_requires=parsed_reqs, test_suite='nose.collector', diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 40a77075..3bb9c8c3 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -39,7 +39,6 @@ # M:::::::::::~NMMM7???7MMMM:::::::::::::::::::::::NMMMI??I7MMMM:::::::::::::M # M::::::::::::::7MMMMMMM+:::::::::::::::::::::::::::?MMMMMMMZ:::::::::::::::M # (thanks to: http://www.glassgiant.com/ascii/) - import logging import signal import sys @@ -105,9 +104,16 @@ def add_logger_handlers(debug=False, logfile=None): formatter = logging.Formatter(log_format) # Console handler - console = logging.StreamHandler() - console.setLevel(level) - console.setFormatter(formatter) + try: + import coloredlogs + console = coloredlogs.ColoredStreamHandler(level=level) + except ImportError: + console = logging.StreamHandler() + console.setLevel(level) + console.setFormatter(formatter) + using_coloredlog = False + else: + using_coloredlog = True silencer = log_silencer.SelectiveSilencerFilter() console.addFilter(silencer) @@ -131,6 +137,9 @@ def add_logger_handlers(debug=False, logfile=None): logger.addHandler(fileh) logger.debug('File handler plugged!') + if not using_coloredlog: + replace_stdout_stderr_with_logging(logger) + return logger @@ -185,7 +194,6 @@ def main(): BaseConfig.standalone = standalone logger = add_logger_handlers(debug, logfile) - replace_stdout_stderr_with_logging(logger) # And then we import all the other stuff from leap.bitmask.gui import locale_rc diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py index 44698d83..e80b2337 100644 --- a/src/leap/bitmask/config/providerconfig.py +++ b/src/leap/bitmask/config/providerconfig.py @@ -169,11 +169,12 @@ class ProviderConfig(BaseConfig): checking if the cert exists because we are about to write it. :type about_to_download: bool + + :rtype: unicode """ cert_path = os.path.join(get_path_prefix(), "leap", "providers", - self.get_domain(), - "keys", "ca", "cacert.pem") + self.get_domain(), "keys", "ca", "cacert.pem") if not about_to_download: cert_exists = os.path.exists(cert_path) diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 47ed21b0..ab98850d 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -17,6 +17,7 @@ import binascii import logging +import sys import requests import srp @@ -31,6 +32,7 @@ from PySide import QtCore from twisted.internet import threads from leap.bitmask.util import request_helpers as reqhelper +from leap.bitmask.util.compat import requests_has_max_retries from leap.bitmask.util.constants import REQUEST_TIMEOUT from leap.common.check import leap_assert from leap.common.events import signal as events_signal @@ -184,7 +186,11 @@ class SRPAuth(QtCore.QObject): # NOTE: This is a workaround for the moment, the server # side seems to return correctly every time, but it fails # on the client end. - self._session.mount('https://', HTTPAdapter(max_retries=30)) + if requests_has_max_retries: + adapter = HTTPAdapter(max_retries=30) + else: + adapter = HTTPAdapter() + self._session.mount('https://', adapter) def _safe_unhexlify(self, val): """ @@ -211,10 +217,9 @@ class SRPAuth(QtCore.QObject): """ logger.debug("Authentication preprocessing...") - self._srp_user = self._srp.User(username, - password, - self._hashfun, - self._ng) + self._srp_user = self._srp.User(username.encode('utf-8'), + password.encode('utf-8'), + self._hashfun, self._ng) _, A = self._srp_user.start_authentication() self._srp_a = A @@ -249,10 +254,13 @@ class SRPAuth(QtCore.QObject): (self._provider_config.get_api_uri(), self._provider_config.get_api_version(), "sessions") + + ca_cert_path = self._provider_config.get_ca_cert_path() + ca_cert_path = ca_cert_path.encode(sys.getfilesystemencoding()) + init_session = self._session.post(sessions_url, data=auth_data, - verify=self._provider_config. - get_ca_cert_path(), + verify=ca_cert_path, timeout=REQUEST_TIMEOUT) # Clean up A value, we don't need it anymore self._srp_a = None @@ -478,7 +486,8 @@ class SRPAuth(QtCore.QObject): self.get_uid()) salt, verifier = self._srp.create_salted_verification_key( - self._username, new_password, self._hashfun, self._ng) + self._username.encode('utf-8'), new_password.encode('utf-8'), + self._hashfun, self._ng) cookies = {self.SESSION_ID_KEY: self.get_session_id()} headers = { @@ -509,9 +518,9 @@ class SRPAuth(QtCore.QObject): Might raise SRPAuthenticationError :param username: username for this session - :type username: str + :type username: unicode :param password: password for this user - :type password: str + :type password: unicode :returns: A defer on a different thread :rtype: twisted.internet.defer.Deferred diff --git a/src/leap/bitmask/gui/eip_preferenceswindow.py b/src/leap/bitmask/gui/eip_preferenceswindow.py index 9f8c4ff4..e0c5d51f 100644 --- a/src/leap/bitmask/gui/eip_preferenceswindow.py +++ b/src/leap/bitmask/gui/eip_preferenceswindow.py @@ -50,7 +50,6 @@ class EIPPreferencesWindow(QtGui.QDialog): self.ui = Ui_EIPPreferences() self.ui.setupUi(self) self.ui.lblProvidersGatewayStatus.setVisible(False) - self.ui.lblAutoStartEIPStatus.setVisible(False) # Connections self.ui.cbProvidersGateway.currentIndexChanged[unicode].connect( @@ -59,40 +58,8 @@ class EIPPreferencesWindow(QtGui.QDialog): self.ui.cbGateways.currentIndexChanged[unicode].connect( lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False)) - self.ui.cbProvidersEIP.currentIndexChanged[unicode].connect( - lambda x: self.ui.lblAutoStartEIPStatus.setVisible(False)) - - self.ui.cbAutoStartEIP.toggled.connect( - lambda x: self.ui.lblAutoStartEIPStatus.setVisible(False)) - - self.ui.pbSaveAutoStartEIP.clicked.connect(self._save_auto_start_eip) - self._add_configured_providers() - # Load auto start EIP settings - self.ui.cbAutoStartEIP.setChecked(self._settings.get_autostart_eip()) - default_provider = self._settings.get_defaultprovider() - idx = self.ui.cbProvidersEIP.findText(default_provider) - self.ui.cbProvidersEIP.setCurrentIndex(idx) - - def _save_auto_start_eip(self): - """ - SLOT - TRIGGER: - self.ui.cbAutoStartEIP.toggled - - Saves the automatic start of EIP user preference. - """ - default_provider = self.ui.cbProvidersEIP.currentText() - enabled = self.ui.cbAutoStartEIP.isChecked() - - self._settings.set_autostart_eip(enabled) - self._settings.set_defaultprovider(default_provider) - - self.ui.lblAutoStartEIPStatus.show() - logger.debug('Auto start EIP saved: {0} {1}.'.format( - default_provider, enabled)) - def _set_providers_gateway_status(self, status, success=False, error=False): """ @@ -120,16 +87,13 @@ class EIPPreferencesWindow(QtGui.QDialog): Add the client's configured providers to the providers combo boxes. """ self.ui.cbProvidersGateway.clear() - self.ui.cbProvidersEIP.clear() providers = self._settings.get_configured_providers() if not providers: - self.ui.gbAutomaticEIP.setEnabled(False) self.ui.gbGatewaySelector.setEnabled(False) return for provider in providers: self.ui.cbProvidersGateway.addItem(provider) - self.ui.cbProvidersEIP.addItem(provider) def _save_selected_gateway(self, provider): """ diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 77685cd3..324586c0 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -269,6 +269,10 @@ class EIPStatusWidget(QtGui.QWidget): :type error: bool """ leap_assert_type(error, bool) + if error: + logger.error(status) + else: + logger.debug(status) self._eip_status = status if error: status = "<font color='red'>%s</font>" % (status,) diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index ac34fe23..b21057f0 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -49,6 +49,9 @@ class LoginWidget(QtGui.QWidget): BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$" + # Keyring + KEYRING_KEY = "bitmask" + def __init__(self, settings, parent=None): """ Constructs the LoginWidget. @@ -168,7 +171,7 @@ class LoginWidget(QtGui.QWidget): """ Returns the user that appears in the widget. - :rtype: str + :rtype: unicode """ return self.ui.lnUser.text() @@ -177,7 +180,7 @@ class LoginWidget(QtGui.QWidget): Sets the password for the widget :param password: password to set - :type password: str + :type password: unicode """ self.ui.lnPassword.setText(password) @@ -185,7 +188,7 @@ class LoginWidget(QtGui.QWidget): """ Returns the password that appears in the widget - :rtype: str + :rtype: unicode """ return self.ui.lnPassword.text() @@ -366,3 +369,41 @@ class LoginWidget(QtGui.QWidget): self.ui.btnLogout.setText(self.tr("Logout")) self.ui.btnLogout.setEnabled(True) self.ui.clblErrorMsg.hide() + + def load_user_from_keyring(self, saved_user): + """ + Tries to load a user from the keyring, returns True if it was + loaded successfully, False otherwise. + + :param saved_user: String containing the saved username as + user@domain + :type saved_user: unicode + + :rtype: bool + """ + leap_assert_type(saved_user, unicode) + + try: + username, domain = saved_user.split('@') + except ValueError as e: + # if the saved_user does not contain an '@' + logger.error('Username@provider malformed. %r' % (e, )) + return False + + self.set_user(username) + + self.set_remember(True) + + saved_password = None + try: + saved_password = keyring.get_password(self.KEYRING_KEY, + saved_user + .encode("utf8")) + except ValueError as e: + logger.debug("Incorrect Password. %r." % (e,)) + + if saved_password is not None: + self.set_password(saved_password) + return True + + return False diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 83533666..c1e82d4d 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -50,6 +50,8 @@ class MailStatusWidget(QtGui.QWidget): QtGui.QWidget.__init__(self, parent) self._systray = None + self._disabled = True + self._started = False self.ui = Ui_MailStatusWidget() self.ui.setupUi(self) @@ -98,29 +100,16 @@ class MailStatusWidget(QtGui.QWidget): callback=self._mail_handle_soledad_events, reqcbk=lambda req, resp: None) - register(signal=proto.SMTP_SERVICE_STARTED, - callback=self._mail_handle_smtp_events, - reqcbk=lambda req, resp: None) - - register(signal=proto.SMTP_SERVICE_FAILED_TO_START, - callback=self._mail_handle_smtp_events, - reqcbk=lambda req, resp: None) - - register(signal=proto.IMAP_SERVICE_STARTED, + register(signal=proto.IMAP_UNREAD_MAIL, callback=self._mail_handle_imap_events, reqcbk=lambda req, resp: None) - - register(signal=proto.IMAP_SERVICE_FAILED_TO_START, + register(signal=proto.IMAP_SERVICE_STARTED, callback=self._mail_handle_imap_events, reqcbk=lambda req, resp: None) - - register(signal=proto.IMAP_UNREAD_MAIL, + register(signal=proto.SMTP_SERVICE_STARTED, callback=self._mail_handle_imap_events, reqcbk=lambda req, resp: None) - self._smtp_started = False - self._imap_started = False - self._soledad_event.connect( self._mail_handle_soledad_events_slot) self._imap_event.connect( @@ -176,6 +165,9 @@ class MailStatusWidget(QtGui.QWidget): """ # TODO: Figure out how to handle this with the two status in different # classes + # XXX right now we could connect the state transition signals of the + # two connection machines (EIP/Mail) to a class that keeps track of the + # state -- kali # status = self.tr("Encrypted Internet: {0}").format(self._eip_status) # status += '\n' # status += self.tr("Mail is {0}").format(self._mx_status) @@ -292,11 +284,9 @@ class MailStatusWidget(QtGui.QWidget): """ # We want to ignore this kind of events once everything has # started - if self._smtp_started and self._imap_started: + if self._started: return - self._set_mail_status(self.tr("Starting..."), ready=1) - ext_status = "" if req.event == proto.KEYMANAGER_LOOKING_FOR_KEY: @@ -340,14 +330,9 @@ class MailStatusWidget(QtGui.QWidget): ext_status = "" if req.event == proto.SMTP_SERVICE_STARTED: - ext_status = self.tr("SMTP has started...") self._smtp_started = True - if self._smtp_started and self._imap_started: - self._set_mail_status(self.tr("ON"), ready=2) - ext_status = "" elif req.event == proto.SMTP_SERVICE_FAILED_TO_START: ext_status = self.tr("SMTP failed to start, check the logs.") - self._set_mail_status(self.tr("Failed")) else: leap_assert(False, "Don't know how to handle this state: %s" @@ -355,6 +340,8 @@ class MailStatusWidget(QtGui.QWidget): self._set_mail_status(ext_status, ready=2) + # ----- XXX deprecate (move to mail conductor) + def _mail_handle_imap_events(self, req): """ Callback for the IMAP events @@ -376,27 +363,15 @@ class MailStatusWidget(QtGui.QWidget): """ ext_status = None - if req.event == proto.IMAP_SERVICE_STARTED: - ext_status = self.tr("IMAP has started...") - self._imap_started = True - if self._smtp_started and self._imap_started: - self._set_mail_status(self.tr("ON"), ready=2) - ext_status = "" - elif req.event == proto.IMAP_SERVICE_FAILED_TO_START: - ext_status = self.tr("IMAP failed to start, check the logs.") - self._set_mail_status(self.tr("Failed")) - elif req.event == proto.IMAP_UNREAD_MAIL: - if self._smtp_started and self._imap_started: + if req.event == proto.IMAP_UNREAD_MAIL: + if self._started: if req.content != "0": self._set_mail_status(self.tr("%s Unread Emails") % (req.content,), ready=2) else: self._set_mail_status("", ready=2) - else: - leap_assert(False, # XXX ??? - "Don't know how to handle this state: %s" - % (req.event)) - + elif req.event == proto.IMAP_SERVICE_STARTED: + self._imap_started = True if ext_status is not None: self._set_mail_status(ext_status, ready=1) @@ -414,8 +389,50 @@ class MailStatusWidget(QtGui.QWidget): """ self._set_mail_status(self.tr("Disabled"), -1) - def stopped_mail(self): + # statuses + + # XXX make the signal emit the label and state. + + @QtCore.Slot() + def mail_state_disconnected(self): + """ + Displays the correct UI for the disconnected state. + """ + # XXX this should handle the disabled state better. + self._started = False + if self._disabled: + self.mail_state_disabled() + else: + self._set_mail_status(self.tr("OFF"), -1) + + @QtCore.Slot() + def mail_state_connecting(self): + """ + Displays the correct UI for the connecting state. + """ + self._disabled = False + self._started = True + self._set_mail_status(self.tr("Starting..."), 1) + + @QtCore.Slot() + def mail_state_disconnecting(self): + """ + Displays the correct UI for the connecting state. + """ + self._set_mail_status(self.tr("Disconnecting..."), 1) + + @QtCore.Slot() + def mail_state_connected(self): + """ + Displays the correct UI for the connected state. + """ + self._set_mail_status(self.tr("ON"), 2) + + @QtCore.Slot() + def mail_state_disabled(self): """ - Displayes the correct UI for the stopped state. + Displays the correct UI for the disabled state. """ - self._set_mail_status(self.tr("OFF")) + self._disabled = True + self._set_mail_status( + self.tr("You must be logged in to use encrypted email."), -1) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index f5631c69..5eb9e6dc 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -20,10 +20,9 @@ Main window for Bitmask. import logging import os -import keyring - from PySide import QtCore, QtGui from twisted.internet import threads +from zope.proxy import ProxyBase, setProxiedObject, sameProxiedObjects from leap.bitmask import __version__ as VERSION from leap.bitmask.config.leapsettings import LeapSettings @@ -39,18 +38,15 @@ from leap.bitmask.gui.mail_status import MailStatusWidget from leap.bitmask.gui.wizard import Wizard from leap.bitmask import provider -from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper -from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper -from leap.bitmask.services.eip import eipconfig -# XXX: Soledad might not work out of the box in Windows, issue #2932 -from leap.bitmask.services.soledad.soledadbootstrapper import \ - SoledadBootstrapper -from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper -from leap.bitmask.services.mail import imap from leap.bitmask.platform_init import IS_WIN, IS_MAC from leap.bitmask.platform_init.initializers import init_platform +from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper + +from leap.bitmask.services.mail import conductor as mail_conductor +from leap.bitmask.services.eip import eipconfig from leap.bitmask.services.eip import get_openvpn_management +from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper from leap.bitmask.services.eip.connection import EIPConnection from leap.bitmask.services.eip.vpnprocess import VPN from leap.bitmask.services.eip.vpnprocess import OpenVPNAlreadyRunning @@ -62,12 +58,12 @@ from leap.bitmask.services.eip.linuxvpnlauncher import EIPNoPkexecAvailable from leap.bitmask.services.eip.linuxvpnlauncher import \ EIPNoPolkitAuthAgentAvailable from leap.bitmask.services.eip.darwinvpnlauncher import EIPNoTunKextLoaded +from leap.bitmask.services.soledad.soledadbootstrapper import \ + SoledadBootstrapper from leap.bitmask.util.keyring_helpers import has_keyring from leap.bitmask.util.leap_log_handler import LeapLogHandler -from leap.bitmask.services.mail.smtpconfig import SMTPConfig - if IS_WIN: from leap.bitmask.platform_init.locks import WindowsLock from leap.bitmask.platform_init.locks import raise_window_ack @@ -90,13 +86,6 @@ class MainWindow(QtGui.QMainWindow): LOGIN_INDEX = 0 EIP_STATUS_INDEX = 1 - # Keyring - KEYRING_KEY = "bitmask" - - # SMTP - PORT_KEY = "port" - IP_KEY = "ip_address" - OPENVPN_SERVICE = "openvpn" MX_SERVICE = "mx" @@ -257,10 +246,6 @@ class MainWindow(QtGui.QMainWindow): self._soledad_bootstrapper.soledad_failed.connect( self._mail_status.set_soledad_failed) - self._smtp_bootstrapper = SMTPBootstrapper() - self._smtp_bootstrapper.download_config.connect( - self._smtp_bootstrapped_stage) - 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) @@ -307,12 +292,13 @@ 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._set_soledad_ready) self.mail_client_logged_in.connect(self._fetch_incoming_mail) self.logout.connect(self._stop_imap_service) self.logout.connect(self._stop_smtp_service) - self.logout.connect(self._mail_status.stopped_mail) ################################# end Qt Signals connection ######## @@ -325,17 +311,18 @@ class MainWindow(QtGui.QMainWindow): self._bypass_checks = bypass_checks - self._soledad = None - self._soledad_ready = False - self._keymanager = None - self._smtp_service = None - self._smtp_port = None - self._imap_service = None + # We initialize Soledad and Keymanager instances as + # transparent proxies, so we can pass the reference freely + # around. + self._soledad = ProxyBase(None) + self._keymanager = ProxyBase(None) self._login_defer = None self._download_provider_defer = None - self._smtp_config = SMTPConfig() + self._mail_conductor = mail_conductor.MailConductor( + self._soledad, self._keymanager) + self._mail_conductor.connect_mail_signals(self._mail_status) # Eip machine is a public attribute where the state machine for # the eip connection will be available to the different components. @@ -347,6 +334,7 @@ class MainWindow(QtGui.QMainWindow): self.eip_machine = None # start event machines self.start_eip_machine() + self._mail_conductor.start_mail_machine(parent=self) if self._first_run(): self._wizard_firstrun = True @@ -458,13 +446,11 @@ class MainWindow(QtGui.QMainWindow): Displays the preferences window. """ - preferences_window = PreferencesWindow(self, self._srp_auth) + preferences_window = PreferencesWindow(self, self._srp_auth, + self._provider_config) - if self._soledad_ready: - preferences_window.set_soledad_ready(self._soledad) - else: - self.soledad_ready.connect( - lambda: preferences_window.set_soledad_ready(self._soledad)) + self.soledad_ready.connect( + lambda: preferences_window.set_soledad_ready(self._soledad)) preferences_window.show() @@ -478,16 +464,6 @@ class MainWindow(QtGui.QMainWindow): """ EIPPreferencesWindow(self).show() - def _set_soledad_ready(self): - """ - SLOT - TRIGGERS: - self.soledad_ready - - It sets the soledad object as ready to use. - """ - self._soledad_ready = True - # # updates # @@ -562,6 +538,8 @@ class MainWindow(QtGui.QMainWindow): if IS_MAC: self.raise_() + self._hide_unsupported_services() + if self._wizard: possible_username = self._wizard.get_username() possible_password = self._wizard.get_password() @@ -594,32 +572,34 @@ class MainWindow(QtGui.QMainWindow): saved_user = self._settings.get_user() - try: - username, domain = saved_user.split('@') - except (ValueError, AttributeError) as e: - # if the saved_user does not contain an '@' or its None - logger.error('Username@provider malformed. %r' % (e, )) - saved_user = None - if saved_user is not None and has_keyring(): - # fill the username - self._login_widget.set_user(username) - - self._login_widget.set_remember(True) - - saved_password = None - try: - saved_password = keyring.get_password(self.KEYRING_KEY, - saved_user - .encode("utf8")) - except ValueError, e: - logger.debug("Incorrect Password. %r." % (e,)) - - if saved_password is not None: - self._login_widget.set_password( - saved_password.decode("utf8")) + if self._login_widget.load_user_from_keyring(saved_user): self._login() + def _hide_unsupported_services(self): + """ + Given a set of configured providers, it creates a set of + available services among all of them and displays the service + widgets of only those. + + This means, for example, that with just one provider with EIP + only, the mail widget won't be displayed. + """ + providers = self._settings.get_configured_providers() + + services = set() + + for prov in providers: + provider_config = ProviderConfig() + loaded = provider_config.load( + provider.get_provider_path(prov)) + if loaded: + for service in provider_config.get_services(): + services.add(service) + + self.ui.eipWidget.setVisible(self.OPENVPN_SERVICE in services) + self.ui.mailWidget.setVisible(self.MX_SERVICE in services) + # # systray # @@ -803,6 +783,7 @@ class MainWindow(QtGui.QMainWindow): provider configuration if it's not present, otherwise will emit the corresponding signals inmediately """ + # XXX should rename this provider, name clash. provider = self._login_widget.get_selected_provider() pb = self._provider_bootstrapper @@ -823,6 +804,7 @@ class MainWindow(QtGui.QMainWindow): :type data: dict """ if data[self._provider_bootstrapper.PASSED_KEY]: + # XXX should rename this provider, name clash. provider = self._login_widget.get_selected_provider() # If there's no loaded provider or @@ -895,8 +877,10 @@ class MainWindow(QtGui.QMainWindow): leap_assert(self._provider_config, "We need a provider config!") if data[self._provider_bootstrapper.PASSED_KEY]: - username = self._login_widget.get_user().encode("utf8") - password = self._login_widget.get_password().encode("utf8") + username = self._login_widget.get_user() + password = self._login_widget.get_password() + + self._hide_unsupported_services() if self._srp_auth is None: self._srp_auth = SRPAuth(self._provider_config) @@ -1023,114 +1007,58 @@ class MainWindow(QtGui.QMainWindow): logger.debug("ERROR on soledad bootstrapping:") logger.error("%r" % data[self._soledad_bootstrapper.ERROR_KEY]) return - else: - logger.debug("Done bootstrapping Soledad") - self._soledad = self._soledad_bootstrapper.soledad - self._keymanager = self._soledad_bootstrapper.keymanager + logger.debug("Done bootstrapping Soledad") + + # Update the proxy objects to point to + # the initialized instances. + setProxiedObject(self._soledad, + self._soledad_bootstrapper.soledad) + setProxiedObject(self._keymanager, + self._soledad_bootstrapper.keymanager) # Ok, now soledad is ready, so we can allow other things that # depend on soledad to start. # this will trigger start_imap_service + # and start_smtp_boostrapping self.soledad_ready.emit() - # TODO connect all these activations to the soledad_ready - # signal so the logic is clearer to follow. - - if self._provider_config.provides_mx() and \ - self._enabled_services.count(self.MX_SERVICE) > 0: - self._smtp_bootstrapper.run_smtp_setup_checks( - self._provider_config, - self._smtp_config, - True) - ################################################################### # Service control methods: smtp - def _smtp_bootstrapped_stage(self, data): + @QtCore.Slot() + def _start_smtp_bootstrapping(self): """ SLOT TRIGGERS: - self._smtp_bootstrapper.download_config - - If there was a problem, displays it, otherwise it does nothing. - This is used for intermediate bootstrapping stages, in case - they fail. - - :param data: result from the bootstrapping stage for Soledad - :type data: dict - """ - passed = data[self._smtp_bootstrapper.PASSED_KEY] - if not passed: - logger.error(data[self._smtp_bootstrapper.ERROR_KEY]) - return - logger.debug("Done bootstrapping SMTP") - self._check_smtp_config() - - def _check_smtp_config(self): - """ - Checks smtp config and tries to download smtp client cert if needed. + self.soledad_ready """ - hosts = self._smtp_config.get_hosts() - # TODO handle more than one host and define how to choose - if len(hosts) > 0: - hostname = hosts.keys()[0] - logger.debug("Using hostname %s for SMTP" % (hostname,)) - host = hosts[hostname][self.IP_KEY].encode("utf-8") - port = hosts[hostname][self.PORT_KEY] - - client_cert = self._smtp_config.get_client_cert_path( + # TODO for simmetry, this should be called start_smtp_service + # (and delegate all the checks to the conductor) + if self._provider_config.provides_mx() and \ + self._enabled_services.count(self.MX_SERVICE) > 0: + self._mail_conductor.smtp_bootstrapper.run_smtp_setup_checks( self._provider_config, - about_to_download=True) - - if not os.path.isfile(client_cert): - self._smtp_bootstrapper._download_client_certificates() - if os.path.isfile(client_cert): - self._start_smtp_service(host, port, client_cert) - else: - logger.warning("Tried to download email client " - "certificate, but could not find any") - - else: - logger.warning("No smtp hosts configured") - - def _start_smtp_service(self, host, port, cert): - """ - Starts the smtp service. - """ - # TODO Make the encrypted_only configurable - # TODO pick local smtp port in a better way - # TODO remove hard-coded port and let leap.mail set - # the specific default. - - from leap.mail.smtp import setup_smtp_relay - self._smtp_service, self._smtp_port = setup_smtp_relay( - port=2013, - keymanager=self._keymanager, - smtp_host=host, - smtp_port=port, - smtp_cert=cert, - smtp_key=cert, - encrypted_only=False) + self._mail_conductor.smtp_config, + download_if_needed=True) + # XXX --- should remove from here, and connecte directly to the state + # machine. + @QtCore.Slot() def _stop_smtp_service(self): """ SLOT TRIGGERS: self.logout """ - # There is a subtle difference here: - # we are stopping the factory for the smtp service here, - # but in the imap case we are just stopping the fetcher. - if self._smtp_service is not None: - logger.debug('Stopping smtp service.') - self._smtp_port.stopListening() - self._smtp_service.doStop() + # TODO call stop_mail_service + self._mail_conductor.stop_smtp_service() ################################################################### # Service control methods: imap + @QtCore.Slot() def _start_imap_service(self): """ SLOT @@ -1139,11 +1067,7 @@ class MainWindow(QtGui.QMainWindow): """ if self._provider_config.provides_mx() and \ self._enabled_services.count(self.MX_SERVICE) > 0: - logger.debug('Starting imap service') - - self._imap_service = imap.start_imap_service( - self._soledad, - self._keymanager) + self._mail_conductor.start_imap_service() def _on_mail_client_logged_in(self, req): """ @@ -1151,30 +1075,25 @@ class MainWindow(QtGui.QMainWindow): """ self.mail_client_logged_in.emit() + @QtCore.Slot() def _fetch_incoming_mail(self): """ SLOT TRIGGERS: self.mail_client_logged_in """ - # TODO have a mutex over fetch operation. - if self._imap_service: - logger.debug('Client connected, fetching mail...') - self._imap_service.fetch() + # TODO connect signal directly!!! + self._mail_conductor.fetch_incoming_mail() + @QtCore.Slot() def _stop_imap_service(self): """ SLOT TRIGGERS: self.logout """ - # There is a subtle difference here: - # we are just stopping the fetcher here, - # but in the smtp case we are stopping the factory. - # We should homogenize both services. - if self._imap_service is not None: - logger.debug('Stopping imap service.') - self._imap_service.stop() + # TODO call stop_mail_service + self._mail_conductor.stop_imap_service() # end service control methods (imap) @@ -1623,8 +1542,8 @@ class MainWindow(QtGui.QMainWindow): if ok: self._logged_user = None - self._login_widget.logged_out() + self._mail_status.mail_state_disabled() else: self._login_widget.set_login_status( @@ -1700,8 +1619,7 @@ class MainWindow(QtGui.QMainWindow): """ logger.debug('About to quit, doing cleanup...') - if self._imap_service is not None: - self._imap_service.stop() + self._mail_conductor.stop_imap_service() if self._srp_auth is not None: if self._srp_auth.get_session_id() is not None or \ diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 58cb05ba..acb39b07 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -40,12 +40,14 @@ class PreferencesWindow(QtGui.QDialog): """ Window that displays the preferences. """ - def __init__(self, parent, srp_auth): + def __init__(self, parent, srp_auth, provider_config): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget :param srp_auth: SRPAuth object configured in the main app. :type srp_auth: SRPAuth + :param provider_config: ProviderConfig object. + :type provider_config: ProviderConfig """ QtGui.QDialog.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") @@ -72,6 +74,36 @@ class PreferencesWindow(QtGui.QDialog): else: self._add_configured_providers() + pw_enabled = False + + # check if the user is logged in + if srp_auth is not None and srp_auth.get_token() is not None: + # check if provider has 'mx' ... + domain = provider_config.get_domain() + self._select_provider_by_name(domain) + if provider_config.provides_mx(): + enabled_services = self._settings.get_enabled_services(domain) + mx_name = get_service_display_name('mx') + + # ... and if the user have it enabled + if 'mx' 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) + else: + 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) + else: + pw_enabled = True + else: + 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(pw_enabled) + def set_soledad_ready(self, soledad): """ SLOT @@ -84,6 +116,7 @@ class PreferencesWindow(QtGui.QDialog): :type soledad: Soledad """ self._soledad = soledad + self.ui.lblPasswordChangeStatus.setVisible(False) self.ui.gbPasswordChange.setEnabled(True) def _set_password_change_status(self, status, error=False, success=False): @@ -98,6 +131,9 @@ class PreferencesWindow(QtGui.QDialog): 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) @@ -156,7 +192,7 @@ class PreferencesWindow(QtGui.QDialog): """ logger.debug("SRP password changed successfully.") try: - self._soledad.change_passphrase(str(new_password)) + self._soledad.change_passphrase(new_password) logger.debug("Soledad password changed successfully.") except NoStorageSecret: logger.debug( @@ -218,6 +254,16 @@ class PreferencesWindow(QtGui.QDialog): 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) + def _service_selection_changed(self, service, state): """ SLOT diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py index 94726720..386cb75f 100644 --- a/src/leap/bitmask/gui/statemachines.py +++ b/src/leap/bitmask/gui/statemachines.py @@ -19,7 +19,8 @@ State machines for the Bitmask app. """ import logging -from PySide.QtCore import QStateMachine, QState +from PySide import QtCore +from PySide.QtCore import QStateMachine, QState, Signal from PySide.QtCore import QObject from leap.bitmask.services import connections @@ -36,28 +37,255 @@ _CON = "connecting" _DIS = "disconnecting" -class IntermediateState(QState): +class SignallingState(QState): """ - Intermediate state that emits a custom signal on entry + A state that emits a custom signal on entry. """ - def __init__(self, signal): + def __init__(self, signal, parent=None, name=None): """ Initializer. :param signal: the signal to be emitted on entry on this state. :type signal: QtCore.QSignal """ - super(IntermediateState, self).__init__() + super(SignallingState, self).__init__(parent) self._signal = signal + self._name = name def onEntry(self, *args): """ Emits the signal on entry. """ - logger.debug('IntermediateState entered. Emitting signal ...') + logger.debug('State %s::%s entered. Emitting signal ...' + % (self._name, self.objectName())) if self._signal is not None: self._signal.emit() +class States(object): + """ + States for composite objects + """ + + class Off(SignallingState): + pass + + class Connecting(SignallingState): + pass + + class On(SignallingState): + pass + + class Disconnecting(SignallingState): + pass + + class StepsTrack(QObject): + state_change = Signal() + + def __init__(self, target): + super(States.StepsTrack, self).__init__() + self.received = set([]) + self.target = set(target) + + def is_all_done(self): + return all([ev in self.target for ev in self.received]) + + def is_any_done(self): + return any([ev in self.target for ev in self.received]) + + def seen(self, _type): + if _type in self.target: + self.received.add(_type) + + def reset_seen(self): + self.received = set([]) + + class TransitionOR(QtCore.QSignalTransition): + + def __init__(self, state): + super(States.TransitionOR, self).__init__( + state, QtCore.SIGNAL('state_change()')) + self.state = state + + def eventTest(self, e): + self.state.seen(e.type()) + done = self.state.is_any_done() + if done: + self.state.reset_seen() + return done + + def onTransition(self, e): + pass + + class TransitionAND(QtCore.QSignalTransition): + + def __init__(self, state): + super(States.TransitionAND, self).__init__( + state, QtCore.SIGNAL('state_change()')) + self.state = state + + def eventTest(self, e): + self.state.seen(e.type()) + done = self.state.is_all_done() + if done: + self.state.reset_seen() + return done + + def onTransition(self, e): + pass + + +class CompositeEvent(QtCore.QEvent): + def __init__(self): + super(CompositeEvent, self).__init__( + QtCore.QEvent.Type(self.ID)) + + +class Composite(object): + # TODO we should generate the connectingEvents dinamycally, + # depending on how much composite states do we get. + # This only supports up to 2 composite states. + + class ConnectingEvent1(CompositeEvent): + ID = QtCore.QEvent.User + 1 + + class ConnectingEvent2(CompositeEvent): + ID = QtCore.QEvent.User + 2 + + class ConnectedEvent1(CompositeEvent): + ID = QtCore.QEvent.User + 3 + + class ConnectedEvent2(CompositeEvent): + ID = QtCore.QEvent.User + 4 + + class DisconnectingEvent1(CompositeEvent): + ID = QtCore.QEvent.User + 5 + + class DisconnectingEvent2(CompositeEvent): + ID = QtCore.QEvent.User + 6 + + class DisconnectedEvent1(CompositeEvent): + ID = QtCore.QEvent.User + 7 + + class DisconnectedEvent2(CompositeEvent): + ID = QtCore.QEvent.User + 8 + + +class Events(QtCore.QObject): + """ + A Wrapper object for containing the events that will be + posted to a composite state machine. + """ + def __init__(self, parent=None): + """ + Initializes the QObject with the given parent. + """ + QtCore.QObject.__init__(self, parent) + + +class CompositeMachine(QStateMachine): + + def __init__(self, parent=None): + QStateMachine.__init__(self, parent) + + # events + self.events = Events(parent) + self.create_events() + + def create_events(self): + """ + Creates a bunch of events to be posted to the state machine when + the transitions say so. + """ + # XXX refactor into a dictionary? + self.events.con_ev1 = Composite.ConnectingEvent1() + self.events.con_ev2 = Composite.ConnectingEvent2() + self.events.on_ev1 = Composite.ConnectedEvent1() + self.events.on_ev2 = Composite.ConnectedEvent2() + self.events.dis_ev1 = Composite.DisconnectingEvent1() + self.events.dis_ev2 = Composite.DisconnectingEvent2() + self.events.off_ev1 = Composite.DisconnectedEvent1() + self.events.off_ev2 = Composite.DisconnectedEvent2() + + def beginSelectTransitions(self, e): + """ + Weird. Having this method makes underlying backtraces + to appear magically on the transitions. + :param e: the received event + :type e: QEvent + """ + pass + + def _connect_children(self, child1, child2): + """ + Connects the state transition signals for children machines. + + :param child1: the first child machine + :type child1: QStateMachine + :param child2: the second child machine + :type child2: QStateMachine + """ + # TODO refactor and generalize for composites + # of more than 2 connections. + + c1 = child1.conn + c1.qtsigs.connecting_signal.connect(self.con_ev1_slot) + c1.qtsigs.connected_signal.connect(self.on_ev1_slot) + c1.qtsigs.disconnecting_signal.connect(self.dis_ev1_slot) + c1.qtsigs.disconnected_signal.connect(self.off_ev1_slot) + + c2 = child2.conn + c2.qtsigs.connecting_signal.connect(self.con_ev2_slot) + c2.qtsigs.connected_signal.connect(self.on_ev2_slot) + c2.qtsigs.disconnecting_signal.connect(self.dis_ev2_slot) + c2.qtsigs.disconnected_signal.connect(self.off_ev2_slot) + + # XXX why is this getting deletec in c++? + #Traceback (most recent call last): + #self.postEvent(self.events.on_ev2) + #RuntimeError: Internal C++ object (ConnectedEvent2) already deleted. + # XXX trying the following workaround, since + # I cannot find why in the world this is getting deleted :( + # XXX refactor! + + # slots connection1 + + def con_ev1_slot(self): + # XXX if we just postEvent, we get the Internal C++ object deleted... + # so the workaround is to re-create it each time. + self.events.con_ev1 = Composite.ConnectingEvent1() + self.postEvent(self.events.con_ev1) + + def on_ev1_slot(self): + self.events.on_ev1 = Composite.ConnectedEvent1() + self.postEvent(self.events.on_ev1) + + def dis_ev1_slot(self): + self.events.dis_ev1 = Composite.DisconnectingEvent1() + self.postEvent(self.events.dis_ev1) + + def off_ev1_slot(self): + self.events.off_ev1 = Composite.DisconnectedEvent1() + self.postEvent(self.events.off_ev1) + + # slots connection2 + + def con_ev2_slot(self): + self.events.con_ev2 = Composite.ConnectingEvent2() + self.postEvent(self.events.con_ev2) + + def on_ev2_slot(self): + self.events.on_ev2 = Composite.ConnectedEvent2() + self.postEvent(self.events.on_ev2) + + def dis_ev2_slot(self): + self.events.dis_ev2 = Composite.DisconnectingEvent2() + self.postEvent(self.events.dis_ev2) + + def off_ev2_slot(self): + self.events.off_ev2 = Composite.DisconnectedEvent2() + self.postEvent(self.events.off_ev2) + + class ConnectionMachineBuilder(object): """ Builder class for state machines made from LEAPConnections. @@ -65,16 +293,161 @@ class ConnectionMachineBuilder(object): def __init__(self, connection): """ :param connection: an instance of a concrete LEAPConnection - we will be building a state machine for. + we will be building a state machine for. :type connection: AbstractLEAPConnection """ self._conn = connection leap_assert_type(self._conn, connections.AbstractLEAPConnection) - def make_machine(self, button=None, action=None, label=None): + def make_machine(self, **kwargs): """ Creates a statemachine associated with the passed controls. + It returns the state machine if the connection used for initializing + the ConnectionMachineBuilder inherits exactly from + LEAPAbstractConnection, and a tuple with the Composite Machine and its + individual parts in case that it is a composite machine which + connection definition inherits from more than one class that, on their + time, inherit from LEAPAbstractConnection. + + :params: see parameters for ``_make_simple_machine`` + :returns: a QStateMachine, or a tuple with the form: + (CompositeStateMachine, (StateMachine1, StateMachine2)) + :rtype: QStateMachine or tuple + """ + components = self._conn.components + + if components is None: + # simple case: connection definition inherits directly from + # the abstract connection. + + leap_assert_type(self._conn, connections.AbstractLEAPConnection) + return self._make_simple_machine(self._conn, **kwargs) + + if components: + # composite case: connection definition inherits from several + # classes, each one of which inherit from the abstract connection. + child_machines = tuple( + [ConnectionMachineBuilder(connection()).make_machine() + for connection in components]) + composite_machine = self._make_composite_machine( + self._conn, child_machines, **kwargs) + + composite_machine._connect_children( + *child_machines) + + # XXX should also connect its own states with the signals + # for the composite machine itself + + return (composite_machine, child_machines) + + def _make_composite_machine(self, conn, children, + **kwargs): + """ + Creates a composite machine. + + :param conn: an instance of a connection definition. + :type conn: LEAPAbstractConnection + :param children: children machines + :type children: tuple of state machines + :returns: A composite state machine + :rtype: QStateMachine + """ + # TODO split this method in smaller utility functions. + parent = kwargs.get('parent', None) + + # 1. create machine + machine = CompositeMachine(parent=parent) + + # 2. create states + off = States.Off(conn.qtsigs.disconnected_signal, + parent=machine, + name=conn.name) + off.setObjectName("off") + + on = States.On(conn.qtsigs.connected_signal, + parent=machine, + name=conn.name) + on.setObjectName("on") + + connecting_state = States.Connecting( + conn.qtsigs.connecting_signal, + parent=machine, + name=conn.name) + connecting_state.setObjectName("connecting") + + disconnecting_state = States.Disconnecting( + conn.qtsigs.disconnecting_signal, + parent=machine, + name=conn.name) + disconnecting_state.setObjectName("disconnecting") + + # 3. TODO create as many connectingEvents as needed (dynamically create + # classses for that) + # (we have manually created classes for events under CompositeEvent for + # now, to begin with the simple 2 states case for mail. + + # 4. state tracking objects for each transition stage + + connecting_track0 = States.StepsTrack( + (Composite.ConnectingEvent1.ID, + Composite.ConnectingEvent2.ID)) + connecting_track0.setObjectName("connecting_step_0") + + connecting_track1 = States.StepsTrack( + (Composite.ConnectedEvent1.ID, + Composite.ConnectedEvent2.ID)) + connecting_track1.setObjectName("connecting_step_1") + + disconnecting_track0 = States.StepsTrack( + (Composite.DisconnectingEvent1.ID, + Composite.DisconnectingEvent2.ID)) + disconnecting_track0.setObjectName("disconnecting_step_0") + + disconnecting_track1 = States.StepsTrack( + (Composite.DisconnectedEvent1.ID, + Composite.DisconnectedEvent2.ID)) + disconnecting_track1.setObjectName("disconnecting_step_1") + + # 5. definte the transitions with the matching state-tracking + # objects. + + # off -> connecting + connecting_transition = States.TransitionOR( + connecting_track0) + connecting_transition.setTargetState(connecting_state) + off.addTransition(connecting_transition) + + # connecting -> on + connected_transition = States.TransitionAND( + connecting_track1) + connected_transition.setTargetState(on) + connecting_state.addTransition(connected_transition) + + # on -> disconnecting + disconnecting_transition = States.TransitionOR( + disconnecting_track0) + disconnecting_transition.setTargetState(disconnecting_state) + on.addTransition(disconnecting_transition) + + # disconnecting -> off + disconnected_transition = States.TransitionAND( + disconnecting_track1) + disconnected_transition.setTargetState(off) + disconnecting_state.addTransition(disconnected_transition) + + machine.setInitialState(off) + machine.conn = conn + return machine + + def _make_simple_machine(self, conn, + button=None, action=None, label=None): + """ + Creates a statemachine associated with the passed controls. + + :param conn: the connection instance that defines this machine. + :type conn: AbstractLEAPConnection + :param button: the switch button. :type button: QPushButton @@ -88,9 +461,7 @@ class ConnectionMachineBuilder(object): :rtype: QStateMachine """ machine = QStateMachine() - conn = self._conn - - states = self._make_states(button, action, label) + states = self._make_states(conn, button, action, label) # transitions: @@ -151,11 +522,17 @@ class ConnectionMachineBuilder(object): for state in states.itervalues(): machine.addState(state) machine.setInitialState(states[_OFF]) + + machine.conn = conn return machine - def _make_states(self, button, action, label): + def _make_states(self, conn, button, action, label): """ - Creates the four states for the state machine + Creates the four states for the simple state machine. + Adds the needed properties for the passed controls. + + :param conn: the connection instance that defines this machine. + :type conn: AbstractLEAPConnection :param button: the switch button. :type button: QPushButton @@ -169,7 +546,6 @@ class ConnectionMachineBuilder(object): :returns: a dict of states :rtype: dict """ - conn = self._conn states = {} # TODO add tooltip @@ -190,8 +566,9 @@ class ConnectionMachineBuilder(object): states[_OFF] = off # CONNECTING State ---------------- - connecting = IntermediateState( - conn.qtsigs.connecting_signal) + connecting = SignallingState( + conn.qtsigs.connecting_signal, + name=conn.name) on_label = _tr("Turn {0}").format( conn.Disconnected.short_label) if button: @@ -224,8 +601,9 @@ class ConnectionMachineBuilder(object): states[_ON] = on # DISCONNECTING State ------------- - disconnecting = IntermediateState( - conn.qtsigs.disconnecting_signal) + disconnecting = SignallingState( + conn.qtsigs.disconnecting_signal, + name=conn.name) if button: disconnecting.assignProperty( button, 'enabled', False) diff --git a/src/leap/bitmask/gui/ui/eip_status.ui b/src/leap/bitmask/gui/ui/eip_status.ui index 25831118..d078ca0c 100644 --- a/src/leap/bitmask/gui/ui/eip_status.ui +++ b/src/leap/bitmask/gui/ui/eip_status.ui @@ -185,6 +185,9 @@ <property name="cursor"> <cursorShape>PointingHandCursor</cursorShape> </property> + <property name="styleSheet"> + <string notr="true">text-align: left;</string> + </property> <property name="text"> <string>0.0 KB/s</string> </property> @@ -249,6 +252,9 @@ <property name="cursor"> <cursorShape>PointingHandCursor</cursorShape> </property> + <property name="styleSheet"> + <string notr="true">text-align: left;</string> + </property> <property name="text"> <string>0.0 KB/s</string> </property> diff --git a/src/leap/bitmask/gui/ui/eippreferences.ui b/src/leap/bitmask/gui/ui/eippreferences.ui index cc77c82e..a3050683 100644 --- a/src/leap/bitmask/gui/ui/eippreferences.ui +++ b/src/leap/bitmask/gui/ui/eippreferences.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>435</width> - <height>273</height> + <height>144</height> </rect> </property> <property name="windowTitle"> @@ -18,7 +18,7 @@ <normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset> </property> <layout class="QGridLayout" name="gridLayout_2"> - <item row="1" column="0"> + <item row="0" column="0"> <widget class="QGroupBox" name="gbGatewaySelector"> <property name="enabled"> <bool>true</bool> @@ -33,7 +33,7 @@ <item row="0" column="0"> <widget class="QLabel" name="lblSelectProvider"> <property name="text"> - <string>&Select provider:</string> + <string>Select &provider:</string> </property> <property name="buddy"> <cstring>cbProvidersGateway</cstring> @@ -52,7 +52,7 @@ <item row="7" column="2"> <widget class="QPushButton" name="pbSaveGateway"> <property name="text"> - <string>Save this provider settings</string> + <string>&Save this provider settings</string> </property> </widget> </item> @@ -69,7 +69,10 @@ <item row="1" column="0"> <widget class="QLabel" name="label"> <property name="text"> - <string>Select gateway:</string> + <string>Select &gateway:</string> + </property> + <property name="buddy"> + <cstring>cbGateways</cstring> </property> </widget> </item> @@ -85,76 +88,9 @@ </layout> </widget> </item> - <item row="0" column="0"> - <widget class="QGroupBox" name="gbAutomaticEIP"> - <property name="title"> - <string>Automatic Encrypted Internet start</string> - </property> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="3" column="0"> - <widget class="QLabel" name="lblAutoStartEIPStatus"> - <property name="layoutDirection"> - <enum>Qt::LeftToRight</enum> - </property> - <property name="styleSheet"> - <string notr="true"/> - </property> - <property name="frameShape"> - <enum>QFrame::NoFrame</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Plain</enum> - </property> - <property name="text"> - <string><font color='green'><b>Automatic EIP start saved!</b></font></string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QPushButton" name="pbSaveAutoStartEIP"> - <property name="text"> - <string>Save auto start setting</string> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QCheckBox" name="cbAutoStartEIP"> - <property name="layoutDirection"> - <enum>Qt::LeftToRight</enum> - </property> - <property name="text"> - <string>Enable Automatic start:</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QComboBox" name="cbProvidersEIP"> - <item> - <property name="text"> - <string><Select provider></string> - </property> - </item> - </widget> - </item> - </layout> - <zorder>cbAutoStartEIP</zorder> - <zorder>lblAutoStartEIPStatus</zorder> - <zorder>pbSaveAutoStartEIP</zorder> - <zorder>cbProvidersEIP</zorder> - </widget> - </item> </layout> </widget> <tabstops> - <tabstop>cbAutoStartEIP</tabstop> - <tabstop>cbProvidersEIP</tabstop> - <tabstop>pbSaveAutoStartEIP</tabstop> <tabstop>cbProvidersGateway</tabstop> <tabstop>cbGateways</tabstop> <tabstop>pbSaveGateway</tabstop> @@ -162,22 +98,5 @@ <resources> <include location="../../../../../data/resources/mainwindow.qrc"/> </resources> - <connections> - <connection> - <sender>cbAutoStartEIP</sender> - <signal>toggled(bool)</signal> - <receiver>cbProvidersEIP</receiver> - <slot>setEnabled(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>180</x> - <y>53</y> - </hint> - <hint type="destinationlabel"> - <x>238</x> - <y>53</y> - </hint> - </hints> - </connection> - </connections> + <connections/> </ui> diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index 10c77057..badd291d 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>635</height> + <height>636</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout"> @@ -86,89 +86,101 @@ <number>0</number> </property> <item> - <widget class="QFrame" name="frame_2"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="styleSheet"> - <string notr="true">QFrame{background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(160, 160, 160, 128), stop:1 rgba(255, 255, 255, 0));}</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <property name="leftMargin"> - <number>24</number> + <widget class="QWidget" name="eipWidget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="spacing"> + <number>0</number> </property> - <property name="rightMargin"> - <number>24</number> + <property name="margin"> + <number>0</number> </property> <item> - <widget class="QLabel" name="label_2"> - <property name="font"> - <font> - <pointsize>16</pointsize> - <weight>75</weight> - <bold>true</bold> - </font> + <widget class="QFrame" name="frame_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> <property name="styleSheet"> - <string notr="true">background-color: rgba(255, 255, 255, 0);</string> - </property> - <property name="text"> - <string>Encrypted Internet</string> + <string notr="true">QFrame{background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(160, 160, 160, 128), stop:1 rgba(255, 255, 255, 0));}</string> </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="leftMargin"> + <number>24</number> + </property> + <property name="rightMargin"> + <number>24</number> + </property> + <item> + <widget class="QLabel" name="label_2"> + <property name="font"> + <font> + <pointsize>16</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="styleSheet"> + <string notr="true">background-color: rgba(255, 255, 255, 0);</string> + </property> + <property name="text"> + <string>Encrypted Internet</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnEIPPreferences"> + <property name="maximumSize"> + <size> + <width>48</width> + <height>20</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../../../../../data/resources/mainwindow.qrc"> + <normaloff>:/images/black/32/gear.png</normaloff>:/images/black/32/gear.png</iconset> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + <property name="default"> + <bool>false</bool> + </property> + <property name="flat"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> </widget> </item> <item> - <widget class="QPushButton" name="btnEIPPreferences"> - <property name="maximumSize"> - <size> - <width>48</width> - <height>20</height> - </size> + <layout class="QVBoxLayout" name="eipLayout"> + <property name="leftMargin"> + <number>12</number> </property> - <property name="styleSheet"> - <string notr="true"/> + <property name="topMargin"> + <number>0</number> </property> - <property name="text"> - <string/> + <property name="rightMargin"> + <number>12</number> </property> - <property name="icon"> - <iconset resource="../../../../../data/resources/mainwindow.qrc"> - <normaloff>:/images/black/32/gear.png</normaloff>:/images/black/32/gear.png</iconset> - </property> - <property name="autoDefault"> - <bool>false</bool> + <property name="bottomMargin"> + <number>0</number> </property> - <property name="default"> - <bool>false</bool> - </property> - <property name="flat"> - <bool>false</bool> - </property> - </widget> + </layout> </item> </layout> </widget> </item> <item> - <layout class="QVBoxLayout" name="eipLayout"> - <property name="leftMargin"> - <number>12</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>12</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - </layout> - </item> - <item> <widget class="Line" name="line"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -253,14 +265,26 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb </widget> </item> <item> - <layout class="QVBoxLayout" name="mailLayout" stretch=""> - <property name="spacing"> - <number>-1</number> - </property> - <property name="margin"> - <number>12</number> - </property> - </layout> + <widget class="QWidget" name="mailWidget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>0</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <layout class="QVBoxLayout" name="mailLayout"> + <property name="spacing"> + <number>-1</number> + </property> + <property name="margin"> + <number>12</number> + </property> + </layout> + </item> + </layout> + </widget> </item> <item> <spacer name="verticalSpacer"> diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index 1b5947e1..2adf8aa8 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -20,6 +20,7 @@ Provider bootstrapping import logging import socket import os +import sys import requests @@ -125,9 +126,13 @@ class ProviderBootstrapper(AbstractBootstrapper): # err --- but we can do it after a failure, to diagnose what went # wrong. Right now we're just adding connection overhead. -- kali + verify = self.verify + if verify: + verify = self.verify.encode(sys.getfilesystemencoding()) + try: res = self._session.get("https://%s" % (self._domain,), - verify=self.verify, + verify=verify, timeout=REQUEST_TIMEOUT) res.raise_for_status() except requests.exceptions.SSLError as exc: @@ -180,6 +185,8 @@ class ProviderBootstrapper(AbstractBootstrapper): # no ca? then download from main domain again. pass + if verify: + verify = verify.encode(sys.getfilesystemencoding()) logger.debug("Requesting for provider.json... " "uri: {0}, verify: {1}, headers: {2}".format( uri, verify, headers)) @@ -336,9 +343,9 @@ class ProviderBootstrapper(AbstractBootstrapper): test_uri = "%s/%s/cert" % (self._provider_config.get_api_uri(), self._provider_config.get_api_version()) - res = self._session.get(test_uri, - verify=self._provider_config - .get_ca_cert_path(), + ca_cert_path = self._provider_config.get_ca_cert_path() + ca_cert_path = ca_cert_path.encode(sys.getfilesystemencoding()) + res = self._session.get(test_uri, verify=ca_cert_path, timeout=REQUEST_TIMEOUT) res.raise_for_status() diff --git a/src/leap/bitmask/services/__init__.py b/src/leap/bitmask/services/__init__.py index f9456159..e62277b6 100644 --- a/src/leap/bitmask/services/__init__.py +++ b/src/leap/bitmask/services/__init__.py @@ -19,6 +19,7 @@ Services module. """ import logging import os +import sys from PySide import QtCore @@ -135,8 +136,12 @@ def download_service_config(provider_config, service_config, if token is not None: headers["Authorization"] = 'Token token="{0}"'.format(token) + verify = provider_config.get_ca_cert_path() + if verify: + verify = verify.encode(sys.getfilesystemencoding()) + res = session.get(config_uri, - verify=provider_config.get_ca_cert_path(), + verify=verify, headers=headers, timeout=REQUEST_TIMEOUT, cookies=cookies) diff --git a/src/leap/bitmask/services/connections.py b/src/leap/bitmask/services/connections.py index 8aeb4e0c..ecfd35ff 100644 --- a/src/leap/bitmask/services/connections.py +++ b/src/leap/bitmask/services/connections.py @@ -41,9 +41,6 @@ The different services should declare a ServiceConnection class that inherits from AbstractLEAPConnection, so an instance of such class can be used to inform the StateMachineBuilder of the particularities of the state transitions for each particular connection. - -In the future, we will extend this class to allow composites in connections, -so we can apply conditional logic to the transitions. """ @@ -79,12 +76,7 @@ class AbstractLEAPConnection(object): """ return self._qtsigs - # XXX for conditional transitions with composites, - # we might want to add - # a field with dependencies: what this connection - # needs for (ON) state. - # XXX Look also at child states in the state machine. - #depends = () + components = None # Signals that derived classes # have to implement. diff --git a/src/leap/bitmask/services/eip/connection.py b/src/leap/bitmask/services/eip/connection.py index 962d9cf2..8a35d550 100644 --- a/src/leap/bitmask/services/eip/connection.py +++ b/src/leap/bitmask/services/eip/connection.py @@ -46,5 +46,5 @@ class EIPConnectionSignals(QtCore.QObject): class EIPConnection(AbstractLEAPConnection): def __init__(self): - # XXX this should be public instead self._qtsigs = EIPConnectionSignals() + self._connection_name = "Encrypted Internet" diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py index f3b6bfc8..fe3fe4c1 100644 --- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py +++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py @@ -21,6 +21,7 @@ import commands import getpass import logging import os +import sys from leap.bitmask.services.eip.vpnlauncher import VPNLauncher from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException @@ -185,6 +186,8 @@ class DarwinVPNLauncher(VPNLauncher): :rtype: dict """ + ld_library_path = os.path.join(get_path_prefix(), "..", "lib") + ld_library_path.encode(sys.getfilesystemencoding()) return { - "DYLD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib") + "DYLD_LIBRARY_PATH": ld_library_path } diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index efb23285..d02f6f96 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -21,6 +21,7 @@ import commands import logging import os import subprocess +import sys import time from leap.bitmask.config import flags @@ -231,6 +232,8 @@ class LinuxVPNLauncher(VPNLauncher): :rtype: dict """ + ld_library_path = os.path.join(get_path_prefix(), "..", "lib") + ld_library_path.encode(sys.getfilesystemencoding()) return { - "LD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib") + "LD_LIBRARY_PATH": ld_library_path } diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index bce3599b..07497814 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -250,9 +250,6 @@ class VPNLauncher(object): '--ping-restart', '30'] command_and_args = [openvpn] + args - logger.debug("Running VPN with command:") - logger.debug(" ".join(command_and_args)) - return command_and_args @classmethod diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 19e1aa7b..51f0f738 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -23,6 +23,7 @@ import psutil import psutil.error import shutil import socket +import sys from itertools import chain, repeat @@ -864,15 +865,26 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): """ Gets the vpn command from the aproppriate launcher. - Might throw: VPNLauncherException, OpenVPNNotFoundException. + Might throw: + VPNLauncherException, + OpenVPNNotFoundException. + + :rtype: list of str """ - cmd = self._launcher.get_vpn_command( + command = self._launcher.get_vpn_command( eipconfig=self._eipconfig, providerconfig=self._providerconfig, socket_host=self._socket_host, socket_port=self._socket_port, openvpn_verb=self._openvpn_verb) - return map(str, cmd) + + encoding = sys.getfilesystemencoding() + for i, c in enumerate(command): + if not isinstance(c, str): + command[i] = c.encode(encoding) + + logger.debug("Running VPN with command: {0}".format(command)) + return command # shutdown diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py new file mode 100644 index 00000000..c294381b --- /dev/null +++ b/src/leap/bitmask/services/mail/conductor.py @@ -0,0 +1,384 @@ +# -*- coding: utf-8 -*- +# conductor.py +# Copyright (C) 2013 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/>. +""" +Mail Services Conductor +""" +import logging +import os + +from PySide import QtCore +from zope.proxy import sameProxiedObjects + +from leap.bitmask.gui import statemachines +from leap.bitmask.services.mail import imap +from leap.bitmask.services.mail import connection as mail_connection +from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper +from leap.bitmask.services.mail.smtpconfig import SMTPConfig +from leap.bitmask.util import is_file + +from leap.common.check import leap_assert + +from leap.common.events import register as leap_register +from leap.common.events import events_pb2 as leap_events + +logger = logging.getLogger(__name__) + + +class IMAPControl(object): + """ + Methods related to IMAP control. + """ + def __init__(self): + """ + Initializes smtp variables. + """ + self.imap_machine = None + self.imap_service = None + self.imap_port = None + self.imap_factory = None + self.imap_connection = None + + leap_register(signal=leap_events.IMAP_SERVICE_STARTED, + callback=self._handle_imap_events, + reqcbk=lambda req, resp: None) + leap_register(signal=leap_events.IMAP_SERVICE_FAILED_TO_START, + callback=self._handle_imap_events, + reqcbk=lambda req, resp: None) + + def set_imap_connection(self, imap_connection): + """ + Sets the imap connection to an initialized connection. + + :param imap_connection: an initialized imap connection + :type imap_connection: IMAPConnection instance. + """ + self.imap_connection = imap_connection + + def start_imap_service(self): + """ + Starts imap service. + """ + logger.debug('Starting imap service') + leap_assert(sameProxiedObjects(self._soledad, None) + is not True, + "We need a non-null soledad for initializing imap service") + leap_assert(sameProxiedObjects(self._keymanager, None) + is not True, + "We need a non-null keymanager for initializing imap " + "service") + + if self.imap_service is None: + # first time. + self.imap_service, \ + self.imap_port, \ + self.imap_factory = imap.start_imap_service( + self._soledad, + self._keymanager) + else: + # we have the fetcher. just start it. + self.imap_service.start_loop() + + def stop_imap_service(self): + """ + Stops imap service (fetcher, factory and port). + """ + self.imap_connection.qtsigs.disconnecting_signal.emit() + # TODO We should homogenize both services. + if self.imap_service is not None: + logger.debug('Stopping imap service.') + # Stop the loop call in the fetcher + self.imap_service.stop() + # Stop listening on the IMAP port + self.imap_port.stopListening() + # Stop the protocol + self.imap_factory.doStop() + + def fetch_incoming_mail(self): + """ + Fetches incoming mail. + """ + # TODO have a mutex over fetch operation. + if self.imap_service: + logger.debug('Client connected, fetching mail...') + self.imap_service.fetch() + + # handle events + + def _handle_imap_events(self, req): + """ + Callback handler for the IMAP events + + :param req: Request type + :type req: leap.common.events.events_pb2.SignalRequest + """ + if req.event == leap_events.IMAP_SERVICE_STARTED: + self.on_imap_connected() + elif req.event == leap_events.IMAP_SERVICE_FAILED_TO_START: + self.on_imap_failed() + + # emit connection signals + + def on_imap_connecting(self): + """ + Callback for IMAP connecting state. + """ + self.imap_connection.qtsigs.connecting_signal.emit() + + def on_imap_connected(self): + """ + Callback for IMAP connected state. + """ + self.imap_connection.qtsigs.connected_signal.emit() + + def on_imap_failed(self): + """ + Callback for IMAP failed state. + """ + self.imap_connection.qtsigs.connetion_aborted_signal.emit() + + +class SMTPControl(object): + + PORT_KEY = "port" + IP_KEY = "ip_address" + + def __init__(self): + """ + Initializes smtp variables. + """ + self.smtp_config = SMTPConfig() + self.smtp_connection = None + self.smtp_machine = None + self._smtp_service = None + self._smtp_port = None + + self.smtp_bootstrapper = SMTPBootstrapper() + self.smtp_bootstrapper.download_config.connect( + self.smtp_bootstrapped_stage) + + leap_register(signal=leap_events.SMTP_SERVICE_STARTED, + callback=self._handle_smtp_events, + reqcbk=lambda req, resp: None) + leap_register(signal=leap_events.SMTP_SERVICE_FAILED_TO_START, + callback=self._handle_smtp_events, + reqcbk=lambda req, resp: None) + + def set_smtp_connection(self, smtp_connection): + """ + Sets the smtp connection to an initialized connection. + :param smtp_connection: an initialized smtp connection + :type smtp_connection: SMTPConnection instance. + """ + self.smtp_connection = smtp_connection + + def start_smtp_service(self, host, port, cert): + """ + Starts the smtp service. + + :param host: the hostname of the remove SMTP server. + :type host: str + :param port: the port of the remote SMTP server + :type port: str + :param cert: the client certificate for authentication + :type cert: str + """ + # TODO Make the encrypted_only configurable + # TODO pick local smtp port in a better way + # TODO remove hard-coded port and let leap.mail set + # the specific default. + self.smtp_connection.qtsigs.connecting_signal.emit() + from leap.mail.smtp import setup_smtp_relay + self._smtp_service, self._smtp_port = setup_smtp_relay( + port=2013, + keymanager=self._keymanager, + smtp_host=host, + smtp_port=port, + smtp_cert=cert, + smtp_key=cert, + encrypted_only=False) + + def stop_smtp_service(self): + """ + Stops the smtp service (port and factory). + """ + self.smtp_connection.qtsigs.disconnecting_signal.emit() + # TODO We should homogenize both services. + if self._smtp_service is not None: + logger.debug('Stopping smtp service.') + self._smtp_port.stopListening() + self._smtp_service.doStop() + + @QtCore.Slot() + def smtp_bootstrapped_stage(self, data): + """ + SLOT + TRIGGERS: + self.smtp_bootstrapper.download_config + + If there was a problem, displays it, otherwise it does nothing. + This is used for intermediate bootstrapping stages, in case + they fail. + + :param data: result from the bootstrapping stage for Soledad + :type data: dict + """ + passed = data[self.smtp_bootstrapper.PASSED_KEY] + if not passed: + logger.error(data[self.smtp_bootstrapper.ERROR_KEY]) + return + logger.debug("Done bootstrapping SMTP") + self.check_smtp_config() + + def check_smtp_config(self): + """ + Checks smtp config and tries to download smtp client cert if needed. + Currently called when smtp_bootstrapped_stage has successfuly finished. + """ + logger.debug("Checking SMTP config...") + leap_assert(self.smtp_bootstrapper._provider_config, + "smtp bootstrapper does not have a provider_config") + + provider_config = self.smtp_bootstrapper._provider_config + smtp_config = self.smtp_config + hosts = smtp_config.get_hosts() + # TODO handle more than one host and define how to choose + if len(hosts) > 0: + hostname = hosts.keys()[0] + logger.debug("Using hostname %s for SMTP" % (hostname,)) + host = hosts[hostname][self.IP_KEY].encode("utf-8") + port = hosts[hostname][self.PORT_KEY] + + client_cert = smtp_config.get_client_cert_path( + provider_config, + about_to_download=True) + + # XXX change this logic! + # check_config should be called from within start_service, + # and not the other way around. + if not is_file(client_cert): + self.smtp_bootstrapper._download_client_certificates() + if os.path.isfile(client_cert): + self.start_smtp_service(host, port, client_cert) + else: + logger.warning("Tried to download email client " + "certificate, but could not find any") + + else: + logger.warning("No smtp hosts configured") + + # handle smtp events + + def _handle_smtp_events(self, req): + """ + Callback handler for the SMTP events. + + :param req: Request type + :type req: leap.common.events.events_pb2.SignalRequest + """ + if req.event == leap_events.SMTP_SERVICE_STARTED: + self.on_smtp_connected() + elif req.event == leap_events.SMTP_SERVICE_FAILED_TO_START: + self.on_smtp_failed() + + # emit connection signals + + def on_smtp_connecting(self): + """ + Callback for SMTP connecting state. + """ + self.smtp_connection.qtsigs.connecting_signal.emit() + + def on_smtp_connected(self): + """ + Callback for SMTP connected state. + """ + self.smtp_connection.qtsigs.connected_signal.emit() + + def on_smtp_failed(self): + """ + Callback for SMTP failed state. + """ + self.smtp_connection.qtsigs.connection_aborted_signal.emit() + + +class MailConductor(IMAPControl, SMTPControl): + """ + This class encapsulates everything related to the initialization and + process control for the mail services. + Currently, it initializes IMAPConnection and SMPTConnection. + """ + # XXX We could consider to use composition instead of inheritance here. + + def __init__(self, soledad, keymanager): + """ + Initializes the mail conductor. + + :param soledad: a transparent proxy that eventually will point to a + Soledad Instance. + :type soledad: zope.proxy.ProxyBase + + :param keymanager: a transparent proxy that eventually will point to a + Keymanager Instance. + :type soledad: zope.proxy.ProxyBase + """ + IMAPControl.__init__(self) + SMTPControl.__init__(self) + self._soledad = soledad + self._keymanager = keymanager + + self._mail_machine = None + + self._mail_connection = mail_connection.MailConnection() + + def start_mail_machine(self, **kwargs): + """ + Starts mail machine. + """ + logger.debug("Starting mail state machine...") + builder = statemachines.ConnectionMachineBuilder(self._mail_connection) + (mail, (imap, smtp)) = builder.make_machine(**kwargs) + + # we have instantiated the connections while building the composite + # machines, and we have to use the qtsigs instantiated there. + # XXX we could probably use a proxy here too to make the thing + # transparent. + self.set_imap_connection(imap.conn) + self.set_smtp_connection(smtp.conn) + + self._mail_machine = mail + # XXX ------------------- + # need to keep a reference? + #self._mail_events = mail.events + self._mail_machine.start() + + self._imap_machine = imap + self._imap_machine.start() + self._smtp_machine = smtp + self._smtp_machine.start() + + def connect_mail_signals(self, widget): + """ + Connects the mail signals to the mail_status widget slots. + + :param widget: the widget containing the slots. + :type widget: QtCore.QWidget + """ + qtsigs = self._mail_connection.qtsigs + qtsigs.connected_signal.connect(widget.mail_state_connected) + qtsigs.connecting_signal.connect(widget.mail_state_connecting) + qtsigs.disconnecting_signal.connect(widget.mail_state_disconnecting) + qtsigs.disconnected_signal.connect(widget.mail_state_disconnected) diff --git a/src/leap/bitmask/services/mail/connection.py b/src/leap/bitmask/services/mail/connection.py new file mode 100644 index 00000000..29378f62 --- /dev/null +++ b/src/leap/bitmask/services/mail/connection.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +# connection.py +# Copyright (C) 2013 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 Connections +""" +from PySide import QtCore + +from leap.bitmask.services.connections import AbstractLEAPConnection + + +class IMAPConnectionSignals(QtCore.QObject): + """ + Qt Signals used by IMAPConnection + """ + # commands + do_connect_signal = QtCore.Signal() + do_disconnect_signal = QtCore.Signal() + + # intermediate stages + connecting_signal = QtCore.Signal() + disconnecting_signal = QtCore.Signal() + + connected_signal = QtCore.Signal() + disconnected_signal = QtCore.Signal() + + connection_died_signal = QtCore.Signal() + connection_aborted_signal = QtCore.Signal() + + +class IMAPConnection(AbstractLEAPConnection): + + _connection_name = "IMAP" + + def __init__(self): + self._qtsigs = IMAPConnectionSignals() + + +class SMTPConnectionSignals(QtCore.QObject): + """ + Qt Signals used by SMTPConnection + """ + # commands + do_connect_signal = QtCore.Signal() + do_disconnect_signal = QtCore.Signal() + + # intermediate stages + connecting_signal = QtCore.Signal() + disconnecting_signal = QtCore.Signal() + + connected_signal = QtCore.Signal() + disconnected_signal = QtCore.Signal() + + connection_died_signal = QtCore.Signal() + connection_aborted_signal = QtCore.Signal() + + +class SMTPConnection(AbstractLEAPConnection): + + _connection_name = "IMAP" + + def __init__(self): + self._qtsigs = SMTPConnectionSignals() + + +class MailConnectionSignals(QtCore.QObject): + """ + Qt Signals used by MailConnection + """ + # commands + do_connect_signal = QtCore.Signal() + do_disconnect_signal = QtCore.Signal() + + connecting_signal = QtCore.Signal() + disconnecting_signal = QtCore.Signal() + + connected_signal = QtCore.Signal() + disconnected_signal = QtCore.Signal() + + connection_died_signal = QtCore.Signal() + connection_aborted_signal = QtCore.Signal() + + +class MailConnection(AbstractLEAPConnection): + + components = IMAPConnection, SMTPConnection + _connection_name = "Mail" + + def __init__(self): + self._qtsigs = MailConnectionSignals() diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py index 4828180e..2667f156 100644 --- a/src/leap/bitmask/services/mail/imap.py +++ b/src/leap/bitmask/services/mail/imap.py @@ -41,9 +41,10 @@ def get_mail_check_period(): try: period = int(period_str) except (ValueError, TypeError): - logger.warning("BAD value found for %s: %s" % ( - INCOMING_CHECK_PERIOD_ENV, - period_str)) + if period is not None: + logger.warning("BAD value found for %s: %s" % ( + INCOMING_CHECK_PERIOD_ENV, + period_str)) except Exception as exc: logger.warning("Unhandled error while getting %s: %r" % ( INCOMING_CHECK_PERIOD_ENV, diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 4619ba80..54ef67eb 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -20,11 +20,13 @@ Soledad bootstrapping import logging import os import socket +import sys from ssl import SSLError from PySide import QtCore from u1db import errors as u1db_errors +from zope.proxy import sameProxiedObjects from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig @@ -39,7 +41,7 @@ from leap.common.check import leap_assert, leap_assert_type, leap_check from leap.common.files import which from leap.keymanager import KeyManager, openpgp from leap.keymanager.errors import KeyNotFound -from leap.soledad.client import Soledad +from leap.soledad.client import Soledad, BootstrapSequenceError logger = logging.getLogger(__name__) @@ -190,8 +192,8 @@ class SoledadBootstrapper(AbstractBootstrapper): # soledad-launcher in the gui. raise - leap_check(self._soledad is not None, - "Null soledad, error while initializing") + leap_assert(not sameProxiedObjects(self._soledad, None), + "Null soledad, error while initializing") # and now, let's sync sync_tries = self.MAX_SYNC_RETRIES @@ -239,14 +241,15 @@ class SoledadBootstrapper(AbstractBootstrapper): """ # TODO: If selected server fails, retry with another host # (issue #3309) + encoding = sys.getfilesystemencoding() try: self._soledad = Soledad( uuid, - self._password.encode("utf-8"), - secrets_path=secrets_path, - local_db_path=local_db_path, + self._password, + secrets_path=secrets_path.encode(encoding), + local_db_path=local_db_path.encode(encoding), server_url=server_url, - cert_file=cert_file, + cert_file=cert_file.encode(encoding), auth_token=auth_token) # XXX All these errors should be handled by soledad itself, @@ -257,7 +260,10 @@ class SoledadBootstrapper(AbstractBootstrapper): logger.debug("SOLEDAD initialization TIMED OUT...") self.soledad_timeout.emit() except socket.error as exc: - logger.error("Socket error while initializing soledad") + logger.warning("Socket error while initializing soledad") + self.soledad_timeout.emit() + except BootstrapSequenceError as exc: + logger.warning("Error while initializing soledad") self.soledad_timeout.emit() # unrecoverable @@ -280,7 +286,7 @@ class SoledadBootstrapper(AbstractBootstrapper): Raises SoledadSyncError if not successful. """ try: - logger.error("trying to sync soledad....") + logger.debug("trying to sync soledad....") self._soledad.sync() except SSLError as exc: logger.error("%r" % (exc,)) @@ -412,9 +418,9 @@ class SoledadBootstrapper(AbstractBootstrapper): :param provider_config: Provider configuration :type provider_config: ProviderConfig :param user: User's login - :type user: str + :type user: unicode :param password: User's password - :type password: str + :type password: unicode :param download_if_needed: If True, it will only download files if the have changed since the time it was previously downloaded. diff --git a/src/leap/bitmask/util/compat.py b/src/leap/bitmask/util/compat.py new file mode 100644 index 00000000..e34b9ead --- /dev/null +++ b/src/leap/bitmask/util/compat.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# compat.py +# Copyright (C) 2013 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/>. +""" +Utilities for dealing with compat versions. +""" +from distutils.version import LooseVersion as V + +from requests import __version__ as _requests_version + + +def _requests_has_max_retries(): + """ + Returns True if we can use the max_retries parameter + """ + return V(_requests_version) > V('1.1.0') + +requests_has_max_retries = _requests_has_max_retries() |