diff options
35 files changed, 2541 insertions, 1619 deletions
| @@ -28,6 +28,7 @@ pkg/osx/build  src/*.egg-info  src/pysqlcipher +src/leap/bitmask/_binaries.py  src/leap/bitmask/util/reqs.txt  MANIFEST  _trial_temp* diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4faceb98..92da1573 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,22 @@ History  2014  ==== +0.5.3 June 27 -- the "encrypt ALL THE THINGS" release: +++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +- Disable EIP if the helper files were not installed. Closes #5818. +- Install helpers to /usr/local for bundle. Closes #5741. +- Improve how pinned providers are handled by hardcoding it instead of +  expecting them to be in the config. Closes #4733. +- Remove deprecated policy files. Closes #5651. +- Install helper files only if standalone=True. Related to #5625 +- Use installer helper from within bundle path. Related to #5634 +- Pin Riseup as a provider. Closes #5783. +- Update the bundled binaries to their path if their sha256 is not +  correct. Closes #5759. +- Use a dict instead an object to ease later serialization of +  ProviderConfig. +  0.5.2 June 6 -- the "are we there yet" release:  +++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/data/ts/en_US.ts b/data/ts/en_US.ts index 562df1a3..cf74d7b6 100644 --- a/data/ts/en_US.ts +++ b/data/ts/en_US.ts @@ -68,96 +68,114 @@          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="60"/> -        <source><span style='color:#0000FF;'>NOTE</span>: To use this, you need to enable/start {0}.</source> -        <translation type="unfinished"></translation> -    </message> -    <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="99"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="119"/>          <source>Open keys file</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="191"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="237"/>          <source>Input/Output error</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="112"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="158"/>          <source>There was an error accessing the file.  Import canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="122"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="169"/>          <source>Data mismatch</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="125"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="172"/>          <source>The public and private key should have the same address and fingerprint.  Import canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="130"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="183"/>          <source>Missing key</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="133"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="186"/>          <source>You need to provide the public AND private key in the same file.  Import canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="139"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="197"/>          <source>Address mismatch</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="142"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="200"/>          <source>The identity for the key needs to be the same as your user address.  Import canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="147"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="125"/>          <source>Are you sure that you want to replace the current key pair whith the imported?</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="164"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="144"/>          <source>Import Successful</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="165"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="145"/>          <source>The key pair was imported successfully.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="174"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="207"/>          <source>Save keys file</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="185"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="224"/>          <source>Export Successful</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="187"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="226"/>          <source>The key pair was exported successfully.  Please, store your private key in a safe place.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="193"/> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="239"/>          <source>There was an error accessing the file.  Export canceled.</source>          <translation type="unfinished"></translation>      </message> +    <message> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="59"/> +        <source>The provider that you are using does not support {0}.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="65"/> +        <source>To use this, you need to enable/start {0}.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/advanced_key_management.py" line="110"/> +        <source><span style='color:#0000FF;'>NOTE</span>: </source> +        <translation type="unfinished"></translation> +    </message> +</context> +<context> +    <name>ComplainDialog</name> +    <message> +        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="419"/> +        <source>Ok, thanks</source> +        <translation type="unfinished"></translation> +    </message>  </context>  <context>      <name>EIPPreferences</name> @@ -210,22 +228,22 @@ Export canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="125"/> +        <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="153"/>          <source>Gateway settings for provider '{0}' saved.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="179"/> +        <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="230"/>          <source>There was a problem with configuration files.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="101"/> +        <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="122"/>          <source> (uninitialized)</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="151"/> +        <location filename="../src/leap/bitmask/gui/eip_preferenceswindow.py" line="245"/>          <source>This is an uninitialized provider, please log in first.</source>          <translation type="unfinished"></translation>      </message> @@ -253,88 +271,158 @@ Export canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/eip_status.ui" line="212"/> +        <location filename="../src/leap/bitmask/gui/ui/eip_status.ui" line="213"/>          <source>0.0 KB/s</source>          <translation type="unfinished"></translation>      </message> -</context> -<context> -    <name>EIPStatusWidget</name>      <message> -        <location filename="../src/leap/bitmask/gui/eip_status.py" line="327"/> -        <source>Turn OFF</source> +        <location filename="../src/leap/bitmask/gui/ui/eip_status.ui" line="245"/> +        <source>Turn Off</source>          <translation type="unfinished"></translation>      </message> +</context> +<context> +    <name>EIPStatusWidget</name>      <message> -        <location filename="../src/leap/bitmask/gui/eip_status.py" line="341"/> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="411"/>          <source>Turn ON</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/eip_status.py" line="348"/> -        <source>Traffic is being routed in the clear</source> -        <translation type="unfinished"></translation> -    </message> -    <message> -        <location filename="../src/leap/bitmask/gui/eip_status.py" line="405"/> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="521"/>          <source>Authenticating...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/eip_status.py" line="407"/> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="529"/>          <source>Retrieving configuration...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/eip_status.py" line="409"/> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="531"/>          <source>Waiting to start...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/eip_status.py" line="411"/> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="533"/>          <source>Assigning IP</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/eip_status.py" line="413"/> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="535"/>          <source>Reconnecting...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/eip_status.py" line="420"/> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="543"/>          <source>Unable to start VPN, it's already running.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/eip_status.py" line="463"/> -        <source>Route traffic through: {0}</source> -        <translation type="unfinished"></translation> -    </message> -    <message> -        <location filename="../src/leap/bitmask/gui/eip_status.py" line="260"/> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="304"/>          <source>disabled</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/eip_status.py" line="442"/> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="565"/>          <source>{0}: OFF</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/eip_status.py" line="257"/> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="301"/>          <source>You must login to use {0}</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/eip_status.py" line="447"/> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="570"/>          <source>{0}: Starting...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/eip_status.py" line="450"/> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="573"/>          <source>{0}: ON</source>          <translation type="unfinished"></translation>      </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="525"/> +        <source>Encrypted Internet is starting</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="397"/> +        <source>Retry</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="433"/> +        <source>Traffic is being routed in the clear.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="434"/> +        <source>Network is unreachable.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="436"/> +        <source>Error connecting</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="451"/> +        <source>Error connecting.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="454"/> +        <source>Bitmask is blocking unencrypted traffic.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="587"/> +        <source>Routing traffic through: <b>{0}</b></source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="634"/> +        <source>Could not load {0} configuration.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="643"/> +        <source>Another openvpn instance is already running, and could not be stopped.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="653"/> +        <source>Another openvpn instance is already running, and could not be stopped because it was not launched by Bitmask. Please stop it and try again.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="661"/> +        <source>We could not find openvpn binary.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="682"/> +        <source>We could not find any authentication agent in your system.<br/>Make sure you have<b>polkit-gnome-authentication-agent-1</b> running andtry again.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="690"/> +        <source>We could not find <b>pkexec</b> in your system.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="700"/> +        <source>{0} cannot be started because the tuntap extension is not installed properly in your system.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/eip_status.py" line="720"/> +        <source>Network is unreachable</source> +        <translation type="unfinished"></translation> +    </message>  </context>  <context>      <name>LoggerWindow</name> @@ -399,25 +487,30 @@ Export canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="224"/> +        <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="225"/>          <source>Your pastebin link <a href='{0}'>{0}</a></source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="227"/> +        <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="230"/>          <source>Pastebin OK</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="240"/> +        <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="245"/>          <source>Sending logs to Pastebin failed!</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="241"/> +        <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="251"/>          <source>Pastebin Error</source>          <translation type="unfinished"></translation>      </message> +    <message> +        <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="250"/> +        <source>Maximum posts per day reached</source> +        <translation type="unfinished"></translation> +    </message>  </context>  <context>      <name>LoginWidget</name> @@ -447,17 +540,17 @@ Export canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/login.py" line="247"/> +        <location filename="../src/leap/bitmask/gui/login.py" line="246"/>          <source>Log In</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/login.py" line="125"/> +        <location filename="../src/leap/bitmask/gui/login.py" line="124"/>          <source>Other...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/login.py" line="242"/> +        <location filename="../src/leap/bitmask/gui/login.py" line="241"/>          <source>Cancel</source>          <translation type="unfinished"></translation>      </message> @@ -467,32 +560,32 @@ Export canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/login.py" line="368"/> +        <location filename="../src/leap/bitmask/gui/login.py" line="371"/>          <source>Logout</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/login.py" line="296"/> +        <location filename="../src/leap/bitmask/gui/login.py" line="299"/>          <source>Please select a valid provider</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/login.py" line="301"/> +        <location filename="../src/leap/bitmask/gui/login.py" line="304"/>          <source>Please provide a valid username</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/login.py" line="306"/> +        <location filename="../src/leap/bitmask/gui/login.py" line="309"/>          <source>Please provide a valid password</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/login.py" line="309"/> +        <location filename="../src/leap/bitmask/gui/login.py" line="312"/>          <source>Logging in...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/login.py" line="361"/> +        <location filename="../src/leap/bitmask/gui/login.py" line="364"/>          <source>Logging out...</source>          <translation type="unfinished"></translation>      </message> @@ -515,206 +608,196 @@ Export canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="192"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="196"/>          <source>There was an unexpected problem with Soledad.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="413"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="432"/>          <source>OFF</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="209"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="224"/>          <source>Mail is OFF</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="217"/> -        <source>Starting..</source> -        <translation type="unfinished"></translation> -    </message> -    <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="218"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="233"/>          <source>Mail is starting</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="436"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="455"/>          <source>ON</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="222"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="237"/>          <source>Mail is ON</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="225"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="240"/>          <source>Mail is disabled</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="422"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="441"/>          <source>Starting...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="254"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="270"/>          <source>Soledad has started...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="256"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="272"/>          <source>Soledad is starting, please wait...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="291"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="308"/>          <source>Looking for key for this user</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="295"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="312"/>          <source>Found key! Starting mail...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="300"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="317"/>          <source>Finished generating key!</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="302"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="319"/>          <source>Starting mail...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="334"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="352"/>          <source>SMTP failed to start, check the logs.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="390"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="409"/>          <source>About to start, please wait...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="397"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="416"/>          <source>Disabled</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="162"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="166"/>          <source>{0}: OFF</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="444"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="463"/>          <source>You must be logged in to use {0}.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="298"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="315"/>          <source>Generating new key, this may take a few minutes.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="373"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="392"/>          <source>{0} Unread Emails in your Inbox</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="377"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="396"/>          <source>1 Unread Email in your Inbox</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mail_status.py" line="429"/> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="448"/>          <source>Disconnecting...</source>          <translation type="unfinished"></translation>      </message> +    <message> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="474"/> +        <source>Invalid auth token, try logging in again.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/mail_status.py" line="232"/> +        <source>Starting…</source> +        <translation type="unfinished"></translation> +    </message>  </context>  <context>      <name>MainWindow</name>      <message> -        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="238"/> +        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="228"/>          <source>There are new updates available, please restart.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="280"/> +        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="270"/>          <source>More...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="758"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="870"/>          <source>Help</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="352"/> +        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="342"/>          <source>&Quit</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="362"/> +        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="352"/>          <source>&Help</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="367"/> +        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="357"/>          <source>&Wizard</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="806"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="930"/>          <source>Hide Main Window</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="638"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="728"/>          <source> The following components will be updated:  %s</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="641"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="731"/>          <source>Updates available</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="805"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="929"/>          <source>Show Main Window</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1524"/> -        <source>We could not find any authentication agent in your system.<br/>Make sure you have <b>polkit-gnome-authentication-agent-1</b> running and try again.</source> -        <translation type="unfinished"></translation> -    </message> -    <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1536"/> -        <source>We could not find <b>pkexec</b> in your system.</source> -        <translation type="unfinished"></translation> -    </message> -    <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1541"/> -        <source>We could not find openvpn binary.</source> -        <translation type="unfinished"></translation> -    </message> -    <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1705"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1548"/>          <source>Starting...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1719"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1563"/>          <source>Not supported</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1723"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1567"/>          <source>Disabled</source>          <translation type="unfinished"></translation>      </message> @@ -724,173 +807,203 @@ Export canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="357"/> +        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="347"/>          <source>About &Bitmask</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="289"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="211"/>          <source>Mail is OFF</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="629"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="719"/>          <source>The Bitmask app is ready to update, please restart the application.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="870"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="996"/>          <source>About Bitmask - %s</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1011"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1138"/>          <source>Unable to login: Problem with provider</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1089"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1219"/>          <source>Log in cancelled by the user.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1547"/> -        <source>Another openvpn instance is already running, and could not be stopped.</source> -        <translation type="unfinished"></translation> -    </message> -    <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1554"/> -        <source>Another openvpn instance is already running, and could not be stopped because it was not launched by Bitmask. Please stop it and try again.</source> -        <translation type="unfinished"></translation> -    </message> -    <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1740"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1584"/>          <source>There was a problem with the provider</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1845"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1639"/>          <source>Something went wrong with the logout.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1864"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1606"/>          <source>Unable to connect: Problem with provider</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1836"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1651"/>          <source>Login</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="315"/> +        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="305"/>          <source>&Bitmask</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="372"/> +        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="362"/>          <source>Show &Log</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="377"/> +        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="367"/>          <source>Create a new account...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="278"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="201"/>          <source>File</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1606"/> -        <source>Network is unreachable</source> -        <translation type="unfinished"></translation> -    </message> -    <message> -        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="123"/> +        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="121"/>          <source>Please Log In</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="342"/> +        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="332"/>          <source>Account Preferences...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="347"/> +        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="337"/>          <source>Internet Preferences...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="385"/> +        <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="375"/>          <source>Advanced Key Management</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="742"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="854"/>          <source> (offline mode)</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="765"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="878"/>          <source>OFF</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="884"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1010"/>          <source>Version: <b>%s</b> (%s)<br><br>%sBitmask is the Desktop client application for the LEAP platform, supporting encrypted internet proxy, secure email, and secure chat (coming soon).<br><br>LEAP is a non-profit dedicated to giving all internet users access to secure communication. Our focus is on adapting encryption technology to make it easy to use and widely available. <br><br><a href='https://leap.se'>More about LEAP</a></source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="911"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1038"/>          <source><strong>Instructions to use mail:</strong><br>If you use Thunderbird you can use the Bitmask extension helper. Search for 'Bitmask' in the add-on manager or download it from: {0}.<br><br>You can configure Bitmask manually with these options:<br><em>   Incoming -> IMAP, port: {1}<br>   Outgoing -> SMTP, port: {2}<br>   Username -> your bitmask username.<br>   Password -> does not matter, use any text.  Just don't leave it empty and don't use your account's password.</em></source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="912"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1039"/>          <source>Bitmask Help</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="924"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1051"/>          <source>The current client version is not supported by this provider.<br>Please update to latest version.<br><br>You can get the latest version from <a href='{0}'>{1}</a></source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="925"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1052"/>          <source>Update Needed</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="935"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1062"/>          <source>This provider is not compatible with the client.<br><br>Error: API version incompatible.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="935"/> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1062"/>          <source>Incompatible Provider</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1757"/> -        <source>Could not load {0} configuration.</source> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="302"/> +        <source>Application error</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1531"/> -        <source>{0} cannot be started because the tuntap extension is not installed properly in your system.</source> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="304"/> +        <source>You are trying to do an operation that requires logging in first.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1670"/> -        <source>{0} could not be launched because you did not authenticate properly.</source> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="362"/> +        <source>Unknown error.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1677"/> -        <source>{0} finished in an unexpected manner!</source> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="366"/> +        <source>There was a server problem with authentication.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="370"/> +        <source>Could not establish a connection.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="374"/> +        <source>Invalid username or password.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="897"/> +        <source>Hello!</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="898"/> +        <source>Bitmask has started in the tray.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1252"/> +        <source>Succeeded</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1501"/> +        <source>The server at {0} can't be found, because the DNS lookup failed. DNS is the network service that translates a website's name to its Internet address. Either your computer is having trouble connecting to the network, or you are missing some helper files that are needed to securely use DNS while {1} is active. To install these helper files, quit this application and start it again.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1504"/> +        <source>Connection Error</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1748"/> +        <source>Quitting...</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1749"/> +        <source>Bitmask is quitting, please wait.</source>          <translation type="unfinished"></translation>      </message>  </context> @@ -965,47 +1078,47 @@ Export canceled.</source>  <context>      <name>PreferencesWindow</name>      <message> -        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="63"/> +        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="59"/>          <source>Automatic</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="159"/> +        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="168"/>          <source>Changing password...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="210"/> +        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="249"/>          <source>Password changed successfully.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="225"/> +        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="433"/>          <source>There was a problem changing the password.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="228"/> +        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="437"/>          <source>You did not enter a correct current password.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="371"/> +        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="417"/>          <source>Services settings for provider '{0}' saved.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="97"/> +        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="114"/>          <source>You need to enable {0} in order to change the password.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="103"/> +        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="122"/>          <source>You need to wait until {0} is ready in order to change the password.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="113"/> +        <location filename="../src/leap/bitmask/gui/preferenceswindow.py" line="101"/>          <source>In order to change your password you need to be logged in.</source>          <translation type="unfinished"></translation>      </message> @@ -1013,25 +1126,17 @@ Export canceled.</source>  <context>      <name>ProviderBootstrapper</name>      <message> -        <location filename="../src/leap/bitmask/provider/providerbootstrapper.py" line="146"/> +        <location filename="../src/leap/bitmask/provider/providerbootstrapper.py" line="154"/>          <source>Provider certificate could not be verified</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/provider/providerbootstrapper.py" line="153"/> +        <location filename="../src/leap/bitmask/provider/providerbootstrapper.py" line="161"/>          <source>Provider does not support HTTPS</source>          <translation type="unfinished"></translation>      </message>  </context>  <context> -    <name>SRPAuth</name> -    <message> -        <location filename="../src/leap/bitmask/crypto/srpauth.py" line="714"/> -        <source>Succeeded</source> -        <translation type="unfinished"></translation> -    </message> -</context> -<context>      <name>Wizard</name>      <message>          <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="43"/> @@ -1059,7 +1164,7 @@ Export canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="298"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="340"/>          <source>Check</source>          <translation type="unfinished"></translation>      </message> @@ -1084,196 +1189,181 @@ Export canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="336"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="353"/>          <source>Provider Information</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="339"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="356"/>          <source>Description of services offered by this provider</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="348"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="365"/>          <source>Name</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="380"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="397"/>          <source>Desc</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="390"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="407"/>          <source><b>Services offered:</b></source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="400"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="417"/>          <source>services</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="420"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="437"/>          <source><b>Enrollment policy:</b></source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="430"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="447"/>          <source>policy</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="450"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="467"/>          <source><b>URL:</b></source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="460"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="477"/>          <source>URL</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="467"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="484"/>          <source><b>Description:</b></source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="478"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="495"/>          <source>Provider setup</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="481"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="498"/>          <source>Gathering configuration options for this provider</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="503"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="520"/>          <source>We are downloading some bits that we need to establish a secure connection with the provider for the first time.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="526"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="543"/>          <source>Setting up provider</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="576"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="593"/>          <source>Getting info from the Certificate Authority</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="583"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="600"/>          <source>Do we trust this Certificate Authority?</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="590"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="607"/>          <source>Establishing a trust relationship with this provider</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="649"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="666"/>          <source>Register new user</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="652"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="669"/>          <source>Register a new user with provider</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="667"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="684"/>          <source><b>Password:</b></source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="694"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="711"/>          <source><b>Re-enter password:</b></source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="704"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="721"/>          <source>Register</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="750"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="767"/>          <source>Remember my username and password</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="774"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="791"/>          <source>Service selection</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="777"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="794"/>          <source>Please select the services you would like to have</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/wizard.py" line="132"/> +        <location filename="../src/leap/bitmask/gui/wizard.py" line="131"/>          <source>&Next ></source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/wizard.py" line="134"/> +        <location filename="../src/leap/bitmask/gui/wizard.py" line="133"/>          <source>Connect</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/wizard.py" line="265"/> +        <location filename="../src/leap/bitmask/gui/wizard.py" line="289"/>          <source>Starting registration...</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/wizard.py" line="296"/> +        <location filename="../src/leap/bitmask/gui/wizard.py" line="333"/>          <source>User %s successfully registered.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/wizard.py" line="448"/> +        <location filename="../src/leap/bitmask/gui/wizard.py" line="503"/>          <source><font color='red'><b>Non-existent provider</b></font></source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/wizard.py" line="466"/> +        <location filename="../src/leap/bitmask/gui/wizard.py" line="522"/>          <source><font color='red'><b>%s</b></font></source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/wizard.py" line="492"/> +        <location filename="../src/leap/bitmask/gui/wizard.py" line="551"/>          <source>Unable to load provider configuration</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/wizard.py" line="498"/> +        <location filename="../src/leap/bitmask/gui/wizard.py" line="557"/>          <source><font color='red'><b>Not a valid provider</b></font></source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/wizard.py" line="565"/> -        <source>Services by %s</source> -        <translation type="unfinished"></translation> -    </message> -    <message> -        <location filename="../src/leap/bitmask/gui/wizard.py" line="585"/> +        <location filename="../src/leap/bitmask/gui/wizard.py" line="662"/>          <source>Something went wrong while trying to load service %s</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/wizard.py" line="616"/> -        <source>Description of services offered by %s</source> -        <translation type="unfinished"></translation> -    </message> -    <message> -        <location filename="../src/leap/bitmask/gui/wizard.py" line="637"/> -        <source>Register a new user with %s</source> -        <translation type="unfinished"></translation> -    </message> -    <message>          <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="26"/>          <source>Bitmask first run</source>          <translation type="unfinished"></translation> @@ -1289,7 +1379,7 @@ Export canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="737"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="754"/>          <source><b>Username:</b></source>          <translation type="unfinished"></translation>      </message> @@ -1299,7 +1389,7 @@ Export canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="288"/> +        <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="275"/>          <source>Configure new provider:</source>          <translation type="unfinished"></translation>      </message> @@ -1314,114 +1404,130 @@ Export canceled.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/wizard.py" line="317"/> +        <location filename="../src/leap/bitmask/gui/wizard.py" line="360"/>          <source>Something has gone wrong. Please try again.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/gui/wizard.py" line="607"/> +        <location filename="../src/leap/bitmask/gui/wizard.py" line="688"/>          <source>Gathering configuration options for {0}</source>          <translation type="unfinished"></translation>      </message> -</context> -<context> -    <name>__impl</name>      <message> -        <location filename="../src/leap/bitmask/crypto/srpauth.py" line="298"/> -        <source>The server did not send the salt parameter</source> +        <location filename="../src/leap/bitmask/gui/wizard.py" line="374"/> +        <source>The requested username is taken, choose another.</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/wizard.py" line="643"/> +        <source>Services by {0}</source> +        <translation type="unfinished"></translation> +    </message> +    <message> +        <location filename="../src/leap/bitmask/gui/wizard.py" line="696"/> +        <source>Description of services offered by {0}</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/crypto/srpauth.py" line="302"/> -        <source>The server did not send the B parameter</source> +        <location filename="../src/leap/bitmask/gui/wizard.py" line="711"/> +        <source>Register a new user with {0}</source>          <translation type="unfinished"></translation>      </message> +</context> +<context> +    <name>msg</name>      <message> -        <location filename="../src/leap/bitmask/crypto/srpauth.py" line="334"/> -        <source>The data sent from the server had errors</source> +        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="200"/> +        <source>TAP Driver</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/crypto/srpauth.py" line="356"/> -        <source>Could not connect to the server</source> +        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="207"/> +        <source>Encrypted Internet uses VPN, which needs a TAP device installed and none has been found. This will ask for administrative privileges.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/crypto/srpauth.py" line="382"/> -        <source>Unknown error (%s)</source> +        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="325"/> +        <source>TUN Driver</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/crypto/srpauth.py" line="413"/> -        <source>Problem getting data from server</source> +        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="333"/> +        <source>Encrypted Internet uses VPN, which needs a kernel extension for a TUN device installed, and none has been found. This will ask for administrative privileges.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/crypto/srpauth.py" line="439"/> -        <source>Bad data from server</source> +        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="142"/> +        <source>Problem installing files</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/crypto/srpauth.py" line="446"/> -        <source>Auth verification failed</source> +        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="143"/> +        <source>Some of the files could not be copied.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/crypto/srpauth.py" line="454"/> -        <source>Session cookie verification failed</source> +        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="328"/> +        <source>Bitmask needs to install the necessary drivers for Encrypted Internet to work. Would you like to proceed?</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/crypto/srpauth.py" line="289"/> -        <source>There was a problem with authentication</source> +        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="92"/> +        <source>Missing helper files</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/crypto/srpauth.py" line="178"/> -        <source>Invalid username or password.</source> +        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="380"/> +        <source>Missing resolvconf framework</source>          <translation type="unfinished"></translation>      </message> -</context> -<context> -    <name>msg</name>      <message> -        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="93"/> -        <source>Missing up/down scripts</source> +        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="432"/> +        <source>Missing Bitmask helpers</source>          <translation type="unfinished"></translation>      </message> +</context> +<context> +    <name>msgstr</name>      <message> -        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="184"/> -        <source>TAP Driver</source> +        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="375"/> +        <source>Could not find <b>resolvconf</b> installed in your system. +Do you want to quit Bitmask now?</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="191"/> -        <source>Encrypted Internet uses VPN, which needs a TAP device installed and none has been found. This will ask for administrative privileges.</source> +        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="379"/> +        <source>Encrypted Internet needs resolvconf installed to work properly. +Please use your package manager to install it. +</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="310"/> -        <source>TUN Driver</source> +        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="401"/> +        <source>Some essential helper files are missing in your system.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="318"/> -        <source>Encrypted Internet uses VPN, which needs a kernel extension for a TUN device installed, and none has been found. This will ask for administrative privileges.</source> +        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="404"/> +        <source>Reinstall your debian packages, or make sure you place them by hand.</source>          <translation type="unfinished"></translation>      </message> +</context> +<context> +    <name>self._eip_status</name>      <message> -        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="132"/> -        <source>Problem installing files</source> +        <location filename="../src/leap/bitmask/services/eip/conductor.py" line="184"/> +        <source>{0} is restarting</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="133"/> -        <source>Some of the files could not be copied.</source> +        <location filename="../src/leap/bitmask/services/eip/conductor.py" line="295"/> +        <source>{0} could not be launched because you did not authenticate properly.</source>          <translation type="unfinished"></translation>      </message>      <message> -        <location filename="../src/leap/bitmask/platform_init/initializers.py" line="313"/> -        <source>Bitmask needs to install the necessary drivers for Encrypted Internet to work. Would you like to proceed?</source> +        <location filename="../src/leap/bitmask/services/eip/conductor.py" line="307"/> +        <source>{0} finished in an unexpected manner!</source>          <translation type="unfinished"></translation>      </message>  </context> diff --git a/pkg/linux/README.rst b/pkg/linux/README.rst index 220565ff..f89842d3 100644 --- a/pkg/linux/README.rst +++ b/pkg/linux/README.rst @@ -3,8 +3,38 @@ Files  In GNU/Linux, we expect these files to be in place:: - update-resolv-conf -> /etc/leap/update-resolv-conf - resolv-update -> /etc/leap/resolv-update -   bitmask-root -> /usr/sbin/bitmask-root   polkit/se.leap.bitmask.policy -> /usr/share/polkit-1/actions/se.leap.bitmask.policy + +Bundle +====== + +The bundle will ask for permission to install to a different path. This search +path will be used if the flag ``--standalone`` is set:: + + bitmask-root -> /usr/local/sbin/bitmask-root + polkit/se.leap.bitmask.bundle.policy -> /usr/share/polkit-1/actions/se.leap.bitmask.bundle.policy + +When running with ``--standalone`` flag, the openvpn binary is  expected in the following path:: + + leap-openvpn -> /usr/local/sbin/leap-openvpn + +The bundle will use the script ``leap-install-helper.sh`` to copy the needed +files. If you ever want to use it manually to update the helpers or bins, it +needs a ``--from-path`` parameter to be passed to it. This points to a folder +from where all the needed binaries and scripts can be found. + + +Binary hashing +============== + +To be able to update the binaries when needed, the bundles distribute with the +sha256 hash of the packaged binaries for each release. This info can be found +in:: + +  src/leap/bitmask/_binaries.py + +That file is generated during the bundling process, by issuing the following +command from the root folder:: + +  python setup.py hash_binaries diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 1929b51b..5367a31c 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -67,7 +67,7 @@ OPENVPN_USER = "nobody"  OPENVPN_GROUP = "nogroup"  LEAPOPENVPN = "LEAPOPENVPN"  OPENVPN_SYSTEM_BIN = "/usr/sbin/openvpn"  # Debian location -OPENVPN_LEAP_BIN = "/usr/sbin/leap-openvpn"  # installed by bundle +OPENVPN_LEAP_BIN = "/usr/local/sbin/leap-openvpn"  # installed by bundle  """ diff --git a/pkg/linux/leap-install-helper.sh b/pkg/linux/leap-install-helper.sh new file mode 100755 index 00000000..566dd3d9 --- /dev/null +++ b/pkg/linux/leap-install-helper.sh @@ -0,0 +1,173 @@ +#!/bin/bash + +# File: leap-install-helper.sh +# Copy the needed binaries and helper files to their destination. +# Copyright (C) 2014 LEAP Encryption Access Project. +# +# 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/>. + +LOCAL_SBIN_FOLDER=/usr/local/sbin + +POLKIT_FOLDER="/usr/share/polkit-1/actions" +POLKIT_FILE="se.leap.bitmask.bundle.policy" +POLKIT_PATH="${POLKIT_FOLDER}/${POLKIT_FILE}" + +BITMASK_ROOT_FILE="bitmask-root" +BITMASK_ROOT_PATH="${LOCAL_SBIN_FOLDER}/${BITMASK_ROOT_FILE}" + +OPENVPN_FILE="leap-openvpn" +OPENVPN_PATH="${LOCAL_SBIN_FOLDER}/${OPENVPN_FILE}" + +# The following array stores global files that have been deprecated and we want +# to remove from the system path, after having dropped them there in the past. + +DEPRECATED_FILES=( +  '/usr/share/polkit-1/actions/net.openvpn.gui.leap.policy' +) + + +# Variables for parsing and storing the script options. + +FROM_PATH=NONE +REMOVE_OLD_FILES=NO +INSTALL_BITMASK_ROOT=NO +INSTALL_POLKIT_FILE=NO +INSTALL_OPENVPN=NO + + +# Process the options + +while [[ $# > 1 ]] +do +key="$1" +shift + +case $key in +    -f|--from-path) +    FROM_PATH="$1" +    shift +    ;; +    -r|--remove-old-files) +    REMOVE_OLD_FILES="$1" +    shift +    ;; +    --install-bitmask-root) +    INSTALL_BITMASK_ROOT="$1" +    shift +    ;; +    --install-polkit-file) +    INSTALL_POLKIT_FILE="$1" +    shift +    ;; +    --install-openvpn) +    INSTALL_OPENVPN="$1" +    shift +    ;; +    *) +    # unknown option +    ;; +esac +done +echo "LEAP_INSTALL_HELPER" +echo "-------------------" +echo FROM_PATH	          = "${FROM_PATH}" +echo REMOVE_OLD_FILES     = "${REMOVE_OLD_FILES}" +echo INSTALL_BITMASK_ROOT = "${INSTALL_BITMASK_ROOT}" +echo INSTALL_POLKIT_FILE  = "${INSTALL_POLKIT_FILE}" +echo INSTALL_OPENVPN      = "${INSTALL_OPENVPN}" +echo + + +# +# helper functions +# + +function check_current_uid() { +  current_uid=`id | sed 's/^uid=//;s/(.*$//'` +  if [ $current_uid != 0 ] +  then +    echo "[ERROR] NEED TO BE RUN AS ROOT" +    exit 1 +  fi +} + +function check_from_path() { +  if [ $FROM_PATH == NONE ] +  then +    echo "[ERROR] YOU NEED TO GIVE --from-path VALUE..." +    exit 1 +  fi +} + +function remove_old_files() { +  for file in "${DEPRECATED_FILES[@]}" +  do +    rm $file +  done +} + +function copy_bitmask_root() { +  mkdir -p "${LOCAL_SBIN_FOLDER}" +  cp "${FROM_PATH}/${BITMASK_ROOT_FILE}" "${BITMASK_ROOT_PATH}" +  chmod 744 "${BITMASK_ROOT_PATH}" + +} + +function copy_polkit_file() { +  cp "${FROM_PATH}/${POLKIT_FILE}" "${POLKIT_PATH}" +  chmod 644 "${POLKIT_PATH}" +} + +function copy_openvpn_file() { +  mkdir -p "${LOCAL_SBIN_FOLDER}" +  cp "${FROM_PATH}/${OPENVPN_FILE}" "${OPENVPN_PATH}" +  chmod 744 "${OPENVPN_PATH}" + +} + + +# +# Process options and run functions. +# + +check_current_uid + +if [ $INSTALL_BITMASK_ROOT == YES ] || [ $INSTALL_POLKIT_FILE == YES ] || [ $INSTALL_OPENVPN == YES ] +then +  check_from_path +fi + +if [ $REMOVE_OLD_FILES == YES ] +then +  echo "REMOVING OLD FILES..." +  remove_old_files +fi + +if [ $INSTALL_BITMASK_ROOT == YES ] +then +  echo "INSTALLING bitmask-root..." +  copy_bitmask_root +fi + +if [ $INSTALL_POLKIT_FILE == YES ] +then +  echo "INSTALLING policykit file..." +  copy_polkit_file +fi + +if [ $INSTALL_OPENVPN == YES ] +then +  echo "INSTALLING openvpn..." +  copy_openvpn_file +fi diff --git a/pkg/linux/polkit/se.leap.bitmask.bundle.policy b/pkg/linux/polkit/se.leap.bitmask.bundle.policy new file mode 100644 index 00000000..58fcaaa8 --- /dev/null +++ b/pkg/linux/polkit/se.leap.bitmask.bundle.policy @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE policyconfig PUBLIC + "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" + "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd"> +<policyconfig> + +  <vendor>LEAP Project</vendor> +  <vendor_url>http://leap.se/</vendor_url> + +  <action id="se.leap.bitmask.bundle.policy"> +    <description>Runs bitmask helper to launch firewall and openvpn (bundle version)</description> +    <description xml:lang="es">Ejecuta el asistente de bitmask para lanzar el firewall y openvpn (version bundle)</description> +    <message>Bitmask needs that you authenticate to start</message> +    <message xml:lang="es">Bitmask necesita autorizacion para comenzar</message> +    <icon_name>package-x-generic</icon_name>  +    <defaults> +      <allow_any>yes</allow_any> +      <allow_inactive>yes</allow_inactive> +      <allow_active>yes</allow_active> +    </defaults> +    <annotate key="org.freedesktop.policykit.exec.path">/usr/local/sbin/bitmask-root</annotate> +  </action> +</policyconfig> diff --git a/relnotes.txt b/relnotes.txt index e95e8c15..4fc33b39 100644 --- a/relnotes.txt +++ b/relnotes.txt @@ -1,8 +1,8 @@ -ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.5.2 +ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.5.3  The LEAP  team is  pleased to announce  the immediate  availability of -version 0.5.2  of Bitmask,  the Internet Encryption  Toolkit, codename -"are we there yet". +version 0.5.3  of Bitmask,  the Internet Encryption  Toolkit, codename +"encrypt ALL THE THINGS".  https://downloads.leap.se/client/ @@ -34,7 +34,7 @@ signature verification.  You can read about this and many  other cool things in the user manual  and the developer notes, which can be found online at: -http://bitmask.rtfd.org/ +https://leap.se/en/docs/client  WARNING: This is still  part of a beta release of  our software, a lot  of testing and auditing is still needed, so indeed use it, and feed us @@ -43,7 +43,7 @@ NOT trust your life to it.  WHAT CAN THIS VERSION OF BITMASK DO FOR ME? -Bitmask  0.5.2 improves  greatly  its Encrypted  internet support  and +Bitmask  0.5.3 improves  greatly  its Encrypted  Internet support  and  stability in general, among other various  bug fixes. You can refer to  the CHANGELOG for the meat. @@ -104,6 +104,6 @@ beyond any border.  The LEAP team, -June 6, 2014 +June 27, 2014  Somewhere in the middle of the intertubes.  EOF @@ -20,7 +20,9 @@ Setup file for bitmask.  """  from __future__ import print_function +import hashlib  import sys +import os  import re  if not sys.version_info[0] == 2: @@ -34,7 +36,6 @@ except ImportError:      from pkg import distribute_setup      distribute_setup.use_setuptools()      from setuptools import setup, find_packages -import os  from pkg import utils @@ -168,6 +169,64 @@ class cmd_develop(_develop):  cmdclass["develop"] = cmd_develop + +class cmd_binary_hash(Command): +    """ +    Update the _binaries.py file with hashes for the different helpers. +    This is used from within the bundle. +    """ + +    user_options = [] + +    def initialize_options(self): +        pass + +    def finalize_options(self): +        pass + +    def run(self, *args): + +        OPENVPN_BIN = os.environ.get('OPENVPN_BIN', None) +        BITMASK_ROOT = os.environ.get('BITMASK_ROOT', None) + +        def exit(): +            print("Please set environment variables " +                  "OPENVPN_BIN and BITMASK_ROOT pointing to the right path " +                  "to use this command") +            sys.exit(1) + +        bin_paths = OPENVPN_BIN, BITMASK_ROOT +        if not all(bin_paths): +            exit() + +        if not all(map(os.path.isfile, bin_paths)): +            exit() + +        openvpn_bin_hash, bitmask_root_hash = map( +            lambda path: hashlib.sha256(open(path).read()).hexdigest(), +            bin_paths) + +        template = r""" +# Hashes for binaries used in Bitmask Bundle. +# This file has been automatically generated by `setup.py hash_binaries` +# DO NOT modify it manually. + +OPENVPN_BIN = "{openvpn}" +BITMASK_ROOT = "{bitmask}" +""" +        subst_template = template.format( +            openvpn=openvpn_bin_hash, +            bitmask=bitmask_root_hash) + +        bin_hash_path = os.path.join('src', 'leap', 'bitmask', '_binaries.py') +        with open(bin_hash_path, 'w') as f: +            f.write(subst_template) +        print("Binaries hash file %s has been updated!" % (bin_hash_path,)) + + +cmdclass["hash_binaries"] = cmd_binary_hash + +  # next two classes need to augment the versioneer modified ones  versioneer_build = cmdclass['build'] diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py index c844beb1..0f733f26 100644 --- a/src/leap/bitmask/__init__.py +++ b/src/leap/bitmask/__init__.py @@ -66,7 +66,7 @@ except ImportError:  __appname__ = "unknown"  try: -    from leap._appname import __appname__ +    from leap.bitmask._appname import __appname__  except ImportError:      #running on a tree that has not run      #the setup.py setver diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index e965604a..6a7d6ff1 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -48,9 +48,15 @@ from functools import partial  from PySide import QtCore, QtGui  from leap.bitmask import __version__ as VERSION -from leap.bitmask.util import leap_argparse -from leap.bitmask.logs.utils import get_logger +from leap.bitmask.config import flags +from leap.bitmask.gui import locale_rc  # noqa - silence pylint +from leap.bitmask.gui.mainwindow import MainWindow +from leap.bitmask.logs.utils import create_logger +from leap.bitmask.platform_init.locks import we_are_the_one_and_only  from leap.bitmask.services.mail import plumber +from leap.bitmask.util import leap_argparse +from leap.bitmask.util.requirement_checker import check_requirements +  from leap.common.events import server as event_server  from leap.mail import __version__ as MAIL_VERSION @@ -114,35 +120,16 @@ def main():      """      Starts the main event loop and launches the main window.      """ -    # TODO move boilerplate outa here! -    _, opts = leap_argparse.init_leapc_args() +    # Parse arguments and store them +    opts = leap_argparse.get_options()      do_display_version(opts) -    standalone = opts.standalone -    offline = opts.offline -    bypass_checks = getattr(opts, 'danger', False) -    debug = opts.debug -    logfile = opts.log_file -    mail_logfile = opts.mail_log_file +    bypass_checks = opts.danger      start_hidden = opts.start_hidden -    replace_stdout = True -    if opts.repair or opts.import_maildir: -        # We don't want too much clutter on the comand mode -        # this could be more generic with a Command class. -        replace_stdout = False - -    logger = get_logger(debug, logfile, replace_stdout) - -    ############################################################# -    # Given how paths and bundling works, we need to delay the imports -    # of certain parts that depend on this path settings. -    # So first we set all the places where standalone might be queried. -    from leap.bitmask.config import flags -    from leap.common.config.baseconfig import BaseConfig -    flags.STANDALONE = standalone -    flags.OFFLINE = offline -    flags.MAIL_LOGFILE = mail_logfile +    flags.STANDALONE = opts.standalone +    flags.OFFLINE = opts.offline +    flags.MAIL_LOGFILE = opts.mail_log_file      flags.APP_VERSION_CHECK = opts.app_version_check      flags.API_VERSION_CHECK = opts.api_version_check      flags.OPENVPN_VERBOSITY = opts.openvpn_verb @@ -150,7 +137,13 @@ def main():      flags.CA_CERT_FILE = opts.ca_cert_file -    BaseConfig.standalone = standalone +    replace_stdout = True +    if opts.repair or opts.import_maildir: +        # We don't want too much clutter on the comand mode +        # this could be more generic with a Command class. +        replace_stdout = False + +    logger = create_logger(opts.debug, opts.log_file, replace_stdout)      # ok, we got logging in place, we can satisfy mail plumbing requests      # and show logs there. it normally will exit there if we got that path. @@ -167,18 +160,6 @@ def main():          nice = os.nice(int(PLAY_NICE))          logger.info("Setting NICE: %s" % nice) -    # And then we import all the other stuff -    # I think it's safe to import at the top by now -- kali -    from leap.bitmask.gui import locale_rc -    from leap.bitmask.gui import twisted_main -    from leap.bitmask.gui.mainwindow import MainWindow -    from leap.bitmask.platform_init import IS_MAC -    from leap.bitmask.platform_init.locks import we_are_the_one_and_only -    from leap.bitmask.util.requirement_checker import check_requirements - -    # pylint: avoid unused import -    assert(locale_rc) -      # TODO move to a different module: commands?      if not we_are_the_one_and_only():          # Bitmask is already running @@ -231,10 +212,8 @@ def main():      #timer.timeout.connect(lambda: None)      # XXX --------------------------------------------------------- -    window = MainWindow( -        lambda: twisted_main.quit(app), -        bypass_checks=bypass_checks, -        start_hidden=start_hidden) +    window = MainWindow(bypass_checks=bypass_checks, +                        start_hidden=start_hidden)      sigint_window = partial(sigint_handler, window, logger=logger)      signal.signal(signal.SIGINT, sigint_window) @@ -242,14 +221,6 @@ def main():      # callable used in addSystemEventTrigger to handle SIGTERM      sigterm_window = partial(sigterm_handler, window, logger=logger) -    if IS_MAC: -        window.raise_() - -    # This was a good idea, but for this to work as intended we -    # should centralize the start of all services in there. -    #tx_app = leap_services() -    #assert(tx_app) -      l = LoopingCall(QtCore.QCoreApplication.processEvents, 0, 10)      l.start(0.01) diff --git a/src/leap/bitmask/backend/__init__.py b/src/leap/bitmask/backend/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/leap/bitmask/backend/__init__.py diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend/components.py index 3c97c797..19fcf283 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend/components.py @@ -1,5 +1,5 @@  # -*- coding: utf-8 -*- -# backend.py +# components.py  # Copyright (C) 2013 LEAP  #  # This program is free software: you can redistribute it and/or modify @@ -15,19 +15,17 @@  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  """ -Backend for everything +Backend components  """  import logging  import os +import socket  import time  from functools import partial -from Queue import Queue, Empty  from threading import Condition -from twisted.internet import reactor  from twisted.internet import threads, defer -from twisted.internet.task import LoopingCall  from twisted.python import log  import zope.interface @@ -38,6 +36,7 @@ from leap.bitmask.crypto.srpauth import SRPAuth  from leap.bitmask.crypto.srpregister import SRPRegister  from leap.bitmask.platform_init import IS_LINUX  from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper +from leap.bitmask.provider.pinned import PinnedProviders  from leap.bitmask.services import get_supported  from leap.bitmask.services.eip import eipconfig  from leap.bitmask.services.eip import get_openvpn_management @@ -53,6 +52,7 @@ from leap.bitmask.services.mail.smtpconfig import SMTPConfig  from leap.bitmask.services.soledad.soledadbootstrapper import \      SoledadBootstrapper +from leap.bitmask.util import force_eval  from leap.common import certs as leap_certs @@ -61,9 +61,6 @@ from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch  from leap.soledad.client import NoStorageSecret, PassphraseTooShort -# Frontend side -from PySide import QtCore -  logger = logging.getLogger(__name__) @@ -260,8 +257,7 @@ class Provider(object):      def get_details(self, domain, lang=None):          """ -        Signal a ProviderConfigLight object with the current ProviderConfig -        settings. +        Signal a dict with the current ProviderConfig settings.          :param domain: the domain name of the provider.          :type domain: str @@ -269,12 +265,23 @@ class Provider(object):          :type lang: str          Signals: -            prov_get_details -> ProviderConfigLight +            prov_get_details -> dict          """          self._signaler.signal(              self._signaler.PROV_GET_DETAILS,              self._provider_config.get_light_config(domain, lang)) +    def get_pinned_providers(self): +        """ +        Signal the list of pinned provider domains. + +        Signals: +            prov_get_pinned_providers -> list of provider domains +        """ +        self._signaler.signal( +            self._signaler.PROV_GET_PINNED_PROVIDERS, +            PinnedProviders.domains()) +  class Register(object):      """ @@ -598,7 +605,8 @@ class EIP(object):          eip_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain))          launcher = get_vpn_launcher() -        if not os.path.isfile(launcher.OPENVPN_BIN_PATH): +        ovpn_path = force_eval(launcher.OPENVPN_BIN_PATH) +        if not os.path.isfile(ovpn_path):              logger.error("Cannot start OpenVPN, binary not found")              return False @@ -640,6 +648,45 @@ class EIP(object):              if self._signaler is not None:                  self._signaler.signal(self._signaler.EIP_CANNOT_START) +    def check_dns(self, domain): +        """ +        Check if we can resolve the given domain name. + +        :param domain: the domain to check. +        :type domain: str +        """ +        def do_check(): +            """ +            Try to resolve the domain name. +            """ +            socket.gethostbyname(domain.encode('idna')) + +        def check_ok(_): +            """ +            Callback handler for `do_check`. +            """ +            self._signaler.signal(self._signaler.EIP_DNS_OK) +            logger.debug("DNS check OK") + +        def check_err(failure): +            """ +            Errback handler for `do_check`. + +            :param failure: the failure that triggered the errback. +            :type failure: twisted.python.failure.Failure +            """ +            logger.debug("Can't resolve hostname. {0!r}".format(failure)) + +            self._signaler.signal(self._signaler.EIP_DNS_ERROR) + +            # python 2.7.4 raises socket.error +            # python 2.7.5 raises socket.gaierror +            failure.trap(socket.gaierror, socket.error) + +        d = threads.deferToThread(do_check) +        d.addCallback(check_ok) +        d.addErrback(check_err) +  class Soledad(object):      """ @@ -1098,932 +1145,3 @@ class Authenticate(object):              signal = self._signaler.SRP_STATUS_NOT_LOGGED_IN          self._signaler.signal(signal) - - -class Signaler(QtCore.QObject): -    """ -    Signaler object, handles converting string commands to Qt signals. - -    This is intended for the separation in frontend/backend, this will -    live in the frontend. -    """ - -    #################### -    # These will only exist in the frontend -    # Signals for the ProviderBootstrapper -    prov_name_resolution = QtCore.Signal(object) -    prov_https_connection = QtCore.Signal(object) -    prov_download_provider_info = QtCore.Signal(object) - -    prov_download_ca_cert = QtCore.Signal(object) -    prov_check_ca_fingerprint = QtCore.Signal(object) -    prov_check_api_certificate = QtCore.Signal(object) - -    prov_problem_with_provider = QtCore.Signal(object) - -    prov_unsupported_client = QtCore.Signal(object) -    prov_unsupported_api = QtCore.Signal(object) - -    prov_get_all_services = QtCore.Signal(object) -    prov_get_supported_services = QtCore.Signal(object) -    prov_get_details = QtCore.Signal(object) - -    prov_cancelled_setup = QtCore.Signal(object) - -    # Signals for SRPRegister -    srp_registration_finished = QtCore.Signal(object) -    srp_registration_failed = QtCore.Signal(object) -    srp_registration_taken = QtCore.Signal(object) - -    # Signals for EIP bootstrapping -    eip_config_ready = QtCore.Signal(object) -    eip_client_certificate_ready = QtCore.Signal(object) - -    eip_cancelled_setup = QtCore.Signal(object) - -    # Signals for SRPAuth -    srp_auth_ok = QtCore.Signal(object) -    srp_auth_error = QtCore.Signal(object) -    srp_auth_server_error = QtCore.Signal(object) -    srp_auth_connection_error = QtCore.Signal(object) -    srp_auth_bad_user_or_password = QtCore.Signal(object) -    srp_logout_ok = QtCore.Signal(object) -    srp_logout_error = QtCore.Signal(object) -    srp_password_change_ok = QtCore.Signal(object) -    srp_password_change_error = QtCore.Signal(object) -    srp_password_change_badpw = QtCore.Signal(object) -    srp_not_logged_in_error = QtCore.Signal(object) -    srp_status_logged_in = QtCore.Signal(object) -    srp_status_not_logged_in = QtCore.Signal(object) - -    # Signals for EIP -    eip_connected = QtCore.Signal(object) -    eip_disconnected = QtCore.Signal(object) -    eip_connection_died = QtCore.Signal(object) -    eip_connection_aborted = QtCore.Signal(object) -    eip_stopped = QtCore.Signal(object) - -    # EIP problems -    eip_no_polkit_agent_error = QtCore.Signal(object) -    eip_no_tun_kext_error = QtCore.Signal(object) -    eip_no_pkexec_error = QtCore.Signal(object) -    eip_openvpn_not_found_error = QtCore.Signal(object) -    eip_openvpn_already_running = QtCore.Signal(object) -    eip_alien_openvpn_already_running = QtCore.Signal(object) -    eip_vpn_launcher_exception = QtCore.Signal(object) - -    eip_get_gateways_list = QtCore.Signal(object) -    eip_get_gateways_list_error = QtCore.Signal(object) -    eip_uninitialized_provider = QtCore.Signal(object) -    eip_get_initialized_providers = QtCore.Signal(object) - -    # signals from parsing openvpn output -    eip_network_unreachable = QtCore.Signal(object) -    eip_process_restart_tls = QtCore.Signal(object) -    eip_process_restart_ping = QtCore.Signal(object) - -    # signals from vpnprocess.py -    eip_state_changed = QtCore.Signal(dict) -    eip_status_changed = QtCore.Signal(dict) -    eip_process_finished = QtCore.Signal(int) -    eip_tear_fw_down = QtCore.Signal(object) - -    # signals whether the needed files to start EIP exist or not -    eip_can_start = QtCore.Signal(object) -    eip_cannot_start = QtCore.Signal(object) - -    # Signals for Soledad -    soledad_bootstrap_failed = QtCore.Signal(object) -    soledad_bootstrap_finished = QtCore.Signal(object) -    soledad_offline_failed = QtCore.Signal(object) -    soledad_offline_finished = QtCore.Signal(object) -    soledad_invalid_auth_token = QtCore.Signal(object) -    soledad_cancelled_bootstrap = QtCore.Signal(object) -    soledad_password_change_ok = QtCore.Signal(object) -    soledad_password_change_error = QtCore.Signal(object) - -    # Keymanager signals -    keymanager_export_ok = QtCore.Signal(object) -    keymanager_export_error = QtCore.Signal(object) -    keymanager_keys_list = QtCore.Signal(object) - -    keymanager_import_ioerror = QtCore.Signal(object) -    keymanager_import_datamismatch = QtCore.Signal(object) -    keymanager_import_missingkey = QtCore.Signal(object) -    keymanager_import_addressmismatch = QtCore.Signal(object) -    keymanager_import_ok = QtCore.Signal(object) - -    keymanager_key_details = QtCore.Signal(object) - -    # mail related signals -    imap_stopped = QtCore.Signal(object) - -    # This signal is used to warn the backend user that is doing something -    # wrong -    backend_bad_call = QtCore.Signal(object) - -    #################### -    # These will exist both in the backend AND the front end. -    # The frontend might choose to not "interpret" all the signals -    # from the backend, but the backend needs to have all the signals -    # it's going to emit defined here -    PROV_NAME_RESOLUTION_KEY = "prov_name_resolution" -    PROV_HTTPS_CONNECTION_KEY = "prov_https_connection" -    PROV_DOWNLOAD_PROVIDER_INFO_KEY = "prov_download_provider_info" -    PROV_DOWNLOAD_CA_CERT_KEY = "prov_download_ca_cert" -    PROV_CHECK_CA_FINGERPRINT_KEY = "prov_check_ca_fingerprint" -    PROV_CHECK_API_CERTIFICATE_KEY = "prov_check_api_certificate" -    PROV_PROBLEM_WITH_PROVIDER_KEY = "prov_problem_with_provider" -    PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client" -    PROV_UNSUPPORTED_API = "prov_unsupported_api" -    PROV_CANCELLED_SETUP = "prov_cancelled_setup" -    PROV_GET_ALL_SERVICES = "prov_get_all_services" -    PROV_GET_SUPPORTED_SERVICES = "prov_get_supported_services" -    PROV_GET_DETAILS = "prov_get_details" - -    SRP_REGISTRATION_FINISHED = "srp_registration_finished" -    SRP_REGISTRATION_FAILED = "srp_registration_failed" -    SRP_REGISTRATION_TAKEN = "srp_registration_taken" -    SRP_AUTH_OK = "srp_auth_ok" -    SRP_AUTH_ERROR = "srp_auth_error" -    SRP_AUTH_SERVER_ERROR = "srp_auth_server_error" -    SRP_AUTH_CONNECTION_ERROR = "srp_auth_connection_error" -    SRP_AUTH_BAD_USER_OR_PASSWORD = "srp_auth_bad_user_or_password" -    SRP_LOGOUT_OK = "srp_logout_ok" -    SRP_LOGOUT_ERROR = "srp_logout_error" -    SRP_PASSWORD_CHANGE_OK = "srp_password_change_ok" -    SRP_PASSWORD_CHANGE_ERROR = "srp_password_change_error" -    SRP_PASSWORD_CHANGE_BADPW = "srp_password_change_badpw" -    SRP_NOT_LOGGED_IN_ERROR = "srp_not_logged_in_error" -    SRP_STATUS_LOGGED_IN = "srp_status_logged_in" -    SRP_STATUS_NOT_LOGGED_IN = "srp_status_not_logged_in" - -    EIP_CONFIG_READY = "eip_config_ready" -    EIP_CLIENT_CERTIFICATE_READY = "eip_client_certificate_ready" -    EIP_CANCELLED_SETUP = "eip_cancelled_setup" - -    EIP_CONNECTED = "eip_connected" -    EIP_DISCONNECTED = "eip_disconnected" -    EIP_CONNECTION_DIED = "eip_connection_died" -    EIP_CONNECTION_ABORTED = "eip_connection_aborted" -    EIP_STOPPED = "eip_stopped" - -    EIP_NO_POLKIT_AGENT_ERROR = "eip_no_polkit_agent_error" -    EIP_NO_TUN_KEXT_ERROR = "eip_no_tun_kext_error" -    EIP_NO_PKEXEC_ERROR = "eip_no_pkexec_error" -    EIP_OPENVPN_NOT_FOUND_ERROR = "eip_openvpn_not_found_error" -    EIP_OPENVPN_ALREADY_RUNNING = "eip_openvpn_already_running" -    EIP_ALIEN_OPENVPN_ALREADY_RUNNING = "eip_alien_openvpn_already_running" -    EIP_VPN_LAUNCHER_EXCEPTION = "eip_vpn_launcher_exception" - -    EIP_GET_GATEWAYS_LIST = "eip_get_gateways_list" -    EIP_GET_GATEWAYS_LIST_ERROR = "eip_get_gateways_list_error" -    EIP_UNINITIALIZED_PROVIDER = "eip_uninitialized_provider" -    EIP_GET_INITIALIZED_PROVIDERS = "eip_get_initialized_providers" - -    EIP_NETWORK_UNREACHABLE = "eip_network_unreachable" -    EIP_PROCESS_RESTART_TLS = "eip_process_restart_tls" -    EIP_PROCESS_RESTART_PING = "eip_process_restart_ping" - -    EIP_STATE_CHANGED = "eip_state_changed" -    EIP_STATUS_CHANGED = "eip_status_changed" -    EIP_PROCESS_FINISHED = "eip_process_finished" -    EIP_TEAR_FW_DOWN = "eip_tear_fw_down" - -    EIP_CAN_START = "eip_can_start" -    EIP_CANNOT_START = "eip_cannot_start" - -    SOLEDAD_BOOTSTRAP_FAILED = "soledad_bootstrap_failed" -    SOLEDAD_BOOTSTRAP_FINISHED = "soledad_bootstrap_finished" -    SOLEDAD_OFFLINE_FAILED = "soledad_offline_failed" -    SOLEDAD_OFFLINE_FINISHED = "soledad_offline_finished" -    SOLEDAD_INVALID_AUTH_TOKEN = "soledad_invalid_auth_token" - -    SOLEDAD_PASSWORD_CHANGE_OK = "soledad_password_change_ok" -    SOLEDAD_PASSWORD_CHANGE_ERROR = "soledad_password_change_error" - -    SOLEDAD_CANCELLED_BOOTSTRAP = "soledad_cancelled_bootstrap" - -    KEYMANAGER_EXPORT_OK = "keymanager_export_ok" -    KEYMANAGER_EXPORT_ERROR = "keymanager_export_error" -    KEYMANAGER_KEYS_LIST = "keymanager_keys_list" - -    KEYMANAGER_IMPORT_IOERROR = "keymanager_import_ioerror" -    KEYMANAGER_IMPORT_DATAMISMATCH = "keymanager_import_datamismatch" -    KEYMANAGER_IMPORT_MISSINGKEY = "keymanager_import_missingkey" -    KEYMANAGER_IMPORT_ADDRESSMISMATCH = "keymanager_import_addressmismatch" -    KEYMANAGER_IMPORT_OK = "keymanager_import_ok" -    KEYMANAGER_KEY_DETAILS = "keymanager_key_details" - -    IMAP_STOPPED = "imap_stopped" - -    BACKEND_BAD_CALL = "backend_bad_call" - -    def __init__(self): -        """ -        Constructor for the Signaler -        """ -        QtCore.QObject.__init__(self) -        self._signals = {} - -        signals = [ -            self.PROV_NAME_RESOLUTION_KEY, -            self.PROV_HTTPS_CONNECTION_KEY, -            self.PROV_DOWNLOAD_PROVIDER_INFO_KEY, -            self.PROV_DOWNLOAD_CA_CERT_KEY, -            self.PROV_CHECK_CA_FINGERPRINT_KEY, -            self.PROV_CHECK_API_CERTIFICATE_KEY, -            self.PROV_PROBLEM_WITH_PROVIDER_KEY, -            self.PROV_UNSUPPORTED_CLIENT, -            self.PROV_UNSUPPORTED_API, -            self.PROV_CANCELLED_SETUP, -            self.PROV_GET_ALL_SERVICES, -            self.PROV_GET_SUPPORTED_SERVICES, -            self.PROV_GET_DETAILS, - -            self.SRP_REGISTRATION_FINISHED, -            self.SRP_REGISTRATION_FAILED, -            self.SRP_REGISTRATION_TAKEN, - -            self.EIP_CONFIG_READY, -            self.EIP_CLIENT_CERTIFICATE_READY, -            self.EIP_CANCELLED_SETUP, - -            self.EIP_CONNECTED, -            self.EIP_DISCONNECTED, -            self.EIP_CONNECTION_DIED, -            self.EIP_CONNECTION_ABORTED, -            self.EIP_STOPPED, - -            self.EIP_NO_POLKIT_AGENT_ERROR, -            self.EIP_NO_TUN_KEXT_ERROR, -            self.EIP_NO_PKEXEC_ERROR, -            self.EIP_OPENVPN_NOT_FOUND_ERROR, -            self.EIP_OPENVPN_ALREADY_RUNNING, -            self.EIP_ALIEN_OPENVPN_ALREADY_RUNNING, -            self.EIP_VPN_LAUNCHER_EXCEPTION, - -            self.EIP_GET_GATEWAYS_LIST, -            self.EIP_GET_GATEWAYS_LIST_ERROR, -            self.EIP_UNINITIALIZED_PROVIDER, -            self.EIP_GET_INITIALIZED_PROVIDERS, - -            self.EIP_NETWORK_UNREACHABLE, -            self.EIP_PROCESS_RESTART_TLS, -            self.EIP_PROCESS_RESTART_PING, - -            self.EIP_STATE_CHANGED, -            self.EIP_STATUS_CHANGED, -            self.EIP_PROCESS_FINISHED, - -            self.EIP_CAN_START, -            self.EIP_CANNOT_START, - -            self.SRP_AUTH_OK, -            self.SRP_AUTH_ERROR, -            self.SRP_AUTH_SERVER_ERROR, -            self.SRP_AUTH_CONNECTION_ERROR, -            self.SRP_AUTH_BAD_USER_OR_PASSWORD, -            self.SRP_LOGOUT_OK, -            self.SRP_LOGOUT_ERROR, -            self.SRP_PASSWORD_CHANGE_OK, -            self.SRP_PASSWORD_CHANGE_ERROR, -            self.SRP_PASSWORD_CHANGE_BADPW, -            self.SRP_NOT_LOGGED_IN_ERROR, -            self.SRP_STATUS_LOGGED_IN, -            self.SRP_STATUS_NOT_LOGGED_IN, - -            self.SOLEDAD_BOOTSTRAP_FAILED, -            self.SOLEDAD_BOOTSTRAP_FINISHED, -            self.SOLEDAD_OFFLINE_FAILED, -            self.SOLEDAD_OFFLINE_FINISHED, -            self.SOLEDAD_INVALID_AUTH_TOKEN, -            self.SOLEDAD_CANCELLED_BOOTSTRAP, - -            self.SOLEDAD_PASSWORD_CHANGE_OK, -            self.SOLEDAD_PASSWORD_CHANGE_ERROR, - -            self.KEYMANAGER_EXPORT_OK, -            self.KEYMANAGER_EXPORT_ERROR, -            self.KEYMANAGER_KEYS_LIST, - -            self.KEYMANAGER_IMPORT_IOERROR, -            self.KEYMANAGER_IMPORT_DATAMISMATCH, -            self.KEYMANAGER_IMPORT_MISSINGKEY, -            self.KEYMANAGER_IMPORT_ADDRESSMISMATCH, -            self.KEYMANAGER_IMPORT_OK, -            self.KEYMANAGER_KEY_DETAILS, - -            self.IMAP_STOPPED, - -            self.BACKEND_BAD_CALL, -        ] - -        for sig in signals: -            self._signals[sig] = getattr(self, sig) - -    def signal(self, key, data=None): -        """ -        Emits a Qt signal based on the key provided, with the data if provided. - -        :param key: string identifying the signal to emit -        :type key: str -        :param data: object to send with the data -        :type data: object - -        NOTE: The data object will be a serialized str in the backend, -        and an unserialized object in the frontend, but for now we -        just care about objects. -        """ -        # Right now it emits Qt signals. The backend version of this -        # will do zmq.send_multipart, and the frontend version will be -        # similar to this - -        # for some reason emitting 'None' gives a segmentation fault. -        if data is None: -            data = '' - -        try: -            self._signals[key].emit(data) -        except KeyError: -            log.err("Unknown key for signal %s!" % (key,)) - - -class Backend(object): -    """ -    Backend for everything, the UI should only use this class. -    """ - -    PASSED_KEY = "passed" -    ERROR_KEY = "error" - -    def __init__(self, bypass_checks=False): -        """ -        Constructor for the backend. -        """ -        # Components map for the commands received -        self._components = {} - -        # Ongoing defers that will be cancelled at stop time -        self._ongoing_defers = [] - -        # Signaler object to translate commands into Qt signals -        self._signaler = Signaler() - -        # Objects needed by several components, so we make a proxy and pass -        # them around -        self._soledad_proxy = zope.proxy.ProxyBase(None) -        self._keymanager_proxy = zope.proxy.ProxyBase(None) - -        # Component registration -        self._register(Provider(self._signaler, bypass_checks)) -        self._register(Register(self._signaler)) -        self._register(Authenticate(self._signaler)) -        self._register(EIP(self._signaler)) -        self._register(Soledad(self._soledad_proxy, -                               self._keymanager_proxy, -                               self._signaler)) -        self._register(Keymanager(self._keymanager_proxy, -                                  self._signaler)) -        self._register(Mail(self._soledad_proxy, -                            self._keymanager_proxy, -                            self._signaler)) - -        # We have a looping call on a thread executing all the -        # commands in queue. Right now this queue is an actual Queue -        # object, but it'll become the zmq recv_multipart queue -        self._lc = LoopingCall(threads.deferToThread, self._worker) - -        # Temporal call_queue for worker, will be replaced with -        # recv_multipart os something equivalent in the loopingcall -        self._call_queue = Queue() - -    @property -    def signaler(self): -        """ -        Public signaler access to let the UI connect to its signals. -        """ -        return self._signaler - -    def start(self): -        """ -        Starts the looping call -        """ -        logger.debug("Starting worker...") -        self._lc.start(0.01) - -    def stop(self): -        """ -        Stops the looping call and tries to cancel all the defers. -        """ -        reactor.callLater(2, self._stop) - -    def _stop(self): -        """ -        Delayed stopping of worker. Called from `stop`. -        """ -        logger.debug("Stopping worker...") -        if self._lc.running: -            self._lc.stop() -        else: -            logger.warning("Looping call is not running, cannot stop") - -        logger.debug("Cancelling ongoing defers...") -        while len(self._ongoing_defers) > 0: -            d = self._ongoing_defers.pop() -            d.cancel() -        logger.debug("Defers cancelled.") - -    def _register(self, component): -        """ -        Registers a component in this backend - -        :param component: Component to register -        :type component: any object that implements ILEAPComponent -        """ -        # TODO: assert that the component implements the interfaces -        # expected -        try: -            self._components[component.key] = component -        except Exception: -            logger.error("There was a problem registering %s" % (component,)) - -    def _signal_back(self, _, signal): -        """ -        Helper method to signal back (callback like behavior) to the -        UI that an operation finished. - -        :param signal: signal name -        :type signal: str -        """ -        self._signaler.signal(signal) - -    def _worker(self): -        """ -        Worker method, called from a different thread and as a part of -        a looping call -        """ -        try: -            # this'll become recv_multipart -            cmd = self._call_queue.get(block=False) - -            # cmd is: component, method, signalback, *args -            func = getattr(self._components[cmd[0]], cmd[1]) -            d = func(*cmd[3:]) -            if d is not None:  # d may be None if a defer chain is cancelled. -                # A call might not have a callback signal, but if it does, -                # we add it to the chain -                if cmd[2] is not None: -                    d.addCallbacks(self._signal_back, logger.error, cmd[2]) -                d.addCallbacks(self._done_action, logger.error, -                               callbackKeywords={"d": d}) -                d.addErrback(logger.error) -                self._ongoing_defers.append(d) -        except Empty: -            # If it's just empty we don't have anything to do. -            pass -        except defer.CancelledError: -            logger.debug("defer cancelled somewhere (CancelledError).") -        except Exception as e: -            # But we log the rest -            logger.exception("Unexpected exception: {0!r}".format(e)) - -    def _done_action(self, _, d): -        """ -        Remover of the defer once it's done - -        :param d: defer to remove -        :type d: twisted.internet.defer.Deferred -        """ -        if d in self._ongoing_defers: -            self._ongoing_defers.remove(d) - -    # XXX: Temporal interface until we migrate to zmq -    # We simulate the calls to zmq.send_multipart. Once we separate -    # this in two processes, the methods bellow can be changed to -    # send_multipart and this backend class will be really simple. - -    def provider_setup(self, provider): -        """ -        Initiate the setup for a provider. - -        :param provider: URL for the provider -        :type provider: unicode - -        Signals: -            prov_unsupported_client -            prov_unsupported_api -            prov_name_resolution        -> { PASSED_KEY: bool, ERROR_KEY: str } -            prov_https_connection       -> { PASSED_KEY: bool, ERROR_KEY: str } -            prov_download_provider_info -> { PASSED_KEY: bool, ERROR_KEY: str } -        """ -        self._call_queue.put(("provider", "setup_provider", None, provider)) - -    def provider_cancel_setup(self): -        """ -        Cancel the ongoing setup provider (if any). -        """ -        self._call_queue.put(("provider", "cancel_setup_provider", None)) - -    def provider_bootstrap(self, provider): -        """ -        Second stage of bootstrapping for a provider. - -        :param provider: URL for the provider -        :type provider: unicode - -        Signals: -            prov_problem_with_provider -            prov_download_ca_cert      -> {PASSED_KEY: bool, ERROR_KEY: str} -            prov_check_ca_fingerprint  -> {PASSED_KEY: bool, ERROR_KEY: str} -            prov_check_api_certificate -> {PASSED_KEY: bool, ERROR_KEY: str} -        """ -        self._call_queue.put(("provider", "bootstrap", None, provider)) - -    def provider_get_supported_services(self, domain): -        """ -        Signal a list of supported services provided by the given provider. - -        :param domain: the provider to get the services from. -        :type domain: str - -        Signals: -            prov_get_supported_services -> list of unicode -        """ -        self._call_queue.put(("provider", "get_supported_services", None, -                              domain)) - -    def provider_get_all_services(self, providers): -        """ -        Signal a list of services provided by all the configured providers. - -        :param providers: the list of providers to get the services. -        :type providers: list - -        Signals: -            prov_get_all_services -> list of unicode -        """ -        self._call_queue.put(("provider", "get_all_services", None, -                              providers)) - -    def provider_get_details(self, domain, lang): -        """ -        Signal a ProviderConfigLight object with the current ProviderConfig -        settings. - -        :param domain: the domain name of the provider. -        :type domain: str -        :param lang: the language to use for localized strings. -        :type lang: str - -        Signals: -            prov_get_details -> ProviderConfigLight -        """ -        self._call_queue.put(("provider", "get_details", None, domain, lang)) - -    def user_register(self, provider, username, password): -        """ -        Register a user using the domain and password given as parameters. - -        :param domain: the domain we need to register the user. -        :type domain: unicode -        :param username: the user name -        :type username: unicode -        :param password: the password for the username -        :type password: unicode - -        Signals: -            srp_registration_finished -            srp_registration_taken -            srp_registration_failed -        """ -        self._call_queue.put(("register", "register_user", None, provider, -                              username, password)) - -    def eip_setup(self, provider, skip_network=False): -        """ -        Initiate the setup for a provider - -        :param provider: URL for the provider -        :type provider: unicode -        :param skip_network: Whether checks that involve network should be done -                             or not -        :type skip_network: bool - -        Signals: -            eip_config_ready             -> {PASSED_KEY: bool, ERROR_KEY: str} -            eip_client_certificate_ready -> {PASSED_KEY: bool, ERROR_KEY: str} -            eip_cancelled_setup -        """ -        self._call_queue.put(("eip", "setup_eip", None, provider, -                              skip_network)) - -    def eip_cancel_setup(self): -        """ -        Cancel the ongoing setup EIP (if any). -        """ -        self._call_queue.put(("eip", "cancel_setup_eip", None)) - -    def eip_start(self, restart=False): -        """ -        Start the EIP service. - -        Signals: -            backend_bad_call -            eip_alien_openvpn_already_running -            eip_connected -            eip_connection_aborted -            eip_network_unreachable -            eip_no_pkexec_error -            eip_no_polkit_agent_error -            eip_no_tun_kext_error -            eip_openvpn_already_running -            eip_openvpn_not_found_error -            eip_process_finished -            eip_process_restart_ping -            eip_process_restart_tls -            eip_state_changed -> str -            eip_status_changed -> tuple of str (download, upload) -            eip_vpn_launcher_exception - -        :param restart: whether is is a restart. -        :type restart: bool -        """ -        self._call_queue.put(("eip", "start", None, restart)) - -    def eip_stop(self, shutdown=False, restart=False, failed=False): -        """ -        Stop the EIP service. - -        :param shutdown: whether this is the final shutdown. -        :type shutdown: bool - -        :param restart: whether this is part of a restart. -        :type restart: bool -        """ -        self._call_queue.put(("eip", "stop", None, shutdown, restart)) - -    def eip_terminate(self): -        """ -        Terminate the EIP service, not necessarily in a nice way. -        """ -        self._call_queue.put(("eip", "terminate", None)) - -    def eip_get_gateways_list(self, domain): -        """ -        Signal a list of gateways for the given provider. - -        :param domain: the domain to get the gateways. -        :type domain: str - -        # TODO discuss how to document the expected result object received of -        # the signal -        :signal type: list of str - -        Signals: -            eip_get_gateways_list -> list of unicode -            eip_get_gateways_list_error -            eip_uninitialized_provider -        """ -        self._call_queue.put(("eip", "get_gateways_list", None, domain)) - -    def eip_get_initialized_providers(self, domains): -        """ -        Signal a list of the given domains and if they are initialized or not. - -        :param domains: the list of domains to check. -        :type domain: list of str - -        Signals: -            eip_get_initialized_providers -> list of tuple(unicode, bool) - -        """ -        self._call_queue.put(("eip", "get_initialized_providers", -                              None, domains)) - -    def eip_can_start(self, domain): -        """ -        Signal whether it has everything that is needed to run EIP or not - -        :param domain: the domain for the provider to check -        :type domain: str - -        Signals: -            eip_can_start -            eip_cannot_start -        """ -        self._call_queue.put(("eip", "can_start", -                              None, domain)) - -    def tear_fw_down(self): -        """ -        Signal the need to tear the fw down. -        """ -        self._call_queue.put(("eip", "tear_fw_down", None)) - -    def user_login(self, provider, username, password): -        """ -        Execute the whole authentication process for a user - -        :param domain: the domain where we need to authenticate. -        :type domain: unicode -        :param username: username for this session -        :type username: str -        :param password: password for this user -        :type password: str - -        Signals: -            srp_auth_error -            srp_auth_ok -            srp_auth_bad_user_or_password -            srp_auth_server_error -            srp_auth_connection_error -            srp_auth_error -        """ -        self._call_queue.put(("authenticate", "login", None, provider, -                              username, password)) - -    def user_logout(self): -        """ -        Log out the current session. - -        Signals: -            srp_logout_ok -            srp_logout_error -            srp_not_logged_in_error -        """ -        self._call_queue.put(("authenticate", "logout", None)) - -    def user_cancel_login(self): -        """ -        Cancel the ongoing login (if any). -        """ -        self._call_queue.put(("authenticate", "cancel_login", None)) - -    def user_change_password(self, current_password, new_password): -        """ -        Change the user's password. - -        :param current_password: the current password of the user. -        :type current_password: str -        :param new_password: the new password for the user. -        :type new_password: str - -        Signals: -            srp_not_logged_in_error -            srp_password_change_ok -            srp_password_change_badpw -            srp_password_change_error -        """ -        self._call_queue.put(("authenticate", "change_password", None, -                              current_password, new_password)) - -    def soledad_change_password(self, new_password): -        """ -        Change the database's password. - -        :param new_password: the new password for the user. -        :type new_password: unicode - -        Signals: -            srp_not_logged_in_error -            srp_password_change_ok -            srp_password_change_badpw -            srp_password_change_error -        """ -        self._call_queue.put(("soledad", "change_password", None, -                              new_password)) - -    def user_get_logged_in_status(self): -        """ -        Signal if the user is currently logged in or not. - -        Signals: -            srp_status_logged_in -            srp_status_not_logged_in -        """ -        self._call_queue.put(("authenticate", "get_logged_in_status", None)) - -    def soledad_bootstrap(self, username, domain, password): -        """ -        Bootstrap the soledad database. - -        :param username: the user name -        :type username: unicode -        :param domain: the domain that we are using. -        :type domain: unicode -        :param password: the password for the username -        :type password: unicode - -        Signals: -            soledad_bootstrap_finished -            soledad_bootstrap_failed -            soledad_invalid_auth_token -        """ -        self._call_queue.put(("soledad", "bootstrap", None, -                              username, domain, password)) - -    def soledad_load_offline(self, username, password, uuid): -        """ -        Load the soledad database in offline mode. - -        :param username: full user id (user@provider) -        :type username: str or unicode -        :param password: the soledad passphrase -        :type password: unicode -        :param uuid: the user uuid -        :type uuid: str or unicode - -        Signals: -        """ -        self._call_queue.put(("soledad", "load_offline", None, -                              username, password, uuid)) - -    def soledad_cancel_bootstrap(self): -        """ -        Cancel the ongoing soledad bootstrapping process (if any). -        """ -        self._call_queue.put(("soledad", "cancel_bootstrap", None)) - -    def soledad_close(self): -        """ -        Close soledad database. -        """ -        self._call_queue.put(("soledad", "close", None)) - -    def keymanager_list_keys(self): -        """ -        Signal a list of public keys locally stored. - -        Signals: -            keymanager_keys_list -> list -        """ -        self._call_queue.put(("keymanager", "list_keys", None)) - -    def keymanager_export_keys(self, username, filename): -        """ -        Export the given username's keys to a file. - -        :param username: the username whos keys we need to export. -        :type username: str -        :param filename: the name of the file where we want to save the keys. -        :type filename: str - -        Signals: -            keymanager_export_ok -            keymanager_export_error -        """ -        self._call_queue.put(("keymanager", "export_keys", None, -                              username, filename)) - -    def keymanager_get_key_details(self, username): -        """ -        Signal the given username's key details. - -        :param username: the username whos keys we need to get details. -        :type username: str - -        Signals: -            keymanager_key_details -        """ -        self._call_queue.put(("keymanager", "get_key_details", None, username)) - -    def smtp_start_service(self, full_user_id, download_if_needed=False): -        """ -        Start the SMTP service. - -        :param full_user_id: user id, in the form "user@provider" -        :type full_user_id: str -        :param download_if_needed: True if it should check for mtime -                                   for the file -        :type download_if_needed: bool -        """ -        self._call_queue.put(("mail", "start_smtp_service", None, -                              full_user_id, download_if_needed)) - -    def imap_start_service(self, full_user_id, offline=False): -        """ -        Start the IMAP service. - -        :param full_user_id: user id, in the form "user@provider" -        :type full_user_id: str -        :param offline: whether imap should start in offline mode or not. -        :type offline: bool -        """ -        self._call_queue.put(("mail", "start_imap_service", None, -                              full_user_id, offline)) - -    def smtp_stop_service(self): -        """ -        Stop the SMTP service. -        """ -        self._call_queue.put(("mail", "stop_smtp_service", None)) - -    def imap_stop_service(self): -        """ -        Stop imap service. - -        Signals: -            imap_stopped -        """ -        self._call_queue.put(("mail", "stop_imap_service", None)) diff --git a/src/leap/bitmask/backend/leapbackend.py b/src/leap/bitmask/backend/leapbackend.py new file mode 100644 index 00000000..3c5222f4 --- /dev/null +++ b/src/leap/bitmask/backend/leapbackend.py @@ -0,0 +1,636 @@ +# -*- coding: utf-8 -*- +# leapbackend.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/>. +""" +Backend for GUI/Logic communication. +""" +import logging + +from Queue import Queue, Empty + +from twisted.internet import reactor +from twisted.internet import threads, defer +from twisted.internet.task import LoopingCall + +import zope.interface +import zope.proxy + +from leap.bitmask.backend.leapsignaler import Signaler +from leap.bitmask.backend import components + +logger = logging.getLogger(__name__) + + +class Backend(object): +    """ +    Backend for everything, the UI should only use this class. +    """ + +    PASSED_KEY = "passed" +    ERROR_KEY = "error" + +    def __init__(self, bypass_checks=False): +        """ +        Constructor for the backend. +        """ +        # Components map for the commands received +        self._components = {} + +        # Ongoing defers that will be cancelled at stop time +        self._ongoing_defers = [] + +        # Signaler object to translate commands into Qt signals +        self._signaler = Signaler() + +        # Objects needed by several components, so we make a proxy and pass +        # them around +        self._soledad_proxy = zope.proxy.ProxyBase(None) +        self._keymanager_proxy = zope.proxy.ProxyBase(None) + +        # Component registration +        self._register(components.Provider(self._signaler, bypass_checks)) +        self._register(components.Register(self._signaler)) +        self._register(components.Authenticate(self._signaler)) +        self._register(components.EIP(self._signaler)) +        self._register(components.Soledad(self._soledad_proxy, +                                          self._keymanager_proxy, +                                          self._signaler)) +        self._register(components.Keymanager(self._keymanager_proxy, +                                             self._signaler)) +        self._register(components.Mail(self._soledad_proxy, +                                       self._keymanager_proxy, +                                       self._signaler)) + +        # We have a looping call on a thread executing all the +        # commands in queue. Right now this queue is an actual Queue +        # object, but it'll become the zmq recv_multipart queue +        self._lc = LoopingCall(threads.deferToThread, self._worker) + +        # Temporal call_queue for worker, will be replaced with +        # recv_multipart os something equivalent in the loopingcall +        self._call_queue = Queue() + +    @property +    def signaler(self): +        """ +        Public signaler access to let the UI connect to its signals. +        """ +        return self._signaler + +    def start(self): +        """ +        Starts the looping call +        """ +        logger.debug("Starting worker...") +        self._lc.start(0.01) + +    def stop(self): +        """ +        Stops the looping call and tries to cancel all the defers. +        """ +        reactor.callLater(2, self._stop) + +    def _stop(self): +        """ +        Delayed stopping of worker. Called from `stop`. +        """ +        logger.debug("Stopping worker...") +        if self._lc.running: +            self._lc.stop() +        else: +            logger.warning("Looping call is not running, cannot stop") + +        logger.debug("Cancelling ongoing defers...") +        while len(self._ongoing_defers) > 0: +            d = self._ongoing_defers.pop() +            d.cancel() +        logger.debug("Defers cancelled.") + +    def _register(self, component): +        """ +        Registers a component in this backend + +        :param component: Component to register +        :type component: any object that implements ILEAPComponent +        """ +        # TODO: assert that the component implements the interfaces +        # expected +        try: +            self._components[component.key] = component +        except Exception: +            logger.error("There was a problem registering %s" % (component,)) + +    def _signal_back(self, _, signal): +        """ +        Helper method to signal back (callback like behavior) to the +        UI that an operation finished. + +        :param signal: signal name +        :type signal: str +        """ +        self._signaler.signal(signal) + +    def _worker(self): +        """ +        Worker method, called from a different thread and as a part of +        a looping call +        """ +        try: +            # this'll become recv_multipart +            cmd = self._call_queue.get(block=False) + +            # cmd is: component, method, signalback, *args +            func = getattr(self._components[cmd[0]], cmd[1]) +            d = func(*cmd[3:]) +            if d is not None:  # d may be None if a defer chain is cancelled. +                # A call might not have a callback signal, but if it does, +                # we add it to the chain +                if cmd[2] is not None: +                    d.addCallbacks(self._signal_back, logger.error, cmd[2]) +                d.addCallbacks(self._done_action, logger.error, +                               callbackKeywords={"d": d}) +                d.addErrback(logger.error) +                self._ongoing_defers.append(d) +        except Empty: +            # If it's just empty we don't have anything to do. +            pass +        except defer.CancelledError: +            logger.debug("defer cancelled somewhere (CancelledError).") +        except Exception as e: +            # But we log the rest +            logger.exception("Unexpected exception: {0!r}".format(e)) + +    def _done_action(self, _, d): +        """ +        Remover of the defer once it's done + +        :param d: defer to remove +        :type d: twisted.internet.defer.Deferred +        """ +        if d in self._ongoing_defers: +            self._ongoing_defers.remove(d) + +    # XXX: Temporal interface until we migrate to zmq +    # We simulate the calls to zmq.send_multipart. Once we separate +    # this in two processes, the methods bellow can be changed to +    # send_multipart and this backend class will be really simple. + +    def provider_setup(self, provider): +        """ +        Initiate the setup for a provider. + +        :param provider: URL for the provider +        :type provider: unicode + +        Signals: +            prov_unsupported_client +            prov_unsupported_api +            prov_name_resolution        -> { PASSED_KEY: bool, ERROR_KEY: str } +            prov_https_connection       -> { PASSED_KEY: bool, ERROR_KEY: str } +            prov_download_provider_info -> { PASSED_KEY: bool, ERROR_KEY: str } +        """ +        self._call_queue.put(("provider", "setup_provider", None, provider)) + +    def provider_cancel_setup(self): +        """ +        Cancel the ongoing setup provider (if any). +        """ +        self._call_queue.put(("provider", "cancel_setup_provider", None)) + +    def provider_bootstrap(self, provider): +        """ +        Second stage of bootstrapping for a provider. + +        :param provider: URL for the provider +        :type provider: unicode + +        Signals: +            prov_problem_with_provider +            prov_download_ca_cert      -> {PASSED_KEY: bool, ERROR_KEY: str} +            prov_check_ca_fingerprint  -> {PASSED_KEY: bool, ERROR_KEY: str} +            prov_check_api_certificate -> {PASSED_KEY: bool, ERROR_KEY: str} +        """ +        self._call_queue.put(("provider", "bootstrap", None, provider)) + +    def provider_get_supported_services(self, domain): +        """ +        Signal a list of supported services provided by the given provider. + +        :param domain: the provider to get the services from. +        :type domain: str + +        Signals: +            prov_get_supported_services -> list of unicode +        """ +        self._call_queue.put(("provider", "get_supported_services", None, +                              domain)) + +    def provider_get_all_services(self, providers): +        """ +        Signal a list of services provided by all the configured providers. + +        :param providers: the list of providers to get the services. +        :type providers: list + +        Signals: +            prov_get_all_services -> list of unicode +        """ +        self._call_queue.put(("provider", "get_all_services", None, +                              providers)) + +    def provider_get_details(self, domain, lang): +        """ +        Signal a ProviderConfigLight object with the current ProviderConfig +        settings. + +        :param domain: the domain name of the provider. +        :type domain: str +        :param lang: the language to use for localized strings. +        :type lang: str + +        Signals: +            prov_get_details -> ProviderConfigLight +        """ +        self._call_queue.put(("provider", "get_details", None, domain, lang)) + +    def provider_get_pinned_providers(self): +        """ +        Signal the pinned providers. + +        Signals: +            prov_get_pinned_providers -> list of provider domains +        """ +        self._call_queue.put(("provider", "get_pinned_providers", None)) + +    def user_register(self, provider, username, password): +        """ +        Register a user using the domain and password given as parameters. + +        :param domain: the domain we need to register the user. +        :type domain: unicode +        :param username: the user name +        :type username: unicode +        :param password: the password for the username +        :type password: unicode + +        Signals: +            srp_registration_finished +            srp_registration_taken +            srp_registration_failed +        """ +        self._call_queue.put(("register", "register_user", None, provider, +                              username, password)) + +    def eip_setup(self, provider, skip_network=False): +        """ +        Initiate the setup for a provider + +        :param provider: URL for the provider +        :type provider: unicode +        :param skip_network: Whether checks that involve network should be done +                             or not +        :type skip_network: bool + +        Signals: +            eip_config_ready             -> {PASSED_KEY: bool, ERROR_KEY: str} +            eip_client_certificate_ready -> {PASSED_KEY: bool, ERROR_KEY: str} +            eip_cancelled_setup +        """ +        self._call_queue.put(("eip", "setup_eip", None, provider, +                              skip_network)) + +    def eip_cancel_setup(self): +        """ +        Cancel the ongoing setup EIP (if any). +        """ +        self._call_queue.put(("eip", "cancel_setup_eip", None)) + +    def eip_start(self, restart=False): +        """ +        Start the EIP service. + +        Signals: +            backend_bad_call +            eip_alien_openvpn_already_running +            eip_connected +            eip_connection_aborted +            eip_network_unreachable +            eip_no_pkexec_error +            eip_no_polkit_agent_error +            eip_no_tun_kext_error +            eip_openvpn_already_running +            eip_openvpn_not_found_error +            eip_process_finished +            eip_process_restart_ping +            eip_process_restart_tls +            eip_state_changed -> str +            eip_status_changed -> tuple of str (download, upload) +            eip_vpn_launcher_exception + +        :param restart: whether is is a restart. +        :type restart: bool +        """ +        self._call_queue.put(("eip", "start", None, restart)) + +    def eip_stop(self, shutdown=False, restart=False, failed=False): +        """ +        Stop the EIP service. + +        :param shutdown: whether this is the final shutdown. +        :type shutdown: bool + +        :param restart: whether this is part of a restart. +        :type restart: bool +        """ +        self._call_queue.put(("eip", "stop", None, shutdown, restart)) + +    def eip_terminate(self): +        """ +        Terminate the EIP service, not necessarily in a nice way. +        """ +        self._call_queue.put(("eip", "terminate", None)) + +    def eip_get_gateways_list(self, domain): +        """ +        Signal a list of gateways for the given provider. + +        :param domain: the domain to get the gateways. +        :type domain: str + +        # TODO discuss how to document the expected result object received of +        # the signal +        :signal type: list of str + +        Signals: +            eip_get_gateways_list -> list of unicode +            eip_get_gateways_list_error +            eip_uninitialized_provider +        """ +        self._call_queue.put(("eip", "get_gateways_list", None, domain)) + +    def eip_get_initialized_providers(self, domains): +        """ +        Signal a list of the given domains and if they are initialized or not. + +        :param domains: the list of domains to check. +        :type domain: list of str + +        Signals: +            eip_get_initialized_providers -> list of tuple(unicode, bool) + +        """ +        self._call_queue.put(("eip", "get_initialized_providers", +                              None, domains)) + +    def eip_can_start(self, domain): +        """ +        Signal whether it has everything that is needed to run EIP or not + +        :param domain: the domain for the provider to check +        :type domain: str + +        Signals: +            eip_can_start +            eip_cannot_start +        """ +        self._call_queue.put(("eip", "can_start", +                              None, domain)) + +    def eip_check_dns(self, domain): +        """ +        Check if we can resolve the given domain name. + +        :param domain: the domain for the provider to check +        :type domain: str + +        Signals: +            eip_dns_ok +            eip_dns_error +        """ +        self._call_queue.put(("eip", "check_dns", None, domain)) + +    def tear_fw_down(self): +        """ +        Signal the need to tear the fw down. +        """ +        self._call_queue.put(("eip", "tear_fw_down", None)) + +    def user_login(self, provider, username, password): +        """ +        Execute the whole authentication process for a user + +        :param domain: the domain where we need to authenticate. +        :type domain: unicode +        :param username: username for this session +        :type username: str +        :param password: password for this user +        :type password: str + +        Signals: +            srp_auth_error +            srp_auth_ok +            srp_auth_bad_user_or_password +            srp_auth_server_error +            srp_auth_connection_error +            srp_auth_error +        """ +        self._call_queue.put(("authenticate", "login", None, provider, +                              username, password)) + +    def user_logout(self): +        """ +        Log out the current session. + +        Signals: +            srp_logout_ok +            srp_logout_error +            srp_not_logged_in_error +        """ +        self._call_queue.put(("authenticate", "logout", None)) + +    def user_cancel_login(self): +        """ +        Cancel the ongoing login (if any). +        """ +        self._call_queue.put(("authenticate", "cancel_login", None)) + +    def user_change_password(self, current_password, new_password): +        """ +        Change the user's password. + +        :param current_password: the current password of the user. +        :type current_password: str +        :param new_password: the new password for the user. +        :type new_password: str + +        Signals: +            srp_not_logged_in_error +            srp_password_change_ok +            srp_password_change_badpw +            srp_password_change_error +        """ +        self._call_queue.put(("authenticate", "change_password", None, +                              current_password, new_password)) + +    def soledad_change_password(self, new_password): +        """ +        Change the database's password. + +        :param new_password: the new password for the user. +        :type new_password: unicode + +        Signals: +            srp_not_logged_in_error +            srp_password_change_ok +            srp_password_change_badpw +            srp_password_change_error +        """ +        self._call_queue.put(("soledad", "change_password", None, +                              new_password)) + +    def user_get_logged_in_status(self): +        """ +        Signal if the user is currently logged in or not. + +        Signals: +            srp_status_logged_in +            srp_status_not_logged_in +        """ +        self._call_queue.put(("authenticate", "get_logged_in_status", None)) + +    def soledad_bootstrap(self, username, domain, password): +        """ +        Bootstrap the soledad database. + +        :param username: the user name +        :type username: unicode +        :param domain: the domain that we are using. +        :type domain: unicode +        :param password: the password for the username +        :type password: unicode + +        Signals: +            soledad_bootstrap_finished +            soledad_bootstrap_failed +            soledad_invalid_auth_token +        """ +        self._call_queue.put(("soledad", "bootstrap", None, +                              username, domain, password)) + +    def soledad_load_offline(self, username, password, uuid): +        """ +        Load the soledad database in offline mode. + +        :param username: full user id (user@provider) +        :type username: str or unicode +        :param password: the soledad passphrase +        :type password: unicode +        :param uuid: the user uuid +        :type uuid: str or unicode + +        Signals: +        """ +        self._call_queue.put(("soledad", "load_offline", None, +                              username, password, uuid)) + +    def soledad_cancel_bootstrap(self): +        """ +        Cancel the ongoing soledad bootstrapping process (if any). +        """ +        self._call_queue.put(("soledad", "cancel_bootstrap", None)) + +    def soledad_close(self): +        """ +        Close soledad database. +        """ +        self._call_queue.put(("soledad", "close", None)) + +    def keymanager_list_keys(self): +        """ +        Signal a list of public keys locally stored. + +        Signals: +            keymanager_keys_list -> list +        """ +        self._call_queue.put(("keymanager", "list_keys", None)) + +    def keymanager_export_keys(self, username, filename): +        """ +        Export the given username's keys to a file. + +        :param username: the username whos keys we need to export. +        :type username: str +        :param filename: the name of the file where we want to save the keys. +        :type filename: str + +        Signals: +            keymanager_export_ok +            keymanager_export_error +        """ +        self._call_queue.put(("keymanager", "export_keys", None, +                              username, filename)) + +    def keymanager_get_key_details(self, username): +        """ +        Signal the given username's key details. + +        :param username: the username whos keys we need to get details. +        :type username: str + +        Signals: +            keymanager_key_details +        """ +        self._call_queue.put(("keymanager", "get_key_details", None, username)) + +    def smtp_start_service(self, full_user_id, download_if_needed=False): +        """ +        Start the SMTP service. + +        :param full_user_id: user id, in the form "user@provider" +        :type full_user_id: str +        :param download_if_needed: True if it should check for mtime +                                   for the file +        :type download_if_needed: bool +        """ +        self._call_queue.put(("mail", "start_smtp_service", None, +                              full_user_id, download_if_needed)) + +    def imap_start_service(self, full_user_id, offline=False): +        """ +        Start the IMAP service. + +        :param full_user_id: user id, in the form "user@provider" +        :type full_user_id: str +        :param offline: whether imap should start in offline mode or not. +        :type offline: bool +        """ +        self._call_queue.put(("mail", "start_imap_service", None, +                              full_user_id, offline)) + +    def smtp_stop_service(self): +        """ +        Stop the SMTP service. +        """ +        self._call_queue.put(("mail", "stop_smtp_service", None)) + +    def imap_stop_service(self): +        """ +        Stop imap service. + +        Signals: +            imap_stopped +        """ +        self._call_queue.put(("mail", "stop_imap_service", None)) diff --git a/src/leap/bitmask/backend/leapsignaler.py b/src/leap/bitmask/backend/leapsignaler.py new file mode 100644 index 00000000..da8908fd --- /dev/null +++ b/src/leap/bitmask/backend/leapsignaler.py @@ -0,0 +1,385 @@ +# -*- coding: utf-8 -*- +# components.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/>. +""" +Signaler for Backend/Frontend communication. +""" +import logging + +from PySide import QtCore + +logger = logging.getLogger(__name__) + + +class Signaler(QtCore.QObject): +    """ +    Signaler object, handles converting string commands to Qt signals. + +    This is intended for the separation in frontend/backend, this will +    live in the frontend. +    """ + +    #################### +    # These will only exist in the frontend +    # Signals for the ProviderBootstrapper +    prov_name_resolution = QtCore.Signal(object) +    prov_https_connection = QtCore.Signal(object) +    prov_download_provider_info = QtCore.Signal(object) + +    prov_download_ca_cert = QtCore.Signal(object) +    prov_check_ca_fingerprint = QtCore.Signal(object) +    prov_check_api_certificate = QtCore.Signal(object) + +    prov_problem_with_provider = QtCore.Signal(object) + +    prov_unsupported_client = QtCore.Signal(object) +    prov_unsupported_api = QtCore.Signal(object) + +    prov_get_all_services = QtCore.Signal(object) +    prov_get_supported_services = QtCore.Signal(object) +    prov_get_details = QtCore.Signal(object) +    prov_get_pinned_providers = QtCore.Signal(object) + +    prov_cancelled_setup = QtCore.Signal(object) + +    # Signals for SRPRegister +    srp_registration_finished = QtCore.Signal(object) +    srp_registration_failed = QtCore.Signal(object) +    srp_registration_taken = QtCore.Signal(object) + +    # Signals for EIP bootstrapping +    eip_config_ready = QtCore.Signal(object) +    eip_client_certificate_ready = QtCore.Signal(object) + +    eip_cancelled_setup = QtCore.Signal(object) + +    # Signals for SRPAuth +    srp_auth_ok = QtCore.Signal(object) +    srp_auth_error = QtCore.Signal(object) +    srp_auth_server_error = QtCore.Signal(object) +    srp_auth_connection_error = QtCore.Signal(object) +    srp_auth_bad_user_or_password = QtCore.Signal(object) +    srp_logout_ok = QtCore.Signal(object) +    srp_logout_error = QtCore.Signal(object) +    srp_password_change_ok = QtCore.Signal(object) +    srp_password_change_error = QtCore.Signal(object) +    srp_password_change_badpw = QtCore.Signal(object) +    srp_not_logged_in_error = QtCore.Signal(object) +    srp_status_logged_in = QtCore.Signal(object) +    srp_status_not_logged_in = QtCore.Signal(object) + +    # Signals for EIP +    eip_connected = QtCore.Signal(object) +    eip_disconnected = QtCore.Signal(object) +    eip_connection_died = QtCore.Signal(object) +    eip_connection_aborted = QtCore.Signal(object) +    eip_stopped = QtCore.Signal(object) + +    eip_dns_ok = QtCore.Signal(object) +    eip_dns_error = QtCore.Signal(object) + +    # EIP problems +    eip_no_polkit_agent_error = QtCore.Signal(object) +    eip_no_tun_kext_error = QtCore.Signal(object) +    eip_no_pkexec_error = QtCore.Signal(object) +    eip_openvpn_not_found_error = QtCore.Signal(object) +    eip_openvpn_already_running = QtCore.Signal(object) +    eip_alien_openvpn_already_running = QtCore.Signal(object) +    eip_vpn_launcher_exception = QtCore.Signal(object) + +    eip_get_gateways_list = QtCore.Signal(object) +    eip_get_gateways_list_error = QtCore.Signal(object) +    eip_uninitialized_provider = QtCore.Signal(object) +    eip_get_initialized_providers = QtCore.Signal(object) + +    # signals from parsing openvpn output +    eip_network_unreachable = QtCore.Signal(object) +    eip_process_restart_tls = QtCore.Signal(object) +    eip_process_restart_ping = QtCore.Signal(object) + +    # signals from vpnprocess.py +    eip_state_changed = QtCore.Signal(dict) +    eip_status_changed = QtCore.Signal(dict) +    eip_process_finished = QtCore.Signal(int) +    eip_tear_fw_down = QtCore.Signal(object) + +    # signals whether the needed files to start EIP exist or not +    eip_can_start = QtCore.Signal(object) +    eip_cannot_start = QtCore.Signal(object) + +    # Signals for Soledad +    soledad_bootstrap_failed = QtCore.Signal(object) +    soledad_bootstrap_finished = QtCore.Signal(object) +    soledad_offline_failed = QtCore.Signal(object) +    soledad_offline_finished = QtCore.Signal(object) +    soledad_invalid_auth_token = QtCore.Signal(object) +    soledad_cancelled_bootstrap = QtCore.Signal(object) +    soledad_password_change_ok = QtCore.Signal(object) +    soledad_password_change_error = QtCore.Signal(object) + +    # Keymanager signals +    keymanager_export_ok = QtCore.Signal(object) +    keymanager_export_error = QtCore.Signal(object) +    keymanager_keys_list = QtCore.Signal(object) + +    keymanager_import_ioerror = QtCore.Signal(object) +    keymanager_import_datamismatch = QtCore.Signal(object) +    keymanager_import_missingkey = QtCore.Signal(object) +    keymanager_import_addressmismatch = QtCore.Signal(object) +    keymanager_import_ok = QtCore.Signal(object) + +    keymanager_key_details = QtCore.Signal(object) + +    # mail related signals +    imap_stopped = QtCore.Signal(object) + +    # This signal is used to warn the backend user that is doing something +    # wrong +    backend_bad_call = QtCore.Signal(object) + +    #################### +    # These will exist both in the backend AND the front end. +    # The frontend might choose to not "interpret" all the signals +    # from the backend, but the backend needs to have all the signals +    # it's going to emit defined here +    PROV_NAME_RESOLUTION_KEY = "prov_name_resolution" +    PROV_HTTPS_CONNECTION_KEY = "prov_https_connection" +    PROV_DOWNLOAD_PROVIDER_INFO_KEY = "prov_download_provider_info" +    PROV_DOWNLOAD_CA_CERT_KEY = "prov_download_ca_cert" +    PROV_CHECK_CA_FINGERPRINT_KEY = "prov_check_ca_fingerprint" +    PROV_CHECK_API_CERTIFICATE_KEY = "prov_check_api_certificate" +    PROV_PROBLEM_WITH_PROVIDER_KEY = "prov_problem_with_provider" +    PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client" +    PROV_UNSUPPORTED_API = "prov_unsupported_api" +    PROV_CANCELLED_SETUP = "prov_cancelled_setup" +    PROV_GET_ALL_SERVICES = "prov_get_all_services" +    PROV_GET_SUPPORTED_SERVICES = "prov_get_supported_services" +    PROV_GET_DETAILS = "prov_get_details" +    PROV_GET_PINNED_PROVIDERS = "prov_get_pinned_providers" + +    SRP_REGISTRATION_FINISHED = "srp_registration_finished" +    SRP_REGISTRATION_FAILED = "srp_registration_failed" +    SRP_REGISTRATION_TAKEN = "srp_registration_taken" +    SRP_AUTH_OK = "srp_auth_ok" +    SRP_AUTH_ERROR = "srp_auth_error" +    SRP_AUTH_SERVER_ERROR = "srp_auth_server_error" +    SRP_AUTH_CONNECTION_ERROR = "srp_auth_connection_error" +    SRP_AUTH_BAD_USER_OR_PASSWORD = "srp_auth_bad_user_or_password" +    SRP_LOGOUT_OK = "srp_logout_ok" +    SRP_LOGOUT_ERROR = "srp_logout_error" +    SRP_PASSWORD_CHANGE_OK = "srp_password_change_ok" +    SRP_PASSWORD_CHANGE_ERROR = "srp_password_change_error" +    SRP_PASSWORD_CHANGE_BADPW = "srp_password_change_badpw" +    SRP_NOT_LOGGED_IN_ERROR = "srp_not_logged_in_error" +    SRP_STATUS_LOGGED_IN = "srp_status_logged_in" +    SRP_STATUS_NOT_LOGGED_IN = "srp_status_not_logged_in" + +    EIP_CONFIG_READY = "eip_config_ready" +    EIP_CLIENT_CERTIFICATE_READY = "eip_client_certificate_ready" +    EIP_CANCELLED_SETUP = "eip_cancelled_setup" + +    EIP_CONNECTED = "eip_connected" +    EIP_DISCONNECTED = "eip_disconnected" +    EIP_CONNECTION_DIED = "eip_connection_died" +    EIP_CONNECTION_ABORTED = "eip_connection_aborted" +    EIP_STOPPED = "eip_stopped" + +    EIP_NO_POLKIT_AGENT_ERROR = "eip_no_polkit_agent_error" +    EIP_NO_TUN_KEXT_ERROR = "eip_no_tun_kext_error" +    EIP_NO_PKEXEC_ERROR = "eip_no_pkexec_error" +    EIP_OPENVPN_NOT_FOUND_ERROR = "eip_openvpn_not_found_error" +    EIP_OPENVPN_ALREADY_RUNNING = "eip_openvpn_already_running" +    EIP_ALIEN_OPENVPN_ALREADY_RUNNING = "eip_alien_openvpn_already_running" +    EIP_VPN_LAUNCHER_EXCEPTION = "eip_vpn_launcher_exception" + +    EIP_GET_GATEWAYS_LIST = "eip_get_gateways_list" +    EIP_GET_GATEWAYS_LIST_ERROR = "eip_get_gateways_list_error" +    EIP_UNINITIALIZED_PROVIDER = "eip_uninitialized_provider" +    EIP_GET_INITIALIZED_PROVIDERS = "eip_get_initialized_providers" + +    EIP_NETWORK_UNREACHABLE = "eip_network_unreachable" +    EIP_PROCESS_RESTART_TLS = "eip_process_restart_tls" +    EIP_PROCESS_RESTART_PING = "eip_process_restart_ping" + +    EIP_STATE_CHANGED = "eip_state_changed" +    EIP_STATUS_CHANGED = "eip_status_changed" +    EIP_PROCESS_FINISHED = "eip_process_finished" +    EIP_TEAR_FW_DOWN = "eip_tear_fw_down" + +    EIP_CAN_START = "eip_can_start" +    EIP_CANNOT_START = "eip_cannot_start" + +    EIP_DNS_OK = "eip_dns_ok" +    EIP_DNS_ERROR = "eip_dns_error" + +    SOLEDAD_BOOTSTRAP_FAILED = "soledad_bootstrap_failed" +    SOLEDAD_BOOTSTRAP_FINISHED = "soledad_bootstrap_finished" +    SOLEDAD_OFFLINE_FAILED = "soledad_offline_failed" +    SOLEDAD_OFFLINE_FINISHED = "soledad_offline_finished" +    SOLEDAD_INVALID_AUTH_TOKEN = "soledad_invalid_auth_token" + +    SOLEDAD_PASSWORD_CHANGE_OK = "soledad_password_change_ok" +    SOLEDAD_PASSWORD_CHANGE_ERROR = "soledad_password_change_error" + +    SOLEDAD_CANCELLED_BOOTSTRAP = "soledad_cancelled_bootstrap" + +    KEYMANAGER_EXPORT_OK = "keymanager_export_ok" +    KEYMANAGER_EXPORT_ERROR = "keymanager_export_error" +    KEYMANAGER_KEYS_LIST = "keymanager_keys_list" + +    KEYMANAGER_IMPORT_IOERROR = "keymanager_import_ioerror" +    KEYMANAGER_IMPORT_DATAMISMATCH = "keymanager_import_datamismatch" +    KEYMANAGER_IMPORT_MISSINGKEY = "keymanager_import_missingkey" +    KEYMANAGER_IMPORT_ADDRESSMISMATCH = "keymanager_import_addressmismatch" +    KEYMANAGER_IMPORT_OK = "keymanager_import_ok" +    KEYMANAGER_KEY_DETAILS = "keymanager_key_details" + +    IMAP_STOPPED = "imap_stopped" + +    BACKEND_BAD_CALL = "backend_bad_call" + +    def __init__(self): +        """ +        Constructor for the Signaler +        """ +        QtCore.QObject.__init__(self) +        self._signals = {} + +        signals = [ +            self.PROV_NAME_RESOLUTION_KEY, +            self.PROV_HTTPS_CONNECTION_KEY, +            self.PROV_DOWNLOAD_PROVIDER_INFO_KEY, +            self.PROV_DOWNLOAD_CA_CERT_KEY, +            self.PROV_CHECK_CA_FINGERPRINT_KEY, +            self.PROV_CHECK_API_CERTIFICATE_KEY, +            self.PROV_PROBLEM_WITH_PROVIDER_KEY, +            self.PROV_UNSUPPORTED_CLIENT, +            self.PROV_UNSUPPORTED_API, +            self.PROV_CANCELLED_SETUP, +            self.PROV_GET_ALL_SERVICES, +            self.PROV_GET_SUPPORTED_SERVICES, +            self.PROV_GET_DETAILS, +            self.PROV_GET_PINNED_PROVIDERS, + +            self.SRP_REGISTRATION_FINISHED, +            self.SRP_REGISTRATION_FAILED, +            self.SRP_REGISTRATION_TAKEN, + +            self.EIP_CONFIG_READY, +            self.EIP_CLIENT_CERTIFICATE_READY, +            self.EIP_CANCELLED_SETUP, + +            self.EIP_CONNECTED, +            self.EIP_DISCONNECTED, +            self.EIP_CONNECTION_DIED, +            self.EIP_CONNECTION_ABORTED, +            self.EIP_STOPPED, + +            self.EIP_NO_POLKIT_AGENT_ERROR, +            self.EIP_NO_TUN_KEXT_ERROR, +            self.EIP_NO_PKEXEC_ERROR, +            self.EIP_OPENVPN_NOT_FOUND_ERROR, +            self.EIP_OPENVPN_ALREADY_RUNNING, +            self.EIP_ALIEN_OPENVPN_ALREADY_RUNNING, +            self.EIP_VPN_LAUNCHER_EXCEPTION, + +            self.EIP_GET_GATEWAYS_LIST, +            self.EIP_GET_GATEWAYS_LIST_ERROR, +            self.EIP_UNINITIALIZED_PROVIDER, +            self.EIP_GET_INITIALIZED_PROVIDERS, + +            self.EIP_NETWORK_UNREACHABLE, +            self.EIP_PROCESS_RESTART_TLS, +            self.EIP_PROCESS_RESTART_PING, + +            self.EIP_STATE_CHANGED, +            self.EIP_STATUS_CHANGED, +            self.EIP_PROCESS_FINISHED, + +            self.EIP_CAN_START, +            self.EIP_CANNOT_START, + +            self.EIP_DNS_OK, +            self.EIP_DNS_ERROR, + +            self.SRP_AUTH_OK, +            self.SRP_AUTH_ERROR, +            self.SRP_AUTH_SERVER_ERROR, +            self.SRP_AUTH_CONNECTION_ERROR, +            self.SRP_AUTH_BAD_USER_OR_PASSWORD, +            self.SRP_LOGOUT_OK, +            self.SRP_LOGOUT_ERROR, +            self.SRP_PASSWORD_CHANGE_OK, +            self.SRP_PASSWORD_CHANGE_ERROR, +            self.SRP_PASSWORD_CHANGE_BADPW, +            self.SRP_NOT_LOGGED_IN_ERROR, +            self.SRP_STATUS_LOGGED_IN, +            self.SRP_STATUS_NOT_LOGGED_IN, + +            self.SOLEDAD_BOOTSTRAP_FAILED, +            self.SOLEDAD_BOOTSTRAP_FINISHED, +            self.SOLEDAD_OFFLINE_FAILED, +            self.SOLEDAD_OFFLINE_FINISHED, +            self.SOLEDAD_INVALID_AUTH_TOKEN, +            self.SOLEDAD_CANCELLED_BOOTSTRAP, + +            self.SOLEDAD_PASSWORD_CHANGE_OK, +            self.SOLEDAD_PASSWORD_CHANGE_ERROR, + +            self.KEYMANAGER_EXPORT_OK, +            self.KEYMANAGER_EXPORT_ERROR, +            self.KEYMANAGER_KEYS_LIST, + +            self.KEYMANAGER_IMPORT_IOERROR, +            self.KEYMANAGER_IMPORT_DATAMISMATCH, +            self.KEYMANAGER_IMPORT_MISSINGKEY, +            self.KEYMANAGER_IMPORT_ADDRESSMISMATCH, +            self.KEYMANAGER_IMPORT_OK, +            self.KEYMANAGER_KEY_DETAILS, + +            self.IMAP_STOPPED, + +            self.BACKEND_BAD_CALL, +        ] + +        for sig in signals: +            self._signals[sig] = getattr(self, sig) + +    def signal(self, key, data=None): +        """ +        Emits a Qt signal based on the key provided, with the data if provided. + +        :param key: string identifying the signal to emit +        :type key: str +        :param data: object to send with the data +        :type data: object + +        NOTE: The data object will be a serialized str in the backend, +        and an unserialized object in the frontend, but for now we +        just care about objects. +        """ +        # Right now it emits Qt signals. The backend version of this +        # will do zmq.send_multipart, and the frontend version will be +        # similar to this + +        # for some reason emitting 'None' gives a segmentation fault. +        if data is None: +            data = '' + +        try: +            self._signals[key].emit(data) +        except KeyError: +            logger.error("Unknown key for signal %s!" % (key,)) diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py index cf31b3b2..7b979e61 100644 --- a/src/leap/bitmask/config/providerconfig.py +++ b/src/leap/bitmask/config/providerconfig.py @@ -38,35 +38,6 @@ class MissingCACert(Exception):      pass -class ProviderConfigLight(object): -    """ -    A light config object to hold some provider settings needed by the GUI. -    """ -    def __init__(self): -        """ -        Define the public attributes. -        """ -        self.domain = "" -        self.name = "" -        self.description = "" -        self.enrollment_policy = "" -        self.services = [] - -    @property -    def services_string(self): -        """ -        Return a comma separated list of serices provided by this provider. - -        :rtype: str -        """ -        services = [] -        for service in self.services: -            services.append(get_service_display_name(service)) - -        services_str = ", ".join(services) -        return services_str - -  class ProviderConfig(BaseConfig):      """      Provider configuration abstraction class @@ -76,24 +47,32 @@ class ProviderConfig(BaseConfig):      def get_light_config(self, domain, lang=None):          """ -        Return a ProviderConfigLight object with the data for the loaded -        object. +        Return a dict with the data for the loaded object.          :param domain: the domain name of the provider.          :type domain: str          :param lang: the language to use for localized strings.          :type lang: str -        :rtype: ProviderConfigLight or None if the ProviderConfig isn't loaded. +        :rtype: dict or None if the ProviderConfig isn't loaded.          """          config = self.get_provider_config(domain) -        details = ProviderConfigLight() -        details.domain = config.get_domain() -        details.name = config.get_name(lang=lang) -        details.description = config.get_description(lang=lang) -        details.enrollment_policy = config.get_enrollment_policy() -        details.services = config.get_services() +        if config is None: +            return + +        details = {} +        details["domain"] = config.get_domain() +        details["name"] = config.get_name(lang=lang) +        details["description"] = config.get_description(lang=lang) +        details["enrollment_policy"] = config.get_enrollment_policy() +        details["services"] = config.get_services() + +        services = [] +        for service in config.get_services(): +            services.append(get_service_display_name(service)) + +        details['services_string'] = ", ".join(services)          return details diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 8b9f2d44..bd569343 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -88,6 +88,8 @@ class EIPStatusWidget(QtGui.QWidget):          self.is_restart = False          self.is_cold_start = True +        self.missing_helpers = False +          # Action for the systray          self._eip_disabled_action = QtGui.QAction(              "{0} is {1}".format(self._service_name, self.tr("disabled")), self) @@ -298,7 +300,12 @@ class EIPStatusWidget(QtGui.QWidget):          # probably the best thing would be to make a conditional          # transition there, but that's more involved.          self.eip_button.hide() -        msg = self.tr("You must login to use {0}".format(self._service_name)) +        if self.missing_helpers: +            msg = self.tr( +                "<font color=red>Disabled: missing helper files</font>") +        else: +            msg = self.tr( +                "You must login to use {0}".format(self._service_name))          self.eip_label.setText(msg)          self._eip_status_menu.setTitle("{0} is {1}".format(              self._service_name, self.tr("disabled"))) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 3ef994b1..53a7d95a 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -18,12 +18,10 @@  Main window for Bitmask.  """  import logging -import socket  from datetime import datetime  from PySide import QtCore, QtGui -from twisted.internet import reactor, threads  from leap.bitmask import __version__ as VERSION  from leap.bitmask import __version_hash__ as VERSION_HASH @@ -39,11 +37,13 @@ from leap.bitmask.gui.mail_status import MailStatusWidget  from leap.bitmask.gui.preferenceswindow import PreferencesWindow  from leap.bitmask.gui.systray import SysTray  from leap.bitmask.gui.wizard import Wizard +from leap.bitmask.gui import twisted_main  from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX  from leap.bitmask.platform_init.initializers import init_platform +from leap.bitmask.platform_init.initializers import init_signals -from leap.bitmask import backend +from leap.bitmask.backend import leapbackend  from leap.bitmask.services.eip import conductor as eip_conductor  from leap.bitmask.services.mail import conductor as mail_conductor @@ -89,16 +89,12 @@ class MainWindow(QtGui.QMainWindow):      EIP_START_TIMEOUT = 60000  # in milliseconds      # We give the services some time to a halt before forcing quit. -    SERVICES_STOP_TIMEOUT = 20 +    SERVICES_STOP_TIMEOUT = 20000  # in milliseconds -    def __init__(self, quit_callback, bypass_checks=False, start_hidden=False): +    def __init__(self, bypass_checks=False, start_hidden=False):          """          Constructor for the client main window -        :param quit_callback: Function to be called when closing -                              the application. -        :type quit_callback: callable -          :param bypass_checks: Set to true if the app should bypass first round                                of checks for CA certificates at bootstrap          :type bypass_checks: bool @@ -117,14 +113,13 @@ class MainWindow(QtGui.QMainWindow):                   reqcbk=lambda req, resp: None)  # make rpc call async          # end register leap events #################################### -        self._quit_callback = quit_callback          self._updates_content = ""          # setup UI          self.ui = Ui_MainWindow()          self.ui.setupUi(self)          self.menuBar().setNativeMenuBar(not IS_LINUX) -        self._backend = backend.Backend(bypass_checks) +        self._backend = leapbackend.Backend(bypass_checks)          self._backend.start()          self._settings = LeapSettings() @@ -152,6 +147,9 @@ class MainWindow(QtGui.QMainWindow):              self._settings, self._backend)          self._eip_status = EIPStatusWidget(self, self._eip_conductor) +        init_signals.eip_missing_helpers.connect( +            self._disable_eip_missing_helpers) +          self.ui.eipLayout.addWidget(self._eip_status)          self._eip_conductor.add_eip_widget(self._eip_status) @@ -181,8 +179,8 @@ class MainWindow(QtGui.QMainWindow):          # Set used to track the services being stopped and need wait.          self._services_being_stopped = {} -        # timeout object used to trigger quit -        self._quit_timeout_callater = None +        # used to know if we are in the final steps of quitting +        self._finally_quitting = False          self._backend_connected_signals = []          self._backend_connect() @@ -407,6 +405,8 @@ class MainWindow(QtGui.QMainWindow):          sig.eip_can_start.connect(self._backend_can_start_eip)          sig.eip_cannot_start.connect(self._backend_cannot_start_eip) +        sig.eip_dns_error.connect(self._eip_dns_error) +          # ==================================================================          # Soledad signals @@ -553,7 +553,7 @@ class MainWindow(QtGui.QMainWindow):          details = self._provider_details          mx_provided = False          if details is not None: -            mx_provided = MX_SERVICE in details.services +            mx_provided = MX_SERVICE in details['services']          # XXX: handle differently not logged in user?          akm = AdvancedKeyManagement(self, mx_provided, logged_user, @@ -573,7 +573,7 @@ class MainWindow(QtGui.QMainWindow):          domain = self._login_widget.get_selected_provider()          mx_provided = False          if self._provider_details is not None: -            mx_provided = MX_SERVICE in self._provider_details.services +            mx_provided = MX_SERVICE in self._provider_details['services']          preferences = PreferencesWindow(self, user, domain, self._backend,                                          self._soledad_started, mx_provided) @@ -667,6 +667,16 @@ class MainWindow(QtGui.QMainWindow):              self._eip_status.set_eip_status(self.tr("Disabled"))      @QtCore.Slot() +    def _disable_eip_missing_helpers(self): +        """ +        TRIGGERS: +            init_signals.missing_helpers + +        Set the missing_helpers flag, so we can disable EIP. +        """ +        self._eip_status.missing_helpers = True + +    @QtCore.Slot()      def _show_eip_preferences(self):          """          TRIGGERS: @@ -901,7 +911,7 @@ class MainWindow(QtGui.QMainWindow):                  self.tr('Hello!'),                  self.tr('Bitmask has started in the tray.'))              # we wait for the systray to be ready -            reactor.callLater(1, hello) +            QtDelayedCall(1000, hello)      @QtCore.Slot(int)      def _tray_activated(self, reason=None): @@ -1271,7 +1281,7 @@ class MainWindow(QtGui.QMainWindow):              sig.soledad_bootstrap_failed.connect(lambda: btn_enabled(True))              sig.soledad_bootstrap_finished.connect(lambda: btn_enabled(True)) -        if not MX_SERVICE in self._provider_details.services: +        if not MX_SERVICE in self._provider_details['services']:              self._set_mx_visible(False)      def _start_eip_bootstrap(self): @@ -1314,7 +1324,7 @@ class MainWindow(QtGui.QMainWindow):          Set the details for the just downloaded provider.          :param details: the details of the provider. -        :type details: ProviderConfigLight +        :type details: dict          """          self._provider_details = details @@ -1331,7 +1341,7 @@ class MainWindow(QtGui.QMainWindow):          mx_enabled = MX_SERVICE in enabled_services          mx_provided = False          if self._provider_details is not None: -            mx_provided = MX_SERVICE in self._provider_details.services +            mx_provided = MX_SERVICE in self._provider_details['services']          return mx_enabled and mx_provided @@ -1348,7 +1358,7 @@ class MainWindow(QtGui.QMainWindow):          eip_enabled = EIP_SERVICE in enabled_services          eip_provided = False          if self._provider_details is not None: -            eip_provided = EIP_SERVICE in self._provider_details.services +            eip_provided = EIP_SERVICE in self._provider_details['services']          return eip_enabled and eip_provided @@ -1465,55 +1475,25 @@ class MainWindow(QtGui.QMainWindow):          self._already_started_eip = True          # check for connectivity -        # we might want to leave a little time here... -        self._check_name_resolution(domain) - -    def _check_name_resolution(self, domain): -        # FIXME this has to be moved to backend !!! -        # Should move to netchecks module. -        # and separate qt from reactor... -        """ -        Check if we can resolve the given domain name. - -        :param domain: the domain to check. -        :type domain: str -        """ -        def do_check(): -            """ -            Try to resolve the domain name. -            """ -            socket.gethostbyname(domain.encode('idna')) - -        def check_err(failure): -            """ -            Errback handler for `do_check`. - -            :param failure: the failure that triggered the errback. -            :type failure: twisted.python.failure.Failure -            """ -            logger.error(repr(failure)) -            logger.error("Can't resolve hostname.") - -            msg = self.tr( -                "The server at {0} can't be found, because the DNS lookup " -                "failed. DNS is the network service that translates a " -                "website's name to its Internet address. Either your computer " -                "is having trouble connecting to the network, or you are " -                "missing some helper files that are needed to securely use " -                "DNS while {1} is active. To install these helper files, quit " -                "this application and start it again." -            ).format(domain, self._eip_conductor.eip_name) - -            show_err = lambda: QtGui.QMessageBox.critical( -                self, self.tr("Connection Error"), msg) -            reactor.callLater(0, show_err) - -            # python 2.7.4 raises socket.error -            # python 2.7.5 raises socket.gaierror -            failure.trap(socket.gaierror, socket.error) - -        d = threads.deferToThread(do_check) -        d.addErrback(check_err) +        self._backend.eip_check_dns(domain) + +    @QtCore.Slot() +    def _eip_dns_error(self): +        """ +        Trigger this if we don't have a working DNS resolver. +        """ +        domain = self._login_widget.get_selected_provider() +        msg = self.tr( +            "The server at {0} can't be found, because the DNS lookup " +            "failed. DNS is the network service that translates a " +            "website's name to its Internet address. Either your computer " +            "is having trouble connecting to the network, or you are " +            "missing some helper files that are needed to securely use " +            "DNS while {1} is active. To install these helper files, quit " +            "this application and start it again." +        ).format(domain, self._eip_conductor.eip_name) + +        QtGui.QMessageBox.critical(self, self.tr("Connection Error"), msg)      def _try_autostart_eip(self):          """ @@ -1543,7 +1523,13 @@ class MainWindow(QtGui.QMainWindow):          else:              should_start = self._provides_eip_and_enabled() -        if should_start and not self._already_started_eip: +        missing_helpers = self._eip_status.missing_helpers +        already_started = self._already_started_eip +        can_start = (should_start +                     and not already_started +                     and not missing_helpers) + +        if can_start:              if self._eip_status.is_cold_start:                  self._backend.tear_fw_down()              # XXX this should be handled by the state machine. @@ -1563,12 +1549,16 @@ class MainWindow(QtGui.QMainWindow):          else:              if not self._already_started_eip:                  if EIP_SERVICE in self._enabled_services: -                    self._eip_status.set_eip_status( -                        self.tr("Not supported"), -                        error=True) +                    if missing_helpers: +                        msg = self.tr( +                            "Disabled: missing helper files") +                    else: +                        msg = self.tr("Not supported"), +                    self._eip_status.set_eip_status(msg, error=True)                  else: +                    msg = self.tr("Disabled")                      self._eip_status.disable_eip_start() -                    self._eip_status.set_eip_status(self.tr("Disabled")) +                    self._eip_status.set_eip_status(msg)              # eip will not start, so we start soledad anyway              self._maybe_run_soledad_setup_checks() @@ -1772,8 +1762,7 @@ class MainWindow(QtGui.QMainWindow):          # call final quit when all the services are stopped          self.all_services_stopped.connect(self.final_quit)          # or if we reach the timeout -        self._quit_timeout_callater = reactor.callLater( -            self.SERVICES_STOP_TIMEOUT, self.final_quit) +        QtDelayedCall(self.SERVICES_STOP_TIMEOUT, self.final_quit)      @QtCore.Slot()      def _remove_service(self, service): @@ -1799,16 +1788,13 @@ class MainWindow(QtGui.QMainWindow):          """          logger.debug('Final quit...') -        try: -            # disconnect signal if we get here due a timeout. -            self.all_services_stopped.disconnect(self.final_quit) -        except RuntimeError: -            pass  # Signal was not connected +        # We can reach here because all the services are stopped or because a +        # timeout was triggered. Since we want to run this only once, we exit +        # if this is called twice. +        if self._finally_quitting: +            return -        # Cancel timeout to avoid being called if we reached here through the -        # signal -        if self._quit_timeout_callater.active(): -            self._quit_timeout_callater.cancel() +        self._finally_quitting = True          # Remove lockfiles on a clean shutdown.          logger.debug('Cleaning pidfiles') @@ -1818,4 +1804,4 @@ class MainWindow(QtGui.QMainWindow):          self._backend.stop()          self.close() -        reactor.callLater(1, self._quit_callback) +        QtDelayedCall(100, twisted_main.quit) diff --git a/src/leap/bitmask/gui/twisted_main.py b/src/leap/bitmask/gui/twisted_main.py index dfd69033..b1ce0ead 100644 --- a/src/leap/bitmask/gui/twisted_main.py +++ b/src/leap/bitmask/gui/twisted_main.py @@ -36,11 +36,8 @@ def stop():      logger.debug("Done stopping all the things.") -def quit(app): +def quit():      """      Stop the mainloop. - -    :param app: the main qt QApplication instance. -    :type app: QtCore.QApplication      """      reactor.callLater(0, stop) diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui index 6c592522..8c52897d 100644 --- a/src/leap/bitmask/gui/ui/wizard.ui +++ b/src/leap/bitmask/gui/ui/wizard.ui @@ -38,7 +38,7 @@    <property name="options">     <set>QWizard::IndependentPages</set>    </property> -  <widget class="QWizardPage" name="introduction_page"> +  <widget class="WizardPage" name="introduction_page">     <property name="title">      <string>Welcome</string>     </property> diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 4d774907..f66c553d 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -33,6 +33,7 @@ from leap.bitmask.util.keyring_helpers import has_keyring  from ui_wizard import Ui_Wizard +QtDelayedCall = QtCore.QTimer.singleShot  logger = logging.getLogger(__name__) @@ -64,6 +65,8 @@ class Wizard(QtGui.QWizard):          self.ui = Ui_Wizard()          self.ui.setupUi(self) +        self._connected_signals = [] +          self.setPixmap(QtGui.QWizard.LogoPixmap,                         QtGui.QPixmap(":/images/mask-icon.png")) @@ -79,8 +82,8 @@ class Wizard(QtGui.QWizard):          self._use_existing_provider = False          self.ui.grpCheckProvider.setVisible(False) -        self.ui.btnCheck.clicked.connect(self._check_provider) -        self.ui.lnProvider.returnPressed.connect(self._check_provider) +        self._connect_and_track(self.ui.btnCheck.clicked, self._check_provider) +        self._connect_and_track(self.ui.lnProvider.returnPressed, self._check_provider)          self._backend = backend          self._backend_connect() @@ -95,24 +98,25 @@ class Wizard(QtGui.QWizard):          self._provider_select_defer = None          self._provider_setup_defer = None -        self.currentIdChanged.connect(self._current_id_changed) +        self._connect_and_track(self.currentIdChanged, self._current_id_changed) -        self.ui.lnProvider.textChanged.connect(self._enable_check) -        self.ui.rbNewProvider.toggled.connect( +        self._connect_and_track(self.ui.lnProvider.textChanged, self._enable_check) +        self._connect_and_track(self.ui.rbNewProvider.toggled,              lambda x: self._enable_check()) -        self.ui.cbProviders.currentIndexChanged[int].connect( +        self._connect_and_track(self.ui.cbProviders.currentIndexChanged[int],              self._reset_provider_check) -        self.ui.lblUser.returnPressed.connect( +        self._connect_and_track(self.ui.lblUser.returnPressed,              self._focus_password) -        self.ui.lblPassword.returnPressed.connect( +        self._connect_and_track(self.ui.lblPassword.returnPressed,              self._focus_second_password) -        self.ui.lblPassword2.returnPressed.connect( +        self._connect_and_track(self.ui.lblPassword2.returnPressed,              self._register) -        self.ui.btnRegister.clicked.connect( +        self._connect_and_track(self.ui.btnRegister.clicked,              self._register) -        self.ui.rbExistingProvider.toggled.connect(self._skip_provider_checks) +        self._connect_and_track(self.ui.rbExistingProvider.toggled, +                                self._skip_provider_checks)          usernameRe = QtCore.QRegExp(USERNAME_REGEX)          self.ui.lblUser.setValidator( @@ -137,7 +141,19 @@ class Wizard(QtGui.QWizard):          self._provider_checks_ok = False          self._provider_setup_ok = False -        self.finished.connect(self._wizard_finished) +        self._connect_and_track(self.finished, self._wizard_finished) + +    def _connect_and_track(self, signal, method): +        """ +        Helper to connect signals and keep track of them. + +        :param signal: the signal to connect to. +        :type signal: QtCore.Signal +        :param method: the method to call when the signal is triggered. +        :type method: callable, Slot or Signal +        """ +        self._connected_signals.append((signal, method)) +        signal.connect(method)      @QtCore.Slot()      def _wizard_finished(self): @@ -153,28 +169,35 @@ class Wizard(QtGui.QWizard):          self._provider_setup_ok = False          self.ui.lnProvider.setText('')          self.ui.grpCheckProvider.setVisible(False) -        self._backend_disconnect() +        self._disconnect_tracked()      def _load_configured_providers(self):          """          Loads the configured providers into the wizard providers combo box.          """ +        self._backend.provider_get_pinned_providers() + +    def _load_configured_providers_with_pinned(self, pinned): +        """ +        Once we have the pinned providers from the backend, we +        continue setting everything up + +        :param pinned: list of pinned providers +        :type pinned: list of str +        """          ls = LeapSettings()          providers = ls.get_configured_providers() -        if not providers: +        if not providers and not pinned:              self.ui.rbExistingProvider.setEnabled(False)              self.ui.label_8.setEnabled(False)  # 'https://' label              self.ui.cbProviders.setEnabled(False)              return -        pinned = []          user_added = []          # separate pinned providers from user added ones          for p in providers: -            if ls.is_pinned_provider(p): -                pinned.append(p) -            else: +            if p not in pinned:                  user_added.append(p)          if user_added: @@ -191,6 +214,9 @@ class Wizard(QtGui.QWizard):          # 'Use existing provider' option.          self.ui.rbExistingProvider.setChecked(True) +        # We need to set it as complete explicitly +        self.page(self.INTRO_PAGE).set_completed() +      def get_domain(self):          return self._domain @@ -543,7 +569,7 @@ class Wizard(QtGui.QWizard):          Set the details for the just downloaded provider.          :param details: the details of the provider. -        :type details: ProviderConfigLight +        :type details: dict          """          self._provider_details = details @@ -614,9 +640,9 @@ class Wizard(QtGui.QWizard):          the user to enable or disable.          """          self.ui.grpServices.setTitle( -            self.tr("Services by {0}").format(self._provider_details.name)) +            self.tr("Services by {0}").format(self._provider_details['name'])) -        services = get_supported(self._provider_details.services) +        services = get_supported(self._provider_details['services'])          for service in services:              try: @@ -659,7 +685,7 @@ class Wizard(QtGui.QWizard):              if not self._provider_setup_ok:                  self._reset_provider_setup()                  sub_title = self.tr("Gathering configuration options for {0}") -                sub_title = sub_title.format(self._provider_details.name) +                sub_title = sub_title.format(self._provider_details['name'])                  self.page(pageId).setSubTitle(sub_title)                  self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON)                  self._provider_setup_defer = self._backend.\ @@ -667,22 +693,22 @@ class Wizard(QtGui.QWizard):          if pageId == self.PRESENT_PROVIDER_PAGE:              sub_title = self.tr("Description of services offered by {0}") -            sub_title = sub_title.format(self._provider_details.name) +            sub_title = sub_title.format(self._provider_details['name'])              self.page(pageId).setSubTitle(sub_title)              details = self._provider_details -            name = "<b>{0}</b>".format(details.name) -            domain = "https://{0}".format(details.domain) -            description = "<i>{0}</i>".format(details.description) +            name = "<b>{0}</b>".format(details['name']) +            domain = "https://{0}".format(details['domain']) +            description = "<i>{0}</i>".format(details['description'])              self.ui.lblProviderName.setText(name)              self.ui.lblProviderURL.setText(domain)              self.ui.lblProviderDesc.setText(description) -            self.ui.lblServicesOffered.setText(details.services_string) -            self.ui.lblProviderPolicy.setText(details.enrollment_policy) +            self.ui.lblServicesOffered.setText(details['services_string']) +            self.ui.lblProviderPolicy.setText(details['enrollment_policy'])          if pageId == self.REGISTER_USER_PAGE:              sub_title = self.tr("Register a new user with {0}") -            sub_title = sub_title.format(self._provider_details.name) +            sub_title = sub_title.format(self._provider_details['name'])              self.page(pageId).setSubTitle(sub_title)              self.ui.chkRemember.setVisible(False) @@ -727,36 +753,30 @@ class Wizard(QtGui.QWizard):          Connects all the backend signals with the wizard.          """          sig = self._backend.signaler -        sig.prov_name_resolution.connect(self._name_resolution) -        sig.prov_https_connection.connect(self._https_connection) -        sig.prov_download_provider_info.connect(self._download_provider_info) -        sig.prov_get_details.connect(self._provider_get_details) +        conntrack = self._connect_and_track +        conntrack(sig.prov_name_resolution, self._name_resolution) +        conntrack(sig.prov_https_connection, self._https_connection) +        conntrack(sig.prov_download_provider_info, +                  self._download_provider_info) +        conntrack(sig.prov_get_details, self._provider_get_details) +        conntrack(sig.prov_get_pinned_providers, +                  self._load_configured_providers_with_pinned) -        sig.prov_download_ca_cert.connect(self._download_ca_cert) -        sig.prov_check_ca_fingerprint.connect(self._check_ca_fingerprint) -        sig.prov_check_api_certificate.connect(self._check_api_certificate) +        conntrack(sig.prov_download_ca_cert, self._download_ca_cert) +        conntrack(sig.prov_check_ca_fingerprint, self._check_ca_fingerprint) +        conntrack(sig.prov_check_api_certificate, self._check_api_certificate) -        sig.srp_registration_finished.connect(self._registration_finished) -        sig.srp_registration_failed.connect(self._registration_failed) -        sig.srp_registration_taken.connect(self._registration_taken) +        conntrack(sig.srp_registration_finished, self._registration_finished) +        conntrack(sig.srp_registration_failed, self._registration_failed) +        conntrack(sig.srp_registration_taken, self._registration_taken) -    def _backend_disconnect(self): +    def _disconnect_tracked(self):          """          This method is called when the wizard dialog is closed. -        We disconnect all the backend signals in here. +        We disconnect all the signals in here.          """ -        sig = self._backend.signaler -        try: -            # disconnect backend signals -            sig.prov_name_resolution.disconnect(self._name_resolution) -            sig.prov_https_connection.disconnect(self._https_connection) -            sig.prov_download_provider_info.disconnect( -                self._download_provider_info) - -            sig.prov_download_ca_cert.disconnect(self._download_ca_cert) -            sig.prov_check_ca_fingerprint.disconnect( -                self._check_ca_fingerprint) -            sig.prov_check_api_certificate.disconnect( -                self._check_api_certificate) -        except RuntimeError: -            pass  # Signal was not connected +        for signal, method in self._connected_signals: +            try: +                signal.disconnect(method) +            except RuntimeError: +                pass  # Signal was not connected diff --git a/src/leap/bitmask/logs/utils.py b/src/leap/bitmask/logs/utils.py index 06959c45..8367937a 100644 --- a/src/leap/bitmask/logs/utils.py +++ b/src/leap/bitmask/logs/utils.py @@ -8,7 +8,7 @@ from leap.bitmask.logs.streamtologger import StreamToLogger  from leap.bitmask.platform_init import IS_WIN -def get_logger(debug=False, logfile=None, replace_stdout=True): +def create_logger(debug=False, logfile=None, replace_stdout=True):      """      Create the logger and attach the handlers. diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index b282a229..384e1ec1 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -25,8 +25,9 @@ import sys  import subprocess  import tempfile -from PySide import QtGui +from PySide import QtGui, QtCore +from leap.bitmask.config import flags  from leap.bitmask.config.leapsettings import LeapSettings  from leap.bitmask.services.eip import get_vpn_launcher  from leap.bitmask.services.eip.linuxvpnlauncher import LinuxVPNLauncher @@ -44,6 +45,16 @@ __all__ = ["init_platform"]  _system = platform.system() +class InitSignals(QtCore.QObject): +    """ +    Signal container to communicate initialization events to differnt widgets. +    """ +    eip_missing_helpers = QtCore.Signal() + + +init_signals = InitSignals() + +  def init_platform():      """      Return the right initializer for the platform we are running in, or @@ -70,25 +81,26 @@ NOTFOUND_MSG = ("Tried to install %s, but %s "  BADEXEC_MSG = ("Tried to install %s, but %s "                 "failed to %s.") -UPDOWN_NOTFOUND_MSG = NOTFOUND_MSG % ( -    "updown scripts", "those were") -UPDOWN_BADEXEC_MSG = BADEXEC_MSG % ( -    "updown scripts", "they", "be copied") +HELPERS_NOTFOUND_MSG = NOTFOUND_MSG % ( +    "helper files", "those were") +HELPERS_BADEXEC_MSG = BADEXEC_MSG % ( +    "helper files", "they", "be copied") -def get_missing_updown_dialog(): +def get_missing_helpers_dialog():      """ -    Create a dialog for notifying of missing updown scripts. +    Create a dialog for notifying of missing helpers.      Returns that dialog.      :rtype: QtGui.QMessageBox instance      """      WE_NEED_POWERS = ("To better protect your privacy, "                        "Bitmask needs administrative privileges " -                      "to install helper files. " -                      "Do you want to proceed?") +                      "to install helper files. Encrypted " +                      "Internet cannot work without those files. " +                      "Do you want to install them now?")      msg = QtGui.QMessageBox() -    msg.setWindowTitle(msg.tr("Missing up/down scripts")) +    msg.setWindowTitle(msg.tr("Missing helper files"))      msg.setText(msg.tr(WE_NEED_POWERS))      # but maybe the user really deserve to know more      #msg.setInformativeText(msg.tr(BECAUSE)) @@ -104,14 +116,25 @@ def check_missing():      raises a dialog to ask user for permission to do it.      """      config = LeapSettings() +    complain_missing = False      alert_missing = config.get_alert_missing_scripts() +    if alert_missing and not flags.STANDALONE: +        # We refuse to install missing stuff if not running with standalone +        # flag. Right now we rely on the flag alone, but we can disable this +        # by overwriting some constant from within the debian package. +        alert_missing = False +        complain_missing = True +      launcher = get_vpn_launcher()      missing_scripts = launcher.missing_updown_scripts      missing_other = launcher.missing_other_files -    if alert_missing and (missing_scripts() or missing_other()): -        msg = get_missing_updown_dialog() +    logger.debug("MISSING OTHER: %s" % (str(missing_other()))) + +    missing_some = missing_scripts() or missing_other() +    if alert_missing and missing_some: +        msg = get_missing_helpers_dialog()          ret = msg.exec_()          if ret == QtGui.QMessageBox.Yes: @@ -124,7 +147,7 @@ def check_missing():                  return              # XXX maybe move constants to fun -            ok = install_missing_fun(UPDOWN_BADEXEC_MSG, UPDOWN_NOTFOUND_MSG) +            ok = install_missing_fun(HELPERS_BADEXEC_MSG, HELPERS_NOTFOUND_MSG)              if not ok:                  msg = QtGui.QMessageBox()                  msg.setWindowTitle(msg.tr("Problem installing files")) @@ -135,12 +158,19 @@ def check_missing():          elif ret == QtGui.QMessageBox.No:              logger.debug("Not installing missing scripts, "                           "user decided to ignore our warning.") +            init_signals.eip_missing_helpers.emit()          elif ret == QtGui.QMessageBox.Rejected:              logger.debug(                  "Setting alert_missing_scripts to False, we will not "                  "ask again")              config.set_alert_missing_scripts(False) + +    if complain_missing and missing_some: +        missing = missing_scripts() + missing_other() +        msg = _get_missing_complain_dialog(missing) +        ret = msg.exec_() +  #  # windows initializers  # @@ -246,13 +276,12 @@ def _darwin_install_missing_scripts(badexec, notfound):      # We expect to execute this from some way of bundle, since      # the up/down scripts should be put in place by the installer.      success = False -    installer_path = os.path.join( -        os.getcwd(), -        "..", -        "Resources", -        "openvpn") +    installer_path = os.path.abspath( +        os.path.join( +            os.getcwd(), "..", "Resources", "openvpn"))      launcher = DarwinVPNLauncher +    # XXX FIXME !!! call the bash script!      if os.path.isdir(installer_path):          fd, tempscript = tempfile.mkstemp(prefix="leap_installer-")          try: @@ -350,24 +379,72 @@ def _get_missing_resolvconf_dialog():      :rtype: QtGui.QMessageBox instance      """ -    NO_RESOLVCONF = ( +    msgstr = QtCore.QObject() +    msgstr.NO_RESOLVCONF = msgstr.tr(          "Could not find <b>resolvconf</b> installed in your system.\n"          "Do you want to quit Bitmask now?") -    EXPLAIN = ( +    msgstr.EXPLAIN = msgstr.tr(          "Encrypted Internet needs resolvconf installed to work properly.\n"          "Please use your package manager to install it.\n")      msg = QtGui.QMessageBox()      msg.setWindowTitle(msg.tr("Missing resolvconf framework")) -    msg.setText(msg.tr(NO_RESOLVCONF)) +    msg.setText(msgstr.NO_RESOLVCONF)      # but maybe the user really deserve to know more -    msg.setInformativeText(msg.tr(EXPLAIN)) +    msg.setInformativeText(msgstr.EXPLAIN)      msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)      msg.setDefaultButton(QtGui.QMessageBox.Yes)      return msg +def _get_missing_complain_dialog(stuff): +    """ +    Create a dialog for notifying about missing helpers (but doing nothing). +    Used from non-standalone runs. + +    :param stuff: list of missing items to display +    :type stuff: list +    :rtype: QtGui.QMessageBox instance +    """ +    msgstr = QtCore.QObject() +    msgstr.NO_HELPERS = msgstr.tr( +        "Some essential helper files are missing in your system.") +    msgstr.EXPLAIN = msgstr.tr( +        "Reinstall your debian packages, or make sure you place them by hand.") + +    class ComplainDialog(QtGui.QDialog): + +        def __init__(self, parent=None): +                super(ComplainDialog, self).__init__(parent) + +                label = QtGui.QLabel(msgstr.NO_HELPERS) +                label.setAlignment(QtCore.Qt.AlignLeft) + +                label2 = QtGui.QLabel(msgstr.EXPLAIN) +                label2.setAlignment(QtCore.Qt.AlignLeft) + +                textedit = QtGui.QTextEdit() +                textedit.setText("\n".join(stuff)) + +                ok = QtGui.QPushButton() +                ok.setText(self.tr("Ok, thanks")) +                self.ok = ok +                self.ok.clicked.connect(self.close) + +                mainLayout = QtGui.QGridLayout() +                mainLayout.addWidget(label, 0, 0) +                mainLayout.addWidget(label2, 1, 0) +                mainLayout.addWidget(textedit, 2, 0) +                mainLayout.addWidget(ok, 3, 0) + +                self.setLayout(mainLayout) + +    msg = ComplainDialog() +    msg.setWindowTitle(msg.tr("Missing Bitmask helpers")) +    return msg + +  def _linux_check_resolvconf():      """      Raise a dialog warning about the lack of the resolvconf framework. @@ -385,7 +462,7 @@ def _linux_check_resolvconf():  def _linux_install_missing_scripts(badexec, notfound):      """ -    Try to install the missing up/down scripts. +    Try to install the missing helper files.      :param badexec: error for notifying execution error during command.      :type badexec: str @@ -395,38 +472,32 @@ def _linux_install_missing_scripts(badexec, notfound):      :rtype: bool      """      success = False -    installer_path = os.path.join(os.getcwd(), "apps", "eip", "files") +    installer_path = os.path.abspath( +        os.path.join(os.getcwd(), "apps", "eip", "files"))      launcher = LinuxVPNLauncher -    # XXX refactor with darwin, same block. +    install_helper = "leap-install-helper.sh" +    install_helper_path = os.path.join(installer_path, install_helper) + +    install_opts = ("--from-path %s --install-bitmask-root YES " +                    "--install-polkit-file YES --install-openvpn YES " +                    "--remove-old-files YES" % (installer_path,))      if os.path.isdir(installer_path): -        fd, tempscript = tempfile.mkstemp(prefix="leap_installer-") -        polfd, pol_tempfile = tempfile.mkstemp(prefix="leap_installer-")          try:              pkexec = first(launcher.maybe_pkexec()) -            scriptlines = launcher.cmd_for_missing_scripts(installer_path) -            with os.fdopen(fd, 'w') as f: -                f.write(scriptlines) +            cmdline = ["%s %s %s" % ( +                pkexec, install_helper_path, install_opts)] -            st = os.stat(tempscript) -            os.chmod(tempscript, st.st_mode | stat.S_IEXEC | stat.S_IXUSR | -                     stat.S_IXGRP | stat.S_IXOTH) -            cmdline = ["%s %s" % (pkexec, tempscript)]              ret = subprocess.call(                  cmdline, stdout=subprocess.PIPE,                  shell=True)              success = ret == 0              if not success: -                logger.error("Install missing scripts failed.") +                logger.error("Install of helpers failed.")          except Exception as exc:              logger.error(badexec)              logger.error("Error was: %r" % (exc,)) -        finally: -            try: -                os.remove(tempscript) -            except OSError as exc: -                logger.error("%r" % (exc,))      else:          logger.error(notfound)          logger.debug('path searched: %s' % (installer_path,)) diff --git a/src/leap/bitmask/provider/pinned.py b/src/leap/bitmask/provider/pinned.py new file mode 100644 index 00000000..38851621 --- /dev/null +++ b/src/leap/bitmask/provider/pinned.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# pinned.py +# Copyright (C) 2013-2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +""" +Pinned Providers +""" +import logging + +from leap.bitmask.provider import pinned_demobitmask +from leap.bitmask.provider import pinned_riseup + +logger = logging.getLogger(__name__) + + +class PinnedProviders(object): +    """ +    Represents the providers that are pinned in Bitmask +    """ + +    CONFIG_KEY = "config" +    CACERT_KEY = "cacert" + +    PROVIDERS = { +        pinned_demobitmask.DOMAIN: { +            CONFIG_KEY: pinned_demobitmask.PROVIDER_JSON, +            CACERT_KEY: pinned_demobitmask.CACERT_PEM, +        }, +        pinned_riseup.DOMAIN: { +            CONFIG_KEY: pinned_riseup.PROVIDER_JSON, +            CACERT_KEY: pinned_riseup.CACERT_PEM, +        } +    } + +    def __init__(self): +        pass + +    @classmethod +    def domains(self): +        """ +        Return the domains that are pinned in here + +        :rtype: list of str +        """ +        return self.PROVIDERS.keys() + +    @classmethod +    def save_hardcoded(self, domain, provider_path, cacert_path): +        """ +        Save the pinned content for provider.json and cacert.pem to +        the specified paths + +        :param domain: domain of the pinned provider +        :type domain: str +        :param provider_path: path where the pinned provider.json will +                              be saved +        :type provider_path: str +        :param cacert_path: path where the pinned cacert.pem will be +                            saved +        :type cacert_path: str +        """ +        with open(provider_path, "w") as f: +            f.write(self.PROVIDERS[domain][self.CONFIG_KEY]) + +        with open(cacert_path, "w") as f: +            f.write(self.PROVIDERS[domain][self.CACERT_KEY]) diff --git a/src/leap/bitmask/provider/pinned_demobitmask.py b/src/leap/bitmask/provider/pinned_demobitmask.py new file mode 100644 index 00000000..9c699a4c --- /dev/null +++ b/src/leap/bitmask/provider/pinned_demobitmask.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +# pinned_demobitmask.py +# Copyright (C) 2013-2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +""" +Pinned provider.json and cacert.pem for demo.bitmask.net +""" + +DOMAIN = "demo.bitmask.net" + +PROVIDER_JSON = """ +{ +  "api_uri": "https://api.demo.bitmask.net:4430", +  "api_version": "1", +  "ca_cert_fingerprint": "SHA256: 0f17c033115f6b76ff67871872303ff65034efe7dd1b910062ca323eb4da5c7e", +  "ca_cert_uri": "https://demo.bitmask.net/ca.crt", +  "default_language": "en", +  "description": { +    "el": "demo.bitmask.net allows you to test the Bitmask application. User accounts may be periodically deleted.", +    "en": "demo.bitmask.net allows you to test the Bitmask application. User accounts may be periodically deleted.", +    "es": "demo.bitmask.net allows you to test the Bitmask application. User accounts may be periodically deleted." +  }, +  "domain": "demo.bitmask.net", +  "enrollment_policy": "open", +  "languages": [ +    "en" +  ], +  "name": { +    "en": "Bitmask" +  }, +  "service": { +    "allow_anonymous": true, +    "allow_free": true, +    "allow_limited_bandwidth": false, +    "allow_paid": true, +    "allow_registration": true, +    "allow_unlimited_bandwidth": true, +    "bandwidth_limit": 102400, +    "default_service_level": 1, +    "levels": [ +      { +        "id": 1, +        "name": "free", +        "storage": 50 +      }, +      { +        "id": 2, +        "name": "basic", +        "rate": [ +          "US$10", +          "\u20ac10" +        ], +        "storage": 1000 +      }, +      { +        "id": 3, +        "name": "pro", +        "rate": [ +          "US$20", +          "\u20ac20" +        ], +        "storage": 10000 +      } +    ] +  }, +  "services": [ +    "openvpn" +  ] +} +""" + +CACERT_PEM = """-----BEGIN CERTIFICATE----- +MIIFbzCCA1egAwIBAgIBATANBgkqhkiG9w0BAQ0FADBKMRgwFgYDVQQDDA9CaXRt +YXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNVBAsME2h0dHBzOi8v +Yml0bWFzay5uZXQwHhcNMTIxMTA2MDAwMDAwWhcNMjIxMTA2MDAwMDAwWjBKMRgw +FgYDVQQDDA9CaXRtYXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNV +BAsME2h0dHBzOi8vYml0bWFzay5uZXQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQC1eV4YvayaU+maJbWrD4OHo3d7S1BtDlcvkIRS1Fw3iYDjsyDkZxai +dHp4EUasfNQ+EVtXUvtk6170EmLco6Elg8SJBQ27trE6nielPRPCfX3fQzETRfvB +7tNvGw4Jn2YKiYoMD79kkjgyZjkJ2r/bEHUSevmR09BRp86syHZerdNGpXYhcQ84 +CA1+V+603GFIHnrP+uQDdssW93rgDNYu+exT+Wj6STfnUkugyjmPRPjL7wh0tzy+ +znCeLl4xiV3g9sjPnc7r2EQKd5uaTe3j71sDPF92KRk0SSUndREz+B1+Dbe/RGk4 +MEqGFuOzrtsgEhPIX0hplhb0Tgz/rtug+yTT7oJjBa3u20AAOQ38/M99EfdeJvc4 +lPFF1XBBLh6X9UKF72an2NuANiX6XPySnJgZ7nZ09RiYZqVwu/qt3DfvLfhboq+0 +bQvLUPXrVDr70onv5UDjpmEA/cLmaIqqrduuTkFZOym65/PfAPvpGnt7crQj/Ibl +DEDYZQmP7AS+6zBjoOzNjUGE5r40zWAR1RSi7zliXTu+yfsjXUIhUAWmYR6J3KxB +lfsiHBQ+8dn9kC3YrUexWoOqBiqJOAJzZh5Y1tqgzfh+2nmHSB2dsQRs7rDRRlyy +YMbkpzL9ZsOUO2eTP1mmar6YjCN+rggYjRrX71K2SpBG6b1zZxOG+wIDAQABo2Aw +XjAdBgNVHQ4EFgQUuYGDLL2sswnYpHHvProt1JU+D48wDgYDVR0PAQH/BAQDAgIE +MAwGA1UdEwQFMAMBAf8wHwYDVR0jBBgwFoAUuYGDLL2sswnYpHHvProt1JU+D48w +DQYJKoZIhvcNAQENBQADggIBADeG67vaFcbITGpi51264kHPYPEWaXUa5XYbtmBl +cXYyB6hY5hv/YNuVGJ1gWsDmdeXEyj0j2icGQjYdHRfwhrbEri+h1EZOm1cSBDuY +k/P5+ctHyOXx8IE79DBsZ6IL61UKIaKhqZBfLGYcWu17DVV6+LT+AKtHhOrv3TSj +RnAcKnCbKqXLhUPXpK0eTjPYS2zQGQGIhIy9sQXVXJJJsGrPgMxna1Xw2JikBOCG +htD/JKwt6xBmNwktH0GI/LVtVgSp82Clbn9C4eZN9E5YbVYjLkIEDhpByeC71QhX +EIQ0ZR56bFuJA/CwValBqV/G9gscTPQqd+iETp8yrFpAVHOW+YzSFbxjTEkBte1J +aF0vmbqdMAWLk+LEFPQRptZh0B88igtx6tV5oVd+p5IVRM49poLhuPNJGPvMj99l +mlZ4+AeRUnbOOeAEuvpLJbel4rhwFzmUiGoeTVoPZyMevWcVFq6BMkS+jRR2w0jK +G6b0v5XDHlcFYPOgUrtsOBFJVwbutLvxdk6q37kIFnWCd8L3kmES5q4wjyFK47Co +Ja8zlx64jmMZPg/t3wWqkZgXZ14qnbyG5/lGsj5CwVtfDljrhN0oCWK1FZaUmW3d +69db12/g4f6phldhxiWuGC/W6fCW5kre7nmhshcltqAJJuU47iX+DarBFiIj816e +yV8e +-----END CERTIFICATE-----""" diff --git a/src/leap/bitmask/provider/pinned_riseup.py b/src/leap/bitmask/provider/pinned_riseup.py new file mode 100644 index 00000000..8cc51506 --- /dev/null +++ b/src/leap/bitmask/provider/pinned_riseup.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# pinned_riseup.py +# Copyright (C) 2013-2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +""" +Pinned provider.json and cacert.pem for riseup.net +""" + +DOMAIN = "riseup.net" + +PROVIDER_JSON = """ +{ +  "api_uri": "https://api.black.riseup.net:4430", +  "api_version": "1", +  "ca_cert_fingerprint": "SHA256: a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494", +  "ca_cert_uri": "https://black.riseup.net/ca.crt", +  "default_language": "en", +  "description": { +    "en": "Riseup is a non-profit collective in Seattle that provides online communication tools for people and groups working toward liberatory social change." +  }, +  "domain": "riseup.net", +  "enrollment_policy": "open", +  "languages": [ +    "en" +  ], +  "name": { +    "en": "Riseup Networks" +  }, +  "service": { +    "allow_anonymous": false, +    "allow_free": true, +    "allow_limited_bandwidth": false, +    "allow_paid": false, +    "allow_registration": true, +    "allow_unlimited_bandwidth": true, +    "bandwidth_limit": 102400, +    "default_service_level": 1, +    "levels": { +      "1": { +        "description": "Please donate.", +        "name": "free" +      } +    } +  }, +  "services": [ +    "openvpn" +  ] +} +""" + +CACERT_PEM = """-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIBATANBgkqhkiG9w0BAQ0FADBZMRgwFgYDVQQKDA9SaXNl +dXAgTmV0d29ya3MxGzAZBgNVBAsMEmh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UE +AwwXUmlzZXVwIE5ldHdvcmtzIFJvb3QgQ0EwHhcNMTQwNDI4MDAwMDAwWhcNMjQw +NDI4MDAwMDAwWjBZMRgwFgYDVQQKDA9SaXNldXAgTmV0d29ya3MxGzAZBgNVBAsM +Emh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UEAwwXUmlzZXVwIE5ldHdvcmtzIFJv +b3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC76J4ciMJ8Sg0m +TP7DF2DT9zNe0Csk4myoMFC57rfJeqsAlJCv1XMzBmXrw8wq/9z7XHv6n/0sWU7a +7cF2hLR33ktjwODlx7vorU39/lXLndo492ZBhXQtG1INMShyv+nlmzO6GT7ESfNE +LliFitEzwIegpMqxCIHXFuobGSCWF4N0qLHkq/SYUMoOJ96O3hmPSl1kFDRMtWXY +iw1SEKjUvpyDJpVs3NGxeLCaA7bAWhDY5s5Yb2fA1o8ICAqhowurowJpW7n5ZuLK +5VNTlNy6nZpkjt1QycYvNycffyPOFm/Q/RKDlvnorJIrihPkyniV3YY5cGgP+Qkx +HUOT0uLA6LHtzfiyaOqkXwc4b0ZcQD5Vbf6Prd20Ppt6ei0zazkUPwxld3hgyw58 +m/4UIjG3PInWTNf293GngK2Bnz8Qx9e/6TueMSAn/3JBLem56E0WtmbLVjvko+LF +PM5xA+m0BmuSJtrD1MUCXMhqYTtiOvgLBlUm5zkNxALzG+cXB28k6XikXt6MRG7q +hzIPG38zwkooM55yy5i1YfcIi5NjMH6A+t4IJxxwb67MSb6UFOwg5kFokdONZcwj +shczHdG9gLKSBIvrKa03Nd3W2dF9hMbRu//STcQxOailDBQCnXXfAATj9pYzdY4k +ha8VCAREGAKTDAex9oXf1yRuktES4QIDAQABo2AwXjAdBgNVHQ4EFgQUC4tdmLVu +f9hwfK4AGliaet5KkcgwDgYDVR0PAQH/BAQDAgIEMAwGA1UdEwQFMAMBAf8wHwYD +VR0jBBgwFoAUC4tdmLVuf9hwfK4AGliaet5KkcgwDQYJKoZIhvcNAQENBQADggIB +AGzL+GRnYu99zFoy0bXJKOGCF5XUXP/3gIXPRDqQf5g7Cu/jYMID9dB3No4Zmf7v +qHjiSXiS8jx1j/6/Luk6PpFbT7QYm4QLs1f4BlfZOti2KE8r7KRDPIecUsUXW6P/ +3GJAVYH/+7OjA39za9AieM7+H5BELGccGrM5wfl7JeEz8in+V2ZWDzHQO4hMkiTQ +4ZckuaL201F68YpiItBNnJ9N5nHr1MRiGyApHmLXY/wvlrOpclh95qn+lG6/2jk7 +3AmihLOKYMlPwPakJg4PYczm3icFLgTpjV5sq2md9bRyAg3oPGfAuWHmKj2Ikqch +Td5CHKGxEEWbGUWEMP0s1A/JHWiCbDigc4Cfxhy56CWG4q0tYtnc2GMw8OAUO6Wf +Xu5pYKNkzKSEtT/MrNJt44tTZWbKV/Pi/N2Fx36my7TgTUj7g3xcE9eF4JV2H/sg +tsK3pwE0FEqGnT4qMFbixQmc8bGyuakr23wjMvfO7eZUxBuWYR2SkcP26sozF9PF +tGhbZHQVGZUTVPyvwahMUEhbPGVerOW0IYpxkm0x/eaWdTc4vPpf/rIlgbAjarnJ +UN9SaWRlWKSdP4haujnzCoJbM7dU9bjvlGZNyXEekgeT0W2qFeGGp+yyUWw8tNsp +0BuC1b7uW/bBn/xKm319wXVDvBgZgcktMolak39V7DVO +-----END CERTIFICATE-----""" diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index 6cdfe4f4..8c96a8b5 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -29,6 +29,7 @@ from leap.bitmask import util  from leap.bitmask.config import flags  from leap.bitmask.config.providerconfig import ProviderConfig, MissingCACert  from leap.bitmask.provider import get_provider_path +from leap.bitmask.provider.pinned import PinnedProviders  from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper  from leap.bitmask.util.constants import REQUEST_TIMEOUT  from leap.bitmask.util.request_helpers import get_content @@ -176,6 +177,14 @@ class ProviderBootstrapper(AbstractBootstrapper):          provider_json = os.path.join(util.get_path_prefix(),                                       get_provider_path(domain)) +        if domain in PinnedProviders.domains() and \ +           not os.path.exists(provider_json): +            mkdir_p(os.path.join(os.path.dirname(provider_json), +                                 "keys", "ca")) +            cacert = os.path.join(os.path.dirname(provider_json), +                                  "keys", "ca", "cacert.pem") +            PinnedProviders.save_hardcoded(domain, provider_json, cacert) +          mtime = get_mtime(provider_json)          if self._download_if_needed and mtime: diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 955768d1..8ec0c050 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -29,7 +29,7 @@ from leap.bitmask.util.privilege_policies import LinuxPolicyChecker  from leap.common.files import which  from leap.bitmask.services.eip.vpnlauncher import VPNLauncher  from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException -from leap.bitmask.util import get_path_prefix +from leap.bitmask.util import get_path_prefix, force_eval  from leap.common.check import leap_assert  from leap.bitmask.util import first @@ -105,26 +105,34 @@ leapfile = lambda f: "%s/%s" % (SYSTEM_CONFIG, f)  class LinuxVPNLauncher(VPNLauncher):      PKEXEC_BIN = 'pkexec' -    BITMASK_ROOT = "/usr/sbin/bitmask-root" -    # We assume this is there by our openvpn dependency, and -    # we will put it there on the bundle too. -    if flags.STANDALONE: -        OPENVPN_BIN_PATH = "/usr/sbin/leap-openvpn" -    else: -        OPENVPN_BIN_PATH = "/usr/sbin/openvpn" - -    POLKIT_PATH = LinuxPolicyChecker.get_polkit_path() - -    if flags.STANDALONE: -        RESOLVCONF_BIN_PATH = "/usr/local/sbin/leap-resolvconf" -    else: +    # The following classes depend on force_eval to be called against +    # the classes, to get the evaluation of the standalone flag on runtine. +    # If we keep extending this kind of classes, we should abstract the +    # handling of the STANDALONE flag in a base class + +    class BITMASK_ROOT(object): +        def __call__(self): +            return ("/usr/local/sbin/bitmask-root" if flags.STANDALONE else +                    "/usr/sbin/bitmask-root") + +    class OPENVPN_BIN_PATH(object): +        def __call__(self): +            return ("/usr/local/sbin/leap-openvpn" if flags.STANDALONE else +                    "/usr/sbin/openvpn") + +    class POLKIT_PATH(object): +        def __call__(self): +            # LinuxPolicyChecker will give us the right path if standalone. +            return LinuxPolicyChecker.get_polkit_path() + +    class RESOLVCONF_BIN_PATH(object): +        def __call__(self): +            return ("/usr/local/sbin/leap-resolvconf" if flags.STANDALONE else +                    "/sbin/resolvconf")          # this only will work with debian/ubuntu distros. -        RESOLVCONF_BIN_PATH = "/sbin/resolvconf" -    # XXX openvpn binary TOO -    OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT, OPENVPN_BIN_PATH, -                   RESOLVCONF_BIN_PATH) +    OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT, OPENVPN_BIN_PATH)      @classmethod      def maybe_pkexec(kls): @@ -187,7 +195,7 @@ class LinuxVPNLauncher(VPNLauncher):          command = super(LinuxVPNLauncher, kls).get_vpn_command(              eipconfig, providerconfig, socket_host, socket_port, openvpn_verb) -        command.insert(0, kls.BITMASK_ROOT) +        command.insert(0, force_eval(kls.BITMASK_ROOT))          command.insert(1, "openvpn")          command.insert(2, "start") @@ -207,35 +215,37 @@ class LinuxVPNLauncher(VPNLauncher):          :rtype: str          """ +        bin_paths = force_eval( +            (LinuxVPNLauncher.POLKIT_PATH, +             LinuxVPNLauncher.OPENVPN_BIN_PATH, +             LinuxVPNLauncher.BITMASK_ROOT)) + +        polkit_path, openvpn_bin_path, bitmask_root = bin_paths +          # no system config for now          # sys_config = kls.SYSTEM_CONFIG          (polkit_file, openvpn_bin_file, -         bitmask_root_file, resolvconf_bin_file) = map( +         bitmask_root_file) = map(              lambda p: os.path.split(p)[-1], -            (kls.POLKIT_PATH, kls.OPENVPN_BIN_PATH, -             kls.BITMASK_ROOT, kls.RESOLVCONF_BIN_PATH)) +            bin_paths)          cmd = '#!/bin/sh\n'          cmd += 'mkdir -p /usr/local/sbin\n'          cmd += 'cp "%s" "%s"\n' % (os.path.join(frompath, polkit_file), -                                   kls.POLKIT_PATH) -        cmd += 'chmod 644 "%s"\n' % (kls.POLKIT_PATH, ) +                                   polkit_path) +        cmd += 'chmod 644 "%s"\n' % (polkit_path, )          cmd += 'cp "%s" "%s"\n' % (os.path.join(frompath, bitmask_root_file), -                                   kls.BITMASK_ROOT) -        cmd += 'chmod 744 "%s"\n' % (kls.BITMASK_ROOT, ) +                                   bitmask_root) +        cmd += 'chmod 744 "%s"\n' % (bitmask_root, )          if flags.STANDALONE:              cmd += 'cp "%s" "%s"\n' % (                  os.path.join(frompath, openvpn_bin_file), -                kls.OPENVPN_BIN_PATH) -            cmd += 'chmod 744 "%s"\n' % (kls.POLKIT_PATH, ) +                openvpn_bin_path) +            cmd += 'chmod 744 "%s"\n' % (openvpn_bin_path, ) -            cmd += 'cp "%s" "%s"\n' % ( -                os.path.join(frompath, resolvconf_bin_file), -                kls.RESOLVCONF_BIN_PATH) -            cmd += 'chmod 744 "%s"\n' % (kls.POLKIT_PATH, )          return cmd      @classmethod diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index 9629afae..0731bee3 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -18,6 +18,7 @@  Platform independant VPN launcher interface.  """  import getpass +import hashlib  import logging  import os  import stat @@ -30,6 +31,7 @@ from leap.bitmask.config.leapsettings import LeapSettings  from leap.bitmask.config.providerconfig import ProviderConfig  from leap.bitmask.platform_init import IS_LINUX  from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector +from leap.bitmask.util import force_eval  from leap.common.check import leap_assert, leap_assert_type @@ -76,7 +78,7 @@ def _has_updown_scripts(path, warn=True):  def _has_other_files(path, warn=True):      """ -    Checks the existence of other important files. +    Check the existence of other important files.      :param path: the path to be checked      :type path: str @@ -179,12 +181,13 @@ class VPNLauncher(object):              #raise OpenVPNNotFoundException()          #openvpn = first(openvpn_possibilities)          # ----------------------------------------- -        if not os.path.isfile(kls.OPENVPN_BIN_PATH): +        openvpn_path = force_eval(kls.OPENVPN_BIN_PATH) + +        if not os.path.isfile(openvpn_path):              logger.warning("Could not find openvpn bin in path %s" % ( -                kls.OPENVPN_BIN_PATH)) +                openvpn_path))              raise OpenVPNNotFoundException() -        openvpn = kls.OPENVPN_BIN_PATH          args = []          args += [ @@ -248,13 +251,13 @@ class VPNLauncher(object):              '--ping', '10',              '--ping-restart', '30'] -        command_and_args = [openvpn] + args +        command_and_args = [openvpn_path] + args          return command_and_args      @classmethod      def get_vpn_env(kls):          """ -        Returns a dictionary with the custom env for the platform. +        Return a dictionary with the custom env for the platform.          This is mainly used for setting LD_LIBRARY_PATH to the correct          path when distributing a standalone client @@ -265,7 +268,7 @@ class VPNLauncher(object):      @classmethod      def missing_updown_scripts(kls):          """ -        Returns what updown scripts are missing. +        Return what updown scripts are missing.          :rtype: list          """ @@ -285,7 +288,7 @@ class VPNLauncher(object):      @classmethod      def missing_other_files(kls):          """ -        Returns what other important files are missing during startup. +        Return what other important files are missing during startup.          Same as missing_updown_scripts but does not check for exec bit.          :rtype: list @@ -293,7 +296,57 @@ class VPNLauncher(object):          leap_assert(kls.OTHER_FILES is not None,                      "Need to define OTHER_FILES for this particular "                      "auncher before calling this method") +        other = force_eval(kls.OTHER_FILES)          file_exist = partial(_has_other_files, warn=False) -        zipped = zip(kls.OTHER_FILES, map(file_exist, kls.OTHER_FILES)) -        missing = filter(lambda (path, exists): exists is False, zipped) -        return [path for path, exists in missing] + +        if flags.STANDALONE: +            try: +                from leap.bitmask import _binaries +            except ImportError: +                raise RuntimeError( +                    "Could not find binary hash info in this bundle!") + +            _, bitmask_root_path, openvpn_bin_path = other + +            check_hash = _has_expected_binary_hash +            openvpn_hash = _binaries.OPENVPN_BIN +            bitmask_root_hash = _binaries.BITMASK_ROOT + +            correct_hash = ( +                True,  # we do not check the polkit file +                check_hash(bitmask_root_path, bitmask_root_hash), +                check_hash(openvpn_bin_path, openvpn_hash)) + +            zipped = zip(other, map(file_exist, other), correct_hash) +            missing = filter( +                lambda (path, exists, hash_ok): ( +                    exists is False or hash_ok is False), +                zipped) +            return [path for path, exists, hash_ok in missing] +        else: +            zipped = zip(other, map(file_exist, other)) +            missing = filter(lambda (path, exists): exists is False, zipped) +            return [path for path, exists in missing] + + +def _has_expected_binary_hash(path, expected_hash): +    """ +    Check if the passed path matches the expected hash. + +    Used from within the bundle, to know if we have to reinstall the shipped +    binaries into the system path. + +    This path will be /usr/local/sbin for linux. + +    :param path: the path to check. +    :type path: str +    :param expected_hash: the sha256 hash that we expect +    :type expected_hash: str +    :rtype: bool +    """ +    try: +        with open(path) as f: +            file_hash = hashlib.sha256(f.read()).hexdigest() +        return expected_hash == file_hash +    except IOError: +        return False diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index f56d464e..b54f2925 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -43,7 +43,7 @@ from leap.bitmask.services.eip import get_vpn_launcher  from leap.bitmask.services.eip import linuxvpnlauncher  from leap.bitmask.services.eip.eipconfig import EIPConfig  from leap.bitmask.services.eip.udstelnet import UDSTelnet -from leap.bitmask.util import first +from leap.bitmask.util import first, force_eval  from leap.bitmask.platform_init import IS_MAC, IS_LINUX  from leap.common.check import leap_assert, leap_assert_type @@ -233,7 +233,7 @@ class VPN(object):          # XXX could check for wrapper existence, check it's root owned etc.          # XXX could check that the iptables rules are in place. -        BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT +        BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT)          cmd = ["pkexec", BM_ROOT, "firewall", "start"]          if restart:              cmd.append("restart") @@ -246,7 +246,7 @@ class VPN(object):          :rtype: bool          """ -        BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT +        BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT)          fw_up_cmd = "pkexec {0} firewall isup".format(BM_ROOT)          fw_is_down = lambda: commands.getstatusoutput(fw_up_cmd)[0] == 256          return fw_is_down() @@ -255,7 +255,7 @@ class VPN(object):          """          Tear the firewall down using the privileged wrapper.          """ -        BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT +        BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT)          exitCode = subprocess.call(["pkexec",                                      BM_ROOT, "firewall", "stop"])          return True if exitCode is 0 else False diff --git a/src/leap/bitmask/services/tx.py b/src/leap/bitmask/services/tx.py deleted file mode 100644 index adc6fcea..00000000 --- a/src/leap/bitmask/services/tx.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -# twisted.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/>. -""" -Twisted services launched by the client -""" -import logging - -from twisted.application.service import Application -#from twisted.internet.task import LoopingCall - -logger = logging.getLogger(__name__) - - -def task(): -    """ -    stub periodic task, mainly for tests. -    DELETE-ME when there's real meat here :) -    """ -    from datetime import datetime -    logger.debug("hi there %s", datetime.now()) - - -def leap_services(): -    """ -    Check which twisted services are enabled and -    register them. -    """ -    logger.debug('starting leap services') -    application = Application("Bitmask Local Services") -    #lc = LoopingCall(task) -    #lc.start(5) -    return application diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py index c35be99e..25b86874 100644 --- a/src/leap/bitmask/util/__init__.py +++ b/src/leap/bitmask/util/__init__.py @@ -110,3 +110,22 @@ def make_address(user, provider):      :type provider: basestring      """      return "%s@%s" % (user, provider) + + +def force_eval(items): +    """ +    Return a sequence that evaluates any callable in the sequence, +    instantiating it beforehand if the item is a class, and +    leaves the non-callable items without change. +    """ +    def do_eval(thing): +        if isinstance(thing, type): +            return thing()() +        if callable(thing): +            return thing() +        return thing + +    if isinstance(items, (list, tuple)): +        return map(do_eval, items) +    else: +        return do_eval(items) diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index 84af4e8d..0717aea5 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -123,7 +123,13 @@ def build_parser():      return parser -def init_leapc_args(): +def get_options(): +    """ +    Get the command line options used when the app was started. + +    :return: the command options +    :rtype: argparse.Namespace +    """      parser = build_parser()      opts, unknown = parser.parse_known_args() -    return parser, opts +    return opts diff --git a/src/leap/bitmask/util/pastebin.py b/src/leap/bitmask/util/pastebin.py index a3bdba02..a3bdba02 100755..100644 --- a/src/leap/bitmask/util/pastebin.py +++ b/src/leap/bitmask/util/pastebin.py diff --git a/src/leap/bitmask/util/privilege_policies.py b/src/leap/bitmask/util/privilege_policies.py index 9d1e2c9a..adc3503f 100644 --- a/src/leap/bitmask/util/privilege_policies.py +++ b/src/leap/bitmask/util/privilege_policies.py @@ -24,6 +24,8 @@ import platform  from abc import ABCMeta, abstractmethod +from leap.bitmask.config import flags +  logger = logging.getLogger(__name__) @@ -71,6 +73,8 @@ class LinuxPolicyChecker(PolicyChecker):      """      LINUX_POLKIT_FILE = ("/usr/share/polkit-1/actions/"                           "se.leap.bitmask.policy") +    LINUX_POLKIT_FILE_BUNDLE = ("/usr/share/polkit-1/actions/" +                                "se.leap.bitmask.bundle.policy")      @classmethod      def get_polkit_path(self): @@ -79,7 +83,8 @@ class LinuxPolicyChecker(PolicyChecker):          :rtype: str          """ -        return self.LINUX_POLKIT_FILE +        return (self.LINUX_POLKIT_FILE_BUNDLE if flags.STANDALONE +                else self.LINUX_POLKIT_FILE)      def is_missing_policy_permissions(self):      # FIXME this name is quite confusing, it does not have anything to do with @@ -90,4 +95,5 @@ class LinuxPolicyChecker(PolicyChecker):          :rtype: bool          """ -        return not os.path.isfile(self.LINUX_POLKIT_FILE) +        path = self.get_polkit_path() +        return not os.path.isfile(path) | 
