From 26b92825384e5836a407758fa4b6b14d8726bb9b Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 4 Nov 2015 11:36:57 -0300 Subject: [bug] fix typo on signal name - Resolves: #7568 --- changes/next-changelog.rst | 1 + src/leap/bitmask/services/mail/conductor.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst index 90448d57..87a80dbd 100644 --- a/changes/next-changelog.rst +++ b/changes/next-changelog.rst @@ -15,6 +15,7 @@ Features Bugfixes ~~~~~~~~ +- `#7568 `_: Fix typo on signal name. - `#1235 `_: Description for the fixed stuff corresponding with issue #1235. - Bugfix without related issue number. diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 68197d9d..b79ea9c7 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -113,7 +113,7 @@ class IMAPControl(object): """ Callback for IMAP failed state. """ - self.imap_connection.qtsigs.connetion_aborted_signal.emit() + self.imap_connection.qtsigs.connection_aborted_signal.emit() class SMTPControl(object): -- cgit v1.2.3 From c8bc7169b56405e644933e6fa314e486975e40ce Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 4 Nov 2015 12:12:19 -0300 Subject: [pkg] update supported distros for .deb packages --- docker/debian/apt-bitmask.sh | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docker/debian/apt-bitmask.sh b/docker/debian/apt-bitmask.sh index 68430aff..59226be1 100755 --- a/docker/debian/apt-bitmask.sh +++ b/docker/debian/apt-bitmask.sh @@ -30,15 +30,16 @@ distro(){ is_supported(){ distros=( - 'wheezy' # Debian 7 - stable + # 'wheezy' # Debian 7 - stable 'jessie' # Debian 8 - testing 'sid' # Debian unstable - 'quantal' # Ubuntu 12.10 - 'raring' # Ubuntu 13.04 - 'saucy' # Ubuntu 13.10 - 'trusty' # Ubuntu 14.04 - 'utopic' # Ubuntu 14.10 + # 'quantal' # Ubuntu 12.10 + # 'raring' # Ubuntu 13.04 + # 'saucy' # Ubuntu 13.10 + # 'trusty' # Ubuntu 14.04 + # 'utopic' # Ubuntu 14.10 'vivid' # Ubuntu 15.04 + 'wily' # Ubuntu 15.10 ) my_distro=`distro` -- cgit v1.2.3 From 0d662c174ff0b8c09213b9ecf8153dd283ccb91a Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 4 Nov 2015 15:00:11 -0300 Subject: [feat] improve msg and add margin above box - Related: #7552 --- changes/next-changelog.rst | 1 + src/leap/bitmask/gui/ui/mail_status.ui | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst index 87a80dbd..287b7736 100644 --- a/changes/next-changelog.rst +++ b/changes/next-changelog.rst @@ -10,6 +10,7 @@ I've added a new category `Misc` so we can track doc/style/packaging stuff. Features ~~~~~~~~ +- `#7552 `_: Improve UI message and add some margin above the msg box. - `#1234 `_: Description of the new feature corresponding with issue #1234. - New feature without related issue number. diff --git a/src/leap/bitmask/gui/ui/mail_status.ui b/src/leap/bitmask/gui/ui/mail_status.ui index 8e8f1848..3f103ef6 100644 --- a/src/leap/bitmask/gui/ui/mail_status.ui +++ b/src/leap/bitmask/gui/ui/mail_status.ui @@ -77,7 +77,8 @@ background-color: #e0efd8; -padding: 10px; +padding: 10px; +margin-top:5px; QFrame::NoFrame @@ -89,7 +90,7 @@ padding: 10px; 0 - Congratulations! You are ready to use Bitmask to encrypt your email. Go to <a href="https://bitmask.net/en/help/email">https://bitmask.net/en/help/email</a> for instructions on how to set up your mail client. + Bitmask is ready to encrypt your email. Go to <a href="https://bitmask.net/en/help/email">https://bitmask.net/en/help/email</a> for email application setup instructions. Qt::AutoText -- cgit v1.2.3 From 6abbda889e193f01c13581ae46923fee455522de Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 4 Nov 2015 18:13:20 -0400 Subject: [docs] add codename for 090 --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9af1ebcc..1c8422fa 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,8 +15,8 @@ Bugfixes - `#7563 `_: try to look for /usr/bin/gpg1 - `#7562 `_: use zmq embedded minitornado, instead of system lib. -0.9.0 October 28 -++++++++++++++++ +0.9.0 October 28 - "not in kansas anymore" +++++++++++++++++++++++++++++++++++++++++++ Features ~~~~~~~~ -- cgit v1.2.3 From e9c5a139df2e919733624bec5f9270f944edb60c Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 4 Nov 2015 18:37:34 -0400 Subject: [docs] update manpages --- docs/man/bitmask-root.1.rst | 10 +++++----- docs/man/bitmask.1.rst | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/man/bitmask-root.1.rst b/docs/man/bitmask-root.1.rst index b2f955a8..b25a3362 100644 --- a/docs/man/bitmask-root.1.rst +++ b/docs/man/bitmask-root.1.rst @@ -7,9 +7,9 @@ privileged helper for bitmask, the encrypted internet access toolkit. ------------------------------------------------------------------------ :Author: LEAP Encryption Access Project https://leap.se -:Date: 2014-06-05 +:Date: 2015-11-03 :Copyright: GPLv3+ -:Version: 0.5.2 +:Version: 0.9.1 :Manual section: 1 :Manual group: General Commands Manual @@ -42,7 +42,7 @@ openvpn firewall ---------- +-------- **start** [GATEWAYS] Starts the firewall. GATEWAYS is a list of EIP gateways to allow in the firewall. @@ -53,7 +53,7 @@ firewall fw-email ---------- +-------- **start** UID Starts the email firewall. UID is the user name or unix id that will have access to the email. @@ -71,4 +71,4 @@ version BUGS ==== -Please report any bugs to https://leap.se/code +Please report any bugs to https://leap.se/code/projects/report-issues diff --git a/docs/man/bitmask.1.rst b/docs/man/bitmask.1.rst index 6eae7ff5..0970b449 100644 --- a/docs/man/bitmask.1.rst +++ b/docs/man/bitmask.1.rst @@ -6,10 +6,10 @@ bitmask graphical client to control LEAP, the encrypted internet access toolkit. ------------------------------------------------------------------------ -:Author: LEAP Encryption Access Project https://leap.se -:Date: 2014-06-05 +:Author: The LEAP Encryption Access Project https://leap.se +:Date: 2015-11-03 :Copyright: GPLv3+ -:Version: 0.5.2 +:Version: 0.9.1 :Manual section: 1 :Manual group: General Commands Manual @@ -108,4 +108,4 @@ GUI options BUGS ==== -Please report any bugs to https://leap.se/code +Please report any bugs to https://leap.se/code/projects/report-issues -- cgit v1.2.3 From 5516308ba8a5e47a15c12cfab49f61ee7e4889a8 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 6 Nov 2015 11:23:52 -0400 Subject: [docs] add link to irc-es and bugtracker --- README.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.rst b/README.rst index 0d686350..ab30951f 100644 --- a/README.rst +++ b/README.rst @@ -8,6 +8,9 @@ Bitmask .. image:: https://img.shields.io/badge/IRC-leap-blue.svg :target: http://webchat.freenode.net/?channels=%23leap&uio=d4 :alt: IRC +.. image:: https://img.shields.io/badge/IRC-bitmask_(es)-blue.svg + :target: http://webchat.freenode.net/?channels=%23bitmask-es&uio=d4 + :alt: IRC-es **Bitmask** is the multiplatform desktop client for the services offered by `the LEAP Platform`_. @@ -37,6 +40,13 @@ Bitmask is released under the terms of the `GNU GPL version 3`_ or later. .. _`GNU GPL version 3`: http://www.gnu.org/licenses/gpl.txt +Bugs +==== + +Please report any bugs `in our bug tracker`_. + +.. _`in our bug tracker`: https://leap.se/code/projects/report-issues + Contributing ============ -- cgit v1.2.3 From e086f958dcaf417816025af6f61e6283a3bf7ebf Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 5 Nov 2015 16:50:12 -0400 Subject: [bug] fix set_soledad_invalid_auth_token event cb signature --- changes/next-changelog.rst | 1 + src/leap/bitmask/gui/mail_status.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst index 287b7736..54b2b2ed 100644 --- a/changes/next-changelog.rst +++ b/changes/next-changelog.rst @@ -17,6 +17,7 @@ Features Bugfixes ~~~~~~~~ - `#7568 `_: Fix typo on signal name. +- `#7583 `_: Fix set_soledad_auth_token event callback signature. - `#1235 `_: Description for the fixed stuff corresponding with issue #1235. - Bugfix without related issue number. diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 8b4329d7..f7957c95 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -186,7 +186,7 @@ class MailStatusWidget(QtGui.QWidget): msg = self.tr("There was an unexpected problem with Soledad.") self._set_mail_status(msg, ready=-1) - def set_soledad_invalid_auth_token(self, event, content): + def set_soledad_invalid_auth_token(self, event, content=None): """ This method is called when the auth token is invalid -- cgit v1.2.3 From a5b148a885937452be36ee6c25c4d0b451be4b1f Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 6 Nov 2015 11:44:48 -0400 Subject: [docs] add info for translators --- docs/dev/translations.rst | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 docs/dev/translations.rst diff --git a/docs/dev/translations.rst b/docs/dev/translations.rst new file mode 100644 index 00000000..58a2a8fb --- /dev/null +++ b/docs/dev/translations.rst @@ -0,0 +1,37 @@ +.. _translations: + +Internationalization +==================== + +This part of the documentation covers the localization and translation of Bitmask. +Because we want to *bring fire to the people*, in as many countries and languages as possible. + +Translating Bitmask PySide Application +-------------------------------------- + +.. raw:: html + + + + +For translators +^^^^^^^^^^^^^^^ + +We are using `transifex `_ to coordinate translation efforts. If you want to contribute, just sign up there and send us your translations. + + +Translating help documents +-------------------------- + +There are two sets of online documents that need translation. One is +https://leap.se/docs, and the other is https://bitmask.net/help. Both are +maintained in git repos (`leap.se `_ and `bitmask.net `_). + +In the repo for bitmask.net there are some explanations about how to contribute +to those repos (using the online github editor, or using git directly). + + +Human interaction +----------------- +We're hanging out in the #leap channel on the freenode network. There's also a +spanish channel at #bitmask-es. -- cgit v1.2.3 From b370dfa37265ec7df99c8a85f2b7b7866a9a6b74 Mon Sep 17 00:00:00 2001 From: kwadronaut Date: Sat, 7 Nov 2015 15:01:11 +0100 Subject: Debian capitalization Solves https://www.transifex.com/otf/bitmask/translate/#es/$/31977728 --- data/ts/en_US.ts | 2 +- docker/README.rst | 6 +++--- docs/dev/environment.rst | 4 ++-- docs/dev/tests.rst | 2 +- docs/pkg/debian.rst | 2 +- src/leap/bitmask/platform_init/initializers.py | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/data/ts/en_US.ts b/data/ts/en_US.ts index b6fcc5da..b7cd6d26 100644 --- a/data/ts/en_US.ts +++ b/data/ts/en_US.ts @@ -1622,7 +1622,7 @@ Export canceled. - Reinstall your debian packages, or make sure you place them by hand. + Reinstall your Debian packages, or make sure you place them by hand. diff --git a/docker/README.rst b/docker/README.rst index dcad0ac6..70d44809 100644 --- a/docker/README.rst +++ b/docker/README.rst @@ -21,10 +21,10 @@ will be used to run bitmask. python dependencies, running bitmask, etc. -debian/ +Debian/ ------- -``apt-bitmask.sh`` script that installs bitmask from the debian packages. +``apt-bitmask.sh`` script that installs bitmask from the Debian packages. ``bitmask-on-docker.sh`` installs bitmask and runs it in a dummy X server, waits a little and takes a screenshot. @@ -32,7 +32,7 @@ waits a little and takes a screenshot. ``leap-experimental.key`` is needed by ``apt-bitmask.sh`` to ``apt-key add`` and verify apt sources. -``run-docker-for-bitmask.sh`` is a helper script that runs an ubuntu/debian +``run-docker-for-bitmask.sh`` is a helper script that runs an Ubuntu/Debian container ready to run the ``apt-bitmask.sh`` command, it does (among other stuff) X11 forwarding to display Bitmask UI on the host linux. diff --git a/docs/dev/environment.rst b/docs/dev/environment.rst index a3184b01..99600ec8 100644 --- a/docs/dev/environment.rst +++ b/docs/dev/environment.rst @@ -34,7 +34,7 @@ Bitmask depends on these base libraries: Debian ^^^^^^ -In debian-based systems, you can get everything you need: +In Debian-based systems, you can get everything you need: .. include:: quickstart.rst :start-after: begin-debian-deps @@ -82,7 +82,7 @@ Avoid compiling PySide inside a virtualenv If you attempt to install PySide inside a virtualenv as part of the rest of the dependencies using pip, basically it will take ages to compile. -As a workaround, you can run the following script after creating your virtualenv. It will symlink to your global PySide installation (*this is the recommended way if you are running a debian-based system*):: +As a workaround, you can run the following script after creating your virtualenv. It will symlink to your global PySide installation (*this is the recommended way if you are running a Debian-based system*):: (bitmask)$ pkg/postmkvenv.sh diff --git a/docs/dev/tests.rst b/docs/dev/tests.rst index d55c206a..bdefcfdc 100644 --- a/docs/dev/tests.rst +++ b/docs/dev/tests.rst @@ -16,7 +16,7 @@ Testing dependencies have a look at ``pkg/test-requirements.pip`` The ``./run_tests.sh`` command should install all of them in your virtualenv for you. -If you prefer to install them system wide, this should do in a debian system:: +If you prefer to install them system wide, this should do in a Debian system:: $ apt-get install python-nose python-mock python-coverage diff --git a/docs/pkg/debian.rst b/docs/pkg/debian.rst index 204d4073..13976e2b 100644 --- a/docs/pkg/debian.rst +++ b/docs/pkg/debian.rst @@ -3,7 +3,7 @@ Debian ====== -This section documents all related to the debian package. +This section documents all related to the Debian package. Dependencies diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index eb892cce..82a229ae 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -126,7 +126,7 @@ def check_missing(): 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. + # by overwriting some constant from within the Debian package. alert_missing = False complain_missing = True -- cgit v1.2.3 From 93d9469194ca567d14660572c65996b6330088b2 Mon Sep 17 00:00:00 2001 From: kwadronaut Date: Sat, 7 Nov 2015 17:08:45 +0100 Subject: Change title 'Missing Bitmask helpers' solves https://www.transifex.com/otf/bitmask/translate/#ar/$/31977726 --- src/leap/bitmask/platform_init/initializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index 82a229ae..4e8bbdbb 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -465,7 +465,7 @@ def _get_missing_complain_dialog(stuff): self.setLayout(mainLayout) msg = ComplainDialog() - msg.setWindowTitle(msg.tr("Missing Bitmask helpers")) + msg.setWindowTitle(msg.tr("Missing Bitmask helper files")) return msg -- cgit v1.2.3 From 077a36d20f697865cb334dd809ad9bd201adcb10 Mon Sep 17 00:00:00 2001 From: kwadronaut Date: Sat, 7 Nov 2015 17:47:27 +0100 Subject: solved transifex issues, language https://www.transifex.com/otf/bitmask/translate/#ar/$/31977726 https://www.transifex.com/otf/bitmask/translate/#nl/$/32117870 https://www.transifex.com/otf/bitmask/translate/#ar/$/22113277 https://www.transifex.com/otf/bitmask/translate/#nl/$/22113279 --- src/leap/bitmask/gui/eip_status.py | 2 +- src/leap/bitmask/gui/logwindow.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 64a408c4..470ef88a 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -733,7 +733,7 @@ class EIPStatusWidget(QtGui.QWidget): self.set_eip_status( # XXX this should change to polkit-kde where # applicable. - self.tr("We could not find any authentication agent in your " + self.tr("We could not find any authentication agent on your " "system.
Make sure you have " "polkit-gnome-authentication-agent-1 running and " "try again."), diff --git a/src/leap/bitmask/gui/logwindow.py b/src/leap/bitmask/gui/logwindow.py index 718269c9..5d8c99fc 100644 --- a/src/leap/bitmask/gui/logwindow.py +++ b/src/leap/bitmask/gui/logwindow.py @@ -173,7 +173,7 @@ class LoggerWindow(QtGui.QDialog): :type sending: bool """ if sending: - self.ui.btnPastebin.setText(self.tr("Sending to pastebin...")) + self.ui.btnPastebin.setText(self.tr("Sending to Pastebin.com…")) self.ui.btnPastebin.setEnabled(False) else: self.ui.btnPastebin.setText(self.tr("Send to Pastebin.com")) @@ -193,7 +193,7 @@ class LoggerWindow(QtGui.QDialog): # We save the dialog in an instance member to avoid dialog being # deleted right after we exit this method self._msgBox = msgBox = QtGui.QMessageBox( - QtGui.QMessageBox.Information, self.tr("Pastebin OK"), msg) + QtGui.QMessageBox.Information, self.tr("Pastebin is OK"), msg) msgBox.setWindowModality(QtCore.Qt.NonModal) msgBox.show() -- cgit v1.2.3 From 65f7d1baf31295803f935ac726f4ad95d3c3ca14 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 9 Nov 2015 12:04:54 -0300 Subject: [i18n] update source strings --- data/ts/en_US.ts | 111 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 58 insertions(+), 53 deletions(-) diff --git a/data/ts/en_US.ts b/data/ts/en_US.ts index b7cd6d26..dffb64d3 100644 --- a/data/ts/en_US.ts +++ b/data/ts/en_US.ts @@ -410,7 +410,7 @@ Export canceled. - 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. + We could not find any authentication agent on your system.<br/>Make sure you have <b>polkit-gnome-authentication-agent-1</b> running and try again. @@ -489,21 +489,11 @@ Export canceled. Send to Pastebin.com - - - Sending to pastebin... - - Your pastebin link <a href='{0}'>{0}</a> - - - Pastebin OK - - Sending logs to Pastebin failed! @@ -519,6 +509,16 @@ Export canceled. Maximum amount of submissions reached for today. + + + Sending to Pastebin.com… + + + + + Pastebin is OK + + LoginWidget @@ -641,165 +641,170 @@ Export canceled. - + You must login to use encrypted email. - + Email - + There was an unexpected problem with Soledad. - + OFF - + Mail is OFF - + Mail is starting - + ON - + Mail is ON - + Mail is disabled - + Starting... - + Soledad has started... - + Soledad is starting, please wait... - + Found key! Starting mail... - + Finished generating key! - + Starting mail... - + SMTP failed to start, check the logs. - + About to start, please wait... - + Disabled - + {0}: OFF - + You must be logged in to use {0}. - + Generating new key, this may take a few minutes. - + {0} Unread Emails in your Inbox - + 1 Unread Email in your Inbox - + Disconnecting... - + Invalid auth token, try logging in again. - + Initial sync in progress, please wait... - + Sync: downloading ({0:02}%) - + Sync: download completed. - + Sync: uploading ({0:02}%) - + Sync: upload complete. - + Sync: completed. - + Key not found... + + + Bitmask is ready to encrypt your email. Go to <a href="https://bitmask.net/en/help/email">https://bitmask.net/en/help/email</a> for email application setup instructions. + + MainWindow @@ -1060,13 +1065,13 @@ Export canceled. - - Version: <b>{ver}</b> ({ver_hash})<br><br>{greet}Bitmask is the Desktop client application for the LEAP platform, supporting encrypted internet proxy.<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> + + Alternatively, you can manually configure your mail client to use Bitmask Email with these options: - - Alternatively, you can manually configure your mail client to use Bitmask Email with these options: + + Version: <b>{ver}</b> ({ver_hash})<br><br>{greet}Bitmask is the Desktop client application for the LEAP platform, supporting Encrypted Internet Proxy and <a href='https://bitmask.net/help/email'> Encrypted Email</a>.<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> @@ -1597,11 +1602,6 @@ Export canceled. Missing helper files - - - Missing Bitmask helpers - - No polkit agent running @@ -1612,6 +1612,11 @@ Export canceled. There is no polkit agent running and it is needed to run the Bitmask services.<br>Take a look at the <a href="https://leap.se/en/docs/client/known-issues">known issues</a> page + + + Missing Bitmask helper files + + msgstr @@ -1622,7 +1627,7 @@ Export canceled. - Reinstall your Debian packages, or make sure you place them by hand. + Reinstall your debian packages, or make sure you place them by hand. -- cgit v1.2.3 From fca2246011b8c2402977fde93291d46a9a3438fb Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 9 Nov 2015 13:02:45 -0300 Subject: [bug] open email help link on browser - Resolves: #7585 --- changes/next-changelog.rst | 1 + src/leap/bitmask/gui/ui/mail_status.ui | 3 +++ 2 files changed, 4 insertions(+) diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst index 54b2b2ed..d1623d6f 100644 --- a/changes/next-changelog.rst +++ b/changes/next-changelog.rst @@ -18,6 +18,7 @@ Bugfixes ~~~~~~~~ - `#7568 `_: Fix typo on signal name. - `#7583 `_: Fix set_soledad_auth_token event callback signature. +- `#7585 `_: Open email help link on browser. - `#1235 `_: Description for the fixed stuff corresponding with issue #1235. - Bugfix without related issue number. diff --git a/src/leap/bitmask/gui/ui/mail_status.ui b/src/leap/bitmask/gui/ui/mail_status.ui index 3f103ef6..89e1843f 100644 --- a/src/leap/bitmask/gui/ui/mail_status.ui +++ b/src/leap/bitmask/gui/ui/mail_status.ui @@ -110,6 +110,9 @@ margin-top:5px; -1 + + true + -- cgit v1.2.3 From f4b0c01b6737e4266f12276e6b2a058bf3c0f743 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 11 Nov 2015 19:42:09 +0100 Subject: [but] Fix errback on InvalidAuthToken - Related: #7583 --- changes/next-changelog.rst | 1 + src/leap/bitmask/services/soledad/soledadbootstrapper.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst index d1623d6f..22af9bd8 100644 --- a/changes/next-changelog.rst +++ b/changes/next-changelog.rst @@ -19,6 +19,7 @@ Bugfixes - `#7568 `_: Fix typo on signal name. - `#7583 `_: Fix set_soledad_auth_token event callback signature. - `#7585 `_: Open email help link on browser. +- `#7598 `_: Fix errback on InvalidAuthToken. - `#1235 `_: Description for the fixed stuff corresponding with issue #1235. - Bugfix without related issue number. diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 60a2130b..a10a2731 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -654,7 +654,7 @@ class Syncer(object): logger.error('Invalid auth token while trying to sync Soledad') self._signaler.signal( self._signaler.soledad_invalid_auth_token) - self._callback_deferred.fail(failure) + self._callback_deferred.errback(failure) elif failure.check(sqlite_ProgrammingError, sqlcipher_ProgrammingError): logger.exception("%r" % (failure.value,)) -- cgit v1.2.3 From b6db2310f7595bdd6b93a2cab5680abbe5ce6599 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 10 Nov 2015 14:18:49 -0300 Subject: [i18n] do not translate https label text --- changes/next-changelog.rst | 1 + src/leap/bitmask/gui/ui/wizard.ui | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst index 22af9bd8..c40257b8 100644 --- a/changes/next-changelog.rst +++ b/changes/next-changelog.rst @@ -21,6 +21,7 @@ Bugfixes - `#7585 `_: Open email help link on browser. - `#7598 `_: Fix errback on InvalidAuthToken. - `#1235 `_: Description for the fixed stuff corresponding with issue #1235. +- Do not translate 'https' text on QLabel. - Bugfix without related issue number. Misc diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui index b125577e..37226d13 100644 --- a/src/leap/bitmask/gui/ui/wizard.ui +++ b/src/leap/bitmask/gui/ui/wizard.ui @@ -316,7 +316,7 @@ - https:// + https:// Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -339,7 +339,7 @@ - https:// + https:// Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter -- cgit v1.2.3 From 2a124d7352518cd2a08b01561eb6621b2bd74748 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 16 Nov 2015 14:54:20 -0300 Subject: [feat] use bitmask repo instead of leap.se Also map 'sana' (codename for kali linux 2.0) to jessie. --- docker/debian/apt-bitmask.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/debian/apt-bitmask.sh b/docker/debian/apt-bitmask.sh index 59226be1..99c5f07e 100755 --- a/docker/debian/apt-bitmask.sh +++ b/docker/debian/apt-bitmask.sh @@ -20,6 +20,7 @@ distro(){ ['qiana']='trusty' ['rebecca']='trusty' ['rafaela']='trusty' + ['sana']='jessie' ) # if name is in the above list -> replace @@ -116,8 +117,7 @@ else # $REPO == 'experimental' apt-key add leap-experimental.key fi -echo "deb http://deb.leap.se/$REPO $DISTRO main" > /etc/apt/sources.list.d/bitmask.list -echo "deb-src http://deb.leap.se/$REPO $DISTRO main" >> /etc/apt/sources.list.d/bitmask.list +echo "deb http://deb.bitmask.net/$REPO $DISTRO main" > /etc/apt/sources.list.d/bitmask.list apt-get update apt-get install -y bitmask -- cgit v1.2.3 From 97de43f5ef1b7fa4735e3371581bfaf4c3919de3 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 30 Nov 2015 12:12:57 -0400 Subject: [docs] fix outdated signature description --- src/leap/bitmask/services/mail/imap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py index 5934756d..b06c1d4a 100644 --- a/src/leap/bitmask/services/mail/imap.py +++ b/src/leap/bitmask/services/mail/imap.py @@ -61,7 +61,7 @@ def start_imap_service(*args, **kwargs): """ Initializes and run imap service. - :returns: twisted.internet.task.LoopingCall instance + :returns: LeapIMAPFactory instance """ from leap.bitmask.config import flags logger.debug('Launching imap service') -- cgit v1.2.3 From 8d3309701af51df3046aa0df10545dfa35a64ef7 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 30 Nov 2015 12:15:09 -0400 Subject: [feat] multi-user events. Adapt to users emitting the userid/uuid too. - Resolves: #7656 - Releases: 0.10.0 --- changes/next-changelog.rst | 3 ++- src/leap/bitmask/gui/mail_status.py | 11 ++++++++--- src/leap/bitmask/services/mail/conductor.py | 3 ++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst index c40257b8..77094e78 100644 --- a/changes/next-changelog.rst +++ b/changes/next-changelog.rst @@ -11,7 +11,8 @@ I've added a new category `Misc` so we can track doc/style/packaging stuff. Features ~~~~~~~~ - `#7552 `_: Improve UI message and add some margin above the msg box. -- `#1234 `_: Description of the new feature corresponding with issue #1234. +- `#7552 `_: Improve UI message and add some margin above the msg box. +- `#7656 `_: Adapt to multi-user aware events. - New feature without related issue number. Bugfixes diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index f7957c95..419a85c0 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -233,12 +233,15 @@ class MailStatusWidget(QtGui.QWidget): self._action_mail_status.setText(tray_status) self._update_systray_tooltip() - def _mail_handle_soledad_events(self, event, content): + def _mail_handle_soledad_events(self, event, user_data, content=""): """ Callback for handling events that are emitted from Soledad :param event: The event that triggered the callback. :type event: str + :param user_id: The user_data of the soledad user. Ignored right now, + since we're only contemplating single-user in soledad. + :type user_id: dict :param content: The content of the event. :type content: dict """ @@ -346,7 +349,7 @@ class MailStatusWidget(QtGui.QWidget): logger.warning("don't know to to handle %s" % (event,)) self._set_mail_status(ext_status, ready=1) - def _mail_handle_smtp_events(self, event): + def _mail_handle_smtp_events(self, event, content=""): """ Callback for the SMTP events @@ -380,12 +383,14 @@ class MailStatusWidget(QtGui.QWidget): # ----- XXX deprecate (move to mail conductor) - def _mail_handle_imap_events(self, event, content): + def _mail_handle_imap_events(self, event, uuid, content=""): """ Callback for the IMAP events :param event: The event that triggered the callback. :type event: str + :param uuid: The UUID for the user. Ignored right now. + :type uuid: str :param content: The content of the event. :type content: list """ diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index b79ea9c7..738531e6 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -73,12 +73,13 @@ class IMAPControl(object): self._backend.imap_stop_service() - def _handle_imap_events(self, event, content): + def _handle_imap_events(self, event, userid=None, content=None): """ Callback handler for the IMAP events :param event: The event that triggered the callback. :type event: str + :param userid: The user id of the logged in user. Ignored. :param content: The content of the event. :type content: list """ -- cgit v1.2.3 From 6c0299e7e2df65651ff8738fa18acbc08af18c32 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 30 Nov 2015 16:31:10 -0400 Subject: [style] autopep8 --- src/leap/bitmask/services/mail/conductor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 738531e6..b94b3cc8 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -34,6 +34,7 @@ class IMAPControl(object): """ Methods related to IMAP control. """ + def __init__(self): """ Initializes smtp variables. @@ -118,6 +119,7 @@ class IMAPControl(object): class SMTPControl(object): + def __init__(self): """ Initializes smtp variables. -- cgit v1.2.3 From 98384361a7c49ad4e0ff0127fd923a8b72cc910a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 8 Dec 2015 21:04:11 -0400 Subject: [feat] adapt to use cred-based authentication for imap This includes getting the token for the imap authentication, and displaying it on the help window. - Resolves: #4469 - Releases: 0.10.0 --- changes/next-changelog.rst | 5 ++-- src/leap/bitmask/backend/api.py | 2 ++ src/leap/bitmask/backend/components.py | 23 ++++++++++++++++++ src/leap/bitmask/backend/leapbackend.py | 7 ++++++ src/leap/bitmask/backend/leapsignaler.py | 1 + src/leap/bitmask/gui/mainwindow.py | 18 +++++++++++++- src/leap/bitmask/services/mail/imap.py | 31 ++++++++++++------------ src/leap/bitmask/services/mail/imapcontroller.py | 8 +++--- 8 files changed, 71 insertions(+), 24 deletions(-) diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst index 77094e78..c359b4e2 100644 --- a/changes/next-changelog.rst +++ b/changes/next-changelog.rst @@ -1,4 +1,4 @@ -0.9.2 - xxx +0.10.0 - xxx +++++++++++++++++++++++++++++++ Please add lines to this file, they will be moved to the CHANGELOG.rst during @@ -11,8 +11,9 @@ I've added a new category `Misc` so we can track doc/style/packaging stuff. Features ~~~~~~~~ - `#7552 `_: Improve UI message and add some margin above the msg box. -- `#7552 `_: Improve UI message and add some margin above the msg box. - `#7656 `_: Adapt to multi-user aware events. +- `#4469 `_: Display randomly generated service token on the Help Window. +- `#1234 `_: Description of the new feature corresponding with issue #1234. - New feature without related issue number. Bugfixes diff --git a/src/leap/bitmask/backend/api.py b/src/leap/bitmask/backend/api.py index 48aa2090..134a2d56 100644 --- a/src/leap/bitmask/backend/api.py +++ b/src/leap/bitmask/backend/api.py @@ -57,6 +57,7 @@ API = ( "soledad_change_password", "soledad_close", "soledad_load_offline", + "soledad_get_service_token", "tear_fw_down", "bitmask_root_vpn_down", "user_cancel_login", @@ -135,6 +136,7 @@ SIGNALS = ( "soledad_offline_finished", "soledad_password_change_error", "soledad_password_change_ok", + "soledad_got_service_token", "srp_auth_bad_user_or_password", "srp_auth_connection_error", "srp_auth_error", diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 5f34d290..a07d3bad 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -763,6 +763,7 @@ class Soledad(object): self._signaler = signaler self._soledad_bootstrapper = SoledadBootstrapper(signaler) self._soledad_defer = None + self._service_tokens = {} def bootstrap(self, username, domain, password): """ @@ -786,6 +787,7 @@ class Soledad(object): provider_config, username, password, download_if_needed=True) self._soledad_defer.addCallback(self._set_proxies_cb) + self._soledad_defer.addCallback(self._set_service_tokens_cb) else: if self._signaler is not None: self._signaler.signal(self._signaler.soledad_bootstrap_failed) @@ -793,6 +795,21 @@ class Soledad(object): return self._soledad_defer + def _set_service_tokens_cb(self, result): + + def register_imap_token(imap_token): + self._service_tokens['imap'] = imap_token + if self._signaler is not None: + self._signaler.signal( + self._signaler.soledad_got_service_token, + ('imap', imap_token)) + + sol = self._soledad_bootstrapper.soledad + d = sol.get_or_create_service_token('imap') + d.addCallback(register_imap_token) + d.addCallback(lambda _: result) + return d + def _set_proxies_cb(self, _): """ Update the soledad and keymanager proxies to reference the ones created @@ -803,6 +820,12 @@ class Soledad(object): zope.proxy.setProxiedObject(self._keymanager_proxy, self._soledad_bootstrapper.keymanager) + def get_service_token(self, service): + """ + Get an authentication token for a given service. + """ + return self._service_tokens.get(service, '') + def load_offline(self, username, password, uuid): """ Load the soledad database in offline mode. diff --git a/src/leap/bitmask/backend/leapbackend.py b/src/leap/bitmask/backend/leapbackend.py index cf45c4f8..d0668a1c 100644 --- a/src/leap/bitmask/backend/leapbackend.py +++ b/src/leap/bitmask/backend/leapbackend.py @@ -35,6 +35,7 @@ class LeapBackend(Backend): """ Backend server subclass, used to implement the API methods. """ + def __init__(self, bypass_checks=False, frontend_pid=None): """ Constructor for the backend. @@ -438,6 +439,12 @@ class LeapBackend(Backend): """ self._soledad.load_offline(username, password, uuid) + def soledad_get_service_token(self, service): + """ + Attempt to get an authentication token for a given service. + """ + self._soledad.get_service_token(service) + def soledad_cancel_bootstrap(self): """ Cancel the ongoing soledad bootstrapping process (if any). diff --git a/src/leap/bitmask/backend/leapsignaler.py b/src/leap/bitmask/backend/leapsignaler.py index 1ac51f5e..13a9fa5f 100644 --- a/src/leap/bitmask/backend/leapsignaler.py +++ b/src/leap/bitmask/backend/leapsignaler.py @@ -97,6 +97,7 @@ class LeapSignaler(SignalerQt): soledad_offline_finished = QtCore.Signal() soledad_password_change_error = QtCore.Signal() soledad_password_change_ok = QtCore.Signal() + soledad_got_service_token = QtCore.Signal(object) srp_auth_bad_user_or_password = QtCore.Signal() srp_auth_connection_error = QtCore.Signal() diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index a8a4e41d..189a6295 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -407,6 +407,10 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): sig.soledad_invalid_auth_token.connect( self._mail_status.set_soledad_invalid_auth_token) + self._service_tokens = {} + sig.soledad_got_service_token.connect( + self._set_service_tokens) + # TODO: connect this with something # sig.soledad_cancelled_bootstrap.connect() @@ -1033,6 +1037,13 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): msg = msg.format(ver=VERSION, ver_hash=VERSION_HASH[:10], greet=greet) QtGui.QMessageBox.about(self, title, msg) + def _set_service_tokens(self, data): + """ + Set the received service token. + """ + service, token = data + self._service_tokens[service] = token + def _help(self): """ TRIGGERS: @@ -1063,7 +1074,12 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): manual_imap = self.tr("IMAP: localhost, port {0}".format(IMAP_PORT)) manual_smtp = self.tr("SMTP: localhost, port {0}".format(smtp_port)) manual_username = self.tr("Username: your full email address") - manual_password = self.tr("Password: any non-empty text") + + # TODO this should be a widget that allows to be copied to the + # clipboard. + imap_token = (self._service_tokens.get('imap', None) + or "??? (log in to unlock)") + manual_password = self.tr("Password: ") + "%s" % (imap_token, ) msg = help_url + self.tr( "

{0}

" diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py index b06c1d4a..54935f8c 100644 --- a/src/leap/bitmask/services/mail/imap.py +++ b/src/leap/bitmask/services/mail/imap.py @@ -20,11 +20,14 @@ Initialization of imap service import os import sys +from twisted.python import log + from leap.bitmask.logs.utils import get_logger from leap.mail.constants import INBOX_NAME from leap.mail.imap.service import imap from leap.mail.incoming.service import IncomingMail, INCOMING_CHECK_PERIOD -from twisted.python import log +from leap.mail.mail import Account + logger = get_logger() @@ -57,11 +60,13 @@ def get_mail_check_period(): return period -def start_imap_service(*args, **kwargs): +def start_imap_service(soledad_sessions): """ Initializes and run imap service. - :returns: LeapIMAPFactory instance + :returns: the port as returned by the reactor when starts listening, and + the factory for the protocol. + :rtype: tuple """ from leap.bitmask.config import flags logger.debug('Launching imap service') @@ -70,10 +75,10 @@ def start_imap_service(*args, **kwargs): log.startLogging(open(flags.MAIL_LOGFILE, 'w')) log.startLogging(sys.stdout) - return imap.run_service(*args, **kwargs) + return imap.run_service(soledad_sessions) -def start_incoming_mail_service(keymanager, soledad, imap_factory, userid): +def start_incoming_mail_service(keymanager, soledad, userid): """ Initalizes and starts the incomming mail service. @@ -81,19 +86,13 @@ def start_incoming_mail_service(keymanager, soledad, imap_factory, userid): """ def setUpIncomingMail(inbox): incoming_mail = IncomingMail( - keymanager, - soledad, - inbox.collection, - userid, + keymanager, soledad, + inbox, userid, check_period=get_mail_check_period()) return incoming_mail - # XXX: do I really need to know here how to get a mailbox?? - # XXX: ideally, the parent service in mail would take care of initializing - # the account, and passing the mailbox to the incoming service. - # In an even better world, we just would subscribe to a channel that would - # pass us the serialized object to be inserted. - acc = imap_factory.theAccount - d = acc.callWhenReady(lambda _: acc.getMailbox(INBOX_NAME)) + acc = Account(soledad) + d = acc.callWhenReady(lambda _: acc.get_collection_by_mailbox(INBOX_NAME)) d.addCallback(setUpIncomingMail) + d.addErrback(log.err) return d diff --git a/src/leap/bitmask/services/mail/imapcontroller.py b/src/leap/bitmask/services/mail/imapcontroller.py index 5053d897..855fb74b 100644 --- a/src/leap/bitmask/services/mail/imapcontroller.py +++ b/src/leap/bitmask/services/mail/imapcontroller.py @@ -60,9 +60,9 @@ class IMAPController(object): """ logger.debug('Starting imap service') + soledad_sessions = {userid: self._soledad} self.imap_port, self.imap_factory = imap.start_imap_service( - self._soledad, - userid=userid) + soledad_sessions) def start_and_assign_incoming_service(incoming_mail): # this returns a deferred that will be called when the looping call @@ -74,9 +74,7 @@ class IMAPController(object): if offline is False: d = imap.start_incoming_mail_service( - self._keymanager, - self._soledad, - self.imap_factory, + self._keymanager, self._soledad, userid) d.addCallback(start_and_assign_incoming_service) d.addErrback(lambda f: logger.error(f.printTraceback())) -- cgit v1.2.3 From 7b80dd1fca9828331f3327c418913539a3a303c0 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 16 Dec 2015 15:33:25 -0400 Subject: [feat] adapt to use cred-based authentication for smtp --- changes/next-changelog.rst | 2 ++ src/leap/bitmask/backend/components.py | 14 +++++---- src/leap/bitmask/gui/mainwindow.py | 16 +++++++---- src/leap/bitmask/services/mail/smtpbootstrapper.py | 33 +++++++++++++--------- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst index c359b4e2..64774d30 100644 --- a/changes/next-changelog.rst +++ b/changes/next-changelog.rst @@ -13,6 +13,8 @@ Features - `#7552 `_: Improve UI message and add some margin above the msg box. - `#7656 `_: Adapt to multi-user aware events. - `#4469 `_: Display randomly generated service token on the Help Window. +- Use cred-based authentication on SMTP. + - `#1234 `_: Description of the new feature corresponding with issue #1234. - New feature without related issue number. diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index a07d3bad..e93ca19f 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -797,16 +797,19 @@ class Soledad(object): def _set_service_tokens_cb(self, result): - def register_imap_token(imap_token): - self._service_tokens['imap'] = imap_token + def register_service_token(token, service): + self._service_tokens[service] = token if self._signaler is not None: self._signaler.signal( self._signaler.soledad_got_service_token, - ('imap', imap_token)) + (service, token)) sol = self._soledad_bootstrapper.soledad d = sol.get_or_create_service_token('imap') - d.addCallback(register_imap_token) + d.addCallback(register_service_token, 'imap') + d.addCallback( + lambda _: sol.get_or_create_service_token('smtp')) + d.addCallback(register_service_token, 'smtp') d.addCallback(lambda _: result) return d @@ -1035,7 +1038,8 @@ class Mail(object): """ return threads.deferToThread( self._smtp_bootstrapper.start_smtp_service, - self._keymanager_proxy, full_user_id, download_if_needed) + self._soledad_proxy, self._keymanager_proxy, full_user_id, + download_if_needed) def start_imap_service(self, full_user_id, offline=False): """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 189a6295..759b454f 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1075,11 +1075,14 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): manual_smtp = self.tr("SMTP: localhost, port {0}".format(smtp_port)) manual_username = self.tr("Username: your full email address") - # TODO this should be a widget that allows to be copied to the - # clipboard. - imap_token = (self._service_tokens.get('imap', None) - or "??? (log in to unlock)") - manual_password = self.tr("Password: ") + "%s" % (imap_token, ) + # FIXME on i3, this doens't allow to mouse-select. + # Switch to a dialog in which we can set the QLabel + imap_token = (self._service_tokens.get('imap', None) or + "??? (log in to unlock)") + smtp_token = (self._service_tokens.get('smtp', None) or + "??? (log in to unlock)") + imap_password = self.tr("IMAP Password:") + " %s" % (imap_token,) + smtp_password = self.tr("SMTP Password:") + " %s" % (smtp_token,) msg = help_url + self.tr( "

{0}

" @@ -1090,9 +1093,10 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): "
  •  {4}
  • " "
  •  {5}
  • " "
  •  {6}
  • " + "
  •  {7}
  • " "

    ").format(email_quick_reference, thunderbird_text, manual_text, manual_imap, manual_smtp, - manual_username, manual_password) + manual_username, imap_password, smtp_password) QtGui.QMessageBox.about(self, self.tr("Bitmask Help"), msg) def _needs_update(self): diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py index a577509e..dadf59dd 100644 --- a/src/leap/bitmask/services/mail/smtpbootstrapper.py +++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py @@ -19,6 +19,7 @@ SMTP bootstrapping """ import os import warnings +from collections import namedtuple from requests.exceptions import HTTPError @@ -127,9 +128,6 @@ class SMTPBootstrapper(AbstractBootstrapper): Start the smtp service using the downloaded configurations. """ # TODO Make the encrypted_only configurable - # TODO pick local smtp port in a better way - # TODO remove hard-coded port and let leap.mail set - # the specific default. # TODO handle more than one host and define how to choose hosts = self._smtp_config.get_hosts() hostname = hosts.keys()[0] @@ -138,19 +136,25 @@ class SMTPBootstrapper(AbstractBootstrapper): client_cert_path = self._smtp_config.get_client_cert_path( self._userid, self._provider_config, about_to_download=True) - from leap.mail.smtp import setup_smtp_gateway + # XXX this should be defined in leap.mail.smtp, it's in bitmask.core + # right now. + SendmailOpts = namedtuple( + 'SendmailOpts', ['cert', 'key', 'hostname', 'port']) - self._smtp_service, self._smtp_port = setup_smtp_gateway( - port=2013, - userid=self._userid, - keymanager=self._keymanager, - smtp_host=host, - smtp_port=port, - smtp_cert=client_cert_path, - smtp_key=client_cert_path, - encrypted_only=False) + userid = self._userid + soledad_sessions = {userid: self._soledad} + keymanager_sessions = {userid: self._keymanager} - def start_smtp_service(self, keymanager, userid, download_if_needed=False): + key = cert = client_cert_path + opts = SendmailOpts(cert, key, host, port) + sendmail_opts = {userid: opts} + + from leap.mail.smtp import run_service + self._smtp_service, self._smtp_port = run_service( + soledad_sessions, keymanager_sessions, sendmail_opts) + + def start_smtp_service(self, soledad, keymanager, userid, + download_if_needed=False): """ Starts the SMTP service. @@ -170,6 +174,7 @@ class SMTPBootstrapper(AbstractBootstrapper): raise MalformedUserId() self._provider_config = ProviderConfig.get_provider_config(domain) + self._soledad = soledad self._keymanager = keymanager self._smtp_config = SMTPConfig() self._userid = str(userid) -- cgit v1.2.3 From e847bf6a59783611bf14fa7f480fabe58e0b13dd Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 18 Dec 2015 13:27:18 +0100 Subject: [bug] run make on bitmask_client when updating docker --- docker/leap_bootstrap.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker/leap_bootstrap.sh b/docker/leap_bootstrap.sh index 4f553ee7..a717ab6d 100755 --- a/docker/leap_bootstrap.sh +++ b/docker/leap_bootstrap.sh @@ -271,6 +271,10 @@ EOF install_dependencies setup_develop + cd $REPOS_ROOT/bitmask_client/ + make + cd - + set +x echo "${cc_green}Status: $status done!${cc_normal}" } -- cgit v1.2.3 From fa05e417d58f1cec68a78e235989f01181f9632f Mon Sep 17 00:00:00 2001 From: PaixuAabuizia Date: Sun, 24 Jan 2016 14:48:48 +0100 Subject: [bug] change backend process spawn method the original implementation causes problems in win32 (see #7240) possibly because pyinstaller cannot resolve the lambda correctly. using the documented multiprocessing.Process approach to pass parameters works here --- src/leap/bitmask/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index a1b7481a..982d8a13 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -223,9 +223,9 @@ def start_app(): backend_pid = None if not backend_running: frontend_pid = os.getpid() - backend = lambda: run_backend(opts.danger, flags_dict, frontend_pid) - backend_process = multiprocessing.Process(target=backend, - name='Backend') + backend_process = multiprocessing.Process(target=run_backend, + name='Backend', + args=(opts.danger, flags_dict, frontend_pid)) # we don't set the 'daemon mode' since we need to start child processes # in the backend # backend_process.daemon = True -- cgit v1.2.3 From 449de440ae1d3e533291ff6a2f41edd702c7746a Mon Sep 17 00:00:00 2001 From: PaixuAabuizia Date: Wed, 3 Feb 2016 11:59:52 +0100 Subject: [bug] frozen backend fails to spawn on windows according to [1] the backend should raise a Runtime Error, instead what happens is that the process is spawned again and again but never runs actual code. [1] https://docs.python.org/2.7/library/multiprocessing.html#multiprocessing.freeze_support --- src/leap/bitmask/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 982d8a13..dc1ee6df 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -237,4 +237,5 @@ def start_app(): if __name__ == "__main__": + multiprocessing.freeze_support() start_app() -- cgit v1.2.3 From 8e17fb43b4cbda9ee7b386d084e01fc99345f060 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 21 Dec 2015 15:48:00 +0100 Subject: [feat] use fingerprint instead of key_id to address keys --- src/leap/bitmask/backend/components.py | 3 ++- src/leap/bitmask/gui/advanced_key_management.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index e93ca19f..d8e4edbf 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -984,7 +984,8 @@ class Keymanager(object): List all the keys stored in the local DB. """ def signal_details(public_key): - details = (public_key.key_id, public_key.fingerprint) + # XXX: We should avoid the key-id + details = (public_key.fingerprint[-16:], public_key.fingerprint) self._signaler.signal(self._signaler.keymanager_key_details, details) diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py index 2e315d18..bc496a57 100644 --- a/src/leap/bitmask/gui/advanced_key_management.py +++ b/src/leap/bitmask/gui/advanced_key_management.py @@ -94,6 +94,7 @@ class AdvancedKeyManagement(QtGui.QDialog): """ Set the current user's key details into the gui. """ + # XXX: We should avoid the key-id self.ui.leKeyID.setText(details[0]) self.ui.leFingerprint.setText(details[1]) @@ -246,7 +247,7 @@ class AdvancedKeyManagement(QtGui.QDialog): row = keys_table.rowCount() keys_table.insertRow(row) keys_table.setItem(row, 0, QtGui.QTableWidgetItem(key.address)) - keys_table.setItem(row, 1, QtGui.QTableWidgetItem(key.key_id)) + keys_table.setItem(row, 1, QtGui.QTableWidgetItem(key.fingerprint)) def _backend_connect(self): """ -- cgit v1.2.3 From 9a658c81619af892a4b29e0122cd1c1ba2e428e4 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 4 Mar 2016 11:27:55 -0400 Subject: [pkg] move to versioneer 0.15 --- .gitattributes | 1 + MANIFEST.in | 1 + setup.cfg | 7 + setup.py | 15 +- src/leap/bitmask/__init__.py | 50 +- src/leap/bitmask/_version.py | 553 ++++++++---- versioneer.py | 1978 ++++++++++++++++++++++++++++++++---------- 7 files changed, 1933 insertions(+), 672 deletions(-) diff --git a/.gitattributes b/.gitattributes index eb8672e0..2b5f09d8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17,3 +17,4 @@ share/ export-ignore src/leap.egg-info/ export-ignore src/leap_client.egg-info export-ignore src/leap/_version.py export-subst +src/leap/bitmask/_version.py export-subst diff --git a/MANIFEST.in b/MANIFEST.in index 73355b99..5f1fc00a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -12,3 +12,4 @@ include src/leap/bitmask/crypto/tests/wrongcert.pem include src/leap/bitmask/gui/ui_*.py include src/leap/bitmask/gui/*_rc.py +include src/leap/bitmask/_version.py diff --git a/setup.cfg b/setup.cfg index 1a05d2c3..7732c1cc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,3 +14,10 @@ exclude = *_rc.py,ui_*,_version.py,docker,build,docs,pkg,versioneer.py,_binaries [flake8] ignore = E731 exclude = *_rc.py,ui_*,_version.py,docker,build,docs,pkg,versioneer.py,_binaries.py + +[versioneer] +VCS = git +style = pep440 +versionfile_source = src/leap/bitmask/_version.py +versionfile_build = leap/bitmask/_version.py +tag_prefix = diff --git a/setup.py b/setup.py index 8463c172..e85c4564 100755 --- a/setup.py +++ b/setup.py @@ -41,10 +41,6 @@ except ImportError: from pkg import utils import versioneer -versioneer.versionfile_source = 'src/leap/bitmask/_version.py' -versioneer.versionfile_build = 'leap/bitmask/_version.py' -versioneer.tag_prefix = '' # tags are like 1.2.0 -versioneer.parentdir_prefix = 'leap.bitmask-' # The following import avoids the premature unloading of the `util` submodule @@ -78,7 +74,7 @@ DOWNLOAD_BASE = ('https://github.com/leapcode/bitmask_client/' 'archive/%s.tar.gz') _versions = versioneer.get_versions() VERSION = _versions['version'] -VERSION_FULL = _versions['full'] +VERSION_REVISION = _versions['full-revisionid'] DOWNLOAD_URL = "" # get the short version for the download url @@ -108,12 +104,13 @@ class freeze_debianver(Command): # of this file. version_version = '{version}' -version_full = '{version_full}' +version_revisionid = '{version_revision}' """ templatefun = r""" def get_versions(default={}, verbose=False): - return {'version': version_version, 'full': version_full} + return {'version': version_version, + 'full-revisionid': version_revisionid} """ def initialize_options(self): @@ -130,7 +127,7 @@ def get_versions(default={}, verbose=False): return subst_template = self.template.format( version=VERSION_SHORT, - version_full=VERSION_FULL) + self.templatefun + version_full=VERSION_REVISION) + self.templatefun with open(versioneer.versionfile_source, 'w') as f: f.write(subst_template) @@ -264,7 +261,7 @@ cmdclass["hash_binaries"] = cmd_binary_hash # next two classes need to augment the versioneer modified ones -versioneer_build = cmdclass['build'] +versioneer_build = cmdclass['build_py'] versioneer_sdist = cmdclass['sdist'] diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py index 9ec5aae7..966ce91e 100644 --- a/src/leap/bitmask/__init__.py +++ b/src/leap/bitmask/__init__.py @@ -19,12 +19,6 @@ Init file for leap.bitmask Initializes version and app info. """ -import re - -from pkg_resources import parse_version - -from leap.bitmask.util import first - # HACK: This is a hack so that py2app copies _scrypt.so to the right # place, it can't be technically imported, but that doesn't matter # because the import is never executed @@ -32,7 +26,7 @@ if False: import _scrypt # noqa - skip 'not used' warning -def _is_release_version(version): +def _is_release_version(version_str): """ Helper to determine whether a version is a final release or not. The release needs to be of the form: w.x.y.z containing only numbers @@ -43,40 +37,12 @@ def _is_release_version(version): :returns: if the version is a release version or not. :rtype: bool """ - parsed_version = parse_version(version) - not_number = 0 - for x in parsed_version: - try: - int(x) - except: - not_number += 1 - - return not_number == 1 - - -__version__ = "unknown" -IS_RELEASE_VERSION = False - -__short_version__ = "unknown" - -try: - from leap.bitmask._version import get_versions - __version__ = get_versions()['version'] - __version_hash__ = get_versions()['full'] - IS_RELEASE_VERSION = _is_release_version(__version__) - del get_versions -except ImportError: - # running on a tree that has not run - # the setup.py setver - pass + parts = __version__.split('.') + patch = parts[2] + return patch.isdigit() -__appname__ = "unknown" -try: - from leap.bitmask._appname import __appname__ -except ImportError: - # running on a tree that has not run - # the setup.py setver - pass -__short_version__ = first(re.findall('\d+\.\d+\.\d+', __version__)) -__full_version__ = __appname__ + '/' + str(__version__) +from ._version import get_versions +__version__ = get_versions()['version'] +IS_RELEASE_VERSION = _is_release_version(__version__) +del get_versions diff --git a/src/leap/bitmask/_version.py b/src/leap/bitmask/_version.py index 412b0c9e..93700af1 100644 --- a/src/leap/bitmask/_version.py +++ b/src/leap/bitmask/_version.py @@ -1,201 +1,460 @@ -IN_LONG_VERSION_PY = True # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (build by setup.py sdist) and build +# feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. Generated by -# versioneer-0.7+ (https://github.com/warner/python-versioneer) - -# these strings will be replaced by git during git-archive -git_refnames = "$Format:%d$" -git_full = "$Format:%H$" - +# versioneer-0.15 (https://github.com/warner/python-versioneer) +import errno +import os +import re import subprocess import sys -import re -import os.path -def run_command(args, cwd=None, verbose=False): - try: - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd) - except EnvironmentError: - e = sys.exc_info()[1] +def get_keywords(): + # these strings will be replaced by git during git-archive. + # setup.py/versioneer.py will grep for the variable names, so they must + # each be defined on a line of their own. _version.py will just call + # get_keywords(). + git_refnames = "$Format:%d$" + git_full = "$Format:%H$" + keywords = {"refnames": git_refnames, "full": git_full} + return keywords + + +class VersioneerConfig: + pass + + +def get_config(): + # these strings are filled in when 'setup.py versioneer' creates + # _version.py + cfg = VersioneerConfig() + cfg.VCS = "git" + cfg.style = "pep440" + cfg.tag_prefix = "" + cfg.parentdir_prefix = "None" + cfg.versionfile_source = "src/leap/bitmask/_version.py" + cfg.verbose = False + return cfg + + +class NotThisMethod(Exception): + pass + + +LONG_VERSION_PY = {} +HANDLERS = {} + + +def register_vcs_handler(vcs, method): # decorator + def decorate(f): + if vcs not in HANDLERS: + HANDLERS[vcs] = {} + HANDLERS[vcs][method] = f + return f + return decorate + + +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + assert isinstance(commands, list) + p = None + for c in commands: + try: + dispcmd = str([c] + args) + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) + break + except EnvironmentError: + e = sys.exc_info()[1] + if e.errno == errno.ENOENT: + continue + if verbose: + print("unable to run %s" % dispcmd) + print(e) + return None + else: if verbose: - print("unable to run %s" % args[0]) - print(e) + print("unable to find command, tried %s" % (commands,)) return None stdout = p.communicate()[0].strip() - if sys.version >= '3': + if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: - print("unable to run %s (error)" % args[0]) + print("unable to run %s (error)" % dispcmd) return None return stdout -def get_expanded_variables(versionfile_source): +def versions_from_parentdir(parentdir_prefix, root, verbose): + # Source tarballs conventionally unpack into a directory that includes + # both the project name and a version string. + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print("guessing rootdir is '%s', but '%s' doesn't start with " + "prefix '%s'" % (root, dirname, parentdir_prefix)) + raise NotThisMethod("rootdir doesn't start with parentdir_prefix") + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None} + + +@register_vcs_handler("git", "get_keywords") +def git_get_keywords(versionfile_abs): # the code embedded in _version.py can just fetch the value of these - # variables. When used from setup.py, we don't want to import - # _version.py, so we do it with a regexp instead. This function is not - # used from _version.py. - variables = {} + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} try: - for line in open(versionfile_source, "r").readlines(): + f = open(versionfile_abs, "r") + for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["refnames"] = mo.group(1) + keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["full"] = mo.group(1) + keywords["full"] = mo.group(1) + f.close() except EnvironmentError: pass - return variables + return keywords -def versions_from_expanded_variables(variables, tag_prefix, verbose=False): - refnames = variables["refnames"].strip() +@register_vcs_handler("git", "keywords") +def git_versions_from_keywords(keywords, tag_prefix, verbose): + if not keywords: + raise NotThisMethod("no keywords at all, weird") + refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: - print("variables are unexpanded, not using") - return {} # unexpanded, so not in an unpacked git-archive tarball + print("keywords are unexpanded, not using") + raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) - for ref in list(refs): - if not re.search(r'\d', ref): - if verbose: - print("discarding '%s', no digits" % ref) - refs.discard(ref) - # Assume all version tags have a digit. git's %d expansion - # behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us - # distinguish between branches and tags. By ignoring refnames - # without digits, we filter out many common branch names like - # "release" and "stabilization", as well as "HEAD" and "master". + # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of + # just "foo-1.0". If we see a "tag: " prefix, prefer those. + TAG = "tag: " + tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + if not tags: + # Either we're using git < 1.8.3, or there really are no tags. We use + # a heuristic: assume all version tags have a digit. The old git %d + # expansion behaves like git log --decorate=short and strips out the + # refs/heads/ and refs/tags/ prefixes that would let us distinguish + # between branches and tags. By ignoring refnames without digits, we + # filter out many common branch names like "release" and + # "stabilization", as well as "HEAD" and "master". + tags = set([r for r in refs if re.search(r'\d', r)]) + if verbose: + print("discarding '%s', no digits" % ",".join(refs-tags)) if verbose: - print("remaining refs: %s" % ",".join(sorted(refs))) - for ref in sorted(refs): + print("likely tags: %s" % ",".join(sorted(tags))) + for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: print("picking %s" % r) return {"version": r, - "full": variables["full"].strip()} - # no suitable tags, so we use the full revision id + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": None + } + # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: - print("no suitable tags, using full revision id") - return {"version": variables["full"].strip(), - "full": variables["full"].strip()} - - -def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): - # this runs 'git' from the root of the source tree. That either means - # someone ran a setup.py command (and this code is in versioneer.py, so - # IN_LONG_VERSION_PY=False, thus the containing directory is the root of - # the source tree), or someone ran a project-specific entry point (and - # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the - # containing directory is somewhere deeper in the source tree). This only - # gets called if the git-archive 'subst' variables were *not* expanded, - # and _version.py hasn't already been rewritten with a short version - # string, meaning we're inside a checked out source tree. + print("no suitable tags, using unknown + full revision id") + return {"version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": "no suitable tags"} + + +@register_vcs_handler("git", "pieces_from_vcs") +def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): + # this runs 'git' from the root of the source tree. This only gets called + # if the git-archive 'subst' keywords were *not* expanded, and + # _version.py hasn't already been rewritten with a short version string, + # meaning we're inside a checked out source tree. - try: - here = os.path.abspath(__file__) - except NameError: - # some py2exe/bbfreeze/non-CPython implementations don't do __file__ - return {} # not always correct - - # versionfile_source is the relative path from the top of the source tree - # (where the .git directory might live) to this file. Invert this to find - # the root from __file__. - root = here - if IN_LONG_VERSION_PY: - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - else: - root = os.path.dirname(here) if not os.path.exists(os.path.join(root, ".git")): if verbose: print("no .git in %s" % root) - return {} + raise NotThisMethod("no .git directory") - GIT = "git" + GITS = ["git"] if sys.platform == "win32": - GIT = "git.cmd" - stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"], - cwd=root) - if stdout is None: - return {} - if not stdout.startswith(tag_prefix): - if verbose: - print("tag '%s' doesn't start with prefix '%s'" % ( - stdout, tag_prefix)) - return {} - tag = stdout[len(tag_prefix):] - stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) - if stdout is None: - return {} - full = stdout.strip() - if tag.endswith("-dirty"): - full += "-dirty" - return {"version": tag, "full": full} - - -def versions_from_parentdir(parentdir_prefix, versionfile_source, - verbose=False): - if IN_LONG_VERSION_PY: - # We're running from _version.py. If it's from a source tree - # (execute-in-place), we can work upwards to find the root of the - # tree, and then check the parent directory for a version string. If - # it's in an installed application, there's no hope. - try: - here = os.path.abspath(__file__) - except NameError: - # py2exe/bbfreeze/non-CPython don't have __file__ - return {} # without __file__, we have no hope + GITS = ["git.cmd", "git.exe"] + # if there is a tag, this yields TAG-NUM-gHEX[-dirty] + # if there are no tags, this yields HEX[-dirty] (no NUM) + describe_out = run_command(GITS, ["describe", "--tags", "--dirty", + "--always", "--long"], + cwd=root) + # --long was added in git-1.5.5 + if describe_out is None: + raise NotThisMethod("'git describe' failed") + describe_out = describe_out.strip() + full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + if full_out is None: + raise NotThisMethod("'git rev-parse' failed") + full_out = full_out.strip() + + pieces = {} + pieces["long"] = full_out + pieces["short"] = full_out[:7] # maybe improved later + pieces["error"] = None + + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] + # TAG might have hyphens. + git_describe = describe_out + + # look for -dirty suffix + dirty = git_describe.endswith("-dirty") + pieces["dirty"] = dirty + if dirty: + git_describe = git_describe[:git_describe.rindex("-dirty")] + + # now we have TAG-NUM-gHEX or HEX + + if "-" in git_describe: + # TAG-NUM-gHEX + mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + if not mo: + # unparseable. Maybe git-describe is misbehaving? + pieces["error"] = ("unable to parse git-describe output: '%s'" + % describe_out) + return pieces + + # tag + full_tag = mo.group(1) + if not full_tag.startswith(tag_prefix): + if verbose: + fmt = "tag '%s' doesn't start with prefix '%s'" + print(fmt % (full_tag, tag_prefix)) + pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" + % (full_tag, tag_prefix)) + return pieces + pieces["closest-tag"] = full_tag[len(tag_prefix):] + + # distance: number of commits since tag + pieces["distance"] = int(mo.group(2)) + + # commit: short hex revision ID + pieces["short"] = mo.group(3) + + else: + # HEX: no tags + pieces["closest-tag"] = None + count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], + cwd=root) + pieces["distance"] = int(count_out) # total number of commits + + return pieces + + +def plus_or_dot(pieces): + if "+" in pieces.get("closest-tag", ""): + return "." + return "+" + + +def render_pep440(pieces): + # now build up version string, with post-release "local version + # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + + # exceptions: + # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += plus_or_dot(pieces) + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def render_pep440_pre(pieces): + # TAG[.post.devDISTANCE] . No -dirty + + # exceptions: + # 1: no tags. 0.post.devDISTANCE + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += ".post.dev%d" % pieces["distance"] + else: + # exception #1 + rendered = "0.post.dev%d" % pieces["distance"] + return rendered + + +def render_pep440_post(pieces): + # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that + # .dev0 sorts backwards (a dirty tree will appear "older" than the + # corresponding clean one), but you shouldn't be releasing software with + # -dirty anyways. + + # exceptions: + # 1: no tags. 0.postDISTANCE[.dev0] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%s" % pieces["short"] + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] + return rendered + + +def render_pep440_old(pieces): + # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. + + # exceptions: + # 1: no tags. 0.postDISTANCE[.dev0] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + return rendered + + +def render_git_describe(pieces): + # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty + # --always' + + # exceptions: + # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render_git_describe_long(pieces): + # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty + # --always -long'. The distance/hash is unconditional. + + # exceptions: + # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render(pieces, style): + if pieces["error"]: + return {"version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"]} + + if not style or style == "default": + style = "pep440" # the default + + if style == "pep440": + rendered = render_pep440(pieces) + elif style == "pep440-pre": + rendered = render_pep440_pre(pieces) + elif style == "pep440-post": + rendered = render_pep440_post(pieces) + elif style == "pep440-old": + rendered = render_pep440_old(pieces) + elif style == "git-describe": + rendered = render_git_describe(pieces) + elif style == "git-describe-long": + rendered = render_git_describe_long(pieces) + else: + raise ValueError("unknown style '%s'" % style) + + return {"version": rendered, "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], "error": None} + + +def get_versions(): + # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have + # __file__, we can work backwards from there to the root. Some + # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which + # case we can only use expanded keywords. + + cfg = get_config() + verbose = cfg.verbose + + try: + return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, + verbose) + except NotThisMethod: + pass + + try: + root = os.path.realpath(__file__) # versionfile_source is the relative path from the top of the source - # tree to _version.py. Invert this to find the root from __file__. - root = here - for i in range(len(versionfile_source.split("/"))): + # tree (where the .git directory might live) to this file. Invert + # this to find the root from __file__. + for i in cfg.versionfile_source.split('/'): root = os.path.dirname(root) - else: - # we're running from versioneer.py, which means we're running from - # the setup.py in a source tree. sys.argv[0] is setup.py in the root. - here = os.path.abspath(sys.argv[0]) - root = os.path.dirname(here) + except NameError: + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to find root of source tree"} - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%s', but '%s' " - "doesn't start with prefix '%s'" % - (root, dirname, parentdir_prefix)) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} - -tag_prefix = "" -parentdir_prefix = "bitmask-" -versionfile_source = "src/leap/bitmask/_version.py" - - -def get_versions(default={"version": "unknown", "full": ""}, verbose=False): - variables = {"refnames": git_refnames, "full": git_full} - ver = versions_from_expanded_variables(variables, tag_prefix, verbose) - if not ver: - ver = versions_from_vcs(tag_prefix, versionfile_source, verbose) - if not ver: - ver = versions_from_parentdir(parentdir_prefix, versionfile_source, - verbose) - if not ver: - ver = default - return ver + try: + pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) + return render(pieces, cfg.style) + except NotThisMethod: + pass + + try: + if cfg.parentdir_prefix: + return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) + except NotThisMethod: + pass + + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to compute version"} diff --git a/versioneer.py b/versioneer.py index 885ebcfd..c010f63e 100644 --- a/versioneer.py +++ b/versioneer.py @@ -1,170 +1,611 @@ -#! /usr/bin/python -"""versioneer.py +# Version: 0.15 -(like a rocketeer, but for versions) +""" +The Versioneer +============== +* like a rocketeer, but for versions! * https://github.com/warner/python-versioneer * Brian Warner * License: Public Domain -* Version: 0.7+ - -This file helps distutils-based projects manage their version number by just -creating version-control tags. - -For developers who work from a VCS-generated tree (e.g. 'git clone' etc), -each 'setup.py version', 'setup.py build', 'setup.py sdist' will compute a -version number by asking your version-control tool about the current -checkout. The version number will be written into a generated _version.py -file of your choosing, where it can be included by your __init__.py - -For users who work from a VCS-generated tarball (e.g. 'git archive'), it will -compute a version number by looking at the name of the directory created when -te tarball is unpacked. This conventionally includes both the name of the -project and a version number. - -For users who work from a tarball built by 'setup.py sdist', it will get a -version number from a previously-generated _version.py file. - -As a result, loading code directly from the source tree will not result in a -real version. If you want real versions from VCS trees (where you frequently -update from the upstream repository, or do new development), you will need to -do a 'setup.py version' after each update, and load code from the build/ -directory. - -You need to provide this code with a few configuration values: - - versionfile_source: - A project-relative pathname into which the generated version strings - should be written. This is usually a _version.py next to your project's - main __init__.py file. If your project uses src/myproject/__init__.py, - this should be 'src/myproject/_version.py'. This file should be checked - in to your VCS as usual: the copy created below by 'setup.py - update_files' will include code that parses expanded VCS keywords in - generated tarballs. The 'build' and 'sdist' commands will replace it with - a copy that has just the calculated version string. - - versionfile_build: - Like versionfile_source, but relative to the build directory instead of - the source directory. These will differ when your setup.py uses - 'package_dir='. If you have package_dir={'myproject': 'src/myproject'}, - then you will probably have versionfile_build='myproject/_version.py' and - versionfile_source='src/myproject/_version.py'. - - tag_prefix: a string, like 'PROJECTNAME-', which appears at the start of all - VCS tags. If your tags look like 'myproject-1.2.0', then you - should use tag_prefix='myproject-'. If you use unprefixed tags - like '1.2.0', this should be an empty string. - - parentdir_prefix: a string, frequently the same as tag_prefix, which - appears at the start of all unpacked tarball filenames. If - your tarball unpacks into 'myproject-1.2.0', this should - be 'myproject-'. - -To use it: - - 1: include this file in the top level of your project - 2: make the following changes to the top of your setup.py: - import versioneer - versioneer.versionfile_source = 'src/myproject/_version.py' - versioneer.versionfile_build = 'myproject/_version.py' - versioneer.tag_prefix = '' # tags are like 1.2.0 - versioneer.parentdir_prefix = 'myproject-' # dirname like 'myproject-1.2.0' - 3: add the following arguments to the setup() call in your setup.py: - version=versioneer.get_version(), - cmdclass=versioneer.get_cmdclass(), - 4: run 'setup.py update_files', which will create _version.py, and will - modify your __init__.py to define __version__ (by calling a function - from _version.py) - 5: modify your MANIFEST.in to include versioneer.py - 6: add both versioneer.py and the generated _version.py to your VCS -""" +* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, and pypy +* [![Latest Version] +(https://pypip.in/version/versioneer/badge.svg?style=flat) +](https://pypi.python.org/pypi/versioneer/) +* [![Build Status] +(https://travis-ci.org/warner/python-versioneer.png?branch=master) +](https://travis-ci.org/warner/python-versioneer) + +This is a tool for managing a recorded version number in distutils-based +python projects. The goal is to remove the tedious and error-prone "update +the embedded version string" step from your release process. Making a new +release should be as easy as recording a new tag in your version-control +system, and maybe making new tarballs. + + +## Quick Install + +* `pip install versioneer` to somewhere to your $PATH +* add a `[versioneer]` section to your setup.cfg (see below) +* run `versioneer install` in your source tree, commit the results + +## Version Identifiers + +Source trees come from a variety of places: + +* a version-control system checkout (mostly used by developers) +* a nightly tarball, produced by build automation +* a snapshot tarball, produced by a web-based VCS browser, like github's + "tarball from tag" feature +* a release tarball, produced by "setup.py sdist", distributed through PyPI + +Within each source tree, the version identifier (either a string or a number, +this tool is format-agnostic) can come from a variety of places: + +* ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows + about recent "tags" and an absolute revision-id +* the name of the directory into which the tarball was unpacked +* an expanded VCS keyword ($Id$, etc) +* a `_version.py` created by some earlier build step + +For released software, the version identifier is closely related to a VCS +tag. Some projects use tag names that include more than just the version +string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool +needs to strip the tag prefix to extract the version identifier. For +unreleased software (between tags), the version identifier should provide +enough information to help developers recreate the same tree, while also +giving them an idea of roughly how old the tree is (after version 1.2, before +version 1.3). Many VCS systems can report a description that captures this, +for example `git describe --tags --dirty --always` reports things like +"0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the +0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has +uncommitted changes. + +The version identifier is used for multiple purposes: + +* to allow the module to self-identify its version: `myproject.__version__` +* to choose a name and prefix for a 'setup.py sdist' tarball + +## Theory of Operation + +Versioneer works by adding a special `_version.py` file into your source +tree, where your `__init__.py` can import it. This `_version.py` knows how to +dynamically ask the VCS tool for version information at import time. + +`_version.py` also contains `$Revision$` markers, and the installation +process marks `_version.py` to have this marker rewritten with a tag name +during the `git archive` command. As a result, generated tarballs will +contain enough information to get the proper version. + +To allow `setup.py` to compute a version too, a `versioneer.py` is added to +the top level of your source tree, next to `setup.py` and the `setup.cfg` +that configures it. This overrides several distutils/setuptools commands to +compute the version when invoked, and changes `setup.py build` and `setup.py +sdist` to replace `_version.py` with a small static file that contains just +the generated version data. + +## Installation + +First, decide on values for the following configuration variables: + +* `VCS`: the version control system you use. Currently accepts "git". + +* `style`: the style of version string to be produced. See "Styles" below for + details. Defaults to "pep440", which looks like + `TAG[+DISTANCE.gSHORTHASH[.dirty]]`. + +* `versionfile_source`: + + A project-relative pathname into which the generated version strings should + be written. This is usually a `_version.py` next to your project's main + `__init__.py` file, so it can be imported at runtime. If your project uses + `src/myproject/__init__.py`, this should be `src/myproject/_version.py`. + This file should be checked in to your VCS as usual: the copy created below + by `setup.py setup_versioneer` will include code that parses expanded VCS + keywords in generated tarballs. The 'build' and 'sdist' commands will + replace it with a copy that has just the calculated version string. + + This must be set even if your project does not have any modules (and will + therefore never import `_version.py`), since "setup.py sdist" -based trees + still need somewhere to record the pre-calculated version strings. Anywhere + in the source tree should do. If there is a `__init__.py` next to your + `_version.py`, the `setup.py setup_versioneer` command (described below) + will append some `__version__`-setting assignments, if they aren't already + present. + +* `versionfile_build`: + + Like `versionfile_source`, but relative to the build directory instead of + the source directory. These will differ when your setup.py uses + 'package_dir='. If you have `package_dir={'myproject': 'src/myproject'}`, + then you will probably have `versionfile_build='myproject/_version.py'` and + `versionfile_source='src/myproject/_version.py'`. + + If this is set to None, then `setup.py build` will not attempt to rewrite + any `_version.py` in the built tree. If your project does not have any + libraries (e.g. if it only builds a script), then you should use + `versionfile_build = None` and override `distutils.command.build_scripts` + to explicitly insert a copy of `versioneer.get_version()` into your + generated script. + +* `tag_prefix`: + + a string, like 'PROJECTNAME-', which appears at the start of all VCS tags. + If your tags look like 'myproject-1.2.0', then you should use + tag_prefix='myproject-'. If you use unprefixed tags like '1.2.0', this + should be an empty string. + +* `parentdir_prefix`: + + a optional string, frequently the same as tag_prefix, which appears at the + start of all unpacked tarball filenames. If your tarball unpacks into + 'myproject-1.2.0', this should be 'myproject-'. To disable this feature, + just omit the field from your `setup.cfg`. + +This tool provides one script, named `versioneer`. That script has one mode, +"install", which writes a copy of `versioneer.py` into the current directory +and runs `versioneer.py setup` to finish the installation. + +To versioneer-enable your project: + +* 1: Modify your `setup.cfg`, adding a section named `[versioneer]` and + populating it with the configuration values you decided earlier (note that + the option names are not case-sensitive): + + ```` + [versioneer] + VCS = git + style = pep440 + versionfile_source = src/myproject/_version.py + versionfile_build = myproject/_version.py + tag_prefix = "" + parentdir_prefix = myproject- + ```` + +* 2: Run `versioneer install`. This will do the following: + + * copy `versioneer.py` into the top of your source tree + * create `_version.py` in the right place (`versionfile_source`) + * modify your `__init__.py` (if one exists next to `_version.py`) to define + `__version__` (by calling a function from `_version.py`) + * modify your `MANIFEST.in` to include both `versioneer.py` and the + generated `_version.py` in sdist tarballs + + `versioneer install` will complain about any problems it finds with your + `setup.py` or `setup.cfg`. Run it multiple times until you have fixed all + the problems. + +* 3: add a `import versioneer` to your setup.py, and add the following + arguments to the setup() call: + + version=versioneer.get_version(), + cmdclass=versioneer.get_cmdclass(), + +* 4: commit these changes to your VCS. To make sure you won't forget, + `versioneer install` will mark everything it touched for addition using + `git add`. Don't forget to add `setup.py` and `setup.cfg` too. + +## Post-Installation Usage + +Once established, all uses of your tree from a VCS checkout should get the +current version string. All generated tarballs should include an embedded +version string (so users who unpack them will not need a VCS tool installed). + +If you distribute your project through PyPI, then the release process should +boil down to two steps: + +* 1: git tag 1.0 +* 2: python setup.py register sdist upload + +If you distribute it through github (i.e. users use github to generate +tarballs with `git archive`), the process is: + +* 1: git tag 1.0 +* 2: git push; git push --tags + +Versioneer will report "0+untagged.NUMCOMMITS.gHASH" until your tree has at +least one tag in its history. + +## Version-String Flavors + +Code which uses Versioneer can learn about its version string at runtime by +importing `_version` from your main `__init__.py` file and running the +`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can +import the top-level `versioneer.py` and run `get_versions()`. + +Both functions return a dictionary with different flavors of version +information: + +* `['version']`: A condensed version string, rendered using the selected + style. This is the most commonly used value for the project's version + string. The default "pep440" style yields strings like `0.11`, + `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section + below for alternative styles. + +* `['full-revisionid']`: detailed revision identifier. For Git, this is the + full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". + +* `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that + this is only accurate if run in a VCS checkout, otherwise it is likely to + be False or None + +* `['error']`: if the version string could not be computed, this will be set + to a string describing the problem, otherwise it will be None. It may be + useful to throw an exception in setup.py if this is set, to avoid e.g. + creating tarballs with a version string of "unknown". + +Some variants are more useful than others. Including `full-revisionid` in a +bug report should allow developers to reconstruct the exact code being tested +(or indicate the presence of local changes that should be shared with the +developers). `version` is suitable for display in an "about" box or a CLI +`--version` output: it can be easily compared against release notes and lists +of bugs fixed in various releases. + +The installer adds the following text to your `__init__.py` to place a basic +version in `YOURPROJECT.__version__`: + + from ._version import get_versions + __version__ = get_versions()['version'] + del get_versions + +## Styles + +The setup.cfg `style=` configuration controls how the VCS information is +rendered into a version string. + +The default style, "pep440", produces a PEP440-compliant string, equal to the +un-prefixed tag name for actual releases, and containing an additional "local +version" section with more detail for in-between builds. For Git, this is +TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags +--dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the +tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and +that this commit is two revisions ("+2") beyond the "0.11" tag. For released +software (exactly equal to a known tag), the identifier will only contain the +stripped tag, e.g. "0.11". + +Other styles are available. See details.md in the Versioneer source tree for +descriptions. + +## Debugging + +Versioneer tries to avoid fatal errors: if something goes wrong, it will tend +to return a version of "0+unknown". To investigate the problem, run `setup.py +version`, which will run the version-lookup code in a verbose mode, and will +display the full contents of `get_versions()` (including the `error` string, +which may help identify what went wrong). + +## Updating Versioneer + +To upgrade your project to a new release of Versioneer, do the following: + +* install the new Versioneer (`pip install -U versioneer` or equivalent) +* edit `setup.cfg`, if necessary, to include any new configuration settings + indicated by the release notes +* re-run `versioneer install` in your source tree, to replace + `SRC/_version.py` +* commit any changed files + +### Upgrading to 0.15 + +Starting with this version, Versioneer is configured with a `[versioneer]` +section in your `setup.cfg` file. Earlier versions required the `setup.py` to +set attributes on the `versioneer` module immediately after import. The new +version will refuse to run (raising an exception during import) until you +have provided the necessary `setup.cfg` section. + +In addition, the Versioneer package provides an executable named +`versioneer`, and the installation process is driven by running `versioneer +install`. In 0.14 and earlier, the executable was named +`versioneer-installer` and was run without an argument. + +### Upgrading to 0.14 + +0.14 changes the format of the version string. 0.13 and earlier used +hyphen-separated strings like "0.11-2-g1076c97-dirty". 0.14 and beyond use a +plus-separated "local version" section strings, with dot-separated +components, like "0.11+2.g1076c97". PEP440-strict tools did not like the old +format, but should be ok with the new one. + +### Upgrading from 0.11 to 0.12 + +Nothing special. + +### Upgrading from 0.10 to 0.11 -import os, sys, re -from distutils.core import Command -from distutils.command.sdist import sdist as _sdist -from distutils.command.build import build as _build +You must add a `versioneer.VCS = "git"` to your `setup.py` before re-running +`setup.py setup_versioneer`. This will enable the use of additional +version-control systems (SVN, etc) in the future. -versionfile_source = None -versionfile_build = None -tag_prefix = None -parentdir_prefix = None +## Future Directions -VCS = "git" -IN_LONG_VERSION_PY = False +This tool is designed to make it easily extended to other version-control +systems: all VCS-specific components are in separate directories like +src/git/ . The top-level `versioneer.py` script is assembled from these +components by running make-versioneer.py . In the future, make-versioneer.py +will take a VCS name as an argument, and will construct a version of +`versioneer.py` that is specific to the given VCS. It might also take the +configuration arguments that are currently provided manually during +installation by editing setup.py . Alternatively, it might go the other +direction and include code from all supported VCS systems, reducing the +number of intermediate scripts. -LONG_VERSION_PY = ''' -IN_LONG_VERSION_PY = True +## License + +To make Versioneer easier to embed, all its code is hereby released into the +public domain. The `_version.py` that it creates is also in the public +domain. + +""" + +from __future__ import print_function +try: + import configparser +except ImportError: + import ConfigParser as configparser +import errno +import json +import os +import re +import subprocess +import sys + + +class VersioneerConfig: + pass + + +def get_root(): + # we require that all commands are run from the project root, i.e. the + # directory that contains setup.py, setup.cfg, and versioneer.py . + root = os.path.realpath(os.path.abspath(os.getcwd())) + setup_py = os.path.join(root, "setup.py") + versioneer_py = os.path.join(root, "versioneer.py") + if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): + # allow 'python path/to/setup.py COMMAND' + root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) + setup_py = os.path.join(root, "setup.py") + versioneer_py = os.path.join(root, "versioneer.py") + if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): + err = ("Versioneer was unable to run the project root directory. " + "Versioneer requires setup.py to be executed from " + "its immediate directory (like 'python setup.py COMMAND'), " + "or in a way that lets it use sys.argv[0] to find the root " + "(like 'python path/to/setup.py COMMAND').") + raise VersioneerBadRootError(err) + try: + # Certain runtime workflows (setup.py install/develop in a setuptools + # tree) execute all dependencies in a single python process, so + # "versioneer" may be imported multiple times, and python's shared + # module-import table will cache the first one. So we can't use + # os.path.dirname(__file__), as that will find whichever + # versioneer.py was first imported, even in later projects. + me = os.path.realpath(os.path.abspath(__file__)) + if os.path.splitext(me)[0] != os.path.splitext(versioneer_py)[0]: + print("Warning: build in %s is using versioneer.py from %s" + % (os.path.dirname(me), versioneer_py)) + except NameError: + pass + return root + + +def get_config_from_root(root): + # This might raise EnvironmentError (if setup.cfg is missing), or + # configparser.NoSectionError (if it lacks a [versioneer] section), or + # configparser.NoOptionError (if it lacks "VCS="). See the docstring at + # the top of versioneer.py for instructions on writing your setup.cfg . + setup_cfg = os.path.join(root, "setup.cfg") + parser = configparser.SafeConfigParser() + with open(setup_cfg, "r") as f: + parser.readfp(f) + VCS = parser.get("versioneer", "VCS") # mandatory + + def get(parser, name): + if parser.has_option("versioneer", name): + return parser.get("versioneer", name) + return None + cfg = VersioneerConfig() + cfg.VCS = VCS + cfg.style = get(parser, "style") or "" + cfg.versionfile_source = get(parser, "versionfile_source") + cfg.versionfile_build = get(parser, "versionfile_build") + cfg.tag_prefix = get(parser, "tag_prefix") + cfg.parentdir_prefix = get(parser, "parentdir_prefix") + cfg.verbose = get(parser, "verbose") + return cfg + + +class NotThisMethod(Exception): + pass + +# these dictionaries contain VCS-specific tools +LONG_VERSION_PY = {} +HANDLERS = {} + + +def register_vcs_handler(vcs, method): # decorator + def decorate(f): + if vcs not in HANDLERS: + HANDLERS[vcs] = {} + HANDLERS[vcs][method] = f + return f + return decorate + + +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + assert isinstance(commands, list) + p = None + for c in commands: + try: + dispcmd = str([c] + args) + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) + break + except EnvironmentError: + e = sys.exc_info()[1] + if e.errno == errno.ENOENT: + continue + if verbose: + print("unable to run %s" % dispcmd) + print(e) + return None + else: + if verbose: + print("unable to find command, tried %s" % (commands,)) + return None + stdout = p.communicate()[0].strip() + if sys.version_info[0] >= 3: + stdout = stdout.decode() + if p.returncode != 0: + if verbose: + print("unable to run %s (error)" % dispcmd) + return None + return stdout +LONG_VERSION_PY['git'] = ''' # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (build by setup.py sdist) and build +# feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. Generated by -# versioneer-0.7+ (https://github.com/warner/python-versioneer) - -# these strings will be replaced by git during git-archive -git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" -git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" - +# versioneer-0.15 (https://github.com/warner/python-versioneer) +import errno +import os +import re import subprocess import sys -def run_command(args, cwd=None, verbose=False): - try: - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd) - except EnvironmentError: - e = sys.exc_info()[1] + +def get_keywords(): + # these strings will be replaced by git during git-archive. + # setup.py/versioneer.py will grep for the variable names, so they must + # each be defined on a line of their own. _version.py will just call + # get_keywords(). + git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" + git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" + keywords = {"refnames": git_refnames, "full": git_full} + return keywords + + +class VersioneerConfig: + pass + + +def get_config(): + # these strings are filled in when 'setup.py versioneer' creates + # _version.py + cfg = VersioneerConfig() + cfg.VCS = "git" + cfg.style = "%(STYLE)s" + cfg.tag_prefix = "%(TAG_PREFIX)s" + cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" + cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" + cfg.verbose = False + return cfg + + +class NotThisMethod(Exception): + pass + + +LONG_VERSION_PY = {} +HANDLERS = {} + + +def register_vcs_handler(vcs, method): # decorator + def decorate(f): + if vcs not in HANDLERS: + HANDLERS[vcs] = {} + HANDLERS[vcs][method] = f + return f + return decorate + + +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + assert isinstance(commands, list) + p = None + for c in commands: + try: + dispcmd = str([c] + args) + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) + break + except EnvironmentError: + e = sys.exc_info()[1] + if e.errno == errno.ENOENT: + continue + if verbose: + print("unable to run %%s" %% dispcmd) + print(e) + return None + else: if verbose: - print("unable to run %%s" %% args[0]) - print(e) + print("unable to find command, tried %%s" %% (commands,)) return None stdout = p.communicate()[0].strip() - if sys.version >= '3': + if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: - print("unable to run %%s (error)" %% args[0]) + print("unable to run %%s (error)" %% dispcmd) return None return stdout -import sys -import re -import os.path +def versions_from_parentdir(parentdir_prefix, root, verbose): + # Source tarballs conventionally unpack into a directory that includes + # both the project name and a version string. + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print("guessing rootdir is '%%s', but '%%s' doesn't start with " + "prefix '%%s'" %% (root, dirname, parentdir_prefix)) + raise NotThisMethod("rootdir doesn't start with parentdir_prefix") + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None} + -def get_expanded_variables(versionfile_source): +@register_vcs_handler("git", "get_keywords") +def git_get_keywords(versionfile_abs): # the code embedded in _version.py can just fetch the value of these - # variables. When used from setup.py, we don't want to import - # _version.py, so we do it with a regexp instead. This function is not - # used from _version.py. - variables = {} + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} try: - f = open(versionfile_source,"r") + f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["refnames"] = mo.group(1) + keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["full"] = mo.group(1) + keywords["full"] = mo.group(1) f.close() except EnvironmentError: pass - return variables + return keywords + -def versions_from_expanded_variables(variables, tag_prefix, verbose=False): - refnames = variables["refnames"].strip() +@register_vcs_handler("git", "keywords") +def git_versions_from_keywords(keywords, tag_prefix, verbose): + if not keywords: + raise NotThisMethod("no keywords at all, weird") + refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: - print("variables are unexpanded, not using") - return {} # unexpanded, so not in an unpacked git-archive tarball + print("keywords are unexpanded, not using") + raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. @@ -189,172 +630,336 @@ def versions_from_expanded_variables(variables, tag_prefix, verbose=False): r = ref[len(tag_prefix):] if verbose: print("picking %%s" %% r) - return { "version": r, - "full": variables["full"].strip() } - # no suitable tags, so we use the full revision id + return {"version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": None + } + # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: - print("no suitable tags, using full revision id") - return { "version": variables["full"].strip(), - "full": variables["full"].strip() } - -def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): - # this runs 'git' from the root of the source tree. That either means - # someone ran a setup.py command (and this code is in versioneer.py, so - # IN_LONG_VERSION_PY=False, thus the containing directory is the root of - # the source tree), or someone ran a project-specific entry point (and - # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the - # containing directory is somewhere deeper in the source tree). This only - # gets called if the git-archive 'subst' variables were *not* expanded, - # and _version.py hasn't already been rewritten with a short version - # string, meaning we're inside a checked out source tree. + print("no suitable tags, using unknown + full revision id") + return {"version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": "no suitable tags"} + + +@register_vcs_handler("git", "pieces_from_vcs") +def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): + # this runs 'git' from the root of the source tree. This only gets called + # if the git-archive 'subst' keywords were *not* expanded, and + # _version.py hasn't already been rewritten with a short version string, + # meaning we're inside a checked out source tree. - try: - here = os.path.abspath(__file__) - except NameError: - # some py2exe/bbfreeze/non-CPython implementations don't do __file__ - return {} # not always correct - - # versionfile_source is the relative path from the top of the source tree - # (where the .git directory might live) to this file. Invert this to find - # the root from __file__. - root = here - if IN_LONG_VERSION_PY: - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - else: - root = os.path.dirname(here) if not os.path.exists(os.path.join(root, ".git")): if verbose: print("no .git in %%s" %% root) - return {} + raise NotThisMethod("no .git directory") - GIT = "git" + GITS = ["git"] if sys.platform == "win32": - GIT = "git.exe" - stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"], - cwd=root) - if stdout is None: - return {} - if not stdout.startswith(tag_prefix): - if verbose: - print("tag '%%s' doesn't start with prefix '%%s'" %% (stdout, tag_prefix)) - return {} - tag = stdout[len(tag_prefix):] - stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) - if stdout is None: - return {} - full = stdout.strip() - if tag.endswith("-dirty"): - full += "-dirty" - return {"version": tag, "full": full} - - -def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False): - if IN_LONG_VERSION_PY: - # We're running from _version.py. If it's from a source tree - # (execute-in-place), we can work upwards to find the root of the - # tree, and then check the parent directory for a version string. If - # it's in an installed application, there's no hope. - try: - here = os.path.abspath(__file__) - except NameError: - # py2exe/bbfreeze/non-CPython don't have __file__ - return {} # without __file__, we have no hope - # versionfile_source is the relative path from the top of the source - # tree to _version.py. Invert this to find the root from __file__. - root = here - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) + GITS = ["git.cmd", "git.exe"] + # if there is a tag, this yields TAG-NUM-gHEX[-dirty] + # if there are no tags, this yields HEX[-dirty] (no NUM) + describe_out = run_command(GITS, ["describe", "--tags", "--dirty", + "--always", "--long"], + cwd=root) + # --long was added in git-1.5.5 + if describe_out is None: + raise NotThisMethod("'git describe' failed") + describe_out = describe_out.strip() + full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + if full_out is None: + raise NotThisMethod("'git rev-parse' failed") + full_out = full_out.strip() + + pieces = {} + pieces["long"] = full_out + pieces["short"] = full_out[:7] # maybe improved later + pieces["error"] = None + + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] + # TAG might have hyphens. + git_describe = describe_out + + # look for -dirty suffix + dirty = git_describe.endswith("-dirty") + pieces["dirty"] = dirty + if dirty: + git_describe = git_describe[:git_describe.rindex("-dirty")] + + # now we have TAG-NUM-gHEX or HEX + + if "-" in git_describe: + # TAG-NUM-gHEX + mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + if not mo: + # unparseable. Maybe git-describe is misbehaving? + pieces["error"] = ("unable to parse git-describe output: '%%s'" + %% describe_out) + return pieces + + # tag + full_tag = mo.group(1) + if not full_tag.startswith(tag_prefix): + if verbose: + fmt = "tag '%%s' doesn't start with prefix '%%s'" + print(fmt %% (full_tag, tag_prefix)) + pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" + %% (full_tag, tag_prefix)) + return pieces + pieces["closest-tag"] = full_tag[len(tag_prefix):] + + # distance: number of commits since tag + pieces["distance"] = int(mo.group(2)) + + # commit: short hex revision ID + pieces["short"] = mo.group(3) + else: - # we're running from versioneer.py, which means we're running from - # the setup.py in a source tree. sys.argv[0] is setup.py in the root. - here = os.path.abspath(sys.argv[0]) - root = os.path.dirname(here) + # HEX: no tags + pieces["closest-tag"] = None + count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], + cwd=root) + pieces["distance"] = int(count_out) # total number of commits - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%%s', but '%%s' doesn't start with prefix '%%s'" %% - (root, dirname, parentdir_prefix)) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} - -tag_prefix = "%(TAG_PREFIX)s" -parentdir_prefix = "%(PARENTDIR_PREFIX)s" -versionfile_source = "%(VERSIONFILE_SOURCE)s" - -def get_versions(default={"version": "unknown", "full": ""}, verbose=False): - variables = { "refnames": git_refnames, "full": git_full } - ver = versions_from_expanded_variables(variables, tag_prefix, verbose) - if not ver: - ver = versions_from_vcs(tag_prefix, versionfile_source, verbose) - if not ver: - ver = versions_from_parentdir(parentdir_prefix, versionfile_source, - verbose) - if not ver: - ver = default - return ver + return pieces -''' +def plus_or_dot(pieces): + if "+" in pieces.get("closest-tag", ""): + return "." + return "+" -import subprocess -import sys -def run_command(args, cwd=None, verbose=False): +def render_pep440(pieces): + # now build up version string, with post-release "local version + # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + + # exceptions: + # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += plus_or_dot(pieces) + rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def render_pep440_pre(pieces): + # TAG[.post.devDISTANCE] . No -dirty + + # exceptions: + # 1: no tags. 0.post.devDISTANCE + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += ".post.dev%%d" %% pieces["distance"] + else: + # exception #1 + rendered = "0.post.dev%%d" %% pieces["distance"] + return rendered + + +def render_pep440_post(pieces): + # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that + # .dev0 sorts backwards (a dirty tree will appear "older" than the + # corresponding clean one), but you shouldn't be releasing software with + # -dirty anyways. + + # exceptions: + # 1: no tags. 0.postDISTANCE[.dev0] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%%d" %% pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%%s" %% pieces["short"] + else: + # exception #1 + rendered = "0.post%%d" %% pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += "+g%%s" %% pieces["short"] + return rendered + + +def render_pep440_old(pieces): + # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. + + # exceptions: + # 1: no tags. 0.postDISTANCE[.dev0] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%%d" %% pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + else: + # exception #1 + rendered = "0.post%%d" %% pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + return rendered + + +def render_git_describe(pieces): + # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty + # --always' + + # exceptions: + # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render_git_describe_long(pieces): + # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty + # --always -long'. The distance/hash is unconditional. + + # exceptions: + # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render(pieces, style): + if pieces["error"]: + return {"version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"]} + + if not style or style == "default": + style = "pep440" # the default + + if style == "pep440": + rendered = render_pep440(pieces) + elif style == "pep440-pre": + rendered = render_pep440_pre(pieces) + elif style == "pep440-post": + rendered = render_pep440_post(pieces) + elif style == "pep440-old": + rendered = render_pep440_old(pieces) + elif style == "git-describe": + rendered = render_git_describe(pieces) + elif style == "git-describe-long": + rendered = render_git_describe_long(pieces) + else: + raise ValueError("unknown style '%%s'" %% style) + + return {"version": rendered, "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], "error": None} + + +def get_versions(): + # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have + # __file__, we can work backwards from there to the root. Some + # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which + # case we can only use expanded keywords. + + cfg = get_config() + verbose = cfg.verbose + try: - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd) - except EnvironmentError: - e = sys.exc_info()[1] - if verbose: - print("unable to run %s" % args[0]) - print(e) - return None - stdout = p.communicate()[0].strip() - if sys.version >= '3': - stdout = stdout.decode() - if p.returncode != 0: - if verbose: - print("unable to run %s (error)" % args[0]) - return None - return stdout + return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, + verbose) + except NotThisMethod: + pass + try: + root = os.path.realpath(__file__) + # versionfile_source is the relative path from the top of the source + # tree (where the .git directory might live) to this file. Invert + # this to find the root from __file__. + for i in cfg.versionfile_source.split('/'): + root = os.path.dirname(root) + except NameError: + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to find root of source tree"} -import sys -import re -import os.path + try: + pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) + return render(pieces, cfg.style) + except NotThisMethod: + pass -def get_expanded_variables(versionfile_source): + try: + if cfg.parentdir_prefix: + return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) + except NotThisMethod: + pass + + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to compute version"} +''' + + +@register_vcs_handler("git", "get_keywords") +def git_get_keywords(versionfile_abs): # the code embedded in _version.py can just fetch the value of these - # variables. When used from setup.py, we don't want to import - # _version.py, so we do it with a regexp instead. This function is not - # used from _version.py. - variables = {} + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} try: - f = open(versionfile_source,"r") + f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["refnames"] = mo.group(1) + keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["full"] = mo.group(1) + keywords["full"] = mo.group(1) f.close() except EnvironmentError: pass - return variables + return keywords + -def versions_from_expanded_variables(variables, tag_prefix, verbose=False): - refnames = variables["refnames"].strip() +@register_vcs_handler("git", "keywords") +def git_versions_from_keywords(keywords, tag_prefix, verbose): + if not keywords: + raise NotThisMethod("no keywords at all, weird") + refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: - print("variables are unexpanded, not using") - return {} # unexpanded, so not in an unpacked git-archive tarball + print("keywords are unexpanded, not using") + raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. @@ -379,107 +984,115 @@ def versions_from_expanded_variables(variables, tag_prefix, verbose=False): r = ref[len(tag_prefix):] if verbose: print("picking %s" % r) - return { "version": r, - "full": variables["full"].strip() } - # no suitable tags, so we use the full revision id + return {"version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": None + } + # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: - print("no suitable tags, using full revision id") - return { "version": variables["full"].strip(), - "full": variables["full"].strip() } - -def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): - # this runs 'git' from the root of the source tree. That either means - # someone ran a setup.py command (and this code is in versioneer.py, so - # IN_LONG_VERSION_PY=False, thus the containing directory is the root of - # the source tree), or someone ran a project-specific entry point (and - # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the - # containing directory is somewhere deeper in the source tree). This only - # gets called if the git-archive 'subst' variables were *not* expanded, - # and _version.py hasn't already been rewritten with a short version - # string, meaning we're inside a checked out source tree. + print("no suitable tags, using unknown + full revision id") + return {"version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": "no suitable tags"} + + +@register_vcs_handler("git", "pieces_from_vcs") +def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): + # this runs 'git' from the root of the source tree. This only gets called + # if the git-archive 'subst' keywords were *not* expanded, and + # _version.py hasn't already been rewritten with a short version string, + # meaning we're inside a checked out source tree. - try: - here = os.path.abspath(__file__) - except NameError: - # some py2exe/bbfreeze/non-CPython implementations don't do __file__ - return {} # not always correct - - # versionfile_source is the relative path from the top of the source tree - # (where the .git directory might live) to this file. Invert this to find - # the root from __file__. - root = here - if IN_LONG_VERSION_PY: - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - else: - root = os.path.dirname(here) if not os.path.exists(os.path.join(root, ".git")): if verbose: print("no .git in %s" % root) - return {} + raise NotThisMethod("no .git directory") - GIT = "git" + GITS = ["git"] if sys.platform == "win32": - GIT = "git.exe" - stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"], - cwd=root) - if stdout is None: - return {} - if not stdout.startswith(tag_prefix): - if verbose: - print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix)) - return {} - tag = stdout[len(tag_prefix):] - stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) - if stdout is None: - return {} - full = stdout.strip() - if tag.endswith("-dirty"): - full += "-dirty" - return {"version": tag, "full": full} - - -def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False): - if IN_LONG_VERSION_PY: - # We're running from _version.py. If it's from a source tree - # (execute-in-place), we can work upwards to find the root of the - # tree, and then check the parent directory for a version string. If - # it's in an installed application, there's no hope. - try: - here = os.path.abspath(__file__) - except NameError: - # py2exe/bbfreeze/non-CPython don't have __file__ - return {} # without __file__, we have no hope - # versionfile_source is the relative path from the top of the source - # tree to _version.py. Invert this to find the root from __file__. - root = here - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) + GITS = ["git.cmd", "git.exe"] + # if there is a tag, this yields TAG-NUM-gHEX[-dirty] + # if there are no tags, this yields HEX[-dirty] (no NUM) + describe_out = run_command(GITS, ["describe", "--tags", "--dirty", + "--always", "--long"], + cwd=root) + # --long was added in git-1.5.5 + if describe_out is None: + raise NotThisMethod("'git describe' failed") + describe_out = describe_out.strip() + full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + if full_out is None: + raise NotThisMethod("'git rev-parse' failed") + full_out = full_out.strip() + + pieces = {} + pieces["long"] = full_out + pieces["short"] = full_out[:7] # maybe improved later + pieces["error"] = None + + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] + # TAG might have hyphens. + git_describe = describe_out + + # look for -dirty suffix + dirty = git_describe.endswith("-dirty") + pieces["dirty"] = dirty + if dirty: + git_describe = git_describe[:git_describe.rindex("-dirty")] + + # now we have TAG-NUM-gHEX or HEX + + if "-" in git_describe: + # TAG-NUM-gHEX + mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + if not mo: + # unparseable. Maybe git-describe is misbehaving? + pieces["error"] = ("unable to parse git-describe output: '%s'" + % describe_out) + return pieces + + # tag + full_tag = mo.group(1) + if not full_tag.startswith(tag_prefix): + if verbose: + fmt = "tag '%s' doesn't start with prefix '%s'" + print(fmt % (full_tag, tag_prefix)) + pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" + % (full_tag, tag_prefix)) + return pieces + pieces["closest-tag"] = full_tag[len(tag_prefix):] + + # distance: number of commits since tag + pieces["distance"] = int(mo.group(2)) + + # commit: short hex revision ID + pieces["short"] = mo.group(3) + else: - # we're running from versioneer.py, which means we're running from - # the setup.py in a source tree. sys.argv[0] is setup.py in the root. - here = os.path.abspath(sys.argv[0]) - root = os.path.dirname(here) + # HEX: no tags + pieces["closest-tag"] = None + count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], + cwd=root) + pieces["distance"] = int(count_out) # total number of commits - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" % - (root, dirname, parentdir_prefix)) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} + return pieces -import sys -def do_vcs_install(versionfile_source, ipy): - GIT = "git" +def do_vcs_install(manifest_in, versionfile_source, ipy): + GITS = ["git"] if sys.platform == "win32": - GIT = "git.exe" - run_command([GIT, "add", "versioneer.py"]) - run_command([GIT, "add", versionfile_source]) - run_command([GIT, "add", ipy]) + GITS = ["git.cmd", "git.exe"] + files = [manifest_in, versionfile_source] + if ipy: + files.append(ipy) + try: + me = __file__ + if me.endswith(".pyc") or me.endswith(".pyo"): + me = os.path.splitext(me)[0] + ".py" + versioneer_file = os.path.relpath(me) + except NameError: + versioneer_file = "versioneer.py" + files.append(versioneer_file) present = False try: f = open(".gitattributes", "r") @@ -494,135 +1107,465 @@ def do_vcs_install(versionfile_source, ipy): f = open(".gitattributes", "a+") f.write("%s export-subst\n" % versionfile_source) f.close() - run_command([GIT, "add", ".gitattributes"]) + files.append(".gitattributes") + run_command(GITS, ["add", "--"] + files) +def versions_from_parentdir(parentdir_prefix, root, verbose): + # Source tarballs conventionally unpack into a directory that includes + # both the project name and a version string. + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print("guessing rootdir is '%s', but '%s' doesn't start with " + "prefix '%s'" % (root, dirname, parentdir_prefix)) + raise NotThisMethod("rootdir doesn't start with parentdir_prefix") + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None} + SHORT_VERSION_PY = """ -# This file was generated by 'versioneer.py' (0.7+) from +# This file was generated by 'versioneer.py' (0.15) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. -version_version = '%(version)s' -version_full = '%(full)s' -def get_versions(default={}, verbose=False): - return {'version': version_version, 'full': version_full} +import json +import sys + +version_json = ''' +%s +''' # END VERSION_JSON + +def get_versions(): + return json.loads(version_json) """ -DEFAULT = {"version": "unknown", "full": "unknown"} def versions_from_file(filename): - versions = {} try: - f = open(filename) + with open(filename) as f: + contents = f.read() except EnvironmentError: - return versions - for line in f.readlines(): - mo = re.match("version_version = '([^']+)'", line) - if mo: - versions["version"] = mo.group(1) - mo = re.match("version_full = '([^']+)'", line) - if mo: - versions["full"] = mo.group(1) - f.close() - return versions + raise NotThisMethod("unable to read _version.py") + mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", + contents, re.M | re.S) + if not mo: + raise NotThisMethod("no version_json in _version.py") + return json.loads(mo.group(1)) + def write_to_version_file(filename, versions): - f = open(filename, "w") - f.write(SHORT_VERSION_PY % versions) - f.close() + os.unlink(filename) + contents = json.dumps(versions, sort_keys=True, + indent=1, separators=(",", ": ")) + with open(filename, "w") as f: + f.write(SHORT_VERSION_PY % contents) + print("set %s to '%s'" % (filename, versions["version"])) -def get_best_versions(versionfile, tag_prefix, parentdir_prefix, - default=DEFAULT, verbose=False): +def plus_or_dot(pieces): + if "+" in pieces.get("closest-tag", ""): + return "." + return "+" + + +def render_pep440(pieces): + # now build up version string, with post-release "local version + # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + + # exceptions: + # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += plus_or_dot(pieces) + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def render_pep440_pre(pieces): + # TAG[.post.devDISTANCE] . No -dirty + + # exceptions: + # 1: no tags. 0.post.devDISTANCE + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += ".post.dev%d" % pieces["distance"] + else: + # exception #1 + rendered = "0.post.dev%d" % pieces["distance"] + return rendered + + +def render_pep440_post(pieces): + # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that + # .dev0 sorts backwards (a dirty tree will appear "older" than the + # corresponding clean one), but you shouldn't be releasing software with + # -dirty anyways. + + # exceptions: + # 1: no tags. 0.postDISTANCE[.dev0] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%s" % pieces["short"] + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] + return rendered + + +def render_pep440_old(pieces): + # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. + + # exceptions: + # 1: no tags. 0.postDISTANCE[.dev0] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + return rendered + + +def render_git_describe(pieces): + # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty + # --always' + + # exceptions: + # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render_git_describe_long(pieces): + # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty + # --always -long'. The distance/hash is unconditional. + + # exceptions: + # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render(pieces, style): + if pieces["error"]: + return {"version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"]} + + if not style or style == "default": + style = "pep440" # the default + + if style == "pep440": + rendered = render_pep440(pieces) + elif style == "pep440-pre": + rendered = render_pep440_pre(pieces) + elif style == "pep440-post": + rendered = render_pep440_post(pieces) + elif style == "pep440-old": + rendered = render_pep440_old(pieces) + elif style == "git-describe": + rendered = render_git_describe(pieces) + elif style == "git-describe-long": + rendered = render_git_describe_long(pieces) + else: + raise ValueError("unknown style '%s'" % style) + + return {"version": rendered, "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], "error": None} + + +class VersioneerBadRootError(Exception): + pass + + +def get_versions(verbose=False): # returns dict with two keys: 'version' and 'full' - # - # extract version from first of _version.py, 'git describe', parentdir. - # This is meant to work for developers using a source checkout, for users - # of a tarball created by 'setup.py sdist', and for users of a - # tarball/zipball created by 'git archive' or github's download-from-tag - # feature. - - variables = get_expanded_variables(versionfile_source) - if variables: - ver = versions_from_expanded_variables(variables, tag_prefix) - if ver: - if verbose: print("got version from expanded variable %s" % ver) - return ver - ver = versions_from_file(versionfile) - if ver: - if verbose: print("got version from file %s %s" % (versionfile, ver)) - return ver + if "versioneer" in sys.modules: + # see the discussion in cmdclass.py:get_cmdclass() + del sys.modules["versioneer"] - ver = versions_from_vcs(tag_prefix, versionfile_source, verbose) - if ver: - if verbose: print("got version from git %s" % ver) - return ver + root = get_root() + cfg = get_config_from_root(root) - ver = versions_from_parentdir(parentdir_prefix, versionfile_source, verbose) - if ver: - if verbose: print("got version from parentdir %s" % ver) - return ver + assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" + handlers = HANDLERS.get(cfg.VCS) + assert handlers, "unrecognized VCS '%s'" % cfg.VCS + verbose = verbose or cfg.verbose + assert cfg.versionfile_source is not None, \ + "please set versioneer.versionfile_source" + assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" + + versionfile_abs = os.path.join(root, cfg.versionfile_source) - if verbose: print("got version from default %s" % ver) - return default - -def get_versions(default=DEFAULT, verbose=False): - assert versionfile_source is not None, "please set versioneer.versionfile_source" - assert tag_prefix is not None, "please set versioneer.tag_prefix" - assert parentdir_prefix is not None, "please set versioneer.parentdir_prefix" - return get_best_versions(versionfile_source, tag_prefix, parentdir_prefix, - default=default, verbose=verbose) -def get_version(verbose=False): - return get_versions(verbose=verbose)["version"] - -class cmd_version(Command): - description = "report generated version string" - user_options = [] - boolean_options = [] - def initialize_options(self): + # extract version from first of: _version.py, VCS command (e.g. 'git + # describe'), parentdir. This is meant to work for developers using a + # source checkout, for users of a tarball created by 'setup.py sdist', + # and for users of a tarball/zipball created by 'git archive' or github's + # download-from-tag feature or the equivalent in other VCSes. + + get_keywords_f = handlers.get("get_keywords") + from_keywords_f = handlers.get("keywords") + if get_keywords_f and from_keywords_f: + try: + keywords = get_keywords_f(versionfile_abs) + ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) + if verbose: + print("got version from expanded keyword %s" % ver) + return ver + except NotThisMethod: + pass + + try: + ver = versions_from_file(versionfile_abs) + if verbose: + print("got version from file %s %s" % (versionfile_abs, ver)) + return ver + except NotThisMethod: pass - def finalize_options(self): + + from_vcs_f = handlers.get("pieces_from_vcs") + if from_vcs_f: + try: + pieces = from_vcs_f(cfg.tag_prefix, root, verbose) + ver = render(pieces, cfg.style) + if verbose: + print("got version from VCS %s" % ver) + return ver + except NotThisMethod: + pass + + try: + if cfg.parentdir_prefix: + ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) + if verbose: + print("got version from parentdir %s" % ver) + return ver + except NotThisMethod: pass - def run(self): - ver = get_version(verbose=True) - print("Version is currently: %s" % ver) - - -class cmd_build(_build): - def run(self): - versions = get_versions(verbose=True) - _build.run(self) - # now locate _version.py in the new build/ directory and replace it - # with an updated value - target_versionfile = os.path.join(self.build_lib, versionfile_build) - print("UPDATING %s" % target_versionfile) - os.unlink(target_versionfile) - f = open(target_versionfile, "w") - f.write(SHORT_VERSION_PY % versions) - f.close() -class cmd_sdist(_sdist): - def run(self): - versions = get_versions(verbose=True) - self._versioneer_generated_versions = versions - # unless we update this, the command will keep using the old version - self.distribution.metadata.version = versions["version"] - return _sdist.run(self) - - def make_release_tree(self, base_dir, files): - _sdist.make_release_tree(self, base_dir, files) - # now locate _version.py in the new base_dir directory (remembering - # that it may be a hardlink) and replace it with an updated value - target_versionfile = os.path.join(base_dir, versionfile_source) - print("UPDATING %s" % target_versionfile) - os.unlink(target_versionfile) - f = open(target_versionfile, "w") - f.write(SHORT_VERSION_PY % self._versioneer_generated_versions) - f.close() + if verbose: + print("unable to compute version") + + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, "error": "unable to compute version"} + + +def get_version(): + return get_versions()["version"] + + +def get_cmdclass(): + if "versioneer" in sys.modules: + del sys.modules["versioneer"] + # this fixes the "python setup.py develop" case (also 'install' and + # 'easy_install .'), in which subdependencies of the main project are + # built (using setup.py bdist_egg) in the same python process. Assume + # a main project A and a dependency B, which use different versions + # of Versioneer. A's setup.py imports A's Versioneer, leaving it in + # sys.modules by the time B's setup.py is executed, causing B to run + # with the wrong versioneer. Setuptools wraps the sub-dep builds in a + # sandbox that restores sys.modules to it's pre-build state, so the + # parent is protected against the child's "import versioneer". By + # removing ourselves from sys.modules here, before the child build + # happens, we protect the child from the parent's versioneer too. + # Also see https://github.com/warner/python-versioneer/issues/52 + + cmds = {} + + # we add "version" to both distutils and setuptools + from distutils.core import Command + + class cmd_version(Command): + description = "report generated version string" + user_options = [] + boolean_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + vers = get_versions(verbose=True) + print("Version: %s" % vers["version"]) + print(" full-revisionid: %s" % vers.get("full-revisionid")) + print(" dirty: %s" % vers.get("dirty")) + if vers["error"]: + print(" error: %s" % vers["error"]) + cmds["version"] = cmd_version + + # we override "build_py" in both distutils and setuptools + # + # most invocation pathways end up running build_py: + # distutils/build -> build_py + # distutils/install -> distutils/build ->.. + # setuptools/bdist_wheel -> distutils/install ->.. + # setuptools/bdist_egg -> distutils/install_lib -> build_py + # setuptools/install -> bdist_egg ->.. + # setuptools/develop -> ? + + from distutils.command.build_py import build_py as _build_py + + class cmd_build_py(_build_py): + def run(self): + root = get_root() + cfg = get_config_from_root(root) + versions = get_versions() + _build_py.run(self) + # now locate _version.py in the new build/ directory and replace + # it with an updated value + if cfg.versionfile_build: + target_versionfile = os.path.join(self.build_lib, + cfg.versionfile_build) + print("UPDATING %s" % target_versionfile) + write_to_version_file(target_versionfile, versions) + cmds["build_py"] = cmd_build_py + + if "cx_Freeze" in sys.modules: # cx_freeze enabled? + from cx_Freeze.dist import build_exe as _build_exe + + class cmd_build_exe(_build_exe): + def run(self): + root = get_root() + cfg = get_config_from_root(root) + versions = get_versions() + target_versionfile = cfg.versionfile_source + print("UPDATING %s" % target_versionfile) + write_to_version_file(target_versionfile, versions) + + _build_exe.run(self) + os.unlink(target_versionfile) + with open(cfg.versionfile_source, "w") as f: + LONG = LONG_VERSION_PY[cfg.VCS] + f.write(LONG % + {"DOLLAR": "$", + "STYLE": cfg.style, + "TAG_PREFIX": cfg.tag_prefix, + "PARENTDIR_PREFIX": cfg.parentdir_prefix, + "VERSIONFILE_SOURCE": cfg.versionfile_source, + }) + cmds["build_exe"] = cmd_build_exe + del cmds["build_py"] + + # we override different "sdist" commands for both environments + if "setuptools" in sys.modules: + from setuptools.command.sdist import sdist as _sdist + else: + from distutils.command.sdist import sdist as _sdist + + class cmd_sdist(_sdist): + def run(self): + versions = get_versions() + self._versioneer_generated_versions = versions + # unless we update this, the command will keep using the old + # version + self.distribution.metadata.version = versions["version"] + return _sdist.run(self) + + def make_release_tree(self, base_dir, files): + root = get_root() + cfg = get_config_from_root(root) + _sdist.make_release_tree(self, base_dir, files) + # now locate _version.py in the new base_dir directory + # (remembering that it may be a hardlink) and replace it with an + # updated value + target_versionfile = os.path.join(base_dir, cfg.versionfile_source) + print("UPDATING %s" % target_versionfile) + write_to_version_file(target_versionfile, + self._versioneer_generated_versions) + cmds["sdist"] = cmd_sdist + + return cmds + + +CONFIG_ERROR = """ +setup.cfg is missing the necessary Versioneer configuration. You need +a section like: + + [versioneer] + VCS = git + style = pep440 + versionfile_source = src/myproject/_version.py + versionfile_build = myproject/_version.py + tag_prefix = "" + parentdir_prefix = myproject- + +You will also need to edit your setup.py to use the results: + + import versioneer + setup(version=versioneer.get_version(), + cmdclass=versioneer.get_cmdclass(), ...) + +Please read the docstring in ./versioneer.py for configuration instructions, +edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. +""" + +SAMPLE_CONFIG = """ +# See the docstring in versioneer.py for instructions. Note that you must +# re-run 'versioneer.py setup' after changing this section, and commit the +# resulting files. + +[versioneer] +#VCS = git +#style = pep440 +#versionfile_source = +#versionfile_build = +#tag_prefix = +#parentdir_prefix = + +""" INIT_PY_SNIPPET = """ from ._version import get_versions @@ -630,40 +1573,127 @@ __version__ = get_versions()['version'] del get_versions """ -class cmd_update_files(Command): - description = "modify __init__.py and create _version.py" - user_options = [] - boolean_options = [] - def initialize_options(self): - pass - def finalize_options(self): - pass - def run(self): - ipy = os.path.join(os.path.dirname(versionfile_source), "__init__.py") - print(" creating %s" % versionfile_source) - f = open(versionfile_source, "w") - f.write(LONG_VERSION_PY % {"DOLLAR": "$", - "TAG_PREFIX": tag_prefix, - "PARENTDIR_PREFIX": parentdir_prefix, - "VERSIONFILE_SOURCE": versionfile_source, - }) - f.close() + +def do_setup(): + root = get_root() + try: + cfg = get_config_from_root(root) + except (EnvironmentError, configparser.NoSectionError, + configparser.NoOptionError) as e: + if isinstance(e, (EnvironmentError, configparser.NoSectionError)): + print("Adding sample versioneer config to setup.cfg", + file=sys.stderr) + with open(os.path.join(root, "setup.cfg"), "a") as f: + f.write(SAMPLE_CONFIG) + print(CONFIG_ERROR, file=sys.stderr) + return 1 + + print(" creating %s" % cfg.versionfile_source) + with open(cfg.versionfile_source, "w") as f: + LONG = LONG_VERSION_PY[cfg.VCS] + f.write(LONG % {"DOLLAR": "$", + "STYLE": cfg.style, + "TAG_PREFIX": cfg.tag_prefix, + "PARENTDIR_PREFIX": cfg.parentdir_prefix, + "VERSIONFILE_SOURCE": cfg.versionfile_source, + }) + + ipy = os.path.join(os.path.dirname(cfg.versionfile_source), + "__init__.py") + if os.path.exists(ipy): try: - old = open(ipy, "r").read() + with open(ipy, "r") as f: + old = f.read() except EnvironmentError: old = "" if INIT_PY_SNIPPET not in old: print(" appending to %s" % ipy) - f = open(ipy, "a") - f.write(INIT_PY_SNIPPET) - f.close() + with open(ipy, "a") as f: + f.write(INIT_PY_SNIPPET) else: print(" %s unmodified" % ipy) - do_vcs_install(versionfile_source, ipy) + else: + print(" %s doesn't exist, ok" % ipy) + ipy = None + + # Make sure both the top-level "versioneer.py" and versionfile_source + # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so + # they'll be copied into source distributions. Pip won't be able to + # install the package without this. + manifest_in = os.path.join(root, "MANIFEST.in") + simple_includes = set() + try: + with open(manifest_in, "r") as f: + for line in f: + if line.startswith("include "): + for include in line.split()[1:]: + simple_includes.add(include) + except EnvironmentError: + pass + # That doesn't cover everything MANIFEST.in can do + # (http://docs.python.org/2/distutils/sourcedist.html#commands), so + # it might give some false negatives. Appending redundant 'include' + # lines is safe, though. + if "versioneer.py" not in simple_includes: + print(" appending 'versioneer.py' to MANIFEST.in") + with open(manifest_in, "a") as f: + f.write("include versioneer.py\n") + else: + print(" 'versioneer.py' already in MANIFEST.in") + if cfg.versionfile_source not in simple_includes: + print(" appending versionfile_source ('%s') to MANIFEST.in" % + cfg.versionfile_source) + with open(manifest_in, "a") as f: + f.write("include %s\n" % cfg.versionfile_source) + else: + print(" versionfile_source already in MANIFEST.in") -def get_cmdclass(): - return {'version': cmd_version, - 'update_files': cmd_update_files, - 'build': cmd_build, - 'sdist': cmd_sdist, - } + # Make VCS-specific changes. For git, this means creating/changing + # .gitattributes to mark _version.py for export-time keyword + # substitution. + do_vcs_install(manifest_in, cfg.versionfile_source, ipy) + return 0 + + +def scan_setup_py(): + found = set() + setters = False + errors = 0 + with open("setup.py", "r") as f: + for line in f.readlines(): + if "import versioneer" in line: + found.add("import") + if "versioneer.get_cmdclass()" in line: + found.add("cmdclass") + if "versioneer.get_version()" in line: + found.add("get_version") + if "versioneer.VCS" in line: + setters = True + if "versioneer.versionfile_source" in line: + setters = True + if len(found) != 3: + print("") + print("Your setup.py appears to be missing some important items") + print("(but I might be wrong). Please make sure it has something") + print("roughly like the following:") + print("") + print(" import versioneer") + print(" setup( version=versioneer.get_version(),") + print(" cmdclass=versioneer.get_cmdclass(), ...)") + print("") + errors += 1 + if setters: + print("You should remove lines like 'versioneer.VCS = ' and") + print("'versioneer.versionfile_source = ' . This configuration") + print("now lives in setup.cfg, and should be removed from setup.py") + print("") + errors += 1 + return errors + +if __name__ == "__main__": + cmd = sys.argv[1] + if cmd == "setup": + errors = do_setup() + errors += scan_setup_py() + if errors: + sys.exit(1) -- cgit v1.2.3 From 470d24ec886adedae6d47e684ab1dc14aa2c21ea Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 4 Mar 2016 11:28:30 -0400 Subject: [style] pep8 --- src/leap/bitmask/_version.py | 2 +- versioneer.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/leap/bitmask/_version.py b/src/leap/bitmask/_version.py index 93700af1..8c507c92 100644 --- a/src/leap/bitmask/_version.py +++ b/src/leap/bitmask/_version.py @@ -155,7 +155,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: - print("discarding '%s', no digits" % ",".join(refs-tags)) + print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): diff --git a/versioneer.py b/versioneer.py index c010f63e..89194938 100644 --- a/versioneer.py +++ b/versioneer.py @@ -975,7 +975,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: - print("discarding '%s', no digits" % ",".join(refs-tags)) + print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): @@ -1459,6 +1459,7 @@ def get_cmdclass(): from distutils.command.build_py import build_py as _build_py class cmd_build_py(_build_py): + def run(self): root = get_root() cfg = get_config_from_root(root) @@ -1477,6 +1478,7 @@ def get_cmdclass(): from cx_Freeze.dist import build_exe as _build_exe class cmd_build_exe(_build_exe): + def run(self): root = get_root() cfg = get_config_from_root(root) @@ -1506,6 +1508,7 @@ def get_cmdclass(): from distutils.command.sdist import sdist as _sdist class cmd_sdist(_sdist): + def run(self): versions = get_versions() self._versioneer_generated_versions = versions -- cgit v1.2.3 From 3d2df02b549a32c15b2bb369a33789abadb60c92 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 4 Mar 2016 11:44:18 -0400 Subject: [bug] fix some version handling errors --- setup.py | 11 ++++++----- src/leap/bitmask/__init__.py | 6 +++++- src/leap/bitmask/provider/__init__.py | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index e85c4564..fc9c36c9 100755 --- a/setup.py +++ b/setup.py @@ -24,7 +24,8 @@ import hashlib import sys import os import re -import sys + +from distutils.command.build import build as _build if not sys.version_info[0] == 2: print("[ERROR] Sorry, Python 3 is not supported (yet). " @@ -259,16 +260,16 @@ BITMASK_ROOT = "{bitmask}" cmdclass["hash_binaries"] = cmd_binary_hash -# next two classes need to augment the versioneer modified ones +# sdist class need to augment the versioneer modified ones -versioneer_build = cmdclass['build_py'] +#_build = cmdclass['build_py'] versioneer_sdist = cmdclass['sdist'] -class cmd_build(versioneer_build): +class cmd_build(_build): def run(self): - versioneer_build.run(self) + _build.run(self) copy_reqs(self.build_lib) diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py index 966ce91e..6ab55e53 100644 --- a/src/leap/bitmask/__init__.py +++ b/src/leap/bitmask/__init__.py @@ -38,11 +38,15 @@ def _is_release_version(version_str): :rtype: bool """ parts = __version__.split('.') - patch = parts[2] + try: + patch = parts[2] + except IndexError: + return False return patch.isdigit() from ._version import get_versions __version__ = get_versions()['version'] +__version_hash__ = get_versions()['full-revisionid'] IS_RELEASE_VERSION = _is_release_version(__version__) del get_versions diff --git a/src/leap/bitmask/provider/__init__.py b/src/leap/bitmask/provider/__init__.py index 4385a92f..60a41181 100644 --- a/src/leap/bitmask/provider/__init__.py +++ b/src/leap/bitmask/provider/__init__.py @@ -22,7 +22,7 @@ import os from pkg_resources import parse_version -from leap.bitmask import __short_version__ as BITMASK_VERSION +from leap.bitmask import __version__ as BITMASK_VERSION from leap.common.check import leap_assert logger = logging.getLogger(__name__) -- cgit v1.2.3 From 21a0e07ffccbee57fa02f20525441f96516132dc Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 21 Mar 2016 15:52:43 -0400 Subject: [bug] re-download smtp certificate if needed. the should_redownload function was only called if the pemfile was not present of the fs. - Resolves: #7869 --- changes/next-changelog.rst | 4 +++- src/leap/bitmask/services/mail/smtpbootstrapper.py | 9 +++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst index 64774d30..d07273ec 100644 --- a/changes/next-changelog.rst +++ b/changes/next-changelog.rst @@ -24,8 +24,10 @@ Bugfixes - `#7583 `_: Fix set_soledad_auth_token event callback signature. - `#7585 `_: Open email help link on browser. - `#7598 `_: Fix errback on InvalidAuthToken. -- `#1235 `_: Description for the fixed stuff corresponding with issue #1235. +- `#7869 `_: Redownload smtp certificate if needed. - Do not translate 'https' text on QLabel. + +- `#1235 `_: Description for the fixed stuff corresponding with issue #1235. - Bugfix without related issue number. Misc diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py index dadf59dd..f73687a7 100644 --- a/src/leap/bitmask/services/mail/smtpbootstrapper.py +++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py @@ -29,7 +29,6 @@ from leap.bitmask.logs.utils import get_logger from leap.bitmask.services import download_service_config from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.services.mail.smtpconfig import SMTPConfig -from leap.bitmask.util import is_file from leap.common import certs as leap_certs from leap.common.check import leap_assert @@ -93,11 +92,13 @@ class SMTPBootstrapper(AbstractBootstrapper): client_cert_path = self._smtp_config.get_client_cert_path( self._userid, self._provider_config, about_to_download=True) - if not is_file(client_cert_path): + needs_download = leap_certs.should_redownload(client_cert_path) + + if needs_download: # For re-download if something is wrong with the cert + # FIXME this doesn't read well. should reword the logic here. self._download_if_needed = ( - self._download_if_needed and - not leap_certs.should_redownload(client_cert_path)) + self._download_if_needed and not needs_download) if self._download_if_needed and os.path.isfile(client_cert_path): check_and_fix_urw_only(client_cert_path) -- cgit v1.2.3 From b596eab3591414c4ef58648d74434bc0d5d7c6e3 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 23 Mar 2016 11:52:44 -0400 Subject: [feature] pixelated adaptor --- src/leap/bitmask/pix.py | 145 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 src/leap/bitmask/pix.py diff --git a/src/leap/bitmask/pix.py b/src/leap/bitmask/pix.py new file mode 100644 index 00000000..abc8a822 --- /dev/null +++ b/src/leap/bitmask/pix.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +# pix.py +# Copyright (C) 2016 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 . +""" +Pixelated plugin integration. +""" +import os + +from twisted.internet import defer +from twisted.python import log + +from leap.mail.imap.account import IMAPAccount + +from pixelated.adapter.mailstore import LeapMailStore +from pixelated.adapter.welcome_mail import add_welcome_mail +from pixelated.application import SingleUserServicesFactory +from pixelated.application import UserAgentMode +from pixelated.application import start_site +from pixelated.bitmask_libraries.smtp import LeapSMTPConfig +from pixelated.bitmask_libraries.session import SessionCache +from pixelated.config import services +from pixelated.resources.root_resource import RootResource + + +def start_pixelated_user_agent(userid, soledad, keymanager): + + print 'STARTING PIXELATED USER AGENT...' + + leap_session = LeapSessionAdapter( + userid, soledad, keymanager) + + config = Config() + leap_home = os.path.expanduser('~/.config/leap') + config.leap_home = leap_home + leap_session.config = config + + services_factory = SingleUserServicesFactory( + UserAgentMode(is_single_user=True)) + resource = RootResource(services_factory) + + config.port = 9090 + config.sslkey = None + config.sslcert = None + config.host = 'localhost' + + deferred = _start_in_single_user_mode( + leap_session, config, + resource, services_factory) + return deferred + + +class LeapSessionAdapter(object): + + def __init__(self, userid, soledad, keymanager): + self.userid = userid + + self.soledad = soledad + + self.nicknym = Config() + self.nicknym.keymanager = keymanager + + self.mail_store = LeapMailStore(soledad) + + self.user_auth = Config() + self.user_auth.uuid = soledad.uuid + + # XXX what is this?? path to smtp-service? + # self.config = provider.config + # self.provider = provider + + self.fresh_account = False + self.incoming_mail_fetcher = None + self.account = IMAPAccount(userid, soledad, defer.Deferred()) + + username, provider = userid.split('@') + smtp_client_cert = os.path.expanduser( + '~/.config/leap/providers/{provider}/keys/' + 'client/smtp_{username}.pem'.format( + provider=provider, username=username)) + # TODO --- get from config + smtp_host = 'antelope.mail.bitmask.net' + smtp_port = 2013 + + self.smtp_config = LeapSMTPConfig( + userid, + smtp_client_cert, smtp_host, smtp_port) + + def account_email(self): + return self.userid + + def close(self): + pass + + @property + def is_closed(self): + return self._is_closed + + def remove_from_cache(self): + key = SessionCache.session_key(self.provider, self.userid) + SessionCache.remove_session(key) + + def sync(self): + return self.soledad.sync() + + +class Config(object): + pass + + +def _start_in_single_user_mode(leap_session, config, resource, + services_factory): + start_site(config, resource) + return start_user_agent_in_single_user_mode( + resource, services_factory, + leap_session.config.leap_home, leap_session) + + +@defer.inlineCallbacks +def start_user_agent_in_single_user_mode( + root_resource, services_factory, leap_home, leap_session): + log.msg('Bootstrap done, loading services for user %s' + % leap_session.userid) + + _services = services.Services(leap_session) + yield _services.setup() + + if leap_session.fresh_account: + yield add_welcome_mail(leap_session.mail_store) + + services_factory.add_session(leap_session.user_auth.uuid, _services) + root_resource.initialize() + log.msg('Done, the user agent is ready to be used') -- cgit v1.2.3 From c866892b988912a5b9a88edfd6d1e71491617822 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 23 Mar 2016 11:53:15 -0400 Subject: [feature] quick integration with legacy gui --- src/leap/bitmask/backend/api.py | 2 ++ src/leap/bitmask/backend/components.py | 13 ++++++++++++- src/leap/bitmask/backend/leapbackend.py | 6 ++++++ src/leap/bitmask/services/mail/conductor.py | 15 ++++++++++++++- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/leap/bitmask/backend/api.py b/src/leap/bitmask/backend/api.py index 134a2d56..2fd983ae 100644 --- a/src/leap/bitmask/backend/api.py +++ b/src/leap/bitmask/backend/api.py @@ -42,6 +42,8 @@ API = ( "keymanager_export_keys", "keymanager_get_key_details", "keymanager_list_keys", + "pixelated_start_service", + "pixelated_stop_service", "provider_bootstrap", "provider_cancel_setup", "provider_get_all_services", diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index d8e4edbf..0f8864c3 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -26,7 +26,7 @@ import time from functools import partial -from twisted.internet import threads, defer +from twisted.internet import threads, defer, reactor from twisted.python import log import zope.interface @@ -38,6 +38,7 @@ from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.crypto.srpregister import SRPRegister from leap.bitmask.logs.utils import get_logger from leap.bitmask.platform_init import IS_LINUX +from leap.bitmask.pix import start_pixelated_user_agent from leap.bitmask.provider.pinned import PinnedProviders from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper from leap.bitmask.services import get_supported @@ -1086,6 +1087,16 @@ class Mail(object): """ return threads.deferToThread(self._stop_imap_service) + def start_pixelated_service(self, full_user_id): + reactor.callFromThread( + start_pixelated_user_agent, + full_user_id, + self._soledad_proxy, + self._keymanager_proxy) + + def stop_pixelated_service(self): + pass + class Authenticate(object): """ diff --git a/src/leap/bitmask/backend/leapbackend.py b/src/leap/bitmask/backend/leapbackend.py index d0668a1c..56b1597c 100644 --- a/src/leap/bitmask/backend/leapbackend.py +++ b/src/leap/bitmask/backend/leapbackend.py @@ -531,6 +531,12 @@ class LeapBackend(Backend): """ self._mail.stop_imap_service() + def pixelated_start_service(self, full_user_id): + self._mail.start_pixelated_service(full_user_id) + + def pixelated_stop_service(self): + self._mail.stop_pixelated_service() + def settings_set_selected_gateway(self, provider, gateway): """ Set the selected gateway for a given provider. diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index b94b3cc8..05fafa1a 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -192,7 +192,17 @@ class SMTPControl(object): self.smtp_connection.qtsigs.connection_aborted_signal.emit() -class MailConductor(IMAPControl, SMTPControl): +class PixelatedControl(object): + + def start_pixelated_service(self): + self._backend.pixelated_start_service( + full_user_id=self.userid) + + def stop_pixelated_service(self): + pass + + +class MailConductor(IMAPControl, SMTPControl, PixelatedControl): """ This class encapsulates everything related to the initialization and process control for the mail services. @@ -269,6 +279,9 @@ class MailConductor(IMAPControl, SMTPControl): self.start_smtp_service(download_if_needed=download_if_needed) self.start_imap_service() + # TODO --- check if it's enabled!!! + self.start_pixelated_service() + self._mail_services_started = True def stop_mail_services(self): -- cgit v1.2.3 From e92e4f1e4d57957af5d8c9e08a6c3c9152409612 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 23 Mar 2016 12:03:46 -0400 Subject: [feature] allow to disable pixelmail integration --- src/leap/bitmask/config/leapsettings.py | 11 ++++++++++- src/leap/bitmask/services/mail/conductor.py | 7 +++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index 484a8a25..a060c8a4 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -70,6 +70,7 @@ class LeapSettings(object): PINNED_KEY = "Pinned" SKIPFIRSTRUN_KEY = "SkipFirstRun" UUIDFORUSER_KEY = "%s/%s_uuid" + PIXELMAIL_KEY = "Pixmail" # values GATEWAY_AUTOMATIC = "Automatic" @@ -352,4 +353,12 @@ class LeapSettings(object): :type skip: bool """ leap_assert_type(skip, bool) - self._settings.setValue(self.SKIPFIRSTRUN_KEY, skip) + self._settings.setvalue(self.skipfirstrun_key, skip) + + def get_pixelmail_enabled(self): + return to_bool(self._settings.value(self.PIXELMAIL_KEY, False)) + + def set_pixelmail_enabled(self, enabled): + leap_assert_type(enabled, bool) + self._settings.setvalue(self.PIXELMAIL_KEY, enabled) + diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 05fafa1a..cccbcf14 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -18,6 +18,7 @@ Mail Services Conductor """ from leap.bitmask.config import flags +from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui import statemachines from leap.bitmask.services.mail import connection as mail_connection @@ -279,8 +280,10 @@ class MailConductor(IMAPControl, SMTPControl, PixelatedControl): self.start_smtp_service(download_if_needed=download_if_needed) self.start_imap_service() - # TODO --- check if it's enabled!!! - self.start_pixelated_service() + settings = LeapSettings() + pixelmail = settings.get_pixelmail_enabled() + if pixelmail: + self.start_pixelated_service() self._mail_services_started = True -- cgit v1.2.3 From f9cb960dea642ec2e9cced1ab4712577cc0f3469 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 23 Mar 2016 19:30:54 -0400 Subject: [feature] add QtWebView to render pixelated mua --- src/leap/bitmask/gui/mainwindow.py | 8 ++++++++ src/leap/bitmask/gui/qt_browser.py | 13 +++++++++++++ src/leap/bitmask/gui/ui/mainwindow.ui | 6 ++++++ 3 files changed, 27 insertions(+) create mode 100644 src/leap/bitmask/gui/qt_browser.py diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 759b454f..1f497d2d 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -60,6 +60,8 @@ from leap.bitmask.util.keyring_helpers import has_keyring from leap.common.events import register from leap.common.events import catalog +from .qt_browser import PixelatedWindow + from leap.mail.imap.service.imap import IMAP_PORT from ui_mainwindow import Ui_MainWindow @@ -218,6 +220,8 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._backend_connect() self.ui.action_preferences.triggered.connect(self._show_preferences) + self.ui.action_pixelated_mail.triggered.connect( + self._show_pixelated_browser) self.ui.action_about_leap.triggered.connect(self._about) self.ui.action_quit.triggered.connect(self.quit) self.ui.action_wizard.triggered.connect(self._show_wizard) @@ -568,6 +572,10 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): pref_win = PreferencesWindow(self, account, self.app) pref_win.show() + def _show_pixelated_browser(self): + win = PixelatedWindow(self) + win.show() + def _update_eip_enabled_status(self, account=None, services=None): """ TRIGGER: diff --git a/src/leap/bitmask/gui/qt_browser.py b/src/leap/bitmask/gui/qt_browser.py new file mode 100644 index 00000000..4bd947cb --- /dev/null +++ b/src/leap/bitmask/gui/qt_browser.py @@ -0,0 +1,13 @@ +from PySide import QtCore, QtWebKit, QtGui + +PIXELATED_URI = 'http://localhost:9090' + + +class PixelatedWindow(QtGui.QDialog): + + def __init__(self, parent): + QtGui.QDialog.__init__(self, parent) + self.web = QtWebKit.QWebView(self) + self.web.load(QtCore.QUrl(PIXELATED_URI)) + self.setWindowTitle('Bitmask/Pixelated WebMail') + self.web.show() diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index b1d68c4a..976c0c0a 100644 --- a/src/leap/bitmask/gui/ui/mainwindow.ui +++ b/src/leap/bitmask/gui/ui/mainwindow.ui @@ -315,6 +315,7 @@
    + @@ -370,6 +371,11 @@ Create a new account...
    + + + Bitmask Webmail + + false -- cgit v1.2.3 From 106d202012f8f052a3cabe044d7287d2283655fc Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 24 Mar 2016 10:04:38 -0400 Subject: [feature] hide browser menu entry if pixelated is disabled --- src/leap/bitmask/gui/mainwindow.py | 4 ++++ src/leap/bitmask/gui/qt_browser.py | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 1f497d2d..839aae87 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -222,6 +222,10 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self.ui.action_preferences.triggered.connect(self._show_preferences) self.ui.action_pixelated_mail.triggered.connect( self._show_pixelated_browser) + + pixelated_enabled = self._settings.get_pixelmail_enabled() + self.ui.action_pixelated_mail.setVisible(pixelated_enabled) + self.ui.action_about_leap.triggered.connect(self._about) self.ui.action_quit.triggered.connect(self.quit) self.ui.action_wizard.triggered.connect(self._show_wizard) diff --git a/src/leap/bitmask/gui/qt_browser.py b/src/leap/bitmask/gui/qt_browser.py index 4bd947cb..e480b363 100644 --- a/src/leap/bitmask/gui/qt_browser.py +++ b/src/leap/bitmask/gui/qt_browser.py @@ -1,3 +1,23 @@ +# -*- coding: utf-8 -*- +# qt_browser.py +# Copyright (C) 2016 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 . +""" +QtWebKit-based browser to display Pixelated User Agent +""" + from PySide import QtCore, QtWebKit, QtGui PIXELATED_URI = 'http://localhost:9090' -- cgit v1.2.3 From 8ab91a80f1c0ef4e49f682342e9479d140f55c9a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 24 Mar 2016 10:50:53 -0400 Subject: [fix] get smtp config from smtp-provider.json --- src/leap/bitmask/pix.py | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/leap/bitmask/pix.py b/src/leap/bitmask/pix.py index abc8a822..96ca4299 100644 --- a/src/leap/bitmask/pix.py +++ b/src/leap/bitmask/pix.py @@ -17,11 +17,13 @@ """ Pixelated plugin integration. """ +import json import os from twisted.internet import defer from twisted.python import log +from leap.bitmask.util import get_path_prefix from leap.mail.imap.account import IMAPAccount from pixelated.adapter.mailstore import LeapMailStore @@ -37,13 +39,11 @@ from pixelated.resources.root_resource import RootResource def start_pixelated_user_agent(userid, soledad, keymanager): - print 'STARTING PIXELATED USER AGENT...' - leap_session = LeapSessionAdapter( userid, soledad, keymanager) config = Config() - leap_home = os.path.expanduser('~/.config/leap') + leap_home = os.path.join(get_path_prefix(), 'leap') config.leap_home = leap_home leap_session.config = config @@ -62,6 +62,20 @@ def start_pixelated_user_agent(userid, soledad, keymanager): return deferred +def get_smtp_config(provider): + config_path = os.path.join( + get_path_prefix(), 'leap', 'providers', provider, 'smtp-service.json') + json_config = json.loads(open(config_path).read()) + chosen_host = json_config['hosts'].keys()[0] + hostname = json_config['hosts'][chosen_host]['hostname'] + port = json_config['hosts'][chosen_host]['port'] + + config = Config() + config.host = hostname + config.port = port + return config + + class LeapSessionAdapter(object): def __init__(self, userid, soledad, keymanager): @@ -69,6 +83,7 @@ class LeapSessionAdapter(object): self.soledad = soledad + # FIXME this expects a keymanager-like instance self.nicknym = Config() self.nicknym.keymanager = keymanager @@ -77,22 +92,23 @@ class LeapSessionAdapter(object): self.user_auth = Config() self.user_auth.uuid = soledad.uuid - # XXX what is this?? path to smtp-service? - # self.config = provider.config - # self.provider = provider - self.fresh_account = False self.incoming_mail_fetcher = None self.account = IMAPAccount(userid, soledad, defer.Deferred()) username, provider = userid.split('@') - smtp_client_cert = os.path.expanduser( - '~/.config/leap/providers/{provider}/keys/' - 'client/smtp_{username}.pem'.format( - provider=provider, username=username)) - # TODO --- get from config - smtp_host = 'antelope.mail.bitmask.net' - smtp_port = 2013 + smtp_client_cert = os.path.join( + get_path_prefix(), + 'leap', 'providers', provider, 'keys', + 'client', + 'smtp_{username}.pem'.format( + username=username)) + + assert(os.path.isfile(smtp_client_cert)) + + smtp_config = get_smtp_config(provider) + smtp_host = smtp_config.host + smtp_port = smtp_config.port self.smtp_config = LeapSMTPConfig( userid, -- cgit v1.2.3 From ae5663769f2231f51b1cf2682bc6b6039dc0ab13 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 24 Mar 2016 19:10:24 -0400 Subject: [feature] pass the path to pixelated_www package --- src/leap/bitmask/pix.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/leap/bitmask/pix.py b/src/leap/bitmask/pix.py index 96ca4299..8242255c 100644 --- a/src/leap/bitmask/pix.py +++ b/src/leap/bitmask/pix.py @@ -19,6 +19,7 @@ Pixelated plugin integration. """ import json import os +import sys from twisted.internet import defer from twisted.python import log @@ -26,6 +27,8 @@ from twisted.python import log from leap.bitmask.util import get_path_prefix from leap.mail.imap.account import IMAPAccount +import pixelated_www + from pixelated.adapter.mailstore import LeapMailStore from pixelated.adapter.welcome_mail import add_welcome_mail from pixelated.application import SingleUserServicesFactory @@ -49,12 +52,19 @@ def start_pixelated_user_agent(userid, soledad, keymanager): services_factory = SingleUserServicesFactory( UserAgentMode(is_single_user=True)) - resource = RootResource(services_factory) + if getattr(sys, 'frozen', False): + # we are running in a |PyInstaller| bundle + static_folder = os.path.join(sys._MEIPASS, 'pixelated_www') + else: + static_folder = os.path.abspath(pixelated_www.__path__) + + resource = RootResource(services_factory, static_folder=static_folder) + + config.host = 'localhost' config.port = 9090 config.sslkey = None config.sslcert = None - config.host = 'localhost' deferred = _start_in_single_user_mode( leap_session, config, -- cgit v1.2.3 From 68f11cff01c18750036afdd97ac9074f8538b676 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 28 Mar 2016 12:22:08 -0400 Subject: [docs] readme for pixelated functionality --- pkg/PixelatedWebmail.README | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 pkg/PixelatedWebmail.README diff --git a/pkg/PixelatedWebmail.README b/pkg/PixelatedWebmail.README new file mode 100644 index 00000000..c45dc9ae --- /dev/null +++ b/pkg/PixelatedWebmail.README @@ -0,0 +1,38 @@ +How to enable Pixelated Webmail +------------------------------- + +WARNING! This is an experimental feature. +It can expose your mail to *any* user with access to your machine, since there +is no authentication in place at the moment. It could even eat your data. You +have been warned. + +Ok, how do I enable this wonderful feature? +------------------------------------------- + +First, run the bundle for a first time, and ensure that you can register a new +account with a mail-enabled provider (for instance, mail.bitmask.net). + +Then, you have to edit a config file living inside the bundle folders. You have +to add "Pixmail=true" under the [General] section, like this: + +config/leap/leap.conf: + +[General] +SkipFirstRun=true +Provider=mail.bitmask.net +Pixmail=true + +[mail.bitmask.net] +Services=mx + +Then, run bitmask again: + +./bitmask --debug + +And a new "Bitmask Webmail" option should have appeared under the "Bitmask" +menu. + +If you want to disable the Webmail functionality, just set the Pixmail property +to 'false'. + +Enjoy your local and encrypted pixelated webmail! -- cgit v1.2.3 From 1699a0c9849de6581f1e2c5ee74ea8d3c83d3591 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 28 Mar 2016 12:23:31 -0400 Subject: [pkg] some pyinstaller hacky targets --- Makefile | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Makefile b/Makefile index 1cfeecb4..30e67962 100644 --- a/Makefile +++ b/Makefile @@ -167,6 +167,32 @@ sumo_tarball_latest: checkout_leapdeps_develop pull_leapdeps setup_without_names pyinst: pyinstaller -y pkg/pyinst/bitmask.spec +pyinst-hacks: + cp ../leap_common/src/leap/common/cacert.pem dist/bitmask/ + mkdir -p dist/bitmask/pysqlcipher + cp $(VIRTUAL_ENV)/lib/python2.7/site-packages/pysqlcipher/_sqlite.so dist/bitmask/pysqlcipher + cp -r $(VIRTUAL_ENV)/lib/python2.7/site-packages/pixelated_www dist/bitmask/ + +pyinst-wrapper: + mv dist/bitmask/bitmask dist/bitmask/bitmask-app + cp pkg/linux/bitmask-launcher dist/bitmask/bitmask + cp pkg/PixelatedWebmail.README dist/bitmask + +pyinst-trim: + rm dist/bitmask/libQtOpenGL.so.4 + rm dist/bitmask/libQtSql.so.4 + rm dist/bitmask/libQt3Support.so.4 + rm dist/bitmask/libnvidia-glcore.so.352.79 + rm dist/bitmask/libgstvideo-1.0.so.0 + rm dist/bitmask/libgstaudio-1.0.so.0 + rm dist/bitmask/libgstreamer-1.0.so.0 + rm dist/bitmask/libnvidia-tls.so.352.79 + rm dist/bitmask/libaudio.so.2 + +pyinst-dist: + rm -rf dist/bitmask/config + cd dist/ && tar cvzf Bitmask.0.9.2.alpha1.tar.gz bitmask + clean_pkg: rm -rf build dist -- cgit v1.2.3 From b7a435abc0f0c366fdc91f6be240b76e8332e4c6 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 28 Mar 2016 12:23:59 -0400 Subject: [pkg] pass --standalone flag to binary --- pkg/linux/bitmask-launcher | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/linux/bitmask-launcher b/pkg/linux/bitmask-launcher index 550dd134..90ced511 100755 --- a/pkg/linux/bitmask-launcher +++ b/pkg/linux/bitmask-launcher @@ -6,4 +6,4 @@ [ -f libQtGui.so.4 ] || ln -s libQtGui.so.4.orig libQtGui.so.4 cat /etc/os-release | grep ID | grep -i ubuntu && unlink libQtCore.so.4 && unlink libQtGui.so.4 -./bitmask-app "$@" +./bitmask-app --standalone "$@" -- cgit v1.2.3 From 7133000f82b79470fa91c42b286fffeeece28631 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 28 Mar 2016 12:24:48 -0400 Subject: [pkg] update versions for bundle --- pkg/pyinst/bitmask.spec | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/pyinst/bitmask.spec b/pkg/pyinst/bitmask.spec index 2bc2f9d2..a76ccb17 100644 --- a/pkg/pyinst/bitmask.spec +++ b/pkg/pyinst/bitmask.spec @@ -1,12 +1,13 @@ # -*- mode: python -*- +import sys block_cipher = None -a = Analysis([os.path.join('pkg', 'pyinst', 'bitmask.py')], +a = Analysis([os.path.join('bitmask.py')], hiddenimports=[ 'zope.interface', 'zope.proxy', - 'PySide.QtCore', 'PySide.QtGui'], + 'PySide.QtCore', 'PySide.QtGui', 'PySide.QtWebKit'], hookspath=None, runtime_hooks=None, excludes=None, @@ -33,6 +34,6 @@ if sys.platform.startswith("darwin"): name=os.path.join( 'dist', 'Bitmask.app'), appname='Bitmask', - version='0.9.0rc2', + version='0.9.2alpha1', icon='pkg/osx/bitmask.icns', - bundle_identifier='bitmask-0.9.0rc2') + bundle_identifier='bitmask-0.9.2alpha1') -- cgit v1.2.3 From 054ceb6225ef9c32f3d24870bf3e8085bbe0c432 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 28 Mar 2016 12:25:29 -0400 Subject: [pkg] update freeze_deb command --- setup.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index fc9c36c9..15327bf1 100755 --- a/setup.py +++ b/setup.py @@ -105,13 +105,13 @@ class freeze_debianver(Command): # of this file. version_version = '{version}' -version_revisionid = '{version_revision}' +full_revisionid = '{full_revisionid}' """ templatefun = r""" def get_versions(default={}, verbose=False): return {'version': version_version, - 'full-revisionid': version_revisionid} + 'full-revisionid': full_revisionid} """ def initialize_options(self): @@ -128,8 +128,9 @@ def get_versions(default={}, verbose=False): return subst_template = self.template.format( version=VERSION_SHORT, - version_full=VERSION_REVISION) + self.templatefun - with open(versioneer.versionfile_source, 'w') as f: + full_revisionid=VERSION_REVISION) + self.templatefun + versioneer_cfg = versioneer.get_config_from_root('.') + with open(versioneer_cfg.versionfile_source, 'w') as f: f.write(subst_template) -- cgit v1.2.3 From 098b0974925c44dad1e98352670959cba66f9c0a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 28 Mar 2016 12:27:13 -0400 Subject: [bug] fix case for constant --- src/leap/bitmask/config/leapsettings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index a060c8a4..d7590c24 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -353,7 +353,7 @@ class LeapSettings(object): :type skip: bool """ leap_assert_type(skip, bool) - self._settings.setvalue(self.skipfirstrun_key, skip) + self._settings.setValue(self.SKIPFIRSTRUN_KEY, skip) def get_pixelmail_enabled(self): return to_bool(self._settings.value(self.PIXELMAIL_KEY, False)) -- cgit v1.2.3 From 541cf77d2c7bc9202474cce4c10b535a9c225ccd Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 28 Mar 2016 12:28:52 -0400 Subject: [bug] set standalone flag properly otherwise, the configs are saved to the user home config folder. --- src/leap/bitmask/services/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/leap/bitmask/services/__init__.py b/src/leap/bitmask/services/__init__.py index 54426669..d86f8aa4 100644 --- a/src/leap/bitmask/services/__init__.py +++ b/src/leap/bitmask/services/__init__.py @@ -154,6 +154,9 @@ def download_service_config(provider_config, service_config, # Not modified service_path = ("leap", "providers", provider_config.get_domain(), service_json) + + service_config.__class__.standalone = flags.STANDALONE + if res.status_code == 304: logger.debug( "{0} definition has not been modified".format( -- cgit v1.2.3 From 62e9ae1bc0e28961e10b46646f6131152844d94c Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 28 Mar 2016 12:29:57 -0400 Subject: [bug] instantiate soledadconfig if needed --- src/leap/bitmask/services/soledad/soledadbootstrapper.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index a10a2731..21cdee31 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -331,6 +331,9 @@ class SoledadBootstrapper(AbstractBootstrapper): :returns: the server url :rtype: unicode """ + if not self._soledad_config: + self._soledad_config = SoledadConfig() + # TODO: Select server based on timezone (issue #3308) server_dict = self._soledad_config.get_hosts() -- cgit v1.2.3 From aefc5edec1d6bd693bae72235ab58312e49bb236 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 29 Mar 2016 12:58:14 -0400 Subject: [doc] add entry in next-changelog for the pixelated feature --- changes/next-changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst index d07273ec..b20efdc8 100644 --- a/changes/next-changelog.rst +++ b/changes/next-changelog.rst @@ -14,6 +14,7 @@ Features - `#7656 `_: Adapt to multi-user aware events. - `#4469 `_: Display randomly generated service token on the Help Window. - Use cred-based authentication on SMTP. +- Experimental support for the Pixelated WebMail. - `#1234 `_: Description of the new feature corresponding with issue #1234. - New feature without related issue number. -- cgit v1.2.3 From ea9a9f09c33f4f5e6faf0a8297200f44688eb86f Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 30 Mar 2016 12:22:35 -0400 Subject: [pkg] add requirements file for pixelated user agent --- pkg/requirements-pixelated.pip | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 pkg/requirements-pixelated.pip diff --git a/pkg/requirements-pixelated.pip b/pkg/requirements-pixelated.pip new file mode 100644 index 00000000..d1f8004d --- /dev/null +++ b/pkg/requirements-pixelated.pip @@ -0,0 +1,4 @@ +--find-links https://downloads.leap.se/libs/pixelated/ +pixelated-user-agent +pixelated-www +whoosh -- cgit v1.2.3 From 746d76c8e6f02ba417b7fb9d47630c4e83b40126 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 31 Mar 2016 10:38:11 -0400 Subject: [bug] workaround for incompatible qt library in ubuntu - Resolves: #7990 --- Makefile | 49 ++++++++++++++++++++++++++++------------------ pkg/linux/bitmask-launcher | 15 +++++++++++++- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 30e67962..c3f0e0d2 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,9 @@ PYRCC = pyside-rcc PYLUP = pyside-lupdate LRELE = lrelease +# pyinst dist dir +DIST = dist/bitmask/ + ################################# # DO NOT EDIT FOLLOWING @@ -165,33 +168,41 @@ sumo_tarball_latest: checkout_leapdeps_develop pull_leapdeps setup_without_names git checkout -- setup.py pyinst: + echo "MAKE SURE OF FREEZING VERSION FIRST!" pyinstaller -y pkg/pyinst/bitmask.spec pyinst-hacks: - cp ../leap_common/src/leap/common/cacert.pem dist/bitmask/ - mkdir -p dist/bitmask/pysqlcipher - cp $(VIRTUAL_ENV)/lib/python2.7/site-packages/pysqlcipher/_sqlite.so dist/bitmask/pysqlcipher - cp -r $(VIRTUAL_ENV)/lib/python2.7/site-packages/pixelated_www dist/bitmask/ + cp ../leap_common/src/leap/common/cacert.pem $(DIST) + mkdir -p $(DIST)pysqlcipher + cp $(VIRTUAL_ENV)/lib/python2.7/site-packages/pysqlcipher/_sqlite.so $(DIST)pysqlcipher + cp -r $(VIRTUAL_ENV)/lib/python2.7/site-packages/pixelated_www $(DIST) + +pyinst-trim: + rm -f $(DIST)libQtOpenGL.so.4 + rm -f $(DIST)libQtSql.so.4 + rm -f $(DIST)libQt3Support.so.4 + rm -f $(DIST)libaudio.so.2 + rm -f $(DIST)libnvidia-* + #rm -f dist/bitmask/libgstvideo-1.0.so.0 + #rm -f dist/bitmask/libgstaudio0.0.so.0 + #rm -f dist/bitmask/libgstreamer-1.0.so.0 pyinst-wrapper: - mv dist/bitmask/bitmask dist/bitmask/bitmask-app - cp pkg/linux/bitmask-launcher dist/bitmask/bitmask - cp pkg/PixelatedWebmail.README dist/bitmask + mv $(DIST)libQtCore.so.4 $(DIST)libQtCore.so.4.orig + mv $(DIST)libQtGui.so.4 $(DIST)libQtGui.so.4.orig + mv $(DIST)libQtNetwork.so.4 $(DIST)libQtNetwork.so.4.orig + mv $(DIST)libQtSvg.so.4 $(DIST)libQtSvg.so.4.orig + mv $(DIST)libQtWebKit.so.4 $(DIST)libQtWebKit.so.4.orig + mv $(DIST)libQtXmlPatterns.so.4 $(DIST)libQtXmlPatterns.so.4.orig + mv $(DIST)libQtXml.so.4 $(DIST)libQtXml.so.4.orig + mv $(DIST)bitmask $(DIST)bitmask-app + cp pkg/linux/bitmask-launcher $(DIST)bitmask + cp pkg/PixelatedWebmail.README $(DIST) -pyinst-trim: - rm dist/bitmask/libQtOpenGL.so.4 - rm dist/bitmask/libQtSql.so.4 - rm dist/bitmask/libQt3Support.so.4 - rm dist/bitmask/libnvidia-glcore.so.352.79 - rm dist/bitmask/libgstvideo-1.0.so.0 - rm dist/bitmask/libgstaudio-1.0.so.0 - rm dist/bitmask/libgstreamer-1.0.so.0 - rm dist/bitmask/libnvidia-tls.so.352.79 - rm dist/bitmask/libaudio.so.2 pyinst-dist: - rm -rf dist/bitmask/config - cd dist/ && tar cvzf Bitmask.0.9.2.alpha1.tar.gz bitmask + rm -rf $(DIST)config + cd dist/ && tar cvzf Bitmask.0.9.2.alpha2.tar.gz bitmask clean_pkg: rm -rf build dist diff --git a/pkg/linux/bitmask-launcher b/pkg/linux/bitmask-launcher index 90ced511..3eae57c0 100755 --- a/pkg/linux/bitmask-launcher +++ b/pkg/linux/bitmask-launcher @@ -4,6 +4,19 @@ [ -f libQtCore.so.4 ] || ln -s libQtCore.so.4.orig libQtCore.so.4 [ -f libQtGui.so.4 ] || ln -s libQtGui.so.4.orig libQtGui.so.4 -cat /etc/os-release | grep ID | grep -i ubuntu && unlink libQtCore.so.4 && unlink libQtGui.so.4 +[ -f libQtNetwork.so.4 ] || ln -s libQtNetwork.so.4.orig libQtNetwork.so.4 +[ -f libQtSvg.so.4 ] || ln -s libQtSvg.so.4.orig libQtSvg.so.4 +[ -f libQtWebKit.so.4 ] || ln -s libQtWebKit.so.4.orig libQtWebKit.so.4 +[ -f libQtXmlPatterns.so.4 ] || ln -s libQtXmlPatterns.so.4.orig libQtXmlPatterns.so.4 +[ -f libQtXml.so.4 ] || ln -s libQtXml.so.4.orig libQtXml.so.4 + +cat /etc/os-release | grep ID | grep -i ubuntu && \ + unlink libQtCore.so.4 && \ + unlink libQtGui.so.4 && \ + unlink libQtNetwork.so.4 && \ + unlink libQtSvg.so.4 && \ + unlink libQtWebKit.so.4 && \ + unlink libQtXmlPatterns.so.4 && \ + unlink libQtXml.so.4 ./bitmask-app --standalone "$@" -- cgit v1.2.3 From eb55be39cea024b253ee784dc304e72f1af94d7f Mon Sep 17 00:00:00 2001 From: Elijah Sparrow Date: Wed, 13 Jan 2016 15:27:47 -0800 Subject: update docker readme fix docker readme to reflect the proper commands --- docker/README.rst | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docker/README.rst b/docker/README.rst index 70d44809..4a2d08e7 100644 --- a/docker/README.rst +++ b/docker/README.rst @@ -6,11 +6,9 @@ Here we have several tools that leverages docker to ease Bitmask testing. ``bitmask-docker.sh`` is a helper script to ``build`` and ``run`` the bitmask app, here is an example usage:: - $ ./bitmask-docker build # build docker image - $ ./bitmask-docker init ro bitmask-nightly.json # initialize all the stuff needed - # .... - $ ./bitmask-docker.sh run - + $ ./bitmask-docker.sh build # build docker image + $ ./bitmask-docker.sh init bitmask-nightly.json # initialize (takes time) + $ ./bitmask-docker.sh run # run bitmask in docker ``bitmask-nightly.json`` is the version specifier for each bitmask component that will be used to run bitmask. -- cgit v1.2.3 From 9835bc05eaa0525df91a317c84f86b517ed57c73 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 4 Apr 2016 10:16:52 -0400 Subject: [bug] update bootstrap script so that it installs pixelated reqs too --- pkg/scripts/bootstrap_develop.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/scripts/bootstrap_develop.sh b/pkg/scripts/bootstrap_develop.sh index 68edcd43..3695fabc 100755 --- a/pkg/scripts/bootstrap_develop.sh +++ b/pkg/scripts/bootstrap_develop.sh @@ -118,6 +118,7 @@ setup_develop() { # hack to solve gnupg version problem pip uninstall -y gnupg && pip install gnupg + pip install -r pkg/requirements-pixelated.pip set +x echo "${cc_green}Status: $status done.${cc_normal}" } -- cgit v1.2.3 From fe96e2eef29a538b856f0d8b4bbf3940e42364c1 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 1 Apr 2016 12:55:00 -0400 Subject: [pkg] update to versioneer 0.16 --- setup.py | 11 +- src/leap/bitmask/_version.py | 100 ++++++++++------- versioneer.py | 261 ++++++++++++++++++++++++++++--------------- 3 files changed, 233 insertions(+), 139 deletions(-) diff --git a/setup.py b/setup.py index 15327bf1..5bf5d186 100755 --- a/setup.py +++ b/setup.py @@ -26,6 +26,7 @@ import os import re from distutils.command.build import build as _build +from setuptools import Command if not sys.version_info[0] == 2: print("[ERROR] Sorry, Python 3 is not supported (yet). " @@ -84,11 +85,6 @@ if len(_version_short) > 0: VERSION_SHORT = _version_short[0] DOWNLOAD_URL = DOWNLOAD_BASE % VERSION_SHORT -cmdclass = versioneer.get_cmdclass() - - -from setuptools import Command - class freeze_debianver(Command): @@ -99,7 +95,7 @@ class freeze_debianver(Command): user_options = [] template = r""" # This file was generated by the `freeze_debianver` command in setup.py -# Using 'versioneer.py' (0.7+) from +# Using 'versioneer.py' (0.16) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. @@ -145,12 +141,12 @@ def freeze_pkg_ver(path, version_short, version_full): with open(path, 'w') as f: f.write(subst_template) - if sys.argv[:1] == '--sumo': IS_SUMO = True else: IS_SUMO = False +cmdclass = versioneer.get_cmdclass() cmdclass["freeze_debianver"] = freeze_debianver parsed_reqs = utils.parse_requirements() @@ -263,7 +259,6 @@ cmdclass["hash_binaries"] = cmd_binary_hash # sdist class need to augment the versioneer modified ones -#_build = cmdclass['build_py'] versioneer_sdist = cmdclass['sdist'] diff --git a/src/leap/bitmask/_version.py b/src/leap/bitmask/_version.py index 8c507c92..f032a17a 100644 --- a/src/leap/bitmask/_version.py +++ b/src/leap/bitmask/_version.py @@ -6,7 +6,9 @@ # that just contains the computed version number. # This file is released into the public domain. Generated by -# versioneer-0.15 (https://github.com/warner/python-versioneer) +# versioneer-0.16 (https://github.com/warner/python-versioneer) + +"""Git implementation of _version.py.""" import errno import os @@ -16,6 +18,7 @@ import sys def get_keywords(): + """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call @@ -27,10 +30,11 @@ def get_keywords(): class VersioneerConfig: - pass + """Container for Versioneer configuration parameters.""" def get_config(): + """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() @@ -44,7 +48,7 @@ def get_config(): class NotThisMethod(Exception): - pass + """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY = {} @@ -52,7 +56,9 @@ HANDLERS = {} def register_vcs_handler(vcs, method): # decorator + """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): + """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f @@ -61,6 +67,7 @@ def register_vcs_handler(vcs, method): # decorator def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: @@ -94,8 +101,11 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): def versions_from_parentdir(parentdir_prefix, root, verbose): - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. + """Try to determine the version from the parent directory name. + + Source tarballs conventionally unpack into a directory that includes + both the project name and a version string. + """ dirname = os.path.basename(root) if not dirname.startswith(parentdir_prefix): if verbose: @@ -109,6 +119,7 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): + """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from @@ -133,6 +144,7 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): + """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") refnames = keywords["refnames"].strip() @@ -178,11 +190,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): - # this runs 'git' from the root of the source tree. This only gets called - # if the git-archive 'subst' keywords were *not* expanded, and - # _version.py hasn't already been rewritten with a short version string, - # meaning we're inside a checked out source tree. + """Get version from 'git describe' in the root of the source tree. + This only gets called if the git-archive 'subst' keywords were *not* + expanded, and _version.py hasn't already been rewritten with a short + version string, meaning we're inside a checked out source tree. + """ if not os.path.exists(os.path.join(root, ".git")): if verbose: print("no .git in %s" % root) @@ -191,10 +204,11 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - # if there is a tag, this yields TAG-NUM-gHEX[-dirty] - # if there are no tags, this yields HEX[-dirty] (no NUM) + # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] + # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long"], + "--always", "--long", + "--match", "%s*" % tag_prefix], cwd=root) # --long was added in git-1.5.5 if describe_out is None: @@ -259,19 +273,21 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): def plus_or_dot(pieces): + """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): - # now build up version string, with post-release "local version - # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + """Build up version string, with post-release "local version identifier". - # exceptions: - # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + Exceptions: + 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -289,11 +305,11 @@ def render_pep440(pieces): def render_pep440_pre(pieces): - # TAG[.post.devDISTANCE] . No -dirty - - # exceptions: - # 1: no tags. 0.post.devDISTANCE + """TAG[.post.devDISTANCE] -- No -dirty. + Exceptions: + 1: no tags. 0.post.devDISTANCE + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: @@ -305,14 +321,15 @@ def render_pep440_pre(pieces): def render_pep440_post(pieces): - # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that - # .dev0 sorts backwards (a dirty tree will appear "older" than the - # corresponding clean one), but you shouldn't be releasing software with - # -dirty anyways. + """TAG[.postDISTANCE[.dev0]+gHEX] . - # exceptions: - # 1: no tags. 0.postDISTANCE[.dev0] + The ".dev0" means dirty. Note that .dev0 sorts backwards + (a dirty tree will appear "older" than the corresponding clean one), + but you shouldn't be releasing software with -dirty anyways. + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -331,11 +348,13 @@ def render_pep440_post(pieces): def render_pep440_old(pieces): - # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. + """TAG[.postDISTANCE[.dev0]] . - # exceptions: - # 1: no tags. 0.postDISTANCE[.dev0] + The ".dev0" means dirty. + Eexceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -351,12 +370,13 @@ def render_pep440_old(pieces): def render_git_describe(pieces): - # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty - # --always' + """TAG[-DISTANCE-gHEX][-dirty]. - # exceptions: - # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + Like 'git describe --tags --dirty --always'. + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: @@ -370,12 +390,14 @@ def render_git_describe(pieces): def render_git_describe_long(pieces): - # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty - # --always -long'. The distance/hash is unconditional. + """TAG-DISTANCE-gHEX[-dirty]. - # exceptions: - # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + Like 'git describe --tags --dirty --always -long'. + The distance/hash is unconditional. + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) @@ -388,6 +410,7 @@ def render_git_describe_long(pieces): def render(pieces, style): + """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), @@ -417,6 +440,7 @@ def render(pieces, style): def get_versions(): + """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which diff --git a/versioneer.py b/versioneer.py index 89194938..24a69025 100644 --- a/versioneer.py +++ b/versioneer.py @@ -1,7 +1,8 @@ -# Version: 0.15 +# Version: 0.16 + +"""The Versioneer - like a rocketeer, but for versions. -""" The Versioneer ============== @@ -9,7 +10,7 @@ The Versioneer * https://github.com/warner/python-versioneer * Brian Warner * License: Public Domain -* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, and pypy +* Compatible With: python2.6, 2.7, 3.3, 3.4, 3.5, and pypy * [![Latest Version] (https://pypip.in/version/versioneer/badge.svg?style=flat) ](https://pypi.python.org/pypi/versioneer/) @@ -125,16 +126,18 @@ First, decide on values for the following configuration variables: If this is set to None, then `setup.py build` will not attempt to rewrite any `_version.py` in the built tree. If your project does not have any libraries (e.g. if it only builds a script), then you should use - `versionfile_build = None` and override `distutils.command.build_scripts` - to explicitly insert a copy of `versioneer.get_version()` into your - generated script. + `versionfile_build = None`. To actually use the computed version string, + your `setup.py` will need to override `distutils.command.build_scripts` + with a subclass that explicitly inserts a copy of + `versioneer.get_version()` into your script file. See + `test/demoapp-script-only/setup.py` for an example. * `tag_prefix`: a string, like 'PROJECTNAME-', which appears at the start of all VCS tags. If your tags look like 'myproject-1.2.0', then you should use tag_prefix='myproject-'. If you use unprefixed tags like '1.2.0', this - should be an empty string. + should be an empty string, using either `tag_prefix=` or `tag_prefix=''`. * `parentdir_prefix`: @@ -159,7 +162,7 @@ To versioneer-enable your project: style = pep440 versionfile_source = src/myproject/_version.py versionfile_build = myproject/_version.py - tag_prefix = "" + tag_prefix = parentdir_prefix = myproject- ```` @@ -286,6 +289,10 @@ To upgrade your project to a new release of Versioneer, do the following: `SRC/_version.py` * commit any changed files +### Upgrading to 0.16 + +Nothing special. + ### Upgrading to 0.15 Starting with this version, Versioneer is configured with a `[versioneer]` @@ -333,9 +340,11 @@ number of intermediate scripts. ## License -To make Versioneer easier to embed, all its code is hereby released into the -public domain. The `_version.py` that it creates is also in the public -domain. +To make Versioneer easier to embed, all its code is dedicated to the public +domain. The `_version.py` that it creates is also in the public domain. +Specifically, both are released under the Creative Commons "Public Domain +Dedication" license (CC0-1.0), as described in +https://creativecommons.org/publicdomain/zero/1.0/ . """ @@ -353,12 +362,15 @@ import sys class VersioneerConfig: - pass + """Container for Versioneer configuration parameters.""" def get_root(): - # we require that all commands are run from the project root, i.e. the - # directory that contains setup.py, setup.cfg, and versioneer.py . + """Get the project root directory. + + We require that all commands are run from the project root, i.e. the + directory that contains setup.py, setup.cfg, and versioneer.py . + """ root = os.path.realpath(os.path.abspath(os.getcwd())) setup_py = os.path.join(root, "setup.py") versioneer_py = os.path.join(root, "versioneer.py") @@ -391,6 +403,7 @@ def get_root(): def get_config_from_root(root): + """Read the project setup.cfg file to determine Versioneer config.""" # This might raise EnvironmentError (if setup.cfg is missing), or # configparser.NoSectionError (if it lacks a [versioneer] section), or # configparser.NoOptionError (if it lacks "VCS="). See the docstring at @@ -411,13 +424,15 @@ def get_config_from_root(root): cfg.versionfile_source = get(parser, "versionfile_source") cfg.versionfile_build = get(parser, "versionfile_build") cfg.tag_prefix = get(parser, "tag_prefix") + if cfg.tag_prefix in ("''", '""'): + cfg.tag_prefix = "" cfg.parentdir_prefix = get(parser, "parentdir_prefix") cfg.verbose = get(parser, "verbose") return cfg class NotThisMethod(Exception): - pass + """Exception raised if a method is not valid for the current scenario.""" # these dictionaries contain VCS-specific tools LONG_VERSION_PY = {} @@ -425,7 +440,9 @@ HANDLERS = {} def register_vcs_handler(vcs, method): # decorator + """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): + """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f @@ -434,6 +451,7 @@ def register_vcs_handler(vcs, method): # decorator def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: @@ -472,7 +490,9 @@ LONG_VERSION_PY['git'] = ''' # that just contains the computed version number. # This file is released into the public domain. Generated by -# versioneer-0.15 (https://github.com/warner/python-versioneer) +# versioneer-0.16 (https://github.com/warner/python-versioneer) + +"""Git implementation of _version.py.""" import errno import os @@ -482,6 +502,7 @@ import sys def get_keywords(): + """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call @@ -493,10 +514,11 @@ def get_keywords(): class VersioneerConfig: - pass + """Container for Versioneer configuration parameters.""" def get_config(): + """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() @@ -510,7 +532,7 @@ def get_config(): class NotThisMethod(Exception): - pass + """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY = {} @@ -518,7 +540,9 @@ HANDLERS = {} def register_vcs_handler(vcs, method): # decorator + """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): + """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f @@ -527,6 +551,7 @@ def register_vcs_handler(vcs, method): # decorator def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: @@ -560,8 +585,11 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): def versions_from_parentdir(parentdir_prefix, root, verbose): - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. + """Try to determine the version from the parent directory name. + + Source tarballs conventionally unpack into a directory that includes + both the project name and a version string. + """ dirname = os.path.basename(root) if not dirname.startswith(parentdir_prefix): if verbose: @@ -575,6 +603,7 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): + """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from @@ -599,6 +628,7 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): + """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") refnames = keywords["refnames"].strip() @@ -644,11 +674,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): - # this runs 'git' from the root of the source tree. This only gets called - # if the git-archive 'subst' keywords were *not* expanded, and - # _version.py hasn't already been rewritten with a short version string, - # meaning we're inside a checked out source tree. + """Get version from 'git describe' in the root of the source tree. + This only gets called if the git-archive 'subst' keywords were *not* + expanded, and _version.py hasn't already been rewritten with a short + version string, meaning we're inside a checked out source tree. + """ if not os.path.exists(os.path.join(root, ".git")): if verbose: print("no .git in %%s" %% root) @@ -657,10 +688,11 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - # if there is a tag, this yields TAG-NUM-gHEX[-dirty] - # if there are no tags, this yields HEX[-dirty] (no NUM) + # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] + # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long"], + "--always", "--long", + "--match", "%%s*" %% tag_prefix], cwd=root) # --long was added in git-1.5.5 if describe_out is None: @@ -725,19 +757,21 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): def plus_or_dot(pieces): + """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): - # now build up version string, with post-release "local version - # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + """Build up version string, with post-release "local version identifier". - # exceptions: - # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + Exceptions: + 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -755,11 +789,11 @@ def render_pep440(pieces): def render_pep440_pre(pieces): - # TAG[.post.devDISTANCE] . No -dirty - - # exceptions: - # 1: no tags. 0.post.devDISTANCE + """TAG[.post.devDISTANCE] -- No -dirty. + Exceptions: + 1: no tags. 0.post.devDISTANCE + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: @@ -771,14 +805,15 @@ def render_pep440_pre(pieces): def render_pep440_post(pieces): - # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that - # .dev0 sorts backwards (a dirty tree will appear "older" than the - # corresponding clean one), but you shouldn't be releasing software with - # -dirty anyways. + """TAG[.postDISTANCE[.dev0]+gHEX] . - # exceptions: - # 1: no tags. 0.postDISTANCE[.dev0] + The ".dev0" means dirty. Note that .dev0 sorts backwards + (a dirty tree will appear "older" than the corresponding clean one), + but you shouldn't be releasing software with -dirty anyways. + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -797,11 +832,13 @@ def render_pep440_post(pieces): def render_pep440_old(pieces): - # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. + """TAG[.postDISTANCE[.dev0]] . - # exceptions: - # 1: no tags. 0.postDISTANCE[.dev0] + The ".dev0" means dirty. + Eexceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -817,12 +854,13 @@ def render_pep440_old(pieces): def render_git_describe(pieces): - # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty - # --always' + """TAG[-DISTANCE-gHEX][-dirty]. - # exceptions: - # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + Like 'git describe --tags --dirty --always'. + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: @@ -836,12 +874,14 @@ def render_git_describe(pieces): def render_git_describe_long(pieces): - # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty - # --always -long'. The distance/hash is unconditional. + """TAG-DISTANCE-gHEX[-dirty]. - # exceptions: - # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + Like 'git describe --tags --dirty --always -long'. + The distance/hash is unconditional. + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) @@ -854,6 +894,7 @@ def render_git_describe_long(pieces): def render(pieces, style): + """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), @@ -883,6 +924,7 @@ def render(pieces, style): def get_versions(): + """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which @@ -929,6 +971,7 @@ def get_versions(): @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): + """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from @@ -953,6 +996,7 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): + """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") refnames = keywords["refnames"].strip() @@ -998,11 +1042,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): - # this runs 'git' from the root of the source tree. This only gets called - # if the git-archive 'subst' keywords were *not* expanded, and - # _version.py hasn't already been rewritten with a short version string, - # meaning we're inside a checked out source tree. + """Get version from 'git describe' in the root of the source tree. + This only gets called if the git-archive 'subst' keywords were *not* + expanded, and _version.py hasn't already been rewritten with a short + version string, meaning we're inside a checked out source tree. + """ if not os.path.exists(os.path.join(root, ".git")): if verbose: print("no .git in %s" % root) @@ -1011,10 +1056,11 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - # if there is a tag, this yields TAG-NUM-gHEX[-dirty] - # if there are no tags, this yields HEX[-dirty] (no NUM) + # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] + # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long"], + "--always", "--long", + "--match", "%s*" % tag_prefix], cwd=root) # --long was added in git-1.5.5 if describe_out is None: @@ -1079,6 +1125,11 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): def do_vcs_install(manifest_in, versionfile_source, ipy): + """Git-specific installation logic for Versioneer. + + For Git, this means creating/changing .gitattributes to mark _version.py + for export-time keyword substitution. + """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] @@ -1112,8 +1163,11 @@ def do_vcs_install(manifest_in, versionfile_source, ipy): def versions_from_parentdir(parentdir_prefix, root, verbose): - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. + """Try to determine the version from the parent directory name. + + Source tarballs conventionally unpack into a directory that includes + both the project name and a version string. + """ dirname = os.path.basename(root) if not dirname.startswith(parentdir_prefix): if verbose: @@ -1125,7 +1179,7 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): "dirty": False, "error": None} SHORT_VERSION_PY = """ -# This file was generated by 'versioneer.py' (0.15) from +# This file was generated by 'versioneer.py' (0.16) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. @@ -1144,6 +1198,7 @@ def get_versions(): def versions_from_file(filename): + """Try to determine the version from _version.py if present.""" try: with open(filename) as f: contents = f.read() @@ -1157,6 +1212,7 @@ def versions_from_file(filename): def write_to_version_file(filename, versions): + """Write the given version number to the given _version.py file.""" os.unlink(filename) contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": ")) @@ -1167,19 +1223,21 @@ def write_to_version_file(filename, versions): def plus_or_dot(pieces): + """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): - # now build up version string, with post-release "local version - # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + """Build up version string, with post-release "local version identifier". - # exceptions: - # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + Exceptions: + 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -1197,11 +1255,11 @@ def render_pep440(pieces): def render_pep440_pre(pieces): - # TAG[.post.devDISTANCE] . No -dirty - - # exceptions: - # 1: no tags. 0.post.devDISTANCE + """TAG[.post.devDISTANCE] -- No -dirty. + Exceptions: + 1: no tags. 0.post.devDISTANCE + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: @@ -1213,14 +1271,15 @@ def render_pep440_pre(pieces): def render_pep440_post(pieces): - # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that - # .dev0 sorts backwards (a dirty tree will appear "older" than the - # corresponding clean one), but you shouldn't be releasing software with - # -dirty anyways. + """TAG[.postDISTANCE[.dev0]+gHEX] . - # exceptions: - # 1: no tags. 0.postDISTANCE[.dev0] + The ".dev0" means dirty. Note that .dev0 sorts backwards + (a dirty tree will appear "older" than the corresponding clean one), + but you shouldn't be releasing software with -dirty anyways. + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -1239,11 +1298,13 @@ def render_pep440_post(pieces): def render_pep440_old(pieces): - # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. + """TAG[.postDISTANCE[.dev0]] . - # exceptions: - # 1: no tags. 0.postDISTANCE[.dev0] + The ".dev0" means dirty. + Eexceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -1259,12 +1320,13 @@ def render_pep440_old(pieces): def render_git_describe(pieces): - # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty - # --always' + """TAG[-DISTANCE-gHEX][-dirty]. - # exceptions: - # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + Like 'git describe --tags --dirty --always'. + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: @@ -1278,12 +1340,14 @@ def render_git_describe(pieces): def render_git_describe_long(pieces): - # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty - # --always -long'. The distance/hash is unconditional. + """TAG-DISTANCE-gHEX[-dirty]. - # exceptions: - # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + Like 'git describe --tags --dirty --always -long'. + The distance/hash is unconditional. + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) @@ -1296,6 +1360,7 @@ def render_git_describe_long(pieces): def render(pieces, style): + """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), @@ -1325,12 +1390,14 @@ def render(pieces, style): class VersioneerBadRootError(Exception): - pass + """The project root directory is unknown or missing key files.""" def get_versions(verbose=False): - # returns dict with two keys: 'version' and 'full' + """Get the project version from whatever source is available. + Returns dict with two keys: 'version' and 'full'. + """ if "versioneer" in sys.modules: # see the discussion in cmdclass.py:get_cmdclass() del sys.modules["versioneer"] @@ -1402,10 +1469,12 @@ def get_versions(verbose=False): def get_version(): + """Get the short version string for this project.""" return get_versions()["version"] def get_cmdclass(): + """Get the custom setuptools/distutils subclasses used by Versioneer.""" if "versioneer" in sys.modules: del sys.modules["versioneer"] # this fixes the "python setup.py develop" case (also 'install' and @@ -1456,7 +1525,11 @@ def get_cmdclass(): # setuptools/install -> bdist_egg ->.. # setuptools/develop -> ? - from distutils.command.build_py import build_py as _build_py + # we override different "build_py" commands for both environments + if "setuptools" in sys.modules: + from setuptools.command.build_py import build_py as _build_py + else: + from distutils.command.build_py import build_py as _build_py class cmd_build_py(_build_py): @@ -1542,7 +1615,7 @@ a section like: style = pep440 versionfile_source = src/myproject/_version.py versionfile_build = myproject/_version.py - tag_prefix = "" + tag_prefix = parentdir_prefix = myproject- You will also need to edit your setup.py to use the results: @@ -1578,6 +1651,7 @@ del get_versions def do_setup(): + """Main VCS-independent setup function for installing Versioneer.""" root = get_root() try: cfg = get_config_from_root(root) @@ -1659,6 +1733,7 @@ def do_setup(): def scan_setup_py(): + """Validate the contents of setup.py against Versioneer's expectations.""" found = set() setters = False errors = 0 -- cgit v1.2.3 From 0ce1c02dd59df3e4f23ba0b90e67644258412533 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 4 Apr 2016 21:55:06 -0400 Subject: [bug] fail gracefully if no pixelated modules present currently, we're distributing the wheels for the pixelated modules under downloads.leap.se. bootstrap script tried to download the pixelated modules, but it fails on python versions < 2.7.9, apparently. as a workaround, I make the import of the pixelated modules a non-fatal error by setting a flag, and doing the launching of the pix UA conditional on a successful import. - Related: #8009 --- pkg/scripts/bootstrap_develop.sh | 3 ++- src/leap/bitmask/backend/components.py | 14 ++++++++------ src/leap/bitmask/pix.py | 26 +++++++++++++++----------- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/pkg/scripts/bootstrap_develop.sh b/pkg/scripts/bootstrap_develop.sh index 3695fabc..af3dce20 100755 --- a/pkg/scripts/bootstrap_develop.sh +++ b/pkg/scripts/bootstrap_develop.sh @@ -118,7 +118,8 @@ setup_develop() { # hack to solve gnupg version problem pip uninstall -y gnupg && pip install gnupg - pip install -r pkg/requirements-pixelated.pip + # XXX this fails in trusty; see #8009 + # pip install -r pkg/requirements-pixelated.pip set +x echo "${cc_green}Status: $status done.${cc_normal}" } diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 0f8864c3..0c2b3280 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -38,7 +38,7 @@ from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.crypto.srpregister import SRPRegister from leap.bitmask.logs.utils import get_logger from leap.bitmask.platform_init import IS_LINUX -from leap.bitmask.pix import start_pixelated_user_agent +from leap.bitmask import pix from leap.bitmask.provider.pinned import PinnedProviders from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper from leap.bitmask.services import get_supported @@ -1088,13 +1088,15 @@ class Mail(object): return threads.deferToThread(self._stop_imap_service) def start_pixelated_service(self, full_user_id): - reactor.callFromThread( - start_pixelated_user_agent, - full_user_id, - self._soledad_proxy, - self._keymanager_proxy) + if pix.HAS_PIXELATED: + reactor.callFromThread( + pix.start_pixelated_user_agent, + full_user_id, + self._soledad_proxy, + self._keymanager_proxy) def stop_pixelated_service(self): + # TODO stop it, somehow pass diff --git a/src/leap/bitmask/pix.py b/src/leap/bitmask/pix.py index 8242255c..a05a1d9c 100644 --- a/src/leap/bitmask/pix.py +++ b/src/leap/bitmask/pix.py @@ -27,17 +27,21 @@ from twisted.python import log from leap.bitmask.util import get_path_prefix from leap.mail.imap.account import IMAPAccount -import pixelated_www - -from pixelated.adapter.mailstore import LeapMailStore -from pixelated.adapter.welcome_mail import add_welcome_mail -from pixelated.application import SingleUserServicesFactory -from pixelated.application import UserAgentMode -from pixelated.application import start_site -from pixelated.bitmask_libraries.smtp import LeapSMTPConfig -from pixelated.bitmask_libraries.session import SessionCache -from pixelated.config import services -from pixelated.resources.root_resource import RootResource +try: + import pixelated_www + + from pixelated.adapter.mailstore import LeapMailStore + from pixelated.adapter.welcome_mail import add_welcome_mail + from pixelated.application import SingleUserServicesFactory + from pixelated.application import UserAgentMode + from pixelated.application import start_site + from pixelated.bitmask_libraries.smtp import LeapSMTPConfig + from pixelated.bitmask_libraries.session import SessionCache + from pixelated.config import services + from pixelated.resources.root_resource import RootResource + HAS_PIXELATED = True +except ImportError: + HAS_PIXELATED = False def start_pixelated_user_agent(userid, soledad, keymanager): -- cgit v1.2.3 From 093f84e42192fd8dc2234a00b754515be7ea9016 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 4 Apr 2016 17:51:30 -0400 Subject: [pkg] simple launcher for the linux bundles --- pkg/launcher/.gitignore | 2 ++ pkg/launcher/Makefile | 15 +++++++++++++++ pkg/launcher/README.rst | 14 ++++++++++++++ pkg/launcher/bitmask-launcher.c | 23 +++++++++++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 pkg/launcher/.gitignore create mode 100644 pkg/launcher/Makefile create mode 100644 pkg/launcher/README.rst create mode 100644 pkg/launcher/bitmask-launcher.c diff --git a/pkg/launcher/.gitignore b/pkg/launcher/.gitignore new file mode 100644 index 00000000..60de46d7 --- /dev/null +++ b/pkg/launcher/.gitignore @@ -0,0 +1,2 @@ +*.o +bitmask diff --git a/pkg/launcher/Makefile b/pkg/launcher/Makefile new file mode 100644 index 00000000..8dd1013d --- /dev/null +++ b/pkg/launcher/Makefile @@ -0,0 +1,15 @@ +CC = gcc +CFLAGS = -g -Wall +STRIP = strip + +default: bitmask + +bitmask.o: bitmask-launcher.c + $(CC) $(CFLAGS) -c bitmask-launcher.c -o bitmask.o + +bitmask: bitmask.o + $(CC) bitmask.o -o bitmask + $(STRIP) bitmask +clean: + -rm -f bitmask.o + -rm -f bitmask diff --git a/pkg/launcher/README.rst b/pkg/launcher/README.rst new file mode 100644 index 00000000..9a840dec --- /dev/null +++ b/pkg/launcher/README.rst @@ -0,0 +1,14 @@ +bitmask-launcher.c +------------------ + +A small, portable launcher for bitmask bundles. + +Problem that solves +------------------- +PyInstaller bundles leave everything (libs, data and the main binary) in a +single folder. In a case like ours, there are too many files cluttering this +top-most folder. + +We wanted to have a cleaner folder, with an obviously clickable entrypoint, that +calls the binary that hides in an inferior folder. + diff --git a/pkg/launcher/bitmask-launcher.c b/pkg/launcher/bitmask-launcher.c new file mode 100644 index 00000000..aac5da3f --- /dev/null +++ b/pkg/launcher/bitmask-launcher.c @@ -0,0 +1,23 @@ +/* + * bitmask-launcher.c + * + * part of the bitmask bundle. + * execute main entrypoint in a child folder inside the bundle. + * + * (c) LEAP Encryption Access Project, 2016. + * License: GPL. + * +*/ + +#include +#include + +char* const bitmask_path = "libs"; +char* const entrypoint = "bitmask"; + +int main(int argc, char *argv[]) +{ + argv[0] = entrypoint; + chdir(bitmask_path); + execv(entrypoint, argv); +} -- cgit v1.2.3 From b4dde3d5112c95574f85f62daf0c2c75db391f52 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 4 Apr 2016 22:19:52 -0400 Subject: [pkg] modularize main makefile to remove clutter --- Makefile | 67 ++-------------------------------------------- pkg/pyinst/pyinst-build.mk | 39 +++++++++++++++++++++++++++ pkg/sumo-tarballs.mk | 25 +++++++++++++++++ 3 files changed, 66 insertions(+), 65 deletions(-) create mode 100644 pkg/pyinst/pyinst-build.mk create mode 100644 pkg/sumo-tarballs.mk diff --git a/Makefile b/Makefile index c3f0e0d2..fea03951 100644 --- a/Makefile +++ b/Makefile @@ -141,71 +141,8 @@ checkout_leapdeps_develop: for repo in $(LEAP_REPOS); do cd $(CURDIR)/../$$repo && git checkout develop; done git checkout develop -checkout_leapdeps_release: - pkg/scripts/checkout_leap_versions.sh - -setup_without_namespace: - awk '!/namespace_packages*/' setup.py > file && mv file setup.py - -sumo_tarball_release: checkout_leapdeps_release setup_without_namespace - python setup.py sdist --sumo - git checkout -- src/leap/__init__.py - git checkout -- src/leap/bitmask/_version.py - rm -rf src/leap/soledad - git checkout -- setup.py - -# XXX We need two sets of sumo-tarballs: the one published for a release -# (that will pick the pinned leap deps), and the other which will be used -# for the nightly builds. -# TODO change naming scheme for sumo-latest: should include date (in case -# bitmask is not updated bu the dependencies are) - -sumo_tarball_latest: checkout_leapdeps_develop pull_leapdeps setup_without_namespace - python setup.py sdist --sumo # --latest - git checkout -- src/leap/__init__.py - git checkout -- src/leap/bitmask/_version.py - rm -rf src/leap/soledad - git checkout -- setup.py - -pyinst: - echo "MAKE SURE OF FREEZING VERSION FIRST!" - pyinstaller -y pkg/pyinst/bitmask.spec - -pyinst-hacks: - cp ../leap_common/src/leap/common/cacert.pem $(DIST) - mkdir -p $(DIST)pysqlcipher - cp $(VIRTUAL_ENV)/lib/python2.7/site-packages/pysqlcipher/_sqlite.so $(DIST)pysqlcipher - cp -r $(VIRTUAL_ENV)/lib/python2.7/site-packages/pixelated_www $(DIST) - -pyinst-trim: - rm -f $(DIST)libQtOpenGL.so.4 - rm -f $(DIST)libQtSql.so.4 - rm -f $(DIST)libQt3Support.so.4 - rm -f $(DIST)libaudio.so.2 - rm -f $(DIST)libnvidia-* - #rm -f dist/bitmask/libgstvideo-1.0.so.0 - #rm -f dist/bitmask/libgstaudio0.0.so.0 - #rm -f dist/bitmask/libgstreamer-1.0.so.0 - -pyinst-wrapper: - mv $(DIST)libQtCore.so.4 $(DIST)libQtCore.so.4.orig - mv $(DIST)libQtGui.so.4 $(DIST)libQtGui.so.4.orig - mv $(DIST)libQtNetwork.so.4 $(DIST)libQtNetwork.so.4.orig - mv $(DIST)libQtSvg.so.4 $(DIST)libQtSvg.so.4.orig - mv $(DIST)libQtWebKit.so.4 $(DIST)libQtWebKit.so.4.orig - mv $(DIST)libQtXmlPatterns.so.4 $(DIST)libQtXmlPatterns.so.4.orig - mv $(DIST)libQtXml.so.4 $(DIST)libQtXml.so.4.orig - mv $(DIST)bitmask $(DIST)bitmask-app - cp pkg/linux/bitmask-launcher $(DIST)bitmask - cp pkg/PixelatedWebmail.README $(DIST) - - -pyinst-dist: - rm -rf $(DIST)config - cd dist/ && tar cvzf Bitmask.0.9.2.alpha2.tar.gz bitmask - -clean_pkg: - rm -rf build dist +include pkg/sumo-tarballs.mk +include pkg/pyinst/pyinst-build.mk clean : $(RM) $(COMPILED_UI) $(COMPILED_RESOURCES) $(COMPILED_UI:.py=.pyc) $(COMPILED_RESOURCES:.py=.pyc) diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk new file mode 100644 index 00000000..532ec88c --- /dev/null +++ b/pkg/pyinst/pyinst-build.mk @@ -0,0 +1,39 @@ +pyinst: + echo "MAKE SURE OF FREEZING VERSION FIRST!" + pyinstaller -y pkg/pyinst/bitmask.spec + +pyinst-hacks: + cp ../leap_common/src/leap/common/cacert.pem $(DIST) + mkdir -p $(DIST)pysqlcipher + cp $(VIRTUAL_ENV)/lib/python2.7/site-packages/pysqlcipher/_sqlite.so $(DIST)pysqlcipher + cp -r $(VIRTUAL_ENV)/lib/python2.7/site-packages/pixelated_www $(DIST) + +pyinst-trim: + rm -f $(DIST)libQtOpenGL.so.4 + rm -f $(DIST)libQtSql.so.4 + rm -f $(DIST)libQt3Support.so.4 + rm -f $(DIST)libaudio.so.2 + rm -f $(DIST)libnvidia-* + #rm -f dist/bitmask/libgstvideo-1.0.so.0 + #rm -f dist/bitmask/libgstaudio0.0.so.0 + #rm -f dist/bitmask/libgstreamer-1.0.so.0 + +pyinst-wrapper: + mv $(DIST)libQtCore.so.4 $(DIST)libQtCore.so.4.orig + mv $(DIST)libQtGui.so.4 $(DIST)libQtGui.so.4.orig + mv $(DIST)libQtNetwork.so.4 $(DIST)libQtNetwork.so.4.orig + mv $(DIST)libQtSvg.so.4 $(DIST)libQtSvg.so.4.orig + mv $(DIST)libQtWebKit.so.4 $(DIST)libQtWebKit.so.4.orig + mv $(DIST)libQtXmlPatterns.so.4 $(DIST)libQtXmlPatterns.so.4.orig + mv $(DIST)libQtXml.so.4 $(DIST)libQtXml.so.4.orig + mv $(DIST)bitmask $(DIST)bitmask-app + cp pkg/linux/bitmask-launcher $(DIST)bitmask + cp pkg/PixelatedWebmail.README $(DIST) + + +pyinst-dist: + rm -rf $(DIST)config + cd dist/ && tar cvzf Bitmask.0.9.2.alpha2.tar.gz bitmask + +clean_pkg: + rm -rf build dist diff --git a/pkg/sumo-tarballs.mk b/pkg/sumo-tarballs.mk new file mode 100644 index 00000000..3bd5fa77 --- /dev/null +++ b/pkg/sumo-tarballs.mk @@ -0,0 +1,25 @@ +checkout_leapdeps_release: + pkg/scripts/checkout_leap_versions.sh + +setup_without_namespace: + awk '!/namespace_packages*/' setup.py > file && mv file setup.py + +sumo_tarball_release: checkout_leapdeps_release setup_without_namespace + python setup.py sdist --sumo + git checkout -- src/leap/__init__.py + git checkout -- src/leap/bitmask/_version.py + rm -rf src/leap/soledad + git checkout -- setup.py + +# XXX We need two sets of sumo-tarballs: the one published for a release +# (that will pick the pinned leap deps), and the other which will be used +# for the nightly builds. +# TODO change naming scheme for sumo-latest: should include date (in case +# bitmask is not updated bu the dependencies are) + +sumo_tarball_latest: checkout_leapdeps_develop pull_leapdeps setup_without_namespace + python setup.py sdist --sumo # --latest + git checkout -- src/leap/__init__.py + git checkout -- src/leap/bitmask/_version.py + rm -rf src/leap/soledad + git checkout -- setup.py -- cgit v1.2.3 From c35d9f6338300dcf6f1688ee2df9475dd14b8c94 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 4 Apr 2016 22:49:56 -0400 Subject: [pkg] move the pyinstaller bundle to a child folder --- Makefile | 3 +++ docs/release_checklist.wiki | 3 ++- pkg/next-version | 1 + pkg/pyinst/pyinst-build.mk | 27 ++++++++++++++++++++++----- 4 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 pkg/next-version diff --git a/Makefile b/Makefile index fea03951..6a8fbecb 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,10 @@ PYLUP = pyside-lupdate LRELE = lrelease # pyinst dist dir + DIST = dist/bitmask/ +NEXT_VERSION = $(shell cat pkg/next-version) +DIST_VERSION = dist/bitmask-$(NEXT_VERSION)/ ################################# diff --git a/docs/release_checklist.wiki b/docs/release_checklist.wiki index 075591a7..f831d965 100644 --- a/docs/release_checklist.wiki +++ b/docs/release_checklist.wiki @@ -1,6 +1,7 @@ = Bitmask Release Checklist (*) = * [ ] Check that all tests are passing! * [ ] Check that the version in bitmask_client/pkg/linux/bitmask-root is bumped if needed. + * [ ] Update pkg/next-release * [ ] Tag everything * Should be done for the following packages, in order: * [ ] 1. leap.common @@ -31,7 +32,7 @@ * [ ] git push origin master; git push origin X.Y.Z * [ ] git checkout develop && git merge master && git push origin develop * [ ] Build and upload bundles - * [ ] Use the scripts under pkg// to build the the bundles. + * [ ] Use 'make pyinst-linux' to build bundles. * [ ] Sign them with gpg -a --sign --detach-sign * [ ] Upload bundle and signature to downloads.leap.se/client//Bitmask--.(tar.bz2,dmg,zip) * [ ] Update symbolic link for latest upload and signature: diff --git a/pkg/next-version b/pkg/next-version new file mode 100644 index 00000000..8df8fe62 --- /dev/null +++ b/pkg/next-version @@ -0,0 +1 @@ +0.9.2.alpha3 diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index 532ec88c..637390a8 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -1,5 +1,7 @@ pyinst: - echo "MAKE SURE OF FREEZING VERSION FIRST!" + echo "*********************************************" + echo "MAKE SURE OF MANUALLY FREEZING VERSION FIRST!" + echo "*********************************************" pyinstaller -y pkg/pyinst/bitmask.spec pyinst-hacks: @@ -19,6 +21,7 @@ pyinst-trim: #rm -f dist/bitmask/libgstreamer-1.0.so.0 pyinst-wrapper: + # TODO this *is* an ugly hack, See #7352 mv $(DIST)libQtCore.so.4 $(DIST)libQtCore.so.4.orig mv $(DIST)libQtGui.so.4 $(DIST)libQtGui.so.4.orig mv $(DIST)libQtNetwork.so.4 $(DIST)libQtNetwork.so.4.orig @@ -28,12 +31,26 @@ pyinst-wrapper: mv $(DIST)libQtXml.so.4 $(DIST)libQtXml.so.4.orig mv $(DIST)bitmask $(DIST)bitmask-app cp pkg/linux/bitmask-launcher $(DIST)bitmask - cp pkg/PixelatedWebmail.README $(DIST) - -pyinst-dist: +pyinst-cleanup: rm -rf $(DIST)config - cd dist/ && tar cvzf Bitmask.0.9.2.alpha2.tar.gz bitmask + mkdir -p $(DIST_VERSION) + mv $(DIST) $(DIST_VERSION)libs + cd pkg/launcher && make + mv pkg/launcher/bitmask $(DIST_VERSION) + +pyinst-distribution-data: + cp release-notes.rst $(DIST_VERSION) + cp pkg/PixelatedWebmail.README $(DIST_VERSION) + cp LICENSE $(DIST_VERSION) + +pyinst-tar: + cd dist/ && tar cvzf Bitmask.$(NEXT_VERSION).tar.gz bitmask-$(NEXT_VERSION) + +pyinst-sign: + # TODO ---- get LEAP_MAINTAINER from environment + +pyinst-linux: pyinst pyinst-hacks pyinst-trim pyinst-wrapper pyinst-cleanup pyinst-distribution-data pyinst-tar clean_pkg: rm -rf build dist -- cgit v1.2.3 From bb0dd2d30c7408cf3a50bafd8085aa69ca02e4f2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 6 Apr 2016 15:39:55 -0400 Subject: [pkg] add standalone flag if running inside a frozen bin --- src/leap/bitmask/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index dc1ee6df..0c4c32e2 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -163,6 +163,9 @@ def start_app(): } flags.STANDALONE = opts.standalone + if getattr(sys, 'frozen', False): + flags.STANDALONE = True + flags.OFFLINE = opts.offline flags.MAIL_LOGFILE = opts.mail_log_file flags.APP_VERSION_CHECK = opts.app_version_check -- cgit v1.2.3 From 40604c0b2abe4d51e5e7b41a0e71c78a958b0b87 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 6 Apr 2016 16:02:11 -0400 Subject: [pkg] copy helpers, freeze version --- Makefile | 1 + pkg/pyinst/pyinst-build.mk | 32 ++++++++++++++++++++++++++------ pkg/version-template | 8 ++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 pkg/version-template diff --git a/Makefile b/Makefile index 6a8fbecb..f96d810d 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,7 @@ LRELE = lrelease DIST = dist/bitmask/ NEXT_VERSION = $(shell cat pkg/next-version) DIST_VERSION = dist/bitmask-$(NEXT_VERSION)/ +GIT_COMMIT = $(shell git rev-parse HEAD) ################################# diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index 637390a8..a14f2a8d 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -1,9 +1,14 @@ -pyinst: - echo "*********************************************" - echo "MAKE SURE OF MANUALLY FREEZING VERSION FIRST!" - echo "*********************************************" +freeze-ver: + cp pkg/version-template src/leap/bitmask/_version.py + sed -i 's/^version_version\(.*\)/version_version = "$(NEXT_VERSION)"/' src/leap/bitmask/_version.py + sed -i 's/^full_revisionid\(.*\)/full_revisionid = "$(GIT_COMMIT)"/' src/leap/bitmask/_version.py + +pyinst: freeze-ver pyinstaller -y pkg/pyinst/bitmask.spec +reset-ver: + git checkout -- src/leap/bitmask/_version.py + pyinst-hacks: cp ../leap_common/src/leap/common/cacert.pem $(DIST) mkdir -p $(DIST)pysqlcipher @@ -44,13 +49,28 @@ pyinst-distribution-data: cp pkg/PixelatedWebmail.README $(DIST_VERSION) cp LICENSE $(DIST_VERSION) +pyinst-linux-helpers: + mkdir -p $(DIST_VERSION)apps/eip/files + # TODO compile static + cp /usr/sbin/openvpn $(DIST_VERSION)apps/eip/files/leap-openvpn + cp pkg/linux/bitmask-root $(DIST_VERSION)apps/eip/files/ + cp pkg/linux/leap-install-helper.sh $(DIST_VERSION)apps/eip/files/ + cp pkg/linux/polkit/se.leap.bitmask.bundle.policy $(DIST_VERSION)apps/eip/files/ + mkdir -p $(DIST_VERSION)apps/mail + # TODO compile static + cp /usr/bin/gpg $(DIST_VERSION)apps/mail + pyinst-tar: cd dist/ && tar cvzf Bitmask.$(NEXT_VERSION).tar.gz bitmask-$(NEXT_VERSION) pyinst-sign: - # TODO ---- get LEAP_MAINTAINER from environment + gpg2 -a --sign --detach-sign dist/Bitmask.$(NEXT_VERSION).tar.gz + +pyinst-upload: + scp dist/Bitmask.$(NEXT_VERSION).* salmon.leap.se:./ -pyinst-linux: pyinst pyinst-hacks pyinst-trim pyinst-wrapper pyinst-cleanup pyinst-distribution-data pyinst-tar +#pyinst-linux: pyinst pyinst-hacks pyinst-trim pyinst-wrapper pyinst-cleanup pyinst-distribution-data pyinst-tar +pyinst-linux: pyinst reset-ver pyinst-hacks pyinst-trim pyinst-cleanup pyinst-distribution-data pyinst-linux-helpers pyinst-tar clean_pkg: rm -rf build dist diff --git a/pkg/version-template b/pkg/version-template new file mode 100644 index 00000000..ecb5b987 --- /dev/null +++ b/pkg/version-template @@ -0,0 +1,8 @@ +version_version = "xxx" +full_revisionid = 'deadbeef' + + +def get_versions(default={}, verbose=False): + return {'version': version_version, + 'full-revisionid': full_revisionid} + -- cgit v1.2.3 From 8836413ae5d0d22bca3c512ff4164d7559f9ada9 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 6 Apr 2016 16:07:29 -0400 Subject: typo: mimmick older bundles path --- pkg/launcher/bitmask-launcher.c | 2 +- pkg/pyinst/pyinst-build.mk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/launcher/bitmask-launcher.c b/pkg/launcher/bitmask-launcher.c index aac5da3f..cf5c2a18 100644 --- a/pkg/launcher/bitmask-launcher.c +++ b/pkg/launcher/bitmask-launcher.c @@ -12,7 +12,7 @@ #include #include -char* const bitmask_path = "libs"; +char* const bitmask_path = "lib"; char* const entrypoint = "bitmask"; int main(int argc, char *argv[]) diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index a14f2a8d..16737ead 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -40,7 +40,7 @@ pyinst-wrapper: pyinst-cleanup: rm -rf $(DIST)config mkdir -p $(DIST_VERSION) - mv $(DIST) $(DIST_VERSION)libs + mv $(DIST) $(DIST_VERSION)lib cd pkg/launcher && make mv pkg/launcher/bitmask $(DIST_VERSION) -- cgit v1.2.3 From e420ba19f79cf80c462ebdcf694a7cec453c73b2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 6 Apr 2016 16:10:42 -0400 Subject: [pkg] adjust for the new cwd() inside bundle --- src/leap/bitmask/platform_init/initializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index 4e8bbdbb..f0ec2d53 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -482,7 +482,7 @@ def _linux_install_missing_scripts(badexec, notfound): """ success = False installer_path = os.path.abspath( - os.path.join(os.getcwd(), "apps", "eip", "files")) + os.path.join(os.getcwd(), "..", "apps", "eip", "files")) install_helper = "leap-install-helper.sh" install_helper_path = os.path.join(installer_path, install_helper) -- cgit v1.2.3 From b8acf1a9df87945012387dced43f8de5e13164a0 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 6 Apr 2016 16:11:14 -0400 Subject: [style] autopep8 --- src/leap/bitmask/platform_init/initializers.py | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index f0ec2d53..193bd80a 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -440,29 +440,29 @@ def _get_missing_complain_dialog(stuff): class ComplainDialog(QtGui.QDialog): def __init__(self, parent=None): - super(ComplainDialog, self).__init__(parent) + super(ComplainDialog, self).__init__(parent) - label = QtGui.QLabel(msgstr.NO_HELPERS) - label.setAlignment(QtCore.Qt.AlignLeft) + label = QtGui.QLabel(msgstr.NO_HELPERS) + label.setAlignment(QtCore.Qt.AlignLeft) - label2 = QtGui.QLabel(msgstr.EXPLAIN) - label2.setAlignment(QtCore.Qt.AlignLeft) + label2 = QtGui.QLabel(msgstr.EXPLAIN) + label2.setAlignment(QtCore.Qt.AlignLeft) - textedit = QtGui.QTextEdit() - textedit.setText("\n".join(stuff)) + 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) + 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) + 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) + self.setLayout(mainLayout) msg = ComplainDialog() msg.setWindowTitle(msg.tr("Missing Bitmask helper files")) -- cgit v1.2.3 From dadfb03b90d099d50c7c225da297952cdfb7b9aa Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 7 Apr 2016 10:47:56 -0400 Subject: [docs] fix config path reflecting bundle change --- pkg/PixelatedWebmail.README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/PixelatedWebmail.README b/pkg/PixelatedWebmail.README index c45dc9ae..06e52964 100644 --- a/pkg/PixelatedWebmail.README +++ b/pkg/PixelatedWebmail.README @@ -15,7 +15,7 @@ account with a mail-enabled provider (for instance, mail.bitmask.net). Then, you have to edit a config file living inside the bundle folders. You have to add "Pixmail=true" under the [General] section, like this: -config/leap/leap.conf: +lib/config/leap/leap.conf: [General] SkipFirstRun=true -- cgit v1.2.3 From 42e4458f3f37d50725a37a7e22835ab677cc24aa Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 7 Apr 2016 11:09:05 -0400 Subject: [style] pep8 --- setup.py | 39 ++++++++++++++++---------------- src/leap/bitmask/__init__.py | 3 ++- src/leap/bitmask/app.py | 20 ++++++++-------- src/leap/bitmask/config/leapsettings.py | 1 - src/leap/bitmask/util/keyring_helpers.py | 5 ++-- src/leap/bitmask/util/pastebin.py | 4 ++-- 6 files changed, 37 insertions(+), 35 deletions(-) diff --git a/setup.py b/setup.py index 5bf5d186..62e9b115 100755 --- a/setup.py +++ b/setup.py @@ -20,37 +20,43 @@ Setup file for bitmask. """ from __future__ import print_function +import glob import hashlib -import sys import os +import platform import re +import shutil +import sys from distutils.command.build import build as _build from setuptools import Command +from setuptools.command.develop import develop as _develop -if not sys.version_info[0] == 2: - print("[ERROR] Sorry, Python 3 is not supported (yet). " - "Try running with python2: python2 setup.py ...") - exit() - -try: - from setuptools import setup, find_packages -except ImportError: - from pkg import distribute_setup - distribute_setup.use_setuptools() - from setuptools import setup, find_packages from pkg import utils import versioneer - # The following import avoids the premature unloading of the `util` submodule # when running tests, which would cause an error when nose finishes tests and # calls the exit function of the multiprocessing module. from multiprocessing import util assert(util) + +try: + from setuptools import setup, find_packages +except ImportError: + from pkg import distribute_setup + distribute_setup.use_setuptools() + from setuptools import setup, find_packages + +if not sys.version_info[0] == 2: + print("[ERROR] Sorry, Python 3 is not supported (yet). " + "Try running with python2: python2 setup.py ...") + exit() + + setup_root = os.path.dirname(__file__) sys.path.insert(0, os.path.join(setup_root, "src")) @@ -167,8 +173,6 @@ else: leap_launcher = 'bitmask=leap.bitmask.app:start_app' -from setuptools.command.develop import develop as _develop - def copy_reqs(path, withsrc=False): # add a copy of the processed requirements to the package @@ -365,10 +369,6 @@ class cmd_sdist(versioneer_sdist): pass -import shutil -import glob - - def _get_leap_versions(): versions = {} with open("pkg/leap_versions.txt") as vf: @@ -457,7 +457,6 @@ def copy_recursively(source_folder, destination_folder): cmdclass["build"] = cmd_build cmdclass["sdist"] = cmd_sdist -import platform _system = platform.system() IS_LINUX = _system == "Linux" IS_MAC = _system == "Darwin" diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py index 6ab55e53..c25ae999 100644 --- a/src/leap/bitmask/__init__.py +++ b/src/leap/bitmask/__init__.py @@ -19,6 +19,8 @@ Init file for leap.bitmask Initializes version and app info. """ +from ._version import get_versions + # HACK: This is a hack so that py2app copies _scrypt.so to the right # place, it can't be technically imported, but that doesn't matter # because the import is never executed @@ -45,7 +47,6 @@ def _is_release_version(version_str): return patch.isdigit() -from ._version import get_versions __version__ = get_versions()['version'] __version_hash__ = get_versions()['full-revisionid'] IS_RELEASE_VERSION = _is_release_version(__version__) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 0c4c32e2..0ae60e48 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -46,11 +46,7 @@ import os import platform import sys -if platform.system() == "Darwin": - # We need to tune maximum number of files, due to zmq usage - # we hit the limit. - import resource - resource.setrlimit(resource.RLIMIT_NOFILE, (4096, 10240)) +import psutil from leap.bitmask import __version__ as VERSION from leap.bitmask.backend.backend_proxy import BackendProxy @@ -70,7 +66,12 @@ from leap.mail import __version__ as MAIL_VERSION import codecs codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) -import psutil + +if platform.system() == "Darwin": + # We need to tune maximum number of files, due to zmq usage + # we hit the limit. + import resource + resource.setrlimit(resource.RLIMIT_NOFILE, (4096, 10240)) def kill_the_children(): @@ -226,9 +227,10 @@ def start_app(): backend_pid = None if not backend_running: frontend_pid = os.getpid() - backend_process = multiprocessing.Process(target=run_backend, - name='Backend', - args=(opts.danger, flags_dict, frontend_pid)) + backend_process = multiprocessing.Process( + target=run_backend, + name='Backend', + args=(opts.danger, flags_dict, frontend_pid)) # we don't set the 'daemon mode' since we need to start child processes # in the backend # backend_process.daemon = True diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index d7590c24..01900484 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -361,4 +361,3 @@ class LeapSettings(object): def set_pixelmail_enabled(self, enabled): leap_assert_type(enabled, bool) self._settings.setvalue(self.PIXELMAIL_KEY, enabled) - diff --git a/src/leap/bitmask/util/keyring_helpers.py b/src/leap/bitmask/util/keyring_helpers.py index d81f39b1..c5181348 100644 --- a/src/leap/bitmask/util/keyring_helpers.py +++ b/src/leap/bitmask/util/keyring_helpers.py @@ -24,8 +24,9 @@ try: EncryptedKeyring, PlaintextKeyring ] - canuse = lambda kr: (kr is not None - and kr.__class__ not in OBSOLETE_KEYRINGS) + canuse = lambda kr: ( + kr is not None and + kr.__class__ not in OBSOLETE_KEYRINGS) except Exception: # Problems when importing keyring! It might be a problem binding to the diff --git a/src/leap/bitmask/util/pastebin.py b/src/leap/bitmask/util/pastebin.py index 6d50ac9e..b476ad88 100644 --- a/src/leap/bitmask/util/pastebin.py +++ b/src/leap/bitmask/util/pastebin.py @@ -25,13 +25,13 @@ ############################################################################# +import urllib + __ALL__ = ['delete_paste', 'user_details', 'trending', 'pastes_by_user', 'generate_user_key', 'paste', 'Pastebin', 'PastebinError', 'PostLimitError'] -import urllib - class PastebinError(RuntimeError): """Pastebin API error. -- cgit v1.2.3 From 3340e1a898bacdd05fb6e6bf8d37596f7aff81a2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 7 Apr 2016 09:56:36 -0400 Subject: [feat] use same token for imap/smtp authentication This greatly simplifies the handling of the password in the thunderbird extension. - Related: #6041 --- src/leap/bitmask/backend/components.py | 7 ++----- src/leap/bitmask/gui/mainwindow.py | 14 ++++++-------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 0c2b3280..f9ad1480 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -806,11 +806,8 @@ class Soledad(object): (service, token)) sol = self._soledad_bootstrapper.soledad - d = sol.get_or_create_service_token('imap') - d.addCallback(register_service_token, 'imap') - d.addCallback( - lambda _: sol.get_or_create_service_token('smtp')) - d.addCallback(register_service_token, 'smtp') + d = sol.get_or_create_service_token('mail_auth') + d.addCallback(register_service_token, 'mail_auth') d.addCallback(lambda _: result) return d diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 839aae87..ace3f863 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1089,12 +1089,11 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): # FIXME on i3, this doens't allow to mouse-select. # Switch to a dialog in which we can set the QLabel - imap_token = (self._service_tokens.get('imap', None) or - "??? (log in to unlock)") - smtp_token = (self._service_tokens.get('smtp', None) or - "??? (log in to unlock)") - imap_password = self.tr("IMAP Password:") + " %s" % (imap_token,) - smtp_password = self.tr("SMTP Password:") + " %s" % (smtp_token,) + mail_auth_token = ( + self._service_tokens.get('mail_auth', None) or + "??? (log in to unlock)") + mail_password = self.tr("IMAP/SMTP Password:") + " %s" % ( + mail_auth_token,) msg = help_url + self.tr( "

    {0}

    " @@ -1105,10 +1104,9 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): "
  •  {4}
  • " "
  •  {5}
  • " "
  •  {6}
  • " - "
  •  {7}
  • " "

    ").format(email_quick_reference, thunderbird_text, manual_text, manual_imap, manual_smtp, - manual_username, imap_password, smtp_password) + manual_username, mail_password) QtGui.QMessageBox.about(self, self.tr("Bitmask Help"), msg) def _needs_update(self): -- cgit v1.2.3 From 939fefc012213d3aa433caec47e2e0b19d64901e Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 30 Mar 2016 13:51:01 +0200 Subject: [feat] Write service tokens to a file The thunderbird plugin will read the tokens from there. - Related: #6041 --- changes/next-changelog.rst | 1 + src/leap/bitmask/backend/components.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst index b20efdc8..3240d2bb 100644 --- a/changes/next-changelog.rst +++ b/changes/next-changelog.rst @@ -13,6 +13,7 @@ Features - `#7552 `_: Improve UI message and add some margin above the msg box. - `#7656 `_: Adapt to multi-user aware events. - `#4469 `_: Display randomly generated service token on the Help Window. +- `#6041 `_: Write service tokens to a file to allow email clients to read them from there. - Use cred-based authentication on SMTP. - Experimental support for the Pixelated WebMail. diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index f9ad1480..bc34c84c 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -20,8 +20,11 @@ Backend components # TODO [ ] Get rid of all this deferToThread mess, or at least contain # all of it into its own threadpool. +import json import os +import shutil import socket +import tempfile import time from functools import partial @@ -789,6 +792,8 @@ class Soledad(object): download_if_needed=True) self._soledad_defer.addCallback(self._set_proxies_cb) self._soledad_defer.addCallback(self._set_service_tokens_cb) + self._soledad_defer.addCallback(self._write_tokens_file, + username, domain) else: if self._signaler is not None: self._signaler.signal(self._signaler.soledad_bootstrap_failed) @@ -811,6 +816,23 @@ class Soledad(object): d.addCallback(lambda _: result) return d + def _write_tokens_file(self, result, username, domain): + tokens_folder = os.path.join(tempfile.gettempdir(), "bitmask_tokens") + if os.path.exists(tokens_folder): + try: + shutil.rmtree(tokens_folder) + except OSError as e: + logger.error("Can't remove tokens folder %s: %s" + % (tokens_folder, e)) + return + os.mkdir(tokens_folder, 0700) + + tokens_path = os.path.join(tokens_folder, + "%s@%s.json" % (username, domain)) + with open(tokens_path, 'w') as ftokens: + json.dump(self._service_tokens, ftokens) + return result + def _set_proxies_cb(self, _): """ Update the soledad and keymanager proxies to reference the ones created -- cgit v1.2.3 From 130f1a8753bfe63f5575fc2011a15af9a0752170 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 7 Apr 2016 12:18:24 -0400 Subject: [bug] allow resizing browser window --- src/leap/bitmask/gui/mainwindow.py | 1 + src/leap/bitmask/gui/qt_browser.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index ace3f863..168de8ed 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -579,6 +579,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): def _show_pixelated_browser(self): win = PixelatedWindow(self) win.show() + win.load_app() def _update_eip_enabled_status(self, account=None, services=None): """ diff --git a/src/leap/bitmask/gui/qt_browser.py b/src/leap/bitmask/gui/qt_browser.py index e480b363..b75bfb64 100644 --- a/src/leap/bitmask/gui/qt_browser.py +++ b/src/leap/bitmask/gui/qt_browser.py @@ -26,8 +26,14 @@ PIXELATED_URI = 'http://localhost:9090' class PixelatedWindow(QtGui.QDialog): def __init__(self, parent): - QtGui.QDialog.__init__(self, parent) - self.web = QtWebKit.QWebView(self) - self.web.load(QtCore.QUrl(PIXELATED_URI)) + super(PixelatedWindow, self).__init__(parent) + self.view = QtWebKit.QWebView(self) + + layout = QtGui.QGridLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.view) + self.setLayout(layout) self.setWindowTitle('Bitmask/Pixelated WebMail') - self.web.show() + + def load_app(self): + self.view.load(QtCore.QUrl(PIXELATED_URI)) -- cgit v1.2.3 From fcb9513a8df9b2d690607106d3397b7c785f3b33 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 8 Apr 2016 01:08:53 -0400 Subject: [bug] fix pixelated-www path --- src/leap/bitmask/pix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leap/bitmask/pix.py b/src/leap/bitmask/pix.py index a05a1d9c..aa18c589 100644 --- a/src/leap/bitmask/pix.py +++ b/src/leap/bitmask/pix.py @@ -61,7 +61,7 @@ def start_pixelated_user_agent(userid, soledad, keymanager): # we are running in a |PyInstaller| bundle static_folder = os.path.join(sys._MEIPASS, 'pixelated_www') else: - static_folder = os.path.abspath(pixelated_www.__path__) + static_folder = os.path.abspath(pixelated_www.__path__[0]) resource = RootResource(services_factory, static_folder=static_folder) -- cgit v1.2.3 From 977f594510b70c7f0d1c99d1d77c188ff6ba8e37 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 11 Apr 2016 17:47:50 -0400 Subject: [bug] workaround for qt gui corruption in wily - Resolves: #8028 --- src/leap/bitmask/app.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 0ae60e48..2c41068c 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -74,6 +74,11 @@ if platform.system() == "Darwin": resource.setrlimit(resource.RLIMIT_NOFILE, (4096, 10240)) +def qt_hack_ubuntu(): + """Export an env var to avoid gui corruption, see #8028""" + os.environ['QT_GRAPHICSSYSTEM'] = 'native' + + def kill_the_children(): """ Make sure no lingering subprocesses are left in case of a bad termination. @@ -151,6 +156,8 @@ def start_app(): """ Starts the main event loop and launches the main window. """ + qt_hack_ubuntu() + # Ignore the signals since we handle them in the subprocesses # signal.signal(signal.SIGINT, signal.SIG_IGN) -- cgit v1.2.3 From 5fc9a8c68216bcdd7a4d847e75bac428aafda5e4 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 11 Apr 2016 17:49:01 -0400 Subject: [bug] hash the installed binaries this was the cause for the previous alpha releases keep insisting on re-installing the helper files. - Resolves: #7989 - Releases: 0.9.2 --- pkg/next-version | 2 +- pkg/pyinst/pyinst-build.mk | 6 +++++- setup.py | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/next-version b/pkg/next-version index 8df8fe62..1c3e8a50 100644 --- a/pkg/next-version +++ b/pkg/next-version @@ -1 +1 @@ -0.9.2.alpha3 +0.9.2.alpha4 diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index 16737ead..348395cc 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -3,7 +3,11 @@ freeze-ver: sed -i 's/^version_version\(.*\)/version_version = "$(NEXT_VERSION)"/' src/leap/bitmask/_version.py sed -i 's/^full_revisionid\(.*\)/full_revisionid = "$(GIT_COMMIT)"/' src/leap/bitmask/_version.py -pyinst: freeze-ver +hash-binaries: + # TODO get from a build dir + OPENVPN_BIN=/usr/sbin/openvpn BITMASK_ROOT=pkg/linux/bitmask-root python setup.py hash_binaries + +pyinst: freeze-ver hash-binaries pyinstaller -y pkg/pyinst/bitmask.spec reset-ver: diff --git a/setup.py b/setup.py index 62e9b115..8dc5a694 100755 --- a/setup.py +++ b/setup.py @@ -219,6 +219,7 @@ class cmd_binary_hash(Command): pass def run(self, *args): + # TODO check gnupg binary too. OPENVPN_BIN = os.environ.get('OPENVPN_BIN', None) BITMASK_ROOT = os.environ.get('BITMASK_ROOT', None) -- cgit v1.2.3 From f05ec77ae21f8b01f20267c129307c91683c74d7 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 11 Apr 2016 17:55:19 -0400 Subject: [pkg] remove wrapper hacks, not needed anymore --- pkg/pyinst/pyinst-build.mk | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index 348395cc..cbbef198 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -29,18 +29,6 @@ pyinst-trim: #rm -f dist/bitmask/libgstaudio0.0.so.0 #rm -f dist/bitmask/libgstreamer-1.0.so.0 -pyinst-wrapper: - # TODO this *is* an ugly hack, See #7352 - mv $(DIST)libQtCore.so.4 $(DIST)libQtCore.so.4.orig - mv $(DIST)libQtGui.so.4 $(DIST)libQtGui.so.4.orig - mv $(DIST)libQtNetwork.so.4 $(DIST)libQtNetwork.so.4.orig - mv $(DIST)libQtSvg.so.4 $(DIST)libQtSvg.so.4.orig - mv $(DIST)libQtWebKit.so.4 $(DIST)libQtWebKit.so.4.orig - mv $(DIST)libQtXmlPatterns.so.4 $(DIST)libQtXmlPatterns.so.4.orig - mv $(DIST)libQtXml.so.4 $(DIST)libQtXml.so.4.orig - mv $(DIST)bitmask $(DIST)bitmask-app - cp pkg/linux/bitmask-launcher $(DIST)bitmask - pyinst-cleanup: rm -rf $(DIST)config mkdir -p $(DIST_VERSION) @@ -73,7 +61,6 @@ pyinst-sign: pyinst-upload: scp dist/Bitmask.$(NEXT_VERSION).* salmon.leap.se:./ -#pyinst-linux: pyinst pyinst-hacks pyinst-trim pyinst-wrapper pyinst-cleanup pyinst-distribution-data pyinst-tar pyinst-linux: pyinst reset-ver pyinst-hacks pyinst-trim pyinst-cleanup pyinst-distribution-data pyinst-linux-helpers pyinst-tar clean_pkg: -- cgit v1.2.3 From ed7c2bdf2efc2ce4e0ef4d761d9470604efa9969 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 11 Apr 2016 18:01:42 -0400 Subject: [docs] add some important info about building bundles --- pkg/pyinst/README.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 pkg/pyinst/README.rst diff --git a/pkg/pyinst/README.rst b/pkg/pyinst/README.rst new file mode 100644 index 00000000..b6784a52 --- /dev/null +++ b/pkg/pyinst/README.rst @@ -0,0 +1,17 @@ +Building da bundles +-------------------- +Because, you know, bundles are cool. Who needs a decent package manager nowadays? . + +You need a couple of things in your virtualenv: + +- All the dependencies. A sumo tarball is probably a good idea. +- PyInstaller. Version 3 or higher. +- A PySide build. While the postmkenv.sh hack is good enough for + developing, you will need a wheel built with --standalone flag. + See + http://pyside.readthedocs.org/en/latest/building/linux.html#building-pyside-distribution:: + + $ python2.7 setup.py bdist_wheel --qmake=/usr/bin/qmake-qt4 --standalone + + (since this takes a while, you can probably grab the already built wheel from + the leap servers). -- cgit v1.2.3 From 42bb82b06ea313877023da9f689782b228fac345 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 13 Apr 2016 11:03:20 -0400 Subject: [feat] Adapt to latest pixelated-ua In the latest snapshots, pixelated has migrated to use Account instead of IMAPAccount. Also, created an adaptor for Nicknym that allows to be initialized with just the userid and the keymanager instance. This is ugly and has to go when we converge on a single codebase (bitmask.core, hopefully). --- src/leap/bitmask/pix.py | 56 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/src/leap/bitmask/pix.py b/src/leap/bitmask/pix.py index aa18c589..8d7d0811 100644 --- a/src/leap/bitmask/pix.py +++ b/src/leap/bitmask/pix.py @@ -25,11 +25,10 @@ from twisted.internet import defer from twisted.python import log from leap.bitmask.util import get_path_prefix -from leap.mail.imap.account import IMAPAccount +from leap.mail.mail import Account +from leap.keymanager import openpgp, KeyNotFound try: - import pixelated_www - from pixelated.adapter.mailstore import LeapMailStore from pixelated.adapter.welcome_mail import add_welcome_mail from pixelated.application import SingleUserServicesFactory @@ -39,6 +38,7 @@ try: from pixelated.bitmask_libraries.session import SessionCache from pixelated.config import services from pixelated.resources.root_resource import RootResource + import pixelated_www HAS_PIXELATED = True except ImportError: HAS_PIXELATED = False @@ -70,10 +70,11 @@ def start_pixelated_user_agent(userid, soledad, keymanager): config.sslkey = None config.sslcert = None - deferred = _start_in_single_user_mode( - leap_session, config, - resource, services_factory) - return deferred + d = leap_session.account.callWhenReady( + lambda _: _start_in_single_user_mode( + leap_session, config, + resource, services_factory)) + return d def get_smtp_config(provider): @@ -90,6 +91,39 @@ def get_smtp_config(provider): return config +class NickNym(object): + + def __init__(self, keymanager, userid): + self._email = userid + self.keymanager = keymanager + + @defer.inlineCallbacks + def generate_openpgp_key(self): + key_present = yield self._key_exists(self._email) + if not key_present: + yield self._gen_key() + yield self._send_key_to_leap() + + @defer.inlineCallbacks + def _key_exists(self, email): + try: + yield self.fetch_key(email, private=True, fetch_remote=False) + defer.returnValue(True) + except KeyNotFound: + defer.returnValue(False) + + def fetch_key(self, email, private=False, fetch_remote=True): + return self.keymanager.get_key( + email, openpgp.OpenPGPKey, + private=private, fetch_remote=fetch_remote) + + def _gen_key(self): + return self.keymanager.gen_key(openpgp.OpenPGPKey) + + def _send_key_to_leap(self): + return self.keymanager.send_key(openpgp.OpenPGPKey) + + class LeapSessionAdapter(object): def __init__(self, userid, soledad, keymanager): @@ -97,10 +131,8 @@ class LeapSessionAdapter(object): self.soledad = soledad - # FIXME this expects a keymanager-like instance - self.nicknym = Config() - self.nicknym.keymanager = keymanager - + # XXX this needs to be converged with our public apis. + self.nicknym = NickNym(keymanager, userid) self.mail_store = LeapMailStore(soledad) self.user_auth = Config() @@ -108,7 +140,7 @@ class LeapSessionAdapter(object): self.fresh_account = False self.incoming_mail_fetcher = None - self.account = IMAPAccount(userid, soledad, defer.Deferred()) + self.account = Account(soledad) username, provider = userid.split('@') smtp_client_cert = os.path.join( -- cgit v1.2.3 From 6c91aee9c3a0978a7019f3fdf52f4209977b5f0b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 13 Apr 2016 12:00:36 -0400 Subject: [feature] make branding-logo: patches pixelated app logo and replaces it with bitmask-logo.svg - Releases: 0.9.2 --- Makefile | 1 + pkg/branding/bitmask-logo.svg | 19 ++++++++++++++ pkg/branding/branding.mk | 2 ++ pkg/branding/patch_pixel_logo.py | 57 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+) create mode 100644 pkg/branding/bitmask-logo.svg create mode 100644 pkg/branding/branding.mk create mode 100644 pkg/branding/patch_pixel_logo.py diff --git a/Makefile b/Makefile index f96d810d..8e275e93 100644 --- a/Makefile +++ b/Makefile @@ -147,6 +147,7 @@ checkout_leapdeps_develop: include pkg/sumo-tarballs.mk include pkg/pyinst/pyinst-build.mk +include pkg/branding/branding.mk clean : $(RM) $(COMPILED_UI) $(COMPILED_RESOURCES) $(COMPILED_UI:.py=.pyc) $(COMPILED_RESOURCES:.py=.pyc) diff --git a/pkg/branding/bitmask-logo.svg b/pkg/branding/bitmask-logo.svg new file mode 100644 index 00000000..0eccc057 --- /dev/null +++ b/pkg/branding/bitmask-logo.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pkg/branding/branding.mk b/pkg/branding/branding.mk new file mode 100644 index 00000000..6b98d40d --- /dev/null +++ b/pkg/branding/branding.mk @@ -0,0 +1,2 @@ +branding-logo: + python pkg/branding/patch_pixel_logo.py diff --git a/pkg/branding/patch_pixel_logo.py b/pkg/branding/patch_pixel_logo.py new file mode 100644 index 00000000..464bb729 --- /dev/null +++ b/pkg/branding/patch_pixel_logo.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# patch_pixelated_logo.py +# Copyright (C) 2016 LEAP Encryption Acess 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 . +""" +Patch the Pixelated Logo in the index.html, replacing it with a rebranded +Bitmask Logo. To be used in the pixelated_www assets distributed with the +Bitmask bundles. +""" +__author__ = 'Kali Kaneko ' + +import os +import sys + +from BeautifulSoup import BeautifulSoup + + +def patch_logo(orig_path, replacement_path): + + with open(orig_path, 'r') as of: + orig = BeautifulSoup(of.read()) + + with open(replacement_path, 'r') as rf: + new = BeautifulSoup(rf.read()) + + new_svg = new.find('svg') + old_svg = orig.find('svg') + old_svg.replaceWith(new_svg) + + with open(orig_path, 'w') as f: + f.write(str(orig)) + + +if __name__ == "__main__": + here = os.path.dirname(os.path.realpath(__file__)) + if len(sys.argv) > 1: + orig_path = sys.argv[1] + else: + import pixelated_www + orig_path = os.path.join(pixelated_www.__path__[0], + 'index.html') + assert os.path.isfile(orig_path) + new_path = os.path.join(here, 'bitmask-logo.svg') + print('>>> patching file %s with logo in %s' % (orig_path, new_path)) + patch_logo(orig_path, new_path) -- cgit v1.2.3 From 054e337ecf18e0a3ddd1d5568bfbdef507a3d136 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 14 Apr 2016 15:37:14 -0400 Subject: [bug] add missing assets for pixelated --- pkg/pyinst/pyinst-build.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index cbbef198..2c2fe45a 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -16,8 +16,10 @@ reset-ver: pyinst-hacks: cp ../leap_common/src/leap/common/cacert.pem $(DIST) mkdir -p $(DIST)pysqlcipher + mkdir -p $(DIST)pixelated cp $(VIRTUAL_ENV)/lib/python2.7/site-packages/pysqlcipher/_sqlite.so $(DIST)pysqlcipher cp -r $(VIRTUAL_ENV)/lib/python2.7/site-packages/pixelated_www $(DIST) + cp -r $(VIRTUAL_ENV)/lib/python2.7/site-packages/pixelated/assets/ $(DIST)pixelated pyinst-trim: rm -f $(DIST)libQtOpenGL.so.4 -- cgit v1.2.3 From b4de087fee85b7831b70cde31e7ed34f196a5ca9 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 14 Apr 2016 15:38:39 -0400 Subject: [bug] add twisted.web.failure.xhtml --- pkg/pyinst/pyinst-build.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index 2c2fe45a..b6a7a6f3 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -17,9 +17,11 @@ pyinst-hacks: cp ../leap_common/src/leap/common/cacert.pem $(DIST) mkdir -p $(DIST)pysqlcipher mkdir -p $(DIST)pixelated + mkdir -p $(DIST)twisted/web cp $(VIRTUAL_ENV)/lib/python2.7/site-packages/pysqlcipher/_sqlite.so $(DIST)pysqlcipher cp -r $(VIRTUAL_ENV)/lib/python2.7/site-packages/pixelated_www $(DIST) cp -r $(VIRTUAL_ENV)/lib/python2.7/site-packages/pixelated/assets/ $(DIST)pixelated + cp -r $(VIRTUAL_ENV)/lib/python2.7/site-packages/twisted/web/failure.xhtml $(DIST)twisted/web/ pyinst-trim: rm -f $(DIST)libQtOpenGL.so.4 -- cgit v1.2.3 From c201029c3070da1bd9c2a7d65ac426e49f3cc241 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 14 Apr 2016 15:40:05 -0400 Subject: [pkg] ship statically built binaries - mention the versions in the next relnotes - add the leap_thirdparty_build to gitignore. the build scripts are supposed to place the binaries here. --- .gitignore | 1 + Makefile | 1 + pkg/pyinst/pyinst-build.mk | 9 +++------ release-notes.rst | 19 +++++++++---------- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 62b21fd1..35c5d4a9 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ data/bitmask.pro bitmask-resources.png docker/data +leap_thirdparty_build diff --git a/Makefile b/Makefile index 8e275e93..7d26a7ae 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,7 @@ DIST = dist/bitmask/ NEXT_VERSION = $(shell cat pkg/next-version) DIST_VERSION = dist/bitmask-$(NEXT_VERSION)/ GIT_COMMIT = $(shell git rev-parse HEAD) +LEAP_BUILD_DIR = leap_thirdparty_build/ ################################# diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index b6a7a6f3..9120e7a1 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -4,8 +4,7 @@ freeze-ver: sed -i 's/^full_revisionid\(.*\)/full_revisionid = "$(GIT_COMMIT)"/' src/leap/bitmask/_version.py hash-binaries: - # TODO get from a build dir - OPENVPN_BIN=/usr/sbin/openvpn BITMASK_ROOT=pkg/linux/bitmask-root python setup.py hash_binaries + OPENVPN_BIN=$(LEAP_BUILD_DIR)openvpn BITMASK_ROOT=pkg/linux/bitmask-root python setup.py hash_binaries pyinst: freeze-ver hash-binaries pyinstaller -y pkg/pyinst/bitmask.spec @@ -47,14 +46,12 @@ pyinst-distribution-data: pyinst-linux-helpers: mkdir -p $(DIST_VERSION)apps/eip/files - # TODO compile static - cp /usr/sbin/openvpn $(DIST_VERSION)apps/eip/files/leap-openvpn + cp $(LEAP_BUILD_DIR)openvpn $(DIST_VERSION)apps/eip/files/leap-openvpn cp pkg/linux/bitmask-root $(DIST_VERSION)apps/eip/files/ cp pkg/linux/leap-install-helper.sh $(DIST_VERSION)apps/eip/files/ cp pkg/linux/polkit/se.leap.bitmask.bundle.policy $(DIST_VERSION)apps/eip/files/ mkdir -p $(DIST_VERSION)apps/mail - # TODO compile static - cp /usr/bin/gpg $(DIST_VERSION)apps/mail + cp $(LEAP_BUILD_DIR)gpg $(DIST_VERSION)apps/mail pyinst-tar: cd dist/ && tar cvzf Bitmask.$(NEXT_VERSION).tar.gz bitmask-$(NEXT_VERSION) diff --git a/release-notes.rst b/release-notes.rst index 7a63901e..a9c0ff47 100644 --- a/release-notes.rst +++ b/release-notes.rst @@ -1,25 +1,24 @@ -0.9.1 November 03 - "the day of the calaca" +0.9.2 April XY - "Panis et Circenses" +++++++++++++++++++++++++++++++++++++++++++ -We were very pleased to announce Bitmask stable 0.9.1 :tada:. +We were very pleased to announce Bitmask stable 0.9.2 This is a minor release and you can see changes on `the changelog`_. -The complete list of things that have changed since 0.8.x series can be seen on -`0.9.0 release notes`_ -Don't miss the big pile of changes we brought you on 0.9.0 :) - Using the latest Bitmask, Linux users will be able to use our encrypted email -service, now in beta state! A Mac release is imminent and a windows release is -underway. +service, now in beta state! An alpha version for OSX is also available. Currently we have a test provider for mail @ https://mail.bitmask.net This provider is already bundled with Bitmask for easy access on the wizard. Please help us test this and file bug reports here: https://leap.se/code/projects/report-issues +[Pixelated integration news] + +The standalone bundles for 0.9.2 include statically compiled binaries for +OpenVPN 2.3.10 (compiled against PolarSSL 1.3.9) and GnuPG 1.4.20. + NOTE: beta means that we expect things not to break but we don't promise you won't get any headaches or lose some email, so please be careful. -.. _`the changelog`: https://github.com/leapcode/bitmask_client/blob/0.9.1/CHANGELOG.rst -.. _`0.9.0 release notes`: https://github.com/leapcode/bitmask_client/blob/0.9.0/release-notes.rst +.. _`the changelog`: https://github.com/leapcode/bitmask_client/blob/0.9.2/CHANGELOG.rst -- cgit v1.2.3 From a6dca50aeded076fdac8548be0c8b94857301e01 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 14 Apr 2016 16:04:22 -0400 Subject: [pkg] bump alpha version... closer to rc1! --- pkg/next-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/next-version b/pkg/next-version index 1c3e8a50..6e441158 100644 --- a/pkg/next-version +++ b/pkg/next-version @@ -1 +1 @@ -0.9.2.alpha4 +0.9.2.alpha5 -- cgit v1.2.3 From 6e2e5e08dca279b6ea4f7835415a3900c28dfd84 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 14 Apr 2016 18:01:28 -0400 Subject: [bug] another workaround for the transparent window in wily The previous "fix" attempt with QT_GRAPHICSSYSTEM=native apparently wasn't fixing the issue consistently. This extra env var works 100% of the times by my tests though. - Resolves: #8028 --- pkg/next-version | 2 +- src/leap/bitmask/app.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/next-version b/pkg/next-version index 6e441158..e42c0587 100644 --- a/pkg/next-version +++ b/pkg/next-version @@ -1 +1 @@ -0.9.2.alpha5 +0.9.2.alpha6 diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 2c41068c..9412ccd7 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -75,8 +75,9 @@ if platform.system() == "Darwin": def qt_hack_ubuntu(): - """Export an env var to avoid gui corruption, see #8028""" + """Export two env vars to avoid gui corruption, see #8028""" os.environ['QT_GRAPHICSSYSTEM'] = 'native' + os.environ['LIBOVERLAY_SCROLLBAR'] = '0' def kill_the_children(): -- cgit v1.2.3 From 249864008ae8640042573bd6dfc42b6ce28324e8 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 14 Apr 2016 18:49:26 -0400 Subject: [tools] make uploads resumable --- pkg/pyinst/pyinst-build.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index 9120e7a1..9595ad50 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -60,7 +60,7 @@ pyinst-sign: gpg2 -a --sign --detach-sign dist/Bitmask.$(NEXT_VERSION).tar.gz pyinst-upload: - scp dist/Bitmask.$(NEXT_VERSION).* salmon.leap.se:./ + rsync --rsh='ssh' -avztlpog --progress --partial dist/Bitmask.$(NEXT_VERSION).* salmon.leap.se:./ pyinst-linux: pyinst reset-ver pyinst-hacks pyinst-trim pyinst-cleanup pyinst-distribution-data pyinst-linux-helpers pyinst-tar -- cgit v1.2.3 From 32b01d729ea8c0e6ecd1bd1def909fc41156d405 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 14 Apr 2016 20:25:29 -0400 Subject: [docs] add note about login automation --- docs/dev/automating_login.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/dev/automating_login.rst diff --git a/docs/dev/automating_login.rst b/docs/dev/automating_login.rst new file mode 100644 index 00000000..7017dd8e --- /dev/null +++ b/docs/dev/automating_login.rst @@ -0,0 +1,21 @@ +.. _automating_login: + +Automating login +================ + +There's an annoying bug with python keyring module, that makes the 'remember +login' checkbox non functional. + +That, and the need to script end-to-end tests with the client inside a docker +environment, made us put a mechanism to pass credentials via environment +variables. + +To automate login, set BITMASK_CREDENTIALS env var:: + + BITMASK_CREDENTIALS=/tmp/secrets.conf bitmask --debug + +where the pointed file looks like this:: + + [Credentials] + username = user@provider + password = mypass -- cgit v1.2.3 From 8361cfdf5f6f56da8e1f7297092d1458a72f9444 Mon Sep 17 00:00:00 2001 From: elijah Date: Thu, 14 Apr 2016 12:39:26 -0700 Subject: [bug] eip_can_start should return false if provider does not support eip otherwise, it tries to open eip config. - Resolves: #7538 - Releases: 0.9.2 --- src/leap/bitmask/backend/components.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index bc34c84c..03a92c88 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -44,7 +44,7 @@ from leap.bitmask.platform_init import IS_LINUX from leap.bitmask import pix from leap.bitmask.provider.pinned import PinnedProviders from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper -from leap.bitmask.services import get_supported +from leap.bitmask.services import get_supported, EIP_SERVICE from leap.bitmask.services.eip import eipconfig from leap.bitmask.services.eip import get_openvpn_management from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper @@ -651,8 +651,10 @@ class EIP(object): logger.error("No polkit agent running.") return False - eip_config = eipconfig.EIPConfig() provider_config = ProviderConfig.get_provider_config(domain) + if EIP_SERVICE not in provider_config.get_services(): + return False + eip_config = eipconfig.EIPConfig() api_version = provider_config.get_api_version() eip_config.set_api_version(api_version) -- cgit v1.2.3 From 15cd7c4a53e9120d032d92382691cc9862f31a8f Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 15 Apr 2016 15:03:53 -0400 Subject: [doc] update release notes --- release-notes.rst | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/release-notes.rst b/release-notes.rst index a9c0ff47..2fc1f9ea 100644 --- a/release-notes.rst +++ b/release-notes.rst @@ -1,24 +1,38 @@ 0.9.2 April XY - "Panis et Circenses" -+++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++ -We were very pleased to announce Bitmask stable 0.9.2 +We are pleased to announce the Bitmask 0.9.2 release. You can refer to `the +changelog`_ for the whole changeset. -This is a minor release and you can see changes on `the changelog`_. +In addition to the Encrypted Internet Proxy, the Encrypted Email service is now +available, in Beta state in the Linux bundles. An alpha version for OSX is also +ready. This service can be tested against any LEAP provider that has made it +publicly available. -Using the latest Bitmask, Linux users will be able to use our encrypted email -service, now in beta state! An alpha version for OSX is also available. +The "Beta" in the Email service means that things are getting more stable, but +unforeseen issues are still expected to be found, so please use it accordingly. +We need you to test it as hard as you can, and report any bugs that you find, +but don't trust it yet in situations where data loss, delivery problems or any +other errors can put you at risk or otherwise cause major trouble. -Currently we have a test provider for mail @ https://mail.bitmask.net This -provider is already bundled with Bitmask for easy access on the wizard. Please -help us test this and file bug reports here: -https://leap.se/code/projects/report-issues +Currently we maintain a demo provider for the Mail service at +https://mail.bitmask.net. This provider is already pinned in Bitmask for easy +access when creating a new account from within the wizard. -[Pixelated integration news] +In the standalone bundles beginning with 0.9.2, we are shipping the Pixelated +Webmail interface. Access via Thunderbird or any other mail client of your +choice is still supported, but we hope that this modern web interface will make +the delights of our users. -The standalone bundles for 0.9.2 include statically compiled binaries for -OpenVPN 2.3.10 (compiled against PolarSSL 1.3.9) and GnuPG 1.4.20. +The bundles for 0.9.2 include statically compiled binaries for OpenVPN 2.3.10 +(compiled against PolarSSL 1.3.9) and GnuPG 1.4.20. -NOTE: beta means that we expect things not to break but we don't promise you -won't get any headaches or lose some email, so please be careful. +Please help us test Bitmask and file any bug reports here: +https://leap.se/code/projects/report-issues, that will be a great contribution +towards future improvement! + +Until the next release, see you on the intertubes, and stay safe. + +The Bitmask team. .. _`the changelog`: https://github.com/leapcode/bitmask_client/blob/0.9.2/CHANGELOG.rst -- cgit v1.2.3 From e442eb8a3acf8e6bbddaabe7af90cf66d2c755ac Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 15 Apr 2016 15:04:14 -0400 Subject: [doc] update release checklist --- docs/release_checklist.wiki | 79 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 19 deletions(-) diff --git a/docs/release_checklist.wiki b/docs/release_checklist.wiki index f831d965..7c9dae65 100644 --- a/docs/release_checklist.wiki +++ b/docs/release_checklist.wiki @@ -1,25 +1,39 @@ -= Bitmask Release Checklist (*) = += Bitmask Release Checklist = + + == CI check == * [ ] Check that all tests are passing! - * [ ] Check that the version in bitmask_client/pkg/linux/bitmask-root is bumped if needed. + * [ ] Fix any broken tests. + + == Version bumps and Tagging == * [ ] Update pkg/next-release - * [ ] Tag everything - * Should be done for the following packages, in order: + * [ ] Update release-notes.rst in leap.bitmask if needed. + * [ ] Update version in bitmask_client/pkg/linux/bitmask-root if needed. + + * [ ] Tag everything. Should be done for the following packages, in order: * [ ] 1. leap.common * [ ] 2. leap.keymanager * [ ] 3. leap.soledad * [ ] 4. leap.mail * [ ] 5. leap.bitmask * [ ] 6. leap.mx + * NOTE: It's assumed that origin is the leap.se repo + + * [ ] export version: export RELEASE=0.9.0 * [ ] git fetch origin * [ ] git tag -l, and see the latest tagged version (unless it's not a minor version bump, in which case, just bump to it) - * [ ] Checkout release-X.Y.Z (locally, never pushed) - * [ ] Update relnotes.txt in leap.bitmask if needed. + * [ ] git checkout `release/0.9.x` + - NOTE: the release branch is created when the first release candidate + is tagged, after that the bugfixes and features that are meant to be + shipped with the specific version that we are targetting are merged in that branch + * [ ] git checkout -b release/$RELEASE + * [ ] (maybe) cherry-pick specific commits + * [ ] (maybe) add special fixes for this release - * [ ] Review pkg/requirements.pip for everything and update if needed (that's why the order). + * [ ] Review pkg/requirements.pip for everything, update if needed (that's why the order). - See whatever has been introduced in changes/VERSION_COMPAT - Reset changes/VERSION_COMPAT - * [ ] git commit -av # we should add a commit message here... + * [ ] git commit -am "Update requirements file." * [ ] Fold in changes files into the CHANGELOG - NOTE: For leap.soledad, the CHANGELOG entries should be divided per package (common, client, server). See older releases for reference. @@ -27,22 +41,49 @@ * [ ] git rm changes/feature*; git rm changes/bug* * [ ] git commit -m "Fold in changes." - * [ ] git checkout master && git pull origin master && git merge --no-ff release-X.Y.Z --no-edit - * [ ] git tag -s X.Y.Z -m "Tag version X.Y.Z" # (note the -s so that it's a signed tag and -m to specify the message for the tag) - * [ ] git push origin master; git push origin X.Y.Z - * [ ] git checkout develop && git merge master && git push origin develop + * [ ] git tag --sign $RELEASE -m "Tag leap.bitmask version $RELEASE" + + * If everything went ok, push the changes, and merge back into master&develop: + * [ ] git push origin release/0.9.x + * [ ] git push origin $RELEASE + * [ ] git checkout master && git pull origin master && git merge --no-ff $RELEASE --no-edit + * [ ] git checkout develop && git merge $RELEASE && git push origin develop + + == Bundles == * [ ] Build and upload bundles * [ ] Use 'make pyinst-linux' to build bundles. - * [ ] Sign them with gpg -a --sign --detach-sign + * [ ] Sign: make pyinst-sign * [ ] Upload bundle and signature to downloads.leap.se/client//Bitmask--.(tar.bz2,dmg,zip) + * [ ] make pyinst-upload * [ ] Update symbolic link for latest upload and signature: * [ ] ~/public/client/Bitmask--latest * [ ] ~/public/client/Bitmask--latest.asc - * [ ] Announce - * [ ] Mail leap@lists.riseup.net -Notes ------ -(*) this checklist kindly borrowed from tahoe-lafs documentation =) + === TUF: Relese candidate bundles: RC# (skipped for now) === + * [ ] Upload the TUF unstable repo + * [ ] Upload bundle to staging for release-candidate + * [ ] Sign the bundles, move it to client downloads (micah) + * [ ] Update symlinks for -latest + * [ ] Fix all show stoppers -For a good reference look at http://nvie.com/posts/a-successful-git-branching-model/ + === TUF: Stable bundles (skipped for now) === + * [ ] Upload the TUF Stable Repo to staging + * [ ] Upload bundle to staging for stable + * [ ] move and sign the TUF repo (kwadro) + * [ ] Sign the bundles, move it to client downloads (micah) + * [ ] Update symlinks for -latest + + == Debian packages == + * TBD... + + == Pypi upload == + * [ ] python setup.py sdist upload --sign -i kali@leap.se -r pypi + + == Announcing == + * [ ] Announce (use release-notes.rst) + * [ ] Mail leap@lists.riseup.net + * [ ] Twitter + * [ ] Gnusocial + * [ ] Post in leap.se + * [ ] reddit + * [ ] hackernews -- cgit v1.2.3 From 92f4b40ab48ec537aade244af3e3e4f2c17b1475 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 15 Apr 2016 16:10:00 -0400 Subject: [refactor] adapt to the new Account signature --- src/leap/bitmask/pix.py | 2 +- src/leap/bitmask/services/mail/imap.py | 2 +- src/leap/bitmask/services/mail/plumber.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/leap/bitmask/pix.py b/src/leap/bitmask/pix.py index 8d7d0811..b510106d 100644 --- a/src/leap/bitmask/pix.py +++ b/src/leap/bitmask/pix.py @@ -140,7 +140,7 @@ class LeapSessionAdapter(object): self.fresh_account = False self.incoming_mail_fetcher = None - self.account = Account(soledad) + self.account = Account(soledad, userid) username, provider = userid.split('@') smtp_client_cert = os.path.join( diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py index 54935f8c..2f000b2a 100644 --- a/src/leap/bitmask/services/mail/imap.py +++ b/src/leap/bitmask/services/mail/imap.py @@ -91,7 +91,7 @@ def start_incoming_mail_service(keymanager, soledad, userid): check_period=get_mail_check_period()) return incoming_mail - acc = Account(soledad) + acc = Account(soledad, userid) d = acc.callWhenReady(lambda _: acc.get_collection_by_mailbox(INBOX_NAME)) d.addCallback(setUpIncomingMail) d.addErrback(log.err) diff --git a/src/leap/bitmask/services/mail/plumber.py b/src/leap/bitmask/services/mail/plumber.py index 43203f0c..cd1f06bb 100644 --- a/src/leap/bitmask/services/mail/plumber.py +++ b/src/leap/bitmask/services/mail/plumber.py @@ -60,6 +60,7 @@ def initialize_soledad(uuid, email, passwd, cert_file = "" class Mock(object): + def __init__(self, return_value=None): self._return = return_value @@ -140,7 +141,7 @@ class MBOXPlumber(object): self.sol = initialize_soledad( self.uuid, self.userid, self.passwd, secrets, localdb, "/tmp", "/tmp") - self.acct = IMAPAccount(self.userid, self.sol) + self.acct = IMAPAccount(self.sol, self.userid) return True # -- cgit v1.2.3 From 0a5d24d64b5f637038a15b01bbe1b3d4bf4108f2 Mon Sep 17 00:00:00 2001 From: Paixu Aabuizia Date: Sun, 10 Jan 2016 15:40:35 +0100 Subject: [pkg] reproducible windows installer for bitmask_client provide a environment that allows automated builds of windows installers - prepare dockerized environment with wine, python, openssl, zlib and mingw to build windows binaries from python sourcecode - prepare dockerized environment with nullsoft installer to build installers from binaries - configure pyinstaller to build binaries - configure nsis to build distributable executables for bitmask - configure make all in pkg/windows that results in installers - add documentation - ico conversion from data/images - avoid polluting / in docker image - install dirspec and copy to wine env - remove obsolete comments - fix python path - figure out that pip install leap.a and pyinstalling a leap.b does not work - so the build script fixes that - rename dependencies to pyinstaller and move nsis code to installer - build openvpn, export the binaries for further processing - correct openvpn dependencies, fetch tap installer compatible with openvpn just built - install tap-driver with nsis - pyinstaller-build: fix mixed mkdir / show errors if there are some - installer-build: prepare rw-copy, do not expose nsh files - add openvpn_leap.exe to install directory so it gets picked up by nsis - use setup.py to install bitmask to site-packages to have a version - separate build directories for granular make - copy all openvpn dlls to installer - die to signal failure to parent makefile - cache installDependencies for quick turn-arround times - share openssl version between openvpn and pysqlcipher/other pip builds - collect files during prepare for installer - default to eip:false, mail:true - configuration in pyinstaller-build.sh - win64 tap drivers need special care getting removed from 32bit nsis - correct registry key that identifies if we installed TAP - extract version from git-tree, expose to wine python - create nsh with version for build installer - allow clean/dirty version with patches - cleanup / indent / remove comments - die when pysqlchipher patch failed - add psutil in mingw compatible version --- pkg/pyinst/bitmask.spec | 30 ++- pkg/pyinst/qt.conf | 2 + pkg/windows/Makefile | 25 ++ pkg/windows/README.rst | 144 +++++++++++ pkg/windows/TODO | 15 ++ pkg/windows/bitmask.nis | 2 + pkg/windows/bitmask.nsh | 115 +++++++++ pkg/windows/bitmask_client_product.nsh | 8 + pkg/windows/bitmask_client_registry_install.nsh | 17 ++ pkg/windows/docker-compose.yml | 42 +++ pkg/windows/installer-build.sh | 119 +++++++++ pkg/windows/installer/Dockerfile | 17 ++ pkg/windows/openvpn-build.sh | 62 +++++ pkg/windows/openvpn/Dockerfile | 17 ++ pkg/windows/pyinstaller-build.sh | 287 +++++++++++++++++++++ pkg/windows/pyinstaller/Dockerfile | 105 ++++++++ pkg/windows/pyinstaller/pysqlcipher_setup.py.patch | 14 + pkg/windows/pyinstaller/zlib-mingw-shared.patch | 10 + 18 files changed, 1024 insertions(+), 7 deletions(-) create mode 100644 pkg/pyinst/qt.conf create mode 100644 pkg/windows/Makefile create mode 100644 pkg/windows/README.rst create mode 100644 pkg/windows/TODO create mode 100644 pkg/windows/bitmask.nis create mode 100644 pkg/windows/bitmask.nsh create mode 100644 pkg/windows/bitmask_client_product.nsh create mode 100644 pkg/windows/bitmask_client_registry_install.nsh create mode 100644 pkg/windows/docker-compose.yml create mode 100755 pkg/windows/installer-build.sh create mode 100644 pkg/windows/installer/Dockerfile create mode 100755 pkg/windows/openvpn-build.sh create mode 100644 pkg/windows/openvpn/Dockerfile create mode 100755 pkg/windows/pyinstaller-build.sh create mode 100644 pkg/windows/pyinstaller/Dockerfile create mode 100644 pkg/windows/pyinstaller/pysqlcipher_setup.py.patch create mode 100644 pkg/windows/pyinstaller/zlib-mingw-shared.patch diff --git a/pkg/pyinst/bitmask.spec b/pkg/pyinst/bitmask.spec index a76ccb17..cd207816 100644 --- a/pkg/pyinst/bitmask.spec +++ b/pkg/pyinst/bitmask.spec @@ -3,17 +3,32 @@ import sys block_cipher = None - -a = Analysis([os.path.join('bitmask.py')], +a = Analysis(['bitmask.py'], hiddenimports=[ - 'zope.interface', 'zope.proxy', - 'PySide.QtCore', 'PySide.QtGui', 'PySide.QtWebKit'], + 'zope.interface', 'zope.proxy', + 'PySide.QtCore', 'PySide.QtGui', + 'pysqlcipher', 'service_identity', + 'leap.common', 'leap.bitmask' + ], + binaries=None, + datas=None, hookspath=None, runtime_hooks=None, excludes=None, + win_no_prefer_redirects=None, + win_private_assemblies=None, cipher=block_cipher) -pyz = PYZ(a.pure, +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +# Binary files you need to include in the form of: +# (, , '') + +# Data files you want to include, in the form of: +# (, , '') +data = [ + ('qt.conf', 'qt.conf', 'DATA') +] exe = EXE(pyz, a.scripts, exclude_binaries=True, @@ -21,12 +36,13 @@ exe = EXE(pyz, debug=False, strip=False, upx=True, - console=False ) + console=False, + icon='../../data/images/mask-icon.ico') coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, - strip=False, + strip=None, upx=True, name='bitmask') if sys.platform.startswith("darwin"): diff --git a/pkg/pyinst/qt.conf b/pkg/pyinst/qt.conf new file mode 100644 index 00000000..7eecd342 --- /dev/null +++ b/pkg/pyinst/qt.conf @@ -0,0 +1,2 @@ +[Paths] +Plugins=qt4_plugins \ No newline at end of file diff --git a/pkg/windows/Makefile b/pkg/windows/Makefile new file mode 100644 index 00000000..5d19353e --- /dev/null +++ b/pkg/windows/Makefile @@ -0,0 +1,25 @@ +.PHONY: all pkg installer openvpn pyinstaller +all: + docker-compose build + $(MAKE) pkg + +pkg: + $(MAKE) openvpn + $(MAKE) pyinstaller + $(MAKE) installer + +pyinstaller: + docker-compose run --rm pyinstaller + +openvpn: + docker-compose run --rm openvpn + +installer: + docker-compose run --rm installer + +clean: + docker rmi windows_pyinstaller + docker rmi windows_openvpn + docker rmi windows_installer + rm -rf ../../dist/*.exe + rm -rf ../../build/* \ No newline at end of file diff --git a/pkg/windows/README.rst b/pkg/windows/README.rst new file mode 100644 index 00000000..0bdfb1d1 --- /dev/null +++ b/pkg/windows/README.rst @@ -0,0 +1,144 @@ +Environment setup in debian:jessie +================================== + +basically you need this to setup your environment: + +# apt-get install mingw-w64 +# apt-get install wine +# apt-get install nsis + +this is a incomplete list of dependencies, review the pyinstaller/Dockerfile +to get a understanding of what needs to be setup in order to have a +environment that builds the installer + +Requirements +============ + +docker-compose + +Building the package +==================== + +make pkg + + +Reproducible builds +=================== + +please run the binary and installer builds on a clean machine eg +using docker or any virtual environment that can easily be prepared +by a third party to verify that the binaries are actually what the +sourcecode suggests. + +to use reproducible build you need to install docker which then installs +a clean debian:jessie to install nsis or the mingw environment + + +Installer +========= + +NSIS was choosen because it provided a out of the box toolchain to build +installers for the windows platform with minimal dependencies. The downside +of nsis is that it does not produce msi binaries + +to build the binary dependencies run: + +``` +docker-compose run --rm openvpn +docker-compose run --rm pyinstaller +``` + +the produced binaries will be stored in ${ROOT}/build + +to build the installer run: + +``` +docker-compose run --rm installer +``` + +the produced installer will be stored in ${ROOT}/dist + + +Pyinstaller +=========== + +Pyinstaller is a docker image based on debian:jessie with a cross-compile +toolchain (gcc) for building zlib and openssl in linux and wine (staging) +with installed python and mingw32 for pip/wheel compiling. +All pip installed dependencies are +part of the pyinstaller-build.sh script so they can be re-executed when the +dependencies of the project change. The image should be rebuild when openssl, +python or pyinstaller is updated: + +``` +docker-compose build pyinstaller +``` + +To debug or fine-tune the compile process it may be useful to setup the +following software on the development machine: + +``` +X :1 -listen tcp +DISPLAY=:1 xhost + +docker-compose run --rm pyinstaller /bin/bash +root@0fa19215321f:/# export DISPLAY=${YOUR_LOCAL_IP}:1 +root@0fa19215321f:/# wine cmd +Z:\>python +>>> +``` + +the configured volumes are: + +- the (read-only) sourcecode of the bitmask project in /var/src/bitmask +- the result of the builds in /var/build + +pyinstaller-build.sh +==================== + +Contains all steps to build the win32 executables. The project relies on +a read-write source tree which will pollute the development environment and +make it hard to reproduce 'clean' builds. therefore it expects that the source +is freshly checked out and not used to run in the host-environment. Otherwise +pyc and ui elements will mess up the binary in unpredictable ways. + +* copy the /var/src/bitmask sources to a read-write location (/var/build) +* execute ```make all``` in wine to build the qt ui and other resources +* execute ```pip install $dependencies``` to have all dependencies available +* execute ```pyinstaller``` in wine to compile the executable for +** bitmask (src/leap/bitmask/app.py) +* cleanup +** remove the read-write copy +** remove wine-dlls from the installer + +As the step 'install dependencies' may take long on slow internet connections +during development it is advised to recycle the container and share the +build/executables path with a windows-vm to test the result in short cycles +instead of make pkg, uninstall, install. + +``` +docker-compose run --rm --entrypoint=/bin/bash pyinstalle +root@0fa19215321f:/# cd /var/src/bitmask/pkg/windows +root@0fa19215321f:/var/src/bitmask/pkg/windows# ./pyinstaller-build.sh +root@0fa19215321f:/var/src/bitmask/pkg/windows# ./pyinstaller-build.sh +root@0fa19215321f:/var/src/bitmask/pkg/windows# ./pyinstaller-build.sh +.... +``` + +and test the result binary (accessible in bitmask/build in a separate vm. + +OpenVPN +======= + +OpenVPN is a straight forward cross compile image that builds the openvpn +sourcecode from the git-repository to a windows executable that can be +used by bitmask_root to launch eip. +It needs to be rebuild regulary as openssl gets a new version about every +month. PyInstaller uses the openssl that is compiled by this image + +Installer +========= + +Installer is a straight forward debian image with makensis installed. The +installer-build script lists the previously built files from pyinstaller and +openvpn to pass it as nsh file to makensis. bitmask.nis controls what will +be displayed to the user and how the components are installed and uninstalled \ No newline at end of file diff --git a/pkg/windows/TODO b/pkg/windows/TODO new file mode 100644 index 00000000..49c6bac1 --- /dev/null +++ b/pkg/windows/TODO @@ -0,0 +1,15 @@ +TODO +==== + +fix python-code (0.9.1) that fails on windows: +- fix the race condition for the backend/frontend startup. + spawning the backend takes time. with a introduced 15s timeout + it was possible to ensure the backend is up. It would be ideal + if the backend could signal the app to continue loading the frontend +- fix the ~/leap/events/zmq_certificates/public_keys/server.key +- fix logger (& remove hack in pyinstaller-build.sh:228) +- fix pysqlcipher (/LIB:,https get not working in setup.py, remove hack + in pyinstaller-build:164) +- merge bitmask_root from https://github.com/alirezamirzaeiyan/bitmask-root + +create similar infrastructure for osx dmg, preferably run on osx for compressed dmg \ No newline at end of file diff --git a/pkg/windows/bitmask.nis b/pkg/windows/bitmask.nis new file mode 100644 index 00000000..8705c058 --- /dev/null +++ b/pkg/windows/bitmask.nis @@ -0,0 +1,2 @@ +!define PKGNAME bitmask +!include .\bitmask.nsh \ No newline at end of file diff --git a/pkg/windows/bitmask.nsh b/pkg/windows/bitmask.nsh new file mode 100644 index 00000000..fe02f84e --- /dev/null +++ b/pkg/windows/bitmask.nsh @@ -0,0 +1,115 @@ +# pwd is a ro-mounted source-tree that had all dependencies build into +# package-name directories +!define PKGNAMEPATH ..\..\build\executables\${PKGNAME} +!include ${PKGNAMEPATH}_version.nsh +!include .\bitmask_client_product.nsh +!include ${PKGNAMEPATH}_install_files_size.nsh + +RequestExecutionLevel admin ;Require admin rights on NT6+ (When UAC is turned on) + +InstallDir "$PROGRAMFILES\${APPNAME}" + +LicenseData "..\..\LICENSE" +Name "${COMPANYNAME} - ${APPNAME}" +Icon "..\..\build\executables\mask-icon.ico" + +# /var/dist is a rw mounted volume +outFile "/var/dist/${PKGNAME}-${VERSIONMAJOR}.${VERSIONMINOR}.${VERSIONBUILD}${VERSIONSUFFIX}.exe" +!include LogicLib.nsh + +# Just three pages - license agreement, install location, and installation +page license +page directory +Page instfiles + +!macro VerifyUserIsAdmin +UserInfo::GetAccountType +pop $0 +${If} $0 != "admin" ;Require admin rights on NT4+ + messageBox mb_iconstop "Administrator rights required!" + setErrorLevel 740 ;ERROR_ELEVATION_REQUIRED + quit +${EndIf} +!macroend + +function .onInit + setShellVarContext all + !insertmacro VerifyUserIsAdmin +functionEnd + +section "TAP Virtual Ethernet Adapter" SecTAP + SetOverwrite on + SetOutPath "$TEMP" + File /oname=tap-windows.exe "..\..\build\executables\openvpn\tap-windows.exe" + + DetailPrint "Installing TAP (may need confirmation)..." + nsExec::ExecToLog '"$TEMP\tap-windows.exe" /S /SELECT_UTILITIES=1' + Pop $R0 # return value/error/timeout + + Delete "$TEMP\tap-windows.exe" + WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "tap" "installed" +sectionEnd + +section "install" + setOutPath $INSTDIR + + !include ${PKGNAMEPATH}_install_files.nsh + + # Uninstaller - See function un.onInit and section "uninstall" for configuration + writeUninstaller "$INSTDIR\uninstall.exe" + + # Start Menu + createDirectory "$SMPROGRAMS\${COMPANYNAME}" + createShortCut "$SMPROGRAMS\${COMPANYNAME}\${APPNAME}.lnk" "$INSTDIR\bitmask.exe" "" "$INSTDIR\bitmask.exe" + + !include bitmask_client_registry_install.nsh +sectionEnd + +# Uninstaller + +function un.onInit + SetShellVarContext all + !insertmacro VerifyUserIsAdmin +functionEnd + +section "uninstall" + + delete "$SMPROGRAMS\${COMPANYNAME}\${APPNAME}.lnk" + # Try to remove the Start Menu folder - this will only happen if it is empty + rmDir "$SMPROGRAMS\${COMPANYNAME}" + + # Remove files + !include ${PKGNAMEPATH}_uninstall_files.nsh + + # Remove TAP Drivers + ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "tap" + ${If} $R0 == "installed" + DetailPrint "Uninstalling TAP as we installed it..." + ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\TAP-Windows" "UninstallString" + ${If} $R0 != "" + DetailPrint "Uninstalling TAP..." + nsExec::ExecToLog '"$R0" /S' + Pop $R0 # return value/error/timeout + ${Else} + # on x64 windows the uninstall location needs to be accessed using WOW + SetRegView 64 + ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\TAP-Windows" "UninstallString" + SetRegView 32 + ${If} $R0 != "" + DetailPrint "Uninstalling TAP 64..." + nsExec::ExecToLog '"$R0" /S' + Pop $R0 # return value/error/timeout + ${EndIf} + ${EndIf} + DeleteRegValue HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "tap" + ${EndIf} + + # Always delete uninstaller as the last action + delete $INSTDIR\uninstall.exe + + # Try to remove the install directory - this will only happen if it is empty + rmDir $INSTDIR + + # Remove uninstaller information from the registry + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" +sectionEnd \ No newline at end of file diff --git a/pkg/windows/bitmask_client_product.nsh b/pkg/windows/bitmask_client_product.nsh new file mode 100644 index 00000000..64f59ac7 --- /dev/null +++ b/pkg/windows/bitmask_client_product.nsh @@ -0,0 +1,8 @@ +!define APPNAME "Bitmask" +!define COMPANYNAME "leap.se" +!define DESCRIPTION "With Bitmask VPN, all your traffic is securely routed through your provider before it is decrypted and sent on to the open internet." +# These will be displayed by the "Click here for support information" link in "Add/Remove Programs" +# It is possible to use "mailto:" links in here to open the email client +!define HELPURL "https://bitmask.net/en/help" # "Support Information" link +!define UPDATEURL "https://bitmask.net/en/install" # "Product Updates" link +!define ABOUTURL "https://bitmask.net/" # "Publisher" link \ No newline at end of file diff --git a/pkg/windows/bitmask_client_registry_install.nsh b/pkg/windows/bitmask_client_registry_install.nsh new file mode 100644 index 00000000..5bf04045 --- /dev/null +++ b/pkg/windows/bitmask_client_registry_install.nsh @@ -0,0 +1,17 @@ +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "DisplayName" "${COMPANYNAME} - ${APPNAME} - ${DESCRIPTION}" +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "InstallLocation" "$\"$INSTDIR$\"" +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "DisplayIcon" "$\"$INSTDIR\bitmask.exe$\"" +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "Publisher" "${COMPANYNAME}" +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "HelpLink" "${HELPURL}" +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "URLUpdateInfo" "$\"${UPDATEURL}$\"" +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "URLInfoAbout" "$\"${ABOUTURL}$\"" +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "DisplayVersion" "${VERSIONMAJOR}.${VERSIONMINOR}.${VERSIONBUILD}${VERSIONSUFFIX}" +WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "VersionMajor" ${VERSIONMAJOR} +WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "VersionMinor" ${VERSIONMINOR} +# There is no option for modifying or repairing the install +WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "NoModify" 1 +WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "NoRepair" 1 +# Set the INSTALLSIZE constant (!defined at the top of this script) so Add/Remove Programs can accurately report the size +WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "EstimatedSize" ${INSTALLSIZE} \ No newline at end of file diff --git a/pkg/windows/docker-compose.yml b/pkg/windows/docker-compose.yml new file mode 100644 index 00000000..92b310c8 --- /dev/null +++ b/pkg/windows/docker-compose.yml @@ -0,0 +1,42 @@ +# mingw environment to build dependency binaries in a reproducible environment +# https://wiki.debian.org/ReproducibleBuilds +# service to build a windows executable using pyinstaller +# utilizes wine and pyinstaller-build.sh to produce +# build/executables/pyinstaller/bitmask/* +# usage: docker-compose run --rm pyinstaller +# non-zero exit code on failure +pyinstaller: + build: pyinstaller + volumes: +# bitmask sources + - ../../:/var/src/bitmask:ro +# produced binaries + - ../../build:/var/build +# service to build a windows-executable from openvpn sources +# uses the openvpn-build infrastructure to produce +# build/executables/openvpn/* +# produces the openvpn.exe and provides openssl that is to be +# used by pyinsaller +# usage: docker-compose run --rm openvpn +# non-zero exit code on failure +openvpn: + build: openvpn + volumes: +# bitmask sources + - ../../:/var/src/bitmask:ro +# produced binaries + - ../../build:/var/build +# service to compile a installer using nullsoft installer +# nsis environment to build installer (exe) that contains all required binaries +# for a clean, just installed windows machine +# utilizes the debian makensis and installer-build to produce +# dist/bitmask-VERSION.exe +# usage: docker-compose run --rm installer +# non-zero exit code on failure +installer: + build: installer + volumes: +# bitmask sources + - ../../:/var/src/bitmask:ro +# produced installers - configured in bitmask.nsh + - ../../dist:/var/dist diff --git a/pkg/windows/installer-build.sh b/pkg/windows/installer-build.sh new file mode 100755 index 00000000..cf664200 --- /dev/null +++ b/pkg/windows/installer-build.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +# build installer +# =============== +# +# builds several installers from previously compiled binaries + +product=bitmask +# the location of the nsis installer nis files dictates the path of the files +relative_executable_path=../../build/executables +source_ro_path=/var/src/${product} +temporary_build_path=/var/tmp/installer + +setups=($(ls -1 ${source_ro_path}/pkg/windows | grep '.nis$' | sed 's|.nis$||')) + +# generate nsis file references for installer for single directory +# appends File and Remove to files that are later included by makensis +# separate files for install and uninstall statements +# +# directory_root: the tree root that is currently generated +# subdir: any directory in the tree +# setup_name: the name of the setup this nsh entries are generated for +function generateDirectoryNSISStatements() { + directory_root=$1 + subdir=$2 + setup_name=$3 + find ${subdir} -maxdepth 1 -type f -exec echo 'File "'${relative_executable_path}'/{}"' \;>> ${setup_name}_install_files.nsh + find ${subdir} -maxdepth 1 -type f -exec echo 'Delete "$INSTDIR/{}"' \; >> ${setup_name}_uninstall_files.nsh +} +# generate a tree of files into nsis installer definitions +# directory_root: the tree root that is currently generated +# setup_name: the name of the setup this nsh entries are generated for +function generateDirectoryNSISStatementsTree() { + directory_root=$1 + setup_name=$2 + subdirs=$(find ${directory_root} -type d | sort) + for subdir in ${subdirs[@]} + do + if [ "${directory_root}" != "${subdir}" ]; then + echo 'SetOutPath "$INSTDIR/'${subdir}'"' >> ${setup_name}_install_files.nsh + fi + generateDirectoryNSISStatements ${directory_root} ${subdir} ${setup_name} + done + # again to remove emptied directories on uninstall so reverse + subdirs=$(find ${directory_root} -type d | sort | tac) + for subdir in ${subdirs[@]} + do + if [ "${directory_root}" != "${subdir}" ]; then + echo 'RMDir "$INSTDIR/'${subdir}'"' >> ${setup_name}_uninstall_files.nsh + fi + done +} +# generate installer files for the available setups +# those files include install and uninstall statements and are +# modified (backslashes/source_path) to generate a sane target +# structure +function generateNSISStatements() { + pushd ${temporary_build_path}/build/executables + for setup in "${setups[@]}" + do + echo "setup:" ${setup} + echo "# auto generated by pkg/windows/installer-build.sh please do not modify" > ${setup}_install_files.nsh + echo "# auto generated by pkg/windows/installer-build.sh please do not modify" > ${setup}_uninstall_files.nsh + setup_source_path=${setup} + generateDirectoryNSISStatementsTree ${setup_source_path} ${setup} + # remove the setup_source_path from the nsh files + sed -i "s|INSTDIR/${setup_source_path}/|INSTDIR/|" ${setup}_install_files.nsh + sed -i "s|/${setup_source_path}/|/|" ${setup}_uninstall_files.nsh + # make backslashes + sed -i "s|/|\\\\|g" ${setup}_install_files.nsh ${setup}_uninstall_files.nsh + # make install size + installed_size=$(du -s --block-size=1000 ${setup} | awk '{print $1}') + echo "!define INSTALLSIZE ${installed_size}" > ${setup}_install_files_size.nsh + done + popd +} +# makensis to produce a installer.exe +# the result is placed in /var/dist +function buildInstaller() { + pushd ${temporary_build_path}/pkg/windows + for setup in ${setups[@]} + do + makensis ${setup}.nis || die 'build setup "'${setup}'" failed' + done + popd +} +# prepare build path +# copies files that have been produced by other containers +# merges the product so the nsis files are correct +function prepareBuildPath() { + mkdir -p ${temporary_build_path}/pkg/windows + mkdir -p ${temporary_build_path}/build + cp -r ${source_ro_path}/pkg/windows/* ${temporary_build_path}/pkg/windows + cp -r ${source_ro_path}/build/* ${temporary_build_path}/build + cp -r ${source_ro_path}/LICENSE ${temporary_build_path}/LICENSE + + test -d ${temporary_build_path}/build/executables/bitmask || die 'bitmask not available run docker-compose run --rm pyinstaller' + test -d ${temporary_build_path}/build/executables/openvpn || die 'openvpn not available run docker-compose run --rm openvpn' + pushd ${temporary_build_path}/build/executables + cp openvpn/bin/openvpn.exe bitmask + cp openvpn/bin/*.dll bitmask + popd +} +# remove build files to ensure subsequent builds +function cleanup() { + rm -r ${temporary_build_path} +} +# display failure message and emit non-zero exit code +function die() { + echo "die:" $@ + exit 1 +} +function main() { + prepareBuildPath + generateNSISStatements + buildInstaller + cleanup +} +main $@ \ No newline at end of file diff --git a/pkg/windows/installer/Dockerfile b/pkg/windows/installer/Dockerfile new file mode 100644 index 00000000..ae46acb6 --- /dev/null +++ b/pkg/windows/installer/Dockerfile @@ -0,0 +1,17 @@ +FROM debian:jessie +MAINTAINER paixu@0xn0.de +RUN apt-get update + +###### +# install packages required to build + +RUN apt-get -y install \ + nsis +WORKDIR /var/src/bitmask/pkg/windows + +###### +# set a specific user +# needs external tuning of the /var/dist rights! +# RUN useradd installer +# USER installer +ENTRYPOINT ["/var/src/bitmask/pkg/windows/installer-build.sh"] \ No newline at end of file diff --git a/pkg/windows/openvpn-build.sh b/pkg/windows/openvpn-build.sh new file mode 100755 index 00000000..3f470f55 --- /dev/null +++ b/pkg/windows/openvpn-build.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# render openvpn prepared for installer +# ================================================ +# +# requires +# - a linux host with mingw installed +# - a rw directory mounted to /var/build +# returns nonzero exit code when failed +# +# clone openvpn-build repository +# runs cross-compile build +# - downloads openvpn dependencies +# - compiles +# copy files to executables so they can be installed +# cleans up (remove read-write copy) + +# the location where the openvpn binaries are placed +absolute_executable_path=/var/build/executables +temporary_build_path=/var/build/openvpn + +# cleanup the temporary build path for subsequent executes +function cleanup() { + rm -r ${temporary_build_path} 2>/dev/null +} +# build openvpn source +function buildSource() { + pushd ${temporary_build_path}/openvpn-build/generic + CHOST=i686-w64-mingw32 \ + CBUILD=i686-pc-linux-gnu \ + ./build \ + || die 'build openvpn from source failed' + cp -r image/openvpn ${absolute_executable_path}/openvpn + popd +} +# fetch tap-windows.exe as defined in the openvpn vars +function fetchTapWindows() { + pushd ${temporary_build_path}/openvpn-build + source windows-nsis/build-complete.vars + wget ${TAP_WINDOWS_INSTALLER_URL} -O ${absolute_executable_path}/openvpn/tap-windows.exe || die 'tap-windows.exe could not be fetched' + popd +} +# prepare read-write copy +function prepareBuildPath() { + cleanup + mkdir -p ${temporary_build_path} + pushd ${temporary_build_path} + git clone https://github.com/OpenVPN/openvpn-build || die 'openvpn-build could not be cloned' + popd +} +# display failure message and emit non-zero exit code +function die() { + echo "die:" $@ + exit 1 +} +function main() { + prepareBuildPath + buildSource + fetchTapWindows + cleanup +} +main $@ \ No newline at end of file diff --git a/pkg/windows/openvpn/Dockerfile b/pkg/windows/openvpn/Dockerfile new file mode 100644 index 00000000..471685a1 --- /dev/null +++ b/pkg/windows/openvpn/Dockerfile @@ -0,0 +1,17 @@ +FROM debian:jessie +MAINTAINER paixu@0xn0.de + +###### +# install packages required to build +# https-transport: winehq deb +# winbind: pip install keyring (requirements.pip) needs this somehow +# git-core: clone rw copy of repo and build specific commit +# imagemagick: convert png to ico-files +RUN apt-get update && apt-get -y install \ + unzip bzip2 \ + curl wget \ + apt-transport-https \ + man2html \ + git-core \ + build-essential autoconf mingw-w64 +ENTRYPOINT ["/var/src/bitmask/pkg/windows/openvpn-build.sh"] \ No newline at end of file diff --git a/pkg/windows/pyinstaller-build.sh b/pkg/windows/pyinstaller-build.sh new file mode 100755 index 00000000..06db8d54 --- /dev/null +++ b/pkg/windows/pyinstaller-build.sh @@ -0,0 +1,287 @@ +#!/bin/bash + +# render dependencies into separate subdirectories +# ================================================ +# +# requires +# - a linux host with wine, wine with python and mingw installed +# - the sourcecode mounted to /var/src/ +# - a rw directory mounted to /var/build +# returns nonzero exit code when pyinstaller failed +# +# prepares a read-write copy of the sourcecode +# executes qt-uic and qt-rcc for gui dialogs +# installs dependencies from pkg/dependencies-windows.pip +# runs pyinstaller +# cleans up (remove wine-dlls, remove read-write copy) +# creates nsis install/uninstall scripts for the files for each package +# if $1 is set it is expected to be a branch/git-tag + +product=bitmask +# the location where the pyinstaller results are placed +absolute_executable_path=/var/build/executables +# the location of the nsis installer nis files dictates the path of the files +relative_executable_path=../../build/executables +source_ro_path=/var/src/${product} +temporary_build_path=/var/build/pyinstaller +git_tag=HEAD +version_prefix=leap.bitmask +git_version=unknown +# option that is changed when a dependency-cache is found +install_dependencies=true +# default options for components +with_eip=false +with_mail=true + +setups=($(ls -1 ${source_ro_path}/pkg/windows | grep '.nis$' | sed 's|.nis$||')) +# add mingw dlls that are build in other steps +function addMingwDlls() { + root=$1 + cp /usr/lib/gcc/i686-w64-mingw32/4.9-win32/libgcc_s_sjlj-1.dll ${root} + cp /root/.wine/drive_c/Python27/Lib/site-packages/zmq/libzmq.pyd ${root} + cp /root/.wine/drive_c/Python27/Lib/site-packages/zmq/libzmq.pyd ${root} + mkdir -p ${root}/pysqlcipher + cp /var/build/pyinstaller/pkg/pyinst/build/bitmask/pysqlcipher-2.6.4-py2.7-win32.egg/pysqlcipher/_sqlite.pyd ${root}/pysqlcipher + cp ~/.wine/drive_c/openssl/bin/*.dll ${root} +} +# cleanup the temporary build path for subsequent executes +function cleanup() { + rm -r ${temporary_build_path} 2>/dev/null +} +# create files that are not part of the repository but are needed +# in the windows environment: +# - license with \r\n +# - ico from png (multiple sizes for best results on high-res displays) +function createInstallablesDependencies() { + pushd ${temporary_build_path} > /dev/null + cat LICENSE | sed 's|\n|\r\n|g' > LICENSE.txt + convert data/images/mask-icon.png -filter Cubic -scale 256x256! data/images/mask-icon-256.png + convert data/images/mask-icon-256.png -define icon:auto-resize data/images/mask-icon.ico + # execute qt-uic / qt-rcc + wine mingw32-make all || die 'qt-uic / qt-rcc failed' + # get version using git (only available in host) + git_version=$(python setup.py version| grep 'Version is currently' | awk -F': ' '{print $2}') + # run setup.py in a path with the version contained so versioneer can + # find the information and put it into the egg + versioned_build_path=/var/tmp/${version_prefix}-${git_version} + mkdir -p ${versioned_build_path} + cp -r ${temporary_build_path}/* ${versioned_build_path} + # apply patches to the source that are required for working code + # should not be required in the future as it introduces possible + # hacks that are hard to debug + applyPatches ${versioned_build_path} + pushd ${versioned_build_path} > /dev/null + wine python setup.py update_files || die 'setup.py update_files failed' + wine python setup.py build || die 'setup.py build failed' + wine python setup.py install || die 'setup.py install failed' + popd + rm -rf ${versioned_build_path} + popd +} +# create installer version that may be used by installer-build.sh / makensis +# greps the version-parts from the previously extracted git_version and stores +# the result in a setup_version.nsh +# when the git_version does provide a suffix it is prefixed with a dash so the +# installer output needs no conditional for this +function createInstallerVersion() { + setup=$1 + # [0-9]*.[0-9]*.[0-9]*-[0-9]*_g[0-9a-f]*_dirty + VERSIONMAJOR=$(echo ${git_version} | sed 's|^\([0-9]*\)\..*$|\1|') + VERSIONMINOR=$(echo ${git_version} | sed 's|^[0-9]*\.\([0-9]*\).*$|\1|') + VERSIONBUILD=$(echo ${git_version} | sed 's|^[0-9]*\.[0-9]*\.\([0-9]*\).*$|\1|') + VERSIONSUFFIX=$(echo ${git_version} | sed 's|^[0-9]*\.[0-9]*\.[0-9]*-\(.*\)$|\1|') + echo "!define VERSIONMAJOR ${VERSIONMAJOR}" > ${absolute_executable_path}/${setup}_version.nsh + echo "!define VERSIONMINOR ${VERSIONMINOR}" >> ${absolute_executable_path}/${setup}_version.nsh + echo "!define VERSIONBUILD ${VERSIONBUILD}" >> ${absolute_executable_path}/${setup}_version.nsh + if [ ${VERSIONSUFFIX} != "" ]; then + VERSIONSUFFIX="-${VERSIONSUFFIX}" + fi + echo "!define VERSIONSUFFIX ${VERSIONSUFFIX}" >> ${absolute_executable_path}/${setup}_version.nsh +} +# create installable binaries with dlls +function createInstallables() { + mkdir -p ${absolute_executable_path} + pushd ${temporary_build_path}/pkg/pyinst + # build install directories (contains multiple files with pyd,dll, some of + # them look like windows WS_32.dll but are from wine) + for setup in ${setups[@]} + do + # --clean do not cache anything and overwrite everything --noconfirm + # --distpath to place on correct location + # --debug to see what may be wrong with the result + # --paths=c:\python\lib\site-packages;c:\python27\lib\site-packages + wine pyinstaller \ + --clean \ + --noconfirm \ + --distpath=.\\installables \ + --paths=Z:\\var\\build\\pyinstaller\\src\\ \ + --paths=C:\\Python27\\Lib\\site-packages\\ \ + --debug \ + ${setup}.spec \ + || die 'pyinstaller for "'${setup}'" failed' + removeWineDlls installables/${setup} + addMingwDlls installables/${setup} + rm -r ${absolute_executable_path}/${setup} + cp -r installables/${setup} ${absolute_executable_path} + cp ${absolute_executable_path}/cacert.pem ${absolute_executable_path}/${setup} + rm -r installables + createInstallerVersion ${setup} + done + popd + pushd ${temporary_build_path} + cp data/images/mask-icon.ico ${absolute_executable_path}/ + popd +} +# install (windows)dependencies of project +function installProjectDependencies() { + pushd ${temporary_build_path} > /dev/null + unsupported_packages="dirspec" + pip_flags="--find-links=Z:${temporary_build_path}/wheels" + for unsupported_package in ${unsupported_packages} + do + pip_flags="${pip_flags} --allow-external ${unsupported_package} --allow-unverified ${unsupported_package}" + done + pip_flags="${pip_flags} -r" + + # install dependencies + mkdir -p ${temporary_build_path}/wheels + wine pip install ${pip_flags} pkg/requirements-leap.pip || die 'requirements-leap.pip could not be installed' + # fix requirements + # python-daemon breaks windows build + sed -i 's|^python-daemon|#python-daemon|' pkg/requirements.pip + wine pip install ${pip_flags} pkg/requirements.pip || die 'requirements.pip could not be installed' + git checkout pkg/requirements.pip + popd + cp -r /root/.wine/drive_c/Python27/Lib/site-packages ${absolute_executable_path} + curl https://curl.haxx.se/ca/cacert.pem > ${absolute_executable_path}/cacert.pem || die 'cacert.pem could not be fetched - would result in bad ssl in installer' +} +# workaround for broken dependencies +# runs before pip install requirements +# fixes failure for pysqlcipher as this requests a https file that the +# windows-python fails to request +function installProjectDependenciesBroken() { + pushd ${temporary_build_path} > /dev/null + curl https://pypi.python.org/packages/source/p/pysqlcipher/pysqlcipher-2.6.4.tar.gz \ + > pysqlcipher-2.6.4.tar.gz \ + || die 'fetch pysqlcipher failed' + tar xzf pysqlcipher-2.6.4.tar.gz + pushd pysqlcipher-2.6.4 + curl https://downloads.leap.se/libs/pysqlcipher/amalgamation-sqlcipher-2.1.0.zip \ + > amalgamation-sqlcipher-2.1.0.zip \ + || die 'fetch amalgamation for pysqlcipher failed' + unzip -o amalgamation-sqlcipher-2.1.0.zip || die 'unzip amalgamation failed' + mv sqlcipher amalgamation + patch -p0 < ${source_ro_path}/pkg/windows/pyinstaller/pysqlcipher_setup.py.patch \ + || die 'patch pysqlcipher setup.py failed' + wine python setup.py build install || die 'setup.py for pysqlcipher failed' + popd + popd # temporary_build_path +} +# prepare read-write copy +function prepareBuildPath() { + cleanup + # ensure shared openssl for all pip builds + test -d ${absolute_executable_path}/openvpn || die 'openvpn not available run docker-compose run --rm openvpn' + cp -r ${absolute_executable_path}/openvpn /root/.wine/drive_c/openssl + if [ -d ${absolute_executable_path}/site-packages ]; then + # use pip install cache for slow connections + rm -r /root/.wine/drive_c/Python27/Lib/site-packages + cp -r ${absolute_executable_path}/site-packages /root/.wine/drive_c/Python27/Lib/ + install_dependencies=false + fi + if [ ! -z $1 ]; then + git_tag=$1 + fi + if [ ${git_tag} != "HEAD" ]; then + echo "using ${git_tag} as source for the project" + git clone ${source_ro_path} ${temporary_build_path} + pushd ${temporary_build_path} + git checkout ${git_tag} || die 'checkout "'${git_tag}'" failed' + popd + else + echo "using current source tree for build" + mkdir -p ${temporary_build_path}/data + mkdir -p ${temporary_build_path}/docs + mkdir -p ${temporary_build_path}/pkg + mkdir -p ${temporary_build_path}/src + mkdir -p ${temporary_build_path}/.git + cp -r ${source_ro_path}/data/* ${temporary_build_path}/data + cp -r ${source_ro_path}/data/* ${temporary_build_path}/docs + cp -r ${source_ro_path}/pkg/* ${temporary_build_path}/pkg + cp -r ${source_ro_path}/src/* ${temporary_build_path}/src + cp -r ${source_ro_path}/.git/* ${temporary_build_path}/.git + cp ${source_ro_path}/* ${temporary_build_path}/ + fi +} +# add patches to the sourcetree +# this function should do nothing some day and should be run after +# the version has been evaluated +function applyPatches() { + root_path=$1 + # disable eip + if [ !${with_eip} ]; then + sed -i "s|HAS_EIP = True|HAS_EIP = False|" ${root_path}/src/leap/bitmask/_components.py + fi + # disable mail + if [ !${with_mail} ]; then + sed -i "s|HAS_MAIL = True|HAS_MAIL = False|" ${root_path}/src/leap/bitmask/_components.py + fi + # hack the logger + sed -i "s|'bitmask.log'|str(random.random()) + '_bitmask.log'|;s|import sys|import sys\nimport random|" ${root_path}/src/leap/bitmask/logs/utils.py + sed -i "s|perform_rollover=True|perform_rollover=False|" ${root_path}/src/leap/bitmask/app.py + # fix requirements + # python-daemon breaks windows build + sed -i 's|^python-daemon|#python-daemon|' ${root_path}/pkg/requirements.pip +} +# remove wine dlls that should not be in the installer +# root: path that should be cleaned from dlls +function removeWineDlls() { + root=$1 + declare -a wine_dlls=(\ + advapi32.dll \ + comctl32.dll \ + comdlg32.dll \ + gdi32.dll \ + imm32.dll \ + iphlpapi.dll \ + ktmw32.dll \ + msvcp90.dll \ + msvcrt.dll \ + mswsock.dll \ + mpr.dll \ + netapi32.dll \ + ole32.dll \ + oleaut32.dll \ + opengl32.dll \ + psapi.dll \ + rpcrt4.dll \ + shell32.dll \ + user32.dll \ + version.dll \ + winmm.dll \ + winspool.drv \ + ws2_32.dll \ + wtsapi32.dll \ + ) + for wine_dll in "${wine_dlls[@]}" + do + # not all of the listed dlls are in all directories + rm ${root}/${wine_dll} 2>/dev/null + done +} +# display failure message and emit non-zero exit code +function die() { + echo "die:" $@ + exit 1 +} +function main() { + prepareBuildPath $@ + if [ ${install_dependencies} == true ]; then + installProjectDependenciesBroken + installProjectDependencies + fi + createInstallablesDependencies + createInstallables + cleanup +} +main $@ \ No newline at end of file diff --git a/pkg/windows/pyinstaller/Dockerfile b/pkg/windows/pyinstaller/Dockerfile new file mode 100644 index 00000000..2da0da3a --- /dev/null +++ b/pkg/windows/pyinstaller/Dockerfile @@ -0,0 +1,105 @@ +FROM debian:jessie +MAINTAINER paixu@0xn0.de + +ENV PYTHON_VERSION=2.7.11 +ENV OPENSSL_VERSION=1.0.2f +ENV ZLIB_VERSION=1.2.8 +ENV MINGW_VERSION=0.6.2-beta-20131004-1 +ENV MINGW_BIN_VERSION=0.6.2-mingw32-beta-20131004-1-bin +ENV WINEDEBUG=fixme-all + +###### +# install packages required to build +# https-transport: winehq deb +# winbind: pip install keyring (requirements.pip) needs this somehow +# git-core: clone rw copy of repo and build specific commit +# imagemagick: convert png to ico-files +RUN apt-get update && apt-get -y install \ + unzip curl apt-transport-https \ + winbind \ + build-essential autoconf bison gperf flex libtool mingw-w64 \ + git-core \ + imagemagick \ + pkg-config + +# install wine > 1.6.2 (debian:jessie version fails with pip) +RUN dpkg --add-architecture i386 \ + && curl https://dl.winehq.org/wine-builds/Release.key | apt-key add - \ + && echo 'deb https://dl.winehq.org/wine-builds/debian/ jessie main' >> /etc/apt/sources.list.d/wine.list \ + && apt-get update + +RUN curl https://www.python.org/ftp/python/${PYTHON_VERSION}/python-${PYTHON_VERSION}.msi > /tmp/python-${PYTHON_VERSION}.msi +RUN curl -L http://sourceforge.net/projects/mingw/files/Installer/mingw-get/mingw-get-${MINGW_VERSION}/mingw-get-${MINGW_BIN_VERSION}.zip/download > /tmp/mingw-get.zip + +# alternative with messy python afterwards +# RUN curl -L http://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/VCForPython27.msi > /tmp/msvcforpython27.msi + +RUN curl -L http://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz > /tmp/openssl-${OPENSSL_VERSION}.tar.gz +RUN apt-get install -y winehq-staging + +RUN curl -L http://sourceforge.net/projects/mingw/files/Installer/mingw-get/mingw-get-${MINGW_VERSION}/mingw-get-${MINGW_BIN_VERSION}.zip/download > /tmp/mingw-get.zip +RUN mkdir -p /root/.wine/drive_c/mingw \ + && unzip -d /root/.wine/drive_c/mingw /tmp/mingw-get.zip + +####### +# Build python dependency +# using the 'host' (linux) xcompiler instead of fiddeling in wine +# zlib - needs a update every 5 years +# adds a patch that makes a shared lib - default is static +RUN curl -L http://zlib.net/zlib-${ZLIB_VERSION}.tar.gz > /tmp/zlib-${ZLIB_VERSION}.tar.gz +ADD zlib-mingw-shared.patch /zlib-mingw-shared.patch +RUN mkdir -p /root/.wine/drive_c/zlib/src \ + && mv /tmp/zlib-${ZLIB_VERSION}.tar.gz /root/.wine/drive_c/zlib/src \ + && cd /root/.wine/drive_c/zlib/src \ + && tar xzf zlib-${ZLIB_VERSION}.tar.gz \ + && cd zlib-${ZLIB_VERSION} \ + && patch -p0 < /zlib-mingw-shared.patch \ + && make -f win32/Makefile.gcc PREFIX=/usr/bin/i686-w64-mingw32- \ + && make -f win32/Makefile.gcc INCLUDE_PATH=/root/.wine/drive_c/zlib/include LIBRARY_PATH=/root/.wine/drive_c/zlib/lib BINARY_PATH=/root/.wine/drive_c/zlib/bin install + +###### +# install gcc for most pip builds +# install g++ for pycryptopp +# this is mingw in wine, not to get confused with mingw-w64 in container-host +RUN wine msiexec -i /tmp/python-${PYTHON_VERSION}.msi -q \ + && wine c:/mingw/bin/mingw-get.exe install gcc g++ mingw32-make \ + && rm -r /tmp/.wine-0 + +#### +# pip configuration +# set wine mingw compiler to be used by "python setup build" +# set default include dirs, libraries and library paths +# the libraries=crypto is added because srp will only link using -lssl but links to BN_* (libcrypto) code +# 'install' zlib to mingw so python may find its dlls +# pyside-rcc fix (https://srinikom.github.io/pyside-bz-archive/670.html) +RUN printf "[build]\ncompiler=mingw32\n\n[build_ext]\ninclude_dirs=c:\\openssl\\include;c:\\zlib\\include\nlibraries=crypto\nlibrary_dirs=c:\\openssl\\lib;c:\\openssl\\bin;c:\\zlib\\lib;c:\\zlib\\bin" > /root/.wine/drive_c/Python27/Lib/distutils/distutils.cfg \ + && printf 'REGEDIT4\n\n[HKEY_CURRENT_USER\\Environment]\n"PATH"="C:\\\\python27;C:\\\\python27\\\\Scripts;C:\\\\python27\\\\Lib\\\\site-packages\\\\PySide;C:\\\\mingw\\\\bin;c:\\\\windows;c:\\\\windows\\\\system"' > /root/.wine/drive_c/path.reg \ + && printf 'REGEDIT4\n\n[HKEY_CURRENT_USER\\Environment]\n"OPENSSL_CONF"="C:\\\\openssl"' > /root/.wine/drive_c/openssl_conf.reg \ + && printf 'REGEDIT4\n\n[HKEY_CURRENT_USER\\Environment]\n"PYTHONPATH"="C:\\\\python27\\\\lib\\\\site-packages;Z:\\\\var\\\\build\\\\bitmask_rw\\\\src"' > /root/.wine/drive_c/pythonpath.reg \ + && cp /root/.wine/drive_c/zlib/bin/zlib1.dll /root/.wine/drive_c/mingw/bin \ + && cp /root/.wine/drive_c/zlib/lib/libz.dll.a /root/.wine/drive_c/mingw/lib + +#### +# prepare the environment with all python dependencies installed +# inject dirspec from distribution +# +RUN apt-get install -y python-dirspec \ + && cp -r /usr/lib/python2.7/dist-packages/dirspec* /root/.wine/drive_c/Python27/Lib/site-packages/ +RUN apt-get install -y python-setuptools +RUN wine regedit /root/.wine/drive_c/path.reg \ + && wine regedit /root/.wine/drive_c/openssl_conf.reg \ + && wine regedit /root/.wine/drive_c/pythonpath.reg \ + && wine pip install virtualenv pyinstaller \ + && wine pip install wheel \ + && wine pip install -U setuptools-scm \ + && wine pip install -U setuptools_scm \ + && wine pip install -U pyside python-qt \ + && wine pip install -I psutil==3.4.2 \ + && rm -r /tmp/.wine-0 + +# alternative msvc: after python is installed (or before?) +# && wine msiexec -i /tmp/msvcforpython27.msi -q \ + +RUN apt-get -y install \ + mc +ENTRYPOINT ["/var/src/bitmask/pkg/windows/pyinstaller-build.sh"] \ No newline at end of file diff --git a/pkg/windows/pyinstaller/pysqlcipher_setup.py.patch b/pkg/windows/pyinstaller/pysqlcipher_setup.py.patch new file mode 100644 index 00000000..dcec54fa --- /dev/null +++ b/pkg/windows/pyinstaller/pysqlcipher_setup.py.patch @@ -0,0 +1,14 @@ +--- setup.py.org 2014-11-12 16:38:07.000000000 +0000 ++++ setup.py 2016-01-23 14:08:13.255261595 +0000 +@@ -192,10 +192,7 @@ + ext.define_macros.append(("inline", "__inline")) + + # Configure the linker +- ext.extra_link_args.append("libeay32.lib") +- ext.extra_link_args.append( +- "/LIBPATH:" + os.path.join(openssl, "lib") +- ) ++ ext.extra_link_args.append("-lcrypto") + else: + ext.extra_link_args.append("-lcrypto") + diff --git a/pkg/windows/pyinstaller/zlib-mingw-shared.patch b/pkg/windows/pyinstaller/zlib-mingw-shared.patch new file mode 100644 index 00000000..1b980cb8 --- /dev/null +++ b/pkg/windows/pyinstaller/zlib-mingw-shared.patch @@ -0,0 +1,10 @@ +diff -Naur ../zlib-1.2.8-org/win32/Makefile.gcc ./win32/Makefile.gcc +--- ../zlib-1.2.8-org/win32/Makefile.gcc 2008-10-23 17:44:36.000000000 +0000 ++++ ./win32/Makefile.gcc 2015-12-06 19:20:00.449471787 +0000 +@@ -37,6 +37,6 @@ + # Set to 1 if shared object needs to be installed + # +-SHARED_MODE=0 ++SHARED_MODE=1 + + #LOC = -DASMV \ No newline at end of file -- cgit v1.2.3 From 033bb6e2a0112e909d3c77df3d93ec6c99b729c9 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sat, 16 Apr 2016 20:45:28 -0400 Subject: [pkg] fix lingering var name after versioneer upgrade --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8dc5a694..8f38ca7c 100755 --- a/setup.py +++ b/setup.py @@ -143,7 +143,7 @@ def freeze_pkg_ver(path, version_short, version_full): """ subst_template = freeze_debianver.template.format( version=version_short, - version_full=version_full) + freeze_debianver.templatefun + full_revisionid=version_full) + freeze_debianver.templatefun with open(path, 'w') as f: f.write(subst_template) -- cgit v1.2.3 From 175b993c0c3b20178f2de8549cf2ab0588fa625d Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sun, 17 Apr 2016 15:16:55 -0400 Subject: create executable folder to avoid error --- pkg/windows/openvpn-build.sh | 3 ++- pkg/windows/pyinstaller-build.sh | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/windows/openvpn-build.sh b/pkg/windows/openvpn-build.sh index 3f470f55..7f8ed84f 100755 --- a/pkg/windows/openvpn-build.sh +++ b/pkg/windows/openvpn-build.sh @@ -30,6 +30,7 @@ function buildSource() { CBUILD=i686-pc-linux-gnu \ ./build \ || die 'build openvpn from source failed' + mkdir -p ${absolute_executable_path} cp -r image/openvpn ${absolute_executable_path}/openvpn popd } @@ -59,4 +60,4 @@ function main() { fetchTapWindows cleanup } -main $@ \ No newline at end of file +main $@ diff --git a/pkg/windows/pyinstaller-build.sh b/pkg/windows/pyinstaller-build.sh index 06db8d54..7c9a3cb8 100755 --- a/pkg/windows/pyinstaller-build.sh +++ b/pkg/windows/pyinstaller-build.sh @@ -284,4 +284,4 @@ function main() { createInstallables cleanup } -main $@ \ No newline at end of file +main $@ -- cgit v1.2.3 From f3e5121b5a08979d8b4f0be3ed084c7d45517696 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 18 Apr 2016 12:06:58 -0400 Subject: comment out unrecognized command --- pkg/windows/pyinstaller-build.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/windows/pyinstaller-build.sh b/pkg/windows/pyinstaller-build.sh index 7c9a3cb8..522fc10f 100755 --- a/pkg/windows/pyinstaller-build.sh +++ b/pkg/windows/pyinstaller-build.sh @@ -46,7 +46,7 @@ function addMingwDlls() { } # cleanup the temporary build path for subsequent executes function cleanup() { - rm -r ${temporary_build_path} 2>/dev/null + rm -rf ${temporary_build_path} 2>/dev/null } # create files that are not part of the repository but are needed # in the windows environment: @@ -71,7 +71,8 @@ function createInstallablesDependencies() { # hacks that are hard to debug applyPatches ${versioned_build_path} pushd ${versioned_build_path} > /dev/null - wine python setup.py update_files || die 'setup.py update_files failed' + # XXX what's this update_files command? + #wine python setup.py update_files || die 'setup.py update_files failed' wine python setup.py build || die 'setup.py build failed' wine python setup.py install || die 'setup.py install failed' popd -- cgit v1.2.3 From 7f6a008a5030ac1753d3e6876d7f3d46515823d0 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 18 Apr 2016 12:09:07 -0400 Subject: [docs] minor modifications to the release protocol --- docs/release_checklist.wiki | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/release_checklist.wiki b/docs/release_checklist.wiki index 7c9dae65..95ff2204 100644 --- a/docs/release_checklist.wiki +++ b/docs/release_checklist.wiki @@ -19,34 +19,35 @@ * NOTE: It's assumed that origin is the leap.se repo - * [ ] export version: export RELEASE=0.9.0 * [ ] git fetch origin * [ ] git tag -l, and see the latest tagged version (unless it's not a minor version bump, in which case, just bump to it) + * [ ] export version: export RELEASE=0.9.0 * [ ] git checkout `release/0.9.x` - NOTE: the release branch is created when the first release candidate is tagged, after that the bugfixes and features that are meant to be shipped with the specific version that we are targetting are merged in that branch - * [ ] git checkout -b release/$RELEASE + * [ ] git checkout -b release/$RELEASE (this is a LOCAL branch, never published). * [ ] (maybe) cherry-pick specific commits * [ ] (maybe) add special fixes for this release * [ ] Review pkg/requirements.pip for everything, update if needed (that's why the order). - See whatever has been introduced in changes/VERSION_COMPAT - Reset changes/VERSION_COMPAT - * [ ] git commit -am "Update requirements file." + - Bump all the leap-requirements altogether. + * [ ] git commit -am "Update requirements file" - * [ ] Fold in changes files into the CHANGELOG - - NOTE: For leap.soledad, the CHANGELOG entries should be divided per package (common, client, server). See older releases for reference. - - Helper bash line: for i in $(ls changes); do cat changes/$i; echo; done - * [ ] git rm changes/feature*; git rm changes/bug* - * [ ] git commit -m "Fold in changes." + * [ ] Merge changes/next-changelog.rst into the CHANGELOG + - NOTE: in leap.soledad, 3 sections (common, client, server). + * [ ] reset changes/next-changelog.rst + * [ ] git commit -S -m "[pkg] Update changelog" - * [ ] git tag --sign $RELEASE -m "Tag leap.bitmask version $RELEASE" + * [ ] git tag --sign $RELEASE -m "Tag version $RELEASE" * If everything went ok, push the changes, and merge back into master&develop: + * [ ] git checkout release/0.9.x && git merge $RELEASE * [ ] git push origin release/0.9.x * [ ] git push origin $RELEASE - * [ ] git checkout master && git pull origin master && git merge --no-ff $RELEASE --no-edit + * [ ] git checkout master && git pull origin master && git merge --no-edit $RELEASE * [ ] git checkout develop && git merge $RELEASE && git push origin develop == Bundles == -- cgit v1.2.3 From ca256cf2efabfc94d1c9d075793aa131f6550e24 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 18 Apr 2016 12:22:21 -0400 Subject: [pkg] bump leap versions for sumo tarballs --- pkg/leap_versions.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/leap_versions.txt b/pkg/leap_versions.txt index ff0dd91c..6a5a8b7a 100644 --- a/pkg/leap_versions.txt +++ b/pkg/leap_versions.txt @@ -1,4 +1,4 @@ -soledad 0.7.2 -keymanager 0.4.2 -leap_common 0.4.2 -leap_mail 0.4.0rc2 +soledad 0.8.0 +keymanager 0.5.0 +leap_common 0.5.1 +leap_mail 0.4.1 -- cgit v1.2.3 From 6843558ee9effe466b563fc5c3f380774a54d222 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 18 Apr 2016 12:33:00 -0400 Subject: [pkg] bump bundles version to rc1 --- pkg/next-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/next-version b/pkg/next-version index e42c0587..3d4f0a85 100644 --- a/pkg/next-version +++ b/pkg/next-version @@ -1 +1 @@ -0.9.2.alpha6 +0.9.2.rc1 -- cgit v1.2.3 From 748dea614050dca9d1baf8e6b36900109066b2bb Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 11 Sep 2015 07:44:14 -0700 Subject: [pkg] packaging hacks + update osx bundling notes --- Makefile | 8 +++++ pkg/osx/README.rst | 48 +++++-------------------- pkg/osx/bitmask-wrapper | 3 ++ pkg/osx/bitmask.icns | Bin 239193 -> 47303 bytes pkg/pyinst/bitmask.spec | 8 ++--- src/leap/bitmask/_components.py | 2 +- src/leap/bitmask/_version.py | 1 - src/leap/bitmask/platform_init/initializers.py | 3 ++ 8 files changed, 28 insertions(+), 45 deletions(-) create mode 100755 pkg/osx/bitmask-wrapper diff --git a/Makefile b/Makefile index 7d26a7ae..7bf5d273 100644 --- a/Makefile +++ b/Makefile @@ -150,5 +150,13 @@ include pkg/sumo-tarballs.mk include pkg/pyinst/pyinst-build.mk include pkg/branding/branding.mk +pyinst_osx: pyinst + mv dist/Bitmask.app/Contents/MacOS/bitmask dist/Bitmask.app/Contents/MacOS/bitmask-app + cp pkg/osx/bitmask-wrapper dist/Bitmask.app/Contents/MacOS/bitmask + # XXX hack... this contains the gpg binary (brew), but we need to build it from sources. + cp -r src/leap/bitmask/util/apps dist/Bitmask.app/Contents/MacOS/ + # XXX this should be taken care of by pyinstaller data collector + cp $(VIRTUAL_ENV)/lib/python2.7/site-packages/leap/common/cacert.pem dist/Bitmask.app/Contents/MacOS/ + clean : $(RM) $(COMPILED_UI) $(COMPILED_RESOURCES) $(COMPILED_UI:.py=.pyc) $(COMPILED_RESOURCES:.py=.pyc) diff --git a/pkg/osx/README.rst b/pkg/osx/README.rst index 92799ebd..eaf04fa1 100644 --- a/pkg/osx/README.rst +++ b/pkg/osx/README.rst @@ -1,49 +1,19 @@ environment setup in osx ======================== -TODO:: REALLY old notes, adapting to newest flow. - -basically you need this to setup your environment: - -# check and consolidate - -# install xcode and homebrew - -# brew install python2.7 -# brew install python-virtualenwrapper -# brew install qt -# brew install git -# brew install platypus -# brew install upx - Requirements ============ + pyinstaller ----------- +You need at least version 3.0. -You need the development version. do `python setup.py develop` inside your -virtualenv. - -platypus (tested with latest macports) - -... + install environment as usual, - inside virtualenv. - -Building the package -==================== - -Building the binary -------------------- -We use the scripts in openvpn/build.zsh -The packaging Makefile is expecting the final binary in the location:: - - ../../openvpn/build/openvpn.leap - -Running the build ------------------ -IMPORTANT: activate the VIRTUALENV FIRST! -(you will get an import error otherwise) +pyside +---------- +use repo branch kalikaneko/PySide (has --standalone patch) -For running all steps at once:: +python2.7 setup.py bdist_wheel --version=1.2.2 --standalone - make pkg +Blockers +======== +#7321 - requests bug in merge_environment_settings diff --git a/pkg/osx/bitmask-wrapper b/pkg/osx/bitmask-wrapper new file mode 100755 index 00000000..240fc186 --- /dev/null +++ b/pkg/osx/bitmask-wrapper @@ -0,0 +1,3 @@ +#!/bin/sh +DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +exec $DIR/bitmask-app --debug --danger diff --git a/pkg/osx/bitmask.icns b/pkg/osx/bitmask.icns index 74fa0af6..7cc3e752 100644 Binary files a/pkg/osx/bitmask.icns and b/pkg/osx/bitmask.icns differ diff --git a/pkg/pyinst/bitmask.spec b/pkg/pyinst/bitmask.spec index cd207816..149093eb 100644 --- a/pkg/pyinst/bitmask.spec +++ b/pkg/pyinst/bitmask.spec @@ -33,8 +33,8 @@ exe = EXE(pyz, a.scripts, exclude_binaries=True, name='bitmask', - debug=False, - strip=False, + debug=True, + strip=None, upx=True, console=False, icon='../../data/images/mask-icon.ico') @@ -50,6 +50,6 @@ if sys.platform.startswith("darwin"): name=os.path.join( 'dist', 'Bitmask.app'), appname='Bitmask', - version='0.9.2alpha1', + version='0.9.0rc4', icon='pkg/osx/bitmask.icns', - bundle_identifier='bitmask-0.9.2alpha1') + bundle_identifier='bitmask-0.9.0rc4') diff --git a/src/leap/bitmask/_components.py b/src/leap/bitmask/_components.py index 9be0e6bc..9d6f3f59 100644 --- a/src/leap/bitmask/_components.py +++ b/src/leap/bitmask/_components.py @@ -2,5 +2,5 @@ Enabled Modules in Bitmask. Change these values for builds of the client with only one module enabled. """ -HAS_EIP = True +HAS_EIP = False HAS_MAIL = True diff --git a/src/leap/bitmask/_version.py b/src/leap/bitmask/_version.py index f032a17a..d64032a7 100644 --- a/src/leap/bitmask/_version.py +++ b/src/leap/bitmask/_version.py @@ -1,4 +1,3 @@ - # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index 193bd80a..e8d48e4a 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -368,6 +368,9 @@ def DarwinInitializer(): Raise a dialog in case that the osx tuntap driver has not been found in the registry, asking the user for permission to install the driver """ + logger.debug("Skipping darwin initialization, only-mail build") + return True + # XXX split this function into several TUNTAP_NOTFOUND_MSG = NOTFOUND_MSG % ( -- cgit v1.2.3 From 0bd65c1d3e6c5ee1d861122ec2cd617ad026de43 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 8 Oct 2015 09:41:55 -0700 Subject: [pkg] add Packages project this has the ability to be used from the command line, although I'm using it manually at the moment. --- pkg/osx/Bitmask.pkgproj | 719 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 719 insertions(+) create mode 100755 pkg/osx/Bitmask.pkgproj diff --git a/pkg/osx/Bitmask.pkgproj b/pkg/osx/Bitmask.pkgproj new file mode 100755 index 00000000..5a21dc35 --- /dev/null +++ b/pkg/osx/Bitmask.pkgproj @@ -0,0 +1,719 @@ + + + + + PROJECT + + PACKAGE_FILES + + DEFAULT_INSTALL_LOCATION + /Applications + HIERARCHY + + CHILDREN + + + CHILDREN + + + CHILDREN + + GID + 80 + PATH + /Users/user/leap/bitmask_client/dist/Bitmask.app + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 3 + UID + 0 + + + CHILDREN + + GID + 80 + PATH + Utilities + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 80 + PATH + Applications + PATH_TYPE + 0 + PERMISSIONS + 509 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 80 + PATH + Application Support + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Automator + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Documentation + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Filesystems + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Frameworks + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Input Methods + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Internet Plug-Ins + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + LaunchAgents + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + LaunchDaemons + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + PreferencePanes + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Preferences + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 80 + PATH + Printers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + PrivilegedHelperTools + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + QuickLook + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + QuickTime + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Screen Savers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Scripts + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Services + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Widgets + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + Library + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + Extensions + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + Library + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + System + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + Shared + PATH_TYPE + 0 + PERMISSIONS + 1023 + TYPE + 1 + UID + 0 + + + GID + 80 + PATH + Users + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + / + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + PAYLOAD_TYPE + 0 + VERSION + 3 + + PACKAGE_SCRIPTS + + RESOURCES + + + PACKAGE_SETTINGS + + AUTHENTICATION + 1 + CONCLUSION_ACTION + 0 + IDENTIFIER + se.leap.pkg.Bitmask + OVERWRITE_PERMISSIONS + + RELOCATABLE + + VERSION + 0.9.0rc2 + + PROJECT_COMMENTS + + NOTES + + PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M + IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv + c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l + cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7 + IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250 + ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp + dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u + dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD + b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjEyNjUuMjEiPgo8c3R5bGUg + dHlwZT0idGV4dC9jc3MiPgo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5 + Pgo8L2JvZHk+CjwvaHRtbD4K + + + PROJECT_SETTINGS + + BUILD_PATH + + PATH + /Users/user/Bitmask/build + PATH_TYPE + 0 + + CERTIFICATE + + NAME + Developer ID Installer: LEAP Encryption Access Project (SB5RR8K33W) + PATH + /Users/user/Library/Keychains/login.keychain + + EXCLUDED_FILES + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + .DS_Store + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Remove .DS_Store files + PROXY_TOOLTIP + Remove ".DS_Store" files created by the Finder. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + .pbdevelopment + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Remove .pbdevelopment files + PROXY_TOOLTIP + Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + CVS + TYPE + 1 + + + REGULAR_EXPRESSION + + STRING + .cvsignore + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + .cvspass + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + .svn + TYPE + 1 + + + REGULAR_EXPRESSION + + STRING + .git + TYPE + 1 + + + REGULAR_EXPRESSION + + STRING + .gitignore + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Remove SCM metadata + PROXY_TOOLTIP + Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + classes.nib + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + designable.db + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + info.nib + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Optimize nib files + PROXY_TOOLTIP + Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + Resources Disabled + TYPE + 1 + + + PROTECTED + + PROXY_NAME + Remove Resources Disabled folders + PROXY_TOOLTIP + Remove "Resources Disabled" folders. + STATE + + + + SEPARATOR + + + + NAME + Bitmask + REFERENCE_FOLDER_PATH + /Users/user/leap/bitmask_client/dist + + + TYPE + 1 + VERSION + 2 + + -- cgit v1.2.3 From e9e9abc4ec26be29b3a6b09e6a0b67786269183b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 29 Jan 2016 13:18:36 -0800 Subject: [feature] privileged bitmask helper This is still quite untested, and a bit hacky, but the main idea behind let us have a daemonized bitmask helper, that should be installed by the Bitmask installer. Its responsibilities are to launch the vpn process as a privileged user, and start/stop the firewall. --- Makefile | 10 + pkg/osx/Bitmask.pkgproj | 35 +- pkg/osx/__init__.py | 49 + pkg/osx/_metadata.py | 152 ++ pkg/osx/bitmask-helper | 429 ++++++ pkg/osx/bitmask.pf.conf | 17 + pkg/osx/client.down.sh | 426 ++++++ pkg/osx/client.up.sh | 1521 ++++++++++++++++++++ pkg/osx/daemon.py | 926 ++++++++++++ .../install/ProcessNetworkChanges.plist.template | 16 - pkg/osx/install/client.down.sh | 148 -- pkg/osx/install/client.up.sh | 599 -------- pkg/osx/install/install-leapc.sh | 42 - pkg/osx/install/leap-installer.platypus | 90 -- pkg/osx/pidfile.py | 67 + pkg/osx/post-inst.sh | 6 + pkg/osx/pre-inst.sh | 2 + pkg/osx/runner.py | 324 +++++ pkg/osx/se.leap.bitmask-helper.plist | 29 + pkg/pyinst/bitmask.spec.orig | 38 + pkg/pyinst/cryptography/osrandom_engine.c | 167 +++ pkg/pyinst/cryptography/osrandom_engine.h | 6 + src/leap/bitmask/_components.py | 2 +- src/leap/bitmask/backend/components.py | 2 +- src/leap/bitmask/services/eip/darwinvpnlauncher.py | 63 +- src/leap/bitmask/services/eip/vpnlauncher.py | 8 +- src/leap/bitmask/services/eip/vpnprocess.py | 188 ++- 27 files changed, 4390 insertions(+), 972 deletions(-) create mode 100644 pkg/osx/__init__.py create mode 100644 pkg/osx/_metadata.py create mode 100755 pkg/osx/bitmask-helper create mode 100644 pkg/osx/bitmask.pf.conf create mode 100755 pkg/osx/client.down.sh create mode 100755 pkg/osx/client.up.sh create mode 100644 pkg/osx/daemon.py delete mode 100644 pkg/osx/install/ProcessNetworkChanges.plist.template delete mode 100755 pkg/osx/install/client.down.sh delete mode 100755 pkg/osx/install/client.up.sh delete mode 100755 pkg/osx/install/install-leapc.sh delete mode 100644 pkg/osx/install/leap-installer.platypus create mode 100644 pkg/osx/pidfile.py create mode 100755 pkg/osx/post-inst.sh create mode 100755 pkg/osx/pre-inst.sh create mode 100644 pkg/osx/runner.py create mode 100644 pkg/osx/se.leap.bitmask-helper.plist create mode 100644 pkg/pyinst/bitmask.spec.orig create mode 100644 pkg/pyinst/cryptography/osrandom_engine.c create mode 100644 pkg/pyinst/cryptography/osrandom_engine.h diff --git a/Makefile b/Makefile index 7bf5d273..fdfb0381 100644 --- a/Makefile +++ b/Makefile @@ -153,8 +153,18 @@ include pkg/branding/branding.mk pyinst_osx: pyinst mv dist/Bitmask.app/Contents/MacOS/bitmask dist/Bitmask.app/Contents/MacOS/bitmask-app cp pkg/osx/bitmask-wrapper dist/Bitmask.app/Contents/MacOS/bitmask + mkdir -p dist/Bitmask.app/Contents/Resources/bitmask-helper + cp pkg/osx/client.up.sh dist/Bitmask.app/Contents/Resources/ + cp pkg/osx/client.down.sh dist/Bitmask.app/Contents/Resources/ + cp pkg/osx/bitmask-helper dist/Bitmask.app/Contents/Resources/bitmask-helper/ + cp pkg/osx/bitmask.pf.conf dist/Bitmask.app/Contents/Resources/bitmask-helper/ + cp pkg/osx/se.leap.bitmask-helper.plist dist/Bitmask.app/Contents/Resources/bitmask-helper/ + cp pkg/osx/post-inst.sh dist/Bitmask.app/Contents/Resources/bitmask-helper/ + cp pkg/osx/daemon.py dist/Bitmask.app/Contents/Resources/bitmask-helper/ # XXX hack... this contains the gpg binary (brew), but we need to build it from sources. cp -r src/leap/bitmask/util/apps dist/Bitmask.app/Contents/MacOS/ + # XXX hack... this contains the openvpn binary (brew), but we need to build it from sources. + cp -r src/leap/bitmask/util/openvpn.leap dist/Bitmask.app/Contents/Resources/ # XXX this should be taken care of by pyinstaller data collector cp $(VIRTUAL_ENV)/lib/python2.7/site-packages/leap/common/cacert.pem dist/Bitmask.app/Contents/MacOS/ diff --git a/pkg/osx/Bitmask.pkgproj b/pkg/osx/Bitmask.pkgproj index 5a21dc35..bf882850 100755 --- a/pkg/osx/Bitmask.pkgproj +++ b/pkg/osx/Bitmask.pkgproj @@ -486,8 +486,39 @@ PACKAGE_SCRIPTS + POSTINSTALL_PATH + + PATH + ../pkg/osx/post-inst.sh + PATH_TYPE + 3 + + PREINSTALL_PATH + + PATH + /Users/user/leap/bitmask_client/pkg/osx/pre-inst.sh + PATH_TYPE + 0 + RESOURCES - + + + CHILDREN + + GID + 0 + PATH + ../pkg/osx/se.leap.bitmask-helper.plist + PATH_TYPE + 3 + PERMISSIONS + 420 + TYPE + 3 + UID + 0 + + PACKAGE_SETTINGS @@ -502,7 +533,7 @@ RELOCATABLE VERSION - 0.9.0rc2 + 0.9.0rc4 PROJECT_COMMENTS diff --git a/pkg/osx/__init__.py b/pkg/osx/__init__.py new file mode 100644 index 00000000..4731a6ef --- /dev/null +++ b/pkg/osx/__init__.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +# daemon/__init__.py +# Part of ‘python-daemon’, an implementation of PEP 3143. +# +# Copyright © 2009–2015 Ben Finney +# Copyright © 2006 Robert Niederreiter +# +# This is free software: you may copy, modify, and/or distribute this work +# under the terms of the Apache License, version 2.0 as published by the +# Apache Software Foundation. +# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details. + +""" Library to implement a well-behaved Unix daemon process. + + This library implements the well-behaved daemon specification of + :pep:`3143`, “Standard daemon process library”. + + A well-behaved Unix daemon process is tricky to get right, but the + required steps are much the same for every daemon program. A + `DaemonContext` instance holds the behaviour and configured + process environment for the program; use the instance as a context + manager to enter a daemon state. + + Simple example of usage:: + + import daemon + + from spam import do_main_program + + with daemon.DaemonContext(): + do_main_program() + + Customisation of the steps to become a daemon is available by + setting options on the `DaemonContext` instance; see the + documentation for that class for each option. + + """ + +from __future__ import (absolute_import, unicode_literals) + +from .daemon import DaemonContext + + +# Local variables: +# coding: utf-8 +# mode: python +# End: +# vim: fileencoding=utf-8 filetype=python : diff --git a/pkg/osx/_metadata.py b/pkg/osx/_metadata.py new file mode 100644 index 00000000..6d22a2b7 --- /dev/null +++ b/pkg/osx/_metadata.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- + +# daemon/_metadata.py +# Part of ‘python-daemon’, an implementation of PEP 3143. +# +# Copyright © 2008–2015 Ben Finney +# +# This is free software: you may copy, modify, and/or distribute this work +# under the terms of the Apache License, version 2.0 as published by the +# Apache Software Foundation. +# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details. + +""" Package metadata for the ‘python-daemon’ distribution. """ + +from __future__ import (absolute_import, unicode_literals) + +import json +import re +import collections +import datetime + +import pkg_resources + + +distribution_name = "python-daemon" +version_info_filename = "version_info.json" + +def get_distribution_version_info(filename=version_info_filename): + """ Get the version info from the installed distribution. + + :param filename: Base filename of the version info resource. + :return: The version info as a mapping of fields. If the + distribution is not available, the mapping is empty. + + The version info is stored as a metadata file in the + distribution. + + """ + version_info = { + 'release_date': "UNKNOWN", + 'version': "UNKNOWN", + 'maintainer': "UNKNOWN", + } + + try: + distribution = pkg_resources.get_distribution(distribution_name) + except pkg_resources.DistributionNotFound: + distribution = None + + if distribution is not None: + if distribution.has_metadata(version_info_filename): + content = distribution.get_metadata(version_info_filename) + version_info = json.loads(content) + + return version_info + +version_info = get_distribution_version_info() + +version_installed = version_info['version'] + + +rfc822_person_regex = re.compile( + "^(?P[^<]+) <(?P[^>]+)>$") + +ParsedPerson = collections.namedtuple('ParsedPerson', ['name', 'email']) + +def parse_person_field(value): + """ Parse a person field into name and email address. + + :param value: The text value specifying a person. + :return: A 2-tuple (name, email) for the person's details. + + If the `value` does not match a standard person with email + address, the `email` item is ``None``. + + """ + result = (None, None) + + match = rfc822_person_regex.match(value) + if len(value): + if match is not None: + result = ParsedPerson( + name=match.group('name'), + email=match.group('email')) + else: + result = ParsedPerson(name=value, email=None) + + return result + +author_name = "Ben Finney" +author_email = "ben+python@benfinney.id.au" +author = "{name} <{email}>".format(name=author_name, email=author_email) + + +class YearRange: + """ A range of years spanning a period. """ + + def __init__(self, begin, end=None): + self.begin = begin + self.end = end + + def __unicode__(self): + text = "{range.begin:04d}".format(range=self) + if self.end is not None: + if self.end > self.begin: + text = "{range.begin:04d}–{range.end:04d}".format(range=self) + return text + + __str__ = __unicode__ + + +def make_year_range(begin_year, end_date=None): + """ Construct the year range given a start and possible end date. + + :param begin_date: The beginning year (text) for the range. + :param end_date: The end date (text, ISO-8601 format) for the + range, or a non-date token string. + :return: The range of years as a `YearRange` instance. + + If the `end_date` is not a valid ISO-8601 date string, the + range has ``None`` for the end year. + + """ + begin_year = int(begin_year) + + try: + end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d") + except (TypeError, ValueError): + # Specified end_date value is not a valid date. + end_year = None + else: + end_year = end_date.year + + year_range = YearRange(begin=begin_year, end=end_year) + + return year_range + +copyright_year_begin = "2001" +build_date = version_info['release_date'] +copyright_year_range = make_year_range(copyright_year_begin, build_date) + +copyright = "Copyright © {year_range} {author} and others".format( + year_range=copyright_year_range, author=author) +license = "Apache-2" +url = "https://alioth.debian.org/projects/python-daemon/" + + +# Local variables: +# coding: utf-8 +# mode: python +# End: +# vim: fileencoding=utf-8 filetype=python : diff --git a/pkg/osx/bitmask-helper b/pkg/osx/bitmask-helper new file mode 100755 index 00000000..c22d33d9 --- /dev/null +++ b/pkg/osx/bitmask-helper @@ -0,0 +1,429 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Author: Kali Kaneko +# Copyright (C) 2015-2016 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 . + +""" +This is a privileged helper script for safely running certain commands as root +under OSX. + +It should be run by launchd, and it exposes a Unix Domain Socket to where +the following commmands can be written by the Bitmask application: + + firewall_start [restart] GATEWAY1 GATEWAY2 ... + firewall_stop + openvpn_start CONFIG1 CONFIG1 ... + openvpn_stop + fw_email_start uid + fw_email_stop + +To load it manually: + + sudo launchctl load /Library/LaunchDaemons/se.leap.bitmask-helper + +To see the loaded rules: + + sudo pfctl -s rules -a bitmask + +""" +import os +import socket +import signal +import subprocess +import syslog +import threading + +from commands import getoutput as exec_cmd +from functools import partial + +import daemon + +VERSION = "1" +SCRIPT = "bitmask-helper" +NAMESERVER = "10.42.0.1" +BITMASK_ANCHOR = "com.apple/250.BitmaskFirewall" +BITMASK_ANCHOR_EMAIL = "bitmask_email" + +OPENVPN_USER = 'nobody' +OPENVPN_GROUP = 'nogroup' +LEAPOPENVPN = 'LEAPOPENVPN' +APP_PATH = '/Applications/Bitmask.app/' +RESOURCES_PATH = APP_PATH + 'Contents/Resources/' +OPENVPN_LEAP_BIN = RESOURCES_PATH + 'openvpn.leap' + +FIXED_FLAGS = [ + "--setenv", "LEAPOPENVPN", "1", + "--nobind", + "--client", + "--dev", "tun", + "--tls-client", + "--remote-cert-tls", "server", + "--management-signal", + "--script-security", "1", + "--user", "nobody", + "--remap-usr1", "SIGTERM", + "--group", OPENVPN_GROUP, +] + +ALLOWED_FLAGS = { + "--remote": ["IP", "NUMBER", "PROTO"], + "--tls-cipher": ["CIPHER"], + "--cipher": ["CIPHER"], + "--auth": ["CIPHER"], + "--management": ["DIR", "UNIXSOCKET"], + "--management-client-user": ["USER"], + "--cert": ["FILE"], + "--key": ["FILE"], + "--ca": ["FILE"], + "--fragment": ["NUMBER"] +} + +PARAM_FORMATS = { + "NUMBER": lambda s: re.match("^\d+$", s), + "PROTO": lambda s: re.match("^(tcp|udp)$", s), + "IP": lambda s: is_valid_address(s), + "CIPHER": lambda s: re.match("^[A-Z0-9-]+$", s), + "USER": lambda s: re.match( + "^[a-zA-Z0-9_\.\@][a-zA-Z0-9_\-\.\@]*\$?$", s), # IEEE Std 1003.1-2001 + "FILE": lambda s: os.path.isfile(s), + "DIR": lambda s: os.path.isdir(os.path.split(s)[0]), + "UNIXSOCKET": lambda s: s == "unix", + "UID": lambda s: re.match("^[a-zA-Z0-9]+$", s) +} + +# +# paths (must use absolute paths, since this script is run as root) +# + +PFCTL = '/sbin/pfctl' +ROUTE = '/sbin/route' +AWK = '/usr/bin/awk' +GREP = '/usr/bin/grep' +CAT = '/bin/cat' + +UID = os.getuid() +SERVER_ADDRESS = '/tmp/bitmask-helper.socket' + + +# +# COMMAND DISPATCH +# + +def serve_forever(): + try: + os.unlink(SERVER_ADDRESS) + except OSError: + if os.path.exists(SERVER_ADDRESS): + raise + + syslog.syslog(syslog.LOG_WARNING, "serving forever") + # XXX should check permissions on the socket file + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.bind(SERVER_ADDRESS) + sock.listen(1) + syslog.syslog(syslog.LOG_WARNING, "Binded to %s" % SERVER_ADDRESS) + + while True: + connection, client_address = sock.accept() + thread = threading.Thread(target=handle_command, args=[connection]) + thread.daemon = True + thread.start() + +def recv_until_marker(sock): + end = '/CMD' + total_data=[] + data='' + while True: + data=sock.recv(8192) + if end in data: + total_data.append(data[:data.find(end)]) + break + total_data.append(data) + if len(total_data)>1: + #check if end_of_data was split + last_pair=total_data[-2]+total_data[-1] + if end in last_pair: + total_data[-2] = last_pair[:last_pair.find(end)] + total_data.pop() + break + return ''.join(total_data) + + +def handle_command(sock): + syslog.syslog(syslog.LOG_WARNING, "handle") + + received = recv_until_marker(sock) + syslog.syslog(syslog.LOG_WARNING, "GOT -----> %s" % received) + line = received.replace('\n', '').split(' ') + + command, args = line[0], line[1:] + syslog.syslog(syslog.LOG_WARNING, 'command %s' % (command)) + + cmd_dict = { + 'firewall_start': (firewall_start, args), + 'firewall_stop': (firewall_stop, []), + 'firewall_isup': (firewall_isup, []), + 'openvpn_start': (openvpn_start, args), + 'openvpn_stop': (openvpn_stop, []), + 'openvpn_force_stop': (openvpn_stop, ['KILL']), + 'openvpn_set_watcher': (openvpn_set_watcher, args) + } + + cmd_call = cmd_dict.get(command, None) + syslog.syslog(syslog.LOG_WARNING, 'call: %s' % (str(cmd_call))) + try: + if cmd_call: + syslog.syslog( + syslog.LOG_WARNING, 'GOT "%s"' % (command)) + cmd, args = cmd_call + if args: + cmd = partial(cmd, *args) + + # TODO Use a MUTEX in here + result = cmd() + syslog.syslog(syslog.LOG_WARNING, "Executed") + syslog.syslog(syslog.LOG_WARNING, "Result: %s" % (str(result))) + if result == 'YES': + sock.sendall("%s: YES\n" % command) + elif result == 'NO': + sock.sendall("%s: NO\n" % command) + else: + sock.sendall("%s: OK\n" % command) + + else: + syslog.syslog(syslog.LOG_WARNING, 'invalid command: %s' % (command,)) + sock.sendall("%s: ERROR\n" % command) + except Exception as exc: + syslog.syslog(syslog.LOG_WARNING, "error executing function %r" % (exc)) + finally: + sock.close() + + + +# +# OPENVPN +# + + +openvpn_proc = None +openvpn_watcher_pid = None + + +def openvpn_start(*args): + """ + Sanitize input and run openvpn as a subprocess of this long-running daemon. + Keeps a reference to the subprocess Popen class instance. + + :param args: arguments to be passed to openvpn + :type args: list + """ + syslog.syslog(syslog.LOG_WARNING, "OPENVPN START") + opts = list(args[1:]) + + opts += ['--dhcp-option', 'DNS', '10.42.0.1', + '--up', RESOURCES_PATH + 'client.up.sh', + '--down', RESOURCES_PATH + 'client.down.sh'] + binary = [RESOURCES_PATH + 'openvpn.leap'] + + syslog.syslog(syslog.LOG_WARNING, ' '.join(binary + opts)) + + # TODO sanitize options + global openvpn_proc + openvpn_proc = subprocess.Popen(binary + opts, shell=False) + syslog.syslog(syslog.LOG_WARNING, "OpenVPN PID: %s" % str(openvpn_proc.pid)) + + +def openvpn_stop(sig='TERM'): + """ + Stop the openvpn that has been launched by this privileged helper. + + :param args: arguments to openvpn + :type args: list + """ + global openvpn_proc + + if openvpn_proc: + syslog.syslog(syslog.LOG_WARNING, "OVPN PROC: %s" % str(openvpn_proc.pid)) + + if sig == 'KILL': + stop_signal = signal.SIGKILL + openvpn_proc.kill() + elif sig == 'TERM': + stop_signal = signal.SIGTERM + openvpn_proc.terminate() + + returncode = openvpn_proc.wait() + syslog.syslog(syslog.LOG_WARNING, "openvpn return code: %s" % str(returncode)) + syslog.syslog(syslog.LOG_WARNING, "openvpn_watcher_pid: %s" % str(openvpn_watcher_pid)) + if openvpn_watcher_pid: + os.kill(openvpn_watcher_pid, stop_signal) + + +def openvpn_set_watcher(pid, *args): + global openvpn_watcher_pid + openvpn_watcher_pid = int(pid) + syslog.syslog(syslog.LOG_WARNING, "Watcher PID: %s" % pid) + + +# +# FIREWALL +# + + +def firewall_start(*gateways): + """ + Bring up the firewall. + + :param gws: list of gateways, to be sanitized. + :type gws: list + """ + + gateways = get_gateways(gateways) + + if not gateways: + return False + + _enable_pf() + _reset_bitmask_gateways_table(gateways) + + default_device = _get_default_device() + _load_bitmask_anchor(default_device) + + +def firewall_stop(): + """ + Flush everything from anchor bitmask + """ + cmd = '{pfctl} -a {anchor} -F all'.format( + pfctl=PFCTL, anchor=BITMASK_ANCHOR) + return exec_cmd(cmd) + + +def firewall_isup(): + """ + Return YES if anchor bitmask is loaded with rules + """ + syslog.syslog(syslog.LOG_WARNING, 'PID---->%s' % os.getpid()) + cmd = '{pfctl} -s rules -a {anchor} | wc -l'.format( + pfctl=PFCTL, anchor=BITMASK_ANCHOR) + output = exec_cmd(cmd) + rules = output[-1] + if int(rules) > 0: + return 'YES' + else: + return 'NO' + + +def _enable_pf(): + exec_cmd('{pfctl} -e'.format(pfctl=PFCTL)) + + +def _reset_bitmask_gateways_table(gateways): + cmd = '{pfctl} -a {anchor} -t bitmask_gateways -T delete'.format( + pfctl=PFCTL, anchor=BITMASK_ANCHOR) + output = exec_cmd(cmd) + + for gateway in gateways: + cmd = '{pfctl} -a {anchor} -t bitmask_gateways -T add {gw}'.format( + pfctl=PFCTL, anchor=BITMASK_ANCHOR, gw=gateway) + output = exec_cmd(cmd) + syslog.syslog(syslog.LOG_WARNING, "adding gw %s" % gateway) + + #cmd = '{pfctl} -a {anchor} -t bitmask_nameservers -T delete'.format( + # pfctl=PFCTL, anchor=BITMASK_ANCHOR) + #output = exec_cmd(cmd) + + cmd = '{pfctl} -a {anchor} -t bitmask_gateways -T add {ns}'.format( + pfctl=PFCTL, anchor=BITMASK_ANCHOR, ns=NAMESERVER) + output = exec_cmd(cmd) + syslog.syslog(syslog.LOG_WARNING, "adding ns %s" % NAMESERVER) + +def _load_bitmask_anchor(default_device): + cmd = ('{pfctl} -D default_device={defaultdevice} ' + '-a {anchor} -f {rulefile}').format( + pfctl=PFCTL, defaultdevice=default_device, + anchor=BITMASK_ANCHOR, + rulefile=RESOURCES_PATH + 'bitmask-helper/bitmask.pf.conf') + syslog.syslog(syslog.LOG_WARNING, "LOADING CMD: %s" % cmd) + return exec_cmd(cmd) + + +def _get_default_device(): + """ + Retrieve the current default network device. + + :rtype: str + """ + cmd_def_device = ( + '{route} -n get -net default | ' + '{grep} interface | {awk} "{{print $2}}"').format( + route=ROUTE, grep=GREP, awk=AWK) + iface = exec_cmd(cmd_def_device) + iface = iface.replace("interface: ", "").strip() + syslog.syslog(syslog.LOG_WARNING, "default device %s" % iface) + return iface + + + +# +# UTILITY +# + + +def is_valid_address(value): + """ + Validate that the passed ip is a valid IP address. + + :param value: the value to be validated + :type value: str + :rtype: bool + """ + try: + socket.inet_aton(value) + return True + except Exception: + syslog.syslog(syslog.LOG_WARNING, 'MALFORMED IP: %s!' % (value)) + return False + + +# +# FIREWALL +# + + +def get_gateways(gateways): + """ + Filter a passed sequence of gateways, returning only the valid ones. + + :param gateways: a sequence of gateways to filter. + :type gateways: iterable + :rtype: iterable + """ + syslog.syslog(syslog.LOG_WARNING, 'Filtering %s' % str(gateways)) + result = filter(is_valid_address, gateways) + if not result: + syslog.syslog(syslog.LOG_ERR, 'No valid gateways specified') + return False + else: + return result + + + +if __name__ == "__main__": + with daemon.DaemonContext(): + syslog.syslog(syslog.LOG_WARNING, "Serving...") + serve_forever() diff --git a/pkg/osx/bitmask.pf.conf b/pkg/osx/bitmask.pf.conf new file mode 100644 index 00000000..eb0e858f --- /dev/null +++ b/pkg/osx/bitmask.pf.conf @@ -0,0 +1,17 @@ +default_device = "en99" + +set block-policy drop +set skip on lo0 + +# block all traffic on default device +block out on $default_device all + +# allow traffic to gateways +pass out on $default_device to + +# allow traffic to local networks over the default device +pass out on $default_device to $default_device:network + +# block all DNS, except to the gateways +block out proto udp to any port 53 +pass out proto udp to port 53 diff --git a/pkg/osx/client.down.sh b/pkg/osx/client.down.sh new file mode 100755 index 00000000..1e173bba --- /dev/null +++ b/pkg/osx/client.down.sh @@ -0,0 +1,426 @@ +#!/bin/bash -e +# Note: must be bash; uses bash-specific tricks +# +# ****************************************************************************************************************** +# Copyright By Tunnelblick. Redistributed with Bitmask under the GPL. +# This Tunnelblick script does everything! It handles TUN and TAP interfaces, +# pushed configurations and DHCP leases. :) +# +# This is the "Down" version of the script, executed after the connection is +# closed. +# +# Created by: Nick Williams (using original code and parts of old Tblk scripts) +# +# ****************************************************************************************************************** + +# @param String message - The message to log +logMessage() +{ + echo "${@}" +} + +# @param String message - The message to log +logDebugMessage() +{ + echo "${@}" > /dev/null +} + +trim() +{ +echo ${@} +} + +# @param String list - list of network service names, output from disable_ipv6() +restore_ipv6() { + + # Undoes the actions performed by the disable_ipv6() routine in client.up.tunnelblick.sh by restoring the IPv6 + # 'automatic' setting for each network service for which that routine disabled IPv6. + # + # $1 must contain the output from disable_ipv6() -- the list of network services. + # + # This routine outputs log messages describing its activities. + + if [ "$1" = "" ] ; then + exit + fi + + printf %s "$1 +" | \ + while IFS= read -r ripv6_service ; do + networksetup -setv6automatic "$ripv6_service" + logMessage "Re-enabled IPv6 (automatic) for '$ripv6_service'" + done +} + +########################################################################################## +flushDNSCache() +{ + if ${ARG_FLUSH_DNS_CACHE} ; then + set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors + readonly OSVER="$(sw_vers | grep 'ProductVersion:' | grep -o '10\.[0-9]*')" + set -e # We instruct bash that it CAN again fail on errors + if [ "${OSVER}" = "10.4" ] ; then + + if [ -f /usr/sbin/lookupd ] ; then + set +e # we will catch errors from lookupd + /usr/sbin/lookupd -flushcache + if [ $? != 0 ] ; then + logMessage "WARNING: Unable to flush the DNS cache via lookupd" + else + logMessage "Flushed the DNS cache via lookupd" + fi + set -e # bash should again fail on errors + else + logMessage "WARNING: /usr/sbin/lookupd not present. Not flushing the DNS cache" + fi + + else + + if [ -f /usr/bin/dscacheutil ] ; then + set +e # we will catch errors from dscacheutil + /usr/bin/dscacheutil -flushcache + if [ $? != 0 ] ; then + logMessage "WARNING: Unable to flush the DNS cache via dscacheutil" + else + logMessage "Flushed the DNS cache via dscacheutil" + fi + set -e # bash should again fail on errors + else + logMessage "WARNING: /usr/bin/dscacheutil not present. Not flushing the DNS cache via dscacheutil" + fi + + if [ -f /usr/sbin/discoveryutil ] ; then + set +e # we will catch errors from discoveryutil + /usr/sbin/discoveryutil udnsflushcaches + if [ $? != 0 ] ; then + logMessage "WARNING: Unable to flush the DNS cache via discoveryutil udnsflushcaches" + else + logMessage "Flushed the DNS cache via discoveryutil udnsflushcaches" + fi + /usr/sbin/discoveryutil mdnsflushcache + if [ $? != 0 ] ; then + logMessage "WARNING: Unable to flush the DNS cache via discoveryutil mdnsflushcache" + else + logMessage "Flushed the DNS cache via discoveryutil mdnsflushcache" + fi + set -e # bash should again fail on errors + else + logMessage "/usr/sbin/discoveryutil not present. Not flushing the DNS cache via discoveryutil" + fi + + set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors + hands_off_ps="$( ps -ax | grep HandsOffDaemon | grep -v grep.HandsOffDaemon )" + set -e # We instruct bash that it CAN again fail on errors + if [ "${hands_off_ps}" = "" ] ; then + if [ -f /usr/bin/killall ] ; then + set +e # ignore errors if mDNSResponder isn't currently running + /usr/bin/killall -HUP mDNSResponder + if [ $? != 0 ] ; then + logMessage "mDNSResponder not running. Not notifying it that the DNS cache was flushed" + else + logMessage "Notified mDNSResponder that the DNS cache was flushed" + fi + set -e # bash should again fail on errors + else + logMessage "WARNING: /usr/bin/killall not present. Not notifying mDNSResponder that the DNS cache was flushed" + fi + else + logMessage "WARNING: Hands Off is running. Not notifying mDNSResponder that the DNS cache was flushed" + fi + + fi + fi +} + +########################################################################################## +resetPrimaryInterface() +{ + set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors + WIFI_INTERFACE="$(networksetup -listallhardwareports | awk '$3=="Wi-Fi" {getline; print $2}')" + if [ "${WIFI_INTERFACE}" == "" ] ; then + WIFI_INTERFACE="$(networksetup -listallhardwareports | awk '$3=="AirPort" {getline; print $2}')" + fi + PINTERFACE="$( scutil <<-EOF | + open + show State:/Network/Global/IPv4 + quit +EOF + grep PrimaryInterface | sed -e 's/.*PrimaryInterface : //' + )" + set -e # resume abort on error + + if [ "${PINTERFACE}" != "" ] ; then + if [ "${PINTERFACE}" == "${WIFI_INTERFACE}" -a "${OSVER}" != "10.4" -a -f /usr/sbin/networksetup ] ; then + if [ "${OSVER}" == "10.5" ] ; then + logMessage "Resetting primary interface '${PINTERFACE}' via networksetup -setairportpower off/on..." + /usr/sbin/networksetup -setairportpower off + sleep 2 + /usr/sbin/networksetup -setairportpower on + else + logMessage "Resetting primary interface '${PINTERFACE}' via networksetup -setairportpower ${PINTERFACE} off/on..." + /usr/sbin/networksetup -setairportpower "${PINTERFACE}" off + sleep 2 + /usr/sbin/networksetup -setairportpower "${PINTERFACE}" on + fi + else + if [ -f /sbin/ifconfig ] ; then + logMessage "Resetting primary interface '${PINTERFACE}' via ifconfig ${PINTERFACE} down/up..." + /sbin/ifconfig "${PINTERFACE}" down + sleep 2 + /sbin/ifconfig "${PINTERFACE}" up + else + logMessage "WARNING: Not resetting primary interface because /sbin/ifconfig does not exist." + fi + fi + else + logMessage "WARNING: Not resetting primary interface because it cannot be found." + fi +} + +########################################################################################## +trap "" TSTP +trap "" HUP +trap "" INT +export PATH="/bin:/sbin:/usr/sbin:/usr/bin" + +readonly OUR_NAME=$(basename "${0}") + +logMessage "**********************************************" +logMessage "Start of output from ${OUR_NAME}" + +# Remove the flag file that indicates we need to run the down script + +if [ -e "/tmp/bitmask-downscript-needs-to-be-run.txt" ] ; then + rm -f "/tmp/bitmask-downscript-needs-to-be-run.txt" +fi + +# Test for the "-r" Bitmask option (Reset primary interface after disconnecting) because we _always_ need its value. +# Usually we get the value for that option (and the other options) from State:/Network/OpenVPN, +# but that key may not exist (because, for example, there were no DNS changes). +# So we get the value from the Bitmask options passed to this script by OpenVPN. +# +# We do the same thing for the -f Bitmask option (Flush DNS cache after connecting or disconnecting) +ARG_RESET_PRIMARY_INTERFACE_ON_DISCONNECT="false" +ARG_FLUSH_DNS_CACHE="false" +while [ {$#} ] ; do + if [ "${1:0:1}" != "-" ] ; then # Bitmask arguments start with "-" and come first + break # so if this one doesn't start with "-" we are done processing Bitmask arguments + fi + if [ "$1" = "-r" ] ; then + ARG_RESET_PRIMARY_INTERFACE_ON_DISCONNECT="true" + else + if [ "$1" = "-f" ] ; then + ARG_FLUSH_DNS_CACHE="true" + fi + fi + shift # Shift arguments to examine the next option (if there is one) +done + +# Quick check - is the configuration there? +if ! scutil -w State:/Network/OpenVPN &>/dev/null -t 1 ; then + # Configuration isn't there + logMessage "WARNING: Not restoring DNS settings because no saved Bitmask DNS information was found." + + flushDNSCache + + if ${ARG_RESET_PRIMARY_INTERFACE_ON_DISCONNECT} ; then + resetPrimaryInterface + fi + logMessage "End of output from ${OUR_NAME}" + logMessage "**********************************************" + exit 0 +fi + +# Get info saved by the up script +TUNNELBLICK_CONFIG="$( scutil <<-EOF + open + show State:/Network/OpenVPN + quit +EOF +)" + +ARG_MONITOR_NETWORK_CONFIGURATION="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*MonitorNetwork :' | sed -e 's/^.*: //g')" +LEASEWATCHER_PLIST_PATH="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*LeaseWatcherPlistPath :' | sed -e 's/^.*: //g')" +REMOVE_LEASEWATCHER_PLIST="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*RemoveLeaseWatcherPlist :' | sed -e 's/^.*: //g')" +PSID="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*Service :' | sed -e 's/^.*: //g')" +# Don't need: SCRIPT_LOG_FILE="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*ScriptLogFile :' | sed -e 's/^.*: //g')" +# Don't need: ARG_RESTORE_ON_DNS_RESET="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*RestoreOnDNSReset :' | sed -e 's/^.*: //g')" +# Don't need: ARG_RESTORE_ON_WINS_RESET="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*RestoreOnWINSReset :' | sed -e 's/^.*: //g')" +# Don't need: PROCESS="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*PID :' | sed -e 's/^.*: //g')" +# Don't need: ARG_IGNORE_OPTION_FLAGS="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*IgnoreOptionFlags :' | sed -e 's/^.*: //g')" +ARG_TAP="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*IsTapInterface :' | sed -e 's/^.*: //g')" +ARG_FLUSH_DNS_CACHE="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*FlushDNSCache :' | sed -e 's/^.*: //g')" +ARG_RESET_PRIMARY_INTERFACE_ON_DISCONNECT="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*ResetPrimaryInterface :' | sed -e 's/^.*: //g')" +bRouteGatewayIsDhcp="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*RouteGatewayIsDhcp :' | sed -e 's/^.*: //g')" +bTapDeviceHasBeenSetNone="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*TapDeviceHasBeenSetNone :' | sed -e 's/^.*: //g')" +bAlsoUsingSetupKeys="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*bAlsoUsingSetupKeys :' | sed -e 's/^.*: //g')" +sTunnelDevice="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*TunnelDevice :' | sed -e 's/^.*: //g')" + +# Note: '\n' was translated into '\t', so we translate it back (it was done because grep and sed only work with single lines) +sRestoreIpv6Services="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*RestoreIpv6Services :' | sed -e 's/^.*: //g' | tr '\t' '\n')" + +# Remove leasewatcher +if ${ARG_MONITOR_NETWORK_CONFIGURATION} ; then + launchctl unload "${LEASEWATCHER_PLIST_PATH}" + if ${REMOVE_LEASEWATCHER_PLIST} ; then + rm -f "${LEASEWATCHER_PLIST_PATH}" + fi + logMessage "Cancelled monitoring of system configuration changes" +fi + +if ${ARG_TAP} ; then + if [ "$bRouteGatewayIsDhcp" == "true" ]; then + if [ "$bTapDeviceHasBeenSetNone" == "false" ]; then + if [ -z "$dev" ]; then + # If $dev is not defined, then use TunnelDevice, which was set from $dev by client.up.tunnelblick.sh + # ($def is not defined when this script is called from MenuController to clean up when exiting Bitmask) + if [ -n "${sTunnelDevice}" ]; then + logMessage "WARNING: \$dev not defined; using TunnelDevice: ${sTunnelDevice}" + set +e + ipconfig set "${sTunnelDevice}" NONE 2>/dev/null + set -e + logMessage "Released the DHCP lease via ipconfig set ${sTunnelDevice} NONE." + else + logMessage "WARNING: Cannot configure TAP interface to NONE without \$dev or State:/Network/OpenVPN/TunnelDevice being defined. Device may not have disconnected properly." + fi + else + set +e + ipconfig set "$dev" NONE 2>/dev/null + set -e + logMessage "Released the DHCP lease via ipconfig set $dev NONE." + fi + fi + fi +fi + +# Issue warning if the primary service ID has changed +set +e # "grep" will return error status (1) if no matches are found, so don't fail if not found +PSID_CURRENT="$( scutil <<-EOF | + open + show State:/Network/OpenVPN + quit +EOF +grep 'Service : ' | sed -e 's/.*Service : //' +)" +set -e # resume abort on error +if [ "${PSID}" != "${PSID_CURRENT}" ] ; then + logMessage "Ignoring change of Network Primary Service from ${PSID} to ${PSID_CURRENT}" +fi + +# Restore configurations +DNS_OLD="$( scutil <<-EOF + open + show State:/Network/OpenVPN/OldDNS + quit +EOF +)" +SMB_OLD="$( scutil <<-EOF + open + show State:/Network/OpenVPN/OldSMB + quit +EOF +)" +DNS_OLD_SETUP="$( scutil <<-EOF + open + show State:/Network/OpenVPN/OldDNSSetup + quit +EOF +)" +TB_NO_SUCH_KEY=" { + BitmaskNoSuchKey : true +}" + +if [ "${DNS_OLD}" = "${TB_NO_SUCH_KEY}" ] ; then + scutil <<-EOF + open + remove State:/Network/Service/${PSID}/DNS + quit +EOF +else + scutil <<-EOF + open + get State:/Network/OpenVPN/OldDNS + set State:/Network/Service/${PSID}/DNS + quit +EOF +fi + +if [ "${DNS_OLD_SETUP}" = "${TB_NO_SUCH_KEY}" ] ; then + if ${bAlsoUsingSetupKeys} ; then + logDebugMessage "DEBUG: Removing 'Setup:' DNS key" + scutil <<-EOF + open + remove Setup:/Network/Service/${PSID}/DNS + quit +EOF + else + logDebugMessage "DEBUG: Not removing 'Setup:' DNS key" + fi +else + if ${bAlsoUsingSetupKeys} ; then + logDebugMessage "DEBUG: Restoring 'Setup:' DNS key" + scutil <<-EOF + open + get State:/Network/OpenVPN/OldDNSSetup + set Setup:/Network/Service/${PSID}/DNS + quit +EOF + else + logDebugMessage "DEBUG: Not restoring 'Setup:' DNS key" + fi +fi + +if [ "${SMB_OLD}" = "${TB_NO_SUCH_KEY}" ] ; then + scutil > /dev/null <<-EOF + open + remove State:/Network/Service/${PSID}/SMB + quit +EOF +else + scutil > /dev/null <<-EOF + open + get State:/Network/OpenVPN/OldSMB + set State:/Network/Service/${PSID}/SMB + quit +EOF +fi + +logMessage "Restored the DNS and SMB configurations" + +set +e # "grep" will return error status (1) if no matches are found, so don't fail if not found +new_resolver_contents="$( grep -v '#' < /etc/resolv.conf )" +set -e # resume abort on error +logDebugMessage "DEBUG:" +logDebugMessage "DEBUG: /etc/resolve = ${new_resolver_contents}" + +set +e # scutil --dns will return error status in case dns is already down, so don't fail if no dns found +scutil_dns="$( scutil --dns)" +set -e # resume abort on error +logDebugMessage "DEBUG:" +logDebugMessage "DEBUG: scutil --dns = ${scutil_dns}" +logDebugMessage "DEBUG:" + +restore_ipv6 "$sRestoreIpv6Services" + +flushDNSCache + +# Remove our system configuration data +scutil <<-EOF + open + remove State:/Network/OpenVPN/OldDNS + remove State:/Network/OpenVPN/OldSMB + remove State:/Network/OpenVPN/OldDNSSetup + remove State:/Network/OpenVPN/DNS + remove State:/Network/OpenVPN/SMB + remove State:/Network/OpenVPN + quit +EOF + +if ${ARG_RESET_PRIMARY_INTERFACE_ON_DISCONNECT} ; then + resetPrimaryInterface +fi + +logMessage "End of output from ${OUR_NAME}" +logMessage "**********************************************" + +exit 0 diff --git a/pkg/osx/client.up.sh b/pkg/osx/client.up.sh new file mode 100755 index 00000000..a713c10e --- /dev/null +++ b/pkg/osx/client.up.sh @@ -0,0 +1,1521 @@ +#!/bin/bash -e +# Note: must be bash; uses bash-specific tricks +# +# ****************************************************************************************************************** +# Copyright by Tunnelblick. Redistributed under GPL as part of Bitmask. +# This Tunnelblick script does everything! It handles TUN and TAP interfaces, +# pushed configurations, DHCP with DNS and SMB, and renewed DHCP leases. :) +# +# This is the "Up" version of the script, executed after the interface is +# initialized. +# +# Created by: Nick Williams (using original code and parts of old Tblk scripts) +# Modifed by: Jonathan K. Bullard for Mountain Lion +# Adapted to use by Bitmask by: Kali Kaneko +# +# ****************************************************************************************************************** + + +########################################################################################## +# @param String message - The message to log +logMessage() +{ + echo "${@}" +} + +########################################################################################## +# @param String message - The message to log +logDebugMessage() +{ + if ${ARG_EXTRA_LOGGING} ; then + echo "${@}" + fi +} + +########################################################################################## +# log a change to a setting +# @param String filters - empty, or one or two '#' if not performing the change +# @param String name of setting that is being changed +# @param String new value +# @param String old value +logChange() +{ + if [ "$1" = "" ] ; then + if [ "$3" = "$4" ] ; then + echo "Did not change $2 setting of '$3' (but re-set it)" + else + echo "Changed $2 setting from '$4' to '$3'" + fi + else + echo "Did not change $2 setting of '$4'" + fi +} + +########################################################################################## +# @param String string - Content to trim +trim() +{ + echo ${@} +} + +########################################################################################## +disable_ipv6() { + +# Disables IPv6 on each enabled (active) network service on which it is set to the OS X default "IPv6 Automatic". +# +# For each such service, outputs a line with the name of the service. +# (A separate line is output for each name because a name may include spaces.) +# +# The 'restore_ipv6' routine in client.down.sh undoes the actions performed by this routine. +# +# NOTE: Done only for enabled services because some versions of OS X enable the service if this IPv6 setting is changed. +# +# This only works for OS X 10.5 and higher (10.4 does not implement IPv6.) + + if [ "$OSVER" = "10.4" ] ; then + exit + fi + + # Get list of services and remove the first line which contains a heading + dipv6_services="$( networksetup -listallnetworkservices | sed -e '1,1d')" + + # Go through the list disabling IPv6 for enabled services, and outputting lines with the names of the services + printf %s "$dipv6_services +" | \ + while IFS= read -r dipv6_service ; do + + # If first character of a line is an asterisk, the service is disabled, so we skip it + if [ "${dipv6_service:0:1}" != "*" ] ; then + dipv6_ipv6_status="$( networksetup -getinfo "$dipv6_service" | grep 'IPv6: ' | sed -e 's/IPv6: //')" + if [ "$dipv6_ipv6_status" = "Automatic" ] ; then + networksetup -setv6off "$dipv6_service" + echo "$dipv6_service" + fi + fi + + done +} + +########################################################################################## +# @param String[] dnsServers - The name servers to use +# @param String domainName - The domain name to use +# @param \optional String[] winsServers - The SMB servers to use +# @param \optional String[] searchDomains - The search domains to use +# +# Throughout this routine: +# MAN_ is a prefix for manually set parameters +# DYN_ is a prefix for dynamically set parameters (by a "push", config file, or command line option) +# CUR_ is a prefix for the current parameters (as arbitrated by OS X between manual and DHCP data) +# FIN_ is a prefix for the parameters we want to end up with +# SKP_ is a prefix for an empty string or a "#" used to control execution of statements that set parameters in scutil +# +# DNS_SA is a suffix for the ServerAddresses value in a System Configuration DNS key +# DNS_SD is a suffix for the SearchDomains value in a System Configuration DNS key +# DNS_DN is a suffix for the DomainName value in a System Configuration DNS key +# +# SMB_NN is a suffix for the NetBIOSName value in a System Configuration SMB key +# SMB_WG is a suffix for the Workgroup value in a System Configuration SMB key +# SMB_WA is a suffix for the WINSAddresses value in a System Configuration SMB key +# +# So, for example, MAN_SMB_NN is the manually set NetBIOSName value (or the empty string if not set manually) + +setDnsServersAndDomainName() +{ + set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors + + PSID="$( scutil <<-EOF | + open + show State:/Network/Global/IPv4 + quit +EOF +grep PrimaryService | sed -e 's/.*PrimaryService : //' +)" + + set -e # resume abort on error + + MAN_DNS_CONFIG="$( scutil <<-EOF | + open + show Setup:/Network/Service/${PSID}/DNS + quit +EOF +sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ' +)" + + MAN_SMB_CONFIG="$( scutil <<-EOF | + open + show Setup:/Network/Service/${PSID}/SMB + quit +EOF +sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ' +)" + CUR_DNS_CONFIG="$( scutil <<-EOF | + open + show State:/Network/Global/DNS + quit +EOF +sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ' +)" + + CUR_SMB_CONFIG="$( scutil <<-EOF | + open + show State:/Network/Global/SMB + quit +EOF +sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ' +)" + +# Set up the DYN_... variables to contain what is asked for (dynamically, by a 'push' directive, for example) + + declare -a vDNS=("${!1}") + declare -a vSMB=("${!3}") + declare -a vSD=("${!4}") + + if [ ${#vDNS[*]} -eq 0 ] ; then + readonly DYN_DNS_SA="" + else + readonly DYN_DNS_SA="${!1}" + fi + + if [ ${#vSMB[*]} -eq 0 ] ; then + readonly DYN_SMB_WA="" + else + readonly DYN_SMB_WA="${!3}" + fi + + if [ ${#vSD[*]} -eq 0 ] ; then + readonly DYN_DNS_SD="" + else + readonly DYN_DNS_SD="${!4}" + fi + + DYN_DNS_DN="$2" + + # The variables + # DYN_SMB_WG + # DYN_SMB_NN + # are left empty. There isn't a way for OpenVPN to set them. + + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: MAN_DNS_CONFIG = ${MAN_DNS_CONFIG}" + logDebugMessage "DEBUG: MAN_SMB_CONFIG = ${MAN_SMB_CONFIG}" + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: CUR_DNS_CONFIG = ${CUR_DNS_CONFIG}" + logDebugMessage "DEBUG: CUR_SMB_CONFIG = ${CUR_SMB_CONFIG}" + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: DYN_DNS_DN = ${DYN_DNS_DN}; DYN_DNS_SA = ${DYN_DNS_SA}; DYN_DNS_SD = ${DYN_DNS_SD}" + logDebugMessage "DEBUG: DYN_SMB_NN = ${DYN_SMB_NN}; DYN_SMB_WG = ${DYN_SMB_WG}; DYN_SMB_WA = ${DYN_SMB_WA}" + +# Set up the MAN_... variables to contain manual network settings + + set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors + + if echo "${MAN_DNS_CONFIG}" | grep -q "DomainName" ; then + readonly MAN_DNS_DN="$( trim "$( echo "${MAN_DNS_CONFIG}" | sed -e 's/^.*DomainName[^{]*{[[:space:]]*\([^}]*\)[[:space:]]*}.*$/\1/g' )" )" + else + readonly MAN_DNS_DN=""; + fi + if echo "${MAN_DNS_CONFIG}" | grep -q "ServerAddresses" ; then + readonly MAN_DNS_SA="$( trim "$( echo "${MAN_DNS_CONFIG}" | sed -e 's/^.*ServerAddresses[^{]*{[[:space:]]*\([^}]*\)[[:space:]]*}.*$/\1/g' )" )" + else + readonly MAN_DNS_SA=""; + fi + if echo "${MAN_DNS_CONFIG}" | grep -q "SearchDomains" ; then + readonly MAN_DNS_SD="$( trim "$( echo "${MAN_DNS_CONFIG}" | sed -e 's/^.*SearchDomains[^{]*{[[:space:]]*\([^}]*\)[[:space:]]*}.*$/\1/g' )" )" + else + readonly MAN_DNS_SD=""; + fi + if echo "${MAN_SMB_CONFIG}" | grep -q "NetBIOSName" ; then + readonly MAN_SMB_NN="$( trim "$( echo "${MAN_SMB_CONFIG}" | sed -e 's/^.*NetBIOSName : \([^[:space:]]*\).*$/\1/g' )" )" + else + readonly MAN_SMB_NN=""; + fi + if echo "${MAN_SMB_CONFIG}" | grep -q "Workgroup" ; then + readonly MAN_SMB_WG="$( trim "$( echo "${MAN_SMB_CONFIG}" | sed -e 's/^.*Workgroup : \([^[:space:]]*\).*$/\1/g' )" )" + else + readonly MAN_SMB_WG=""; + fi + if echo "${MAN_SMB_CONFIG}" | grep -q "WINSAddresses" ; then + readonly MAN_SMB_WA="$( trim "$( echo "${MAN_SMB_CONFIG}" | sed -e 's/^.*WINSAddresses[^{]*{[[:space:]]*\([^}]*\)[[:space:]]*}.*$/\1/g' )" )" + else + readonly MAN_SMB_WA=""; + fi + + set -e # resume abort on error + + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: MAN_DNS_DN = ${MAN_DNS_DN}; MAN_DNS_SA = ${MAN_DNS_SA}; MAN_DNS_SD = ${MAN_DNS_SD}" + logDebugMessage "DEBUG: MAN_SMB_NN = ${MAN_SMB_NN}; MAN_SMB_WG = ${MAN_SMB_WG}; MAN_SMB_WA = ${MAN_SMB_WA}" + +# Set up the CUR_... variables to contain the current network settings (from manual or DHCP, as arbitrated by OS X + + set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors + + if echo "${CUR_DNS_CONFIG}" | grep -q "DomainName" ; then + readonly CUR_DNS_DN="$(trim "$( echo "${CUR_DNS_CONFIG}" | sed -e 's/^.*DomainName : \([^[:space:]]*\).*$/\1/g' )")" + else + readonly CUR_DNS_DN=""; + fi + if echo "${CUR_DNS_CONFIG}" | grep -q "ServerAddresses" ; then + readonly CUR_DNS_SA="$(trim "$( echo "${CUR_DNS_CONFIG}" | sed -e 's/^.*ServerAddresses[^{]*{[[:space:]]*\([^}]*\)[[:space:]]*}.*$/\1/g' )")" + else + readonly CUR_DNS_SA=""; + fi + if echo "${CUR_DNS_CONFIG}" | grep -q "SearchDomains" ; then + readonly CUR_DNS_SD="$(trim "$( echo "${CUR_DNS_CONFIG}" | sed -e 's/^.*SearchDomains[^{]*{[[:space:]]*\([^}]*\)[[:space:]]*}.*$/\1/g' )")" + else + readonly CUR_DNS_SD=""; + fi + if echo "${CUR_SMB_CONFIG}" | grep -q "NetBIOSName" ; then + readonly CUR_SMB_NN="$(trim "$( echo "${CUR_SMB_CONFIG}" | sed -e 's/^.*NetBIOSName : \([^[:space:]]*\).*$/\1/g' )")" + else + readonly CUR_SMB_NN=""; + fi + if echo "${CUR_SMB_CONFIG}" | grep -q "Workgroup" ; then + readonly CUR_SMB_WG="$(trim "$( echo "${CUR_SMB_CONFIG}" | sed -e 's/^.*Workgroup : \([^[:space:]]*\).*$/\1/g' )")" + else + readonly CUR_SMB_WG=""; + fi + if echo "${CUR_SMB_CONFIG}" | grep -q "WINSAddresses" ; then + readonly CUR_SMB_WA="$(trim "$( echo "${CUR_SMB_CONFIG}" | sed -e 's/^.*WINSAddresses[^{]*{[[:space:]]*\([^}]*\)[[:space:]]*}.*$/\1/g' )")" + else + readonly CUR_SMB_WA=""; + fi + + set -e # resume abort on error + + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: CUR_DNS_DN = ${CUR_DNS_DN}; CUR_DNS_SA = ${CUR_DNS_SA}; CUR_DNS_SD = ${CUR_DNS_SD}" + logDebugMessage "DEBUG: CUR_SMB_NN = ${CUR_SMB_NN}; CUR_SMB_WG = ${CUR_SMB_WG}; CUR_SMB_WA = ${CUR_SMB_WA}" + +# set up the FIN_... variables with what we want to set things to + + # Three FIN_... variables are simple -- no aggregation is done for them + + if [ "${DYN_DNS_DN}" != "" ] ; then + if [ "${MAN_DNS_DN}" != "" ] ; then + logMessage "WARNING: Ignoring DomainName '$DYN_DNS_DN' because DomainName was set manually" + readonly FIN_DNS_DN="${MAN_DNS_DN}" + else + readonly FIN_DNS_DN="${DYN_DNS_DN}" + fi + else + readonly FIN_DNS_DN="${CUR_DNS_DN}" + fi + + if [ "${DYN_SMB_NN}" != "" ] ; then + if [ "${MAN_SMB_NN}" != "" ] ; then + logMessage "WARNING: Ignoring NetBIOSName '$DYN_SMB_NN' because NetBIOSName was set manually" + readonly FIN_SMB_NN="${MAN_SMB_NN}" + else + readonly FIN_SMB_NN="${DYN_SMB_NN}" + fi + else + readonly FIN_SMB_NN="${CUR_SMB_NN}" + fi + + if [ "${DYN_SMB_WG}" != "" ] ; then + if [ "${MAN_SMB_WG}" != "" ] ; then + logMessage "WARNING: Ignoring Workgroup '$DYN_SMB_WG' because Workgroup was set manually" + readonly FIN_SMB_WG="${MAN_SMB_WG}" + else + readonly FIN_SMB_WG="${DYN_SMB_WG}" + fi + else + readonly FIN_SMB_WG="${CUR_SMB_WG}" + fi + + # DNS ServerAddresses (FIN_DNS_SA) are aggregated for 10.4 and 10.5 + if [ ${#vDNS[*]} -eq 0 ] ; then + readonly FIN_DNS_SA="${CUR_DNS_SA}" + else + if [ "${MAN_DNS_SA}" != "" ] ; then + logMessage "WARNING: Ignoring ServerAddresses '$DYN_DNS_SA' because ServerAddresses was set manually" + readonly FIN_DNS_SA="${CUR_DNS_SA}" + else + case "${OSVER}" in + 10.4 | 10.5 ) + # We need to remove duplicate DNS entries, so that our reference list matches MacOSX's + SDNS="$( echo "${DYN_DNS_SA}" | tr ' ' '\n' )" + (( i=0 )) + for n in "${vDNS[@]}" ; do + if echo "${SDNS}" | grep -q "${n}" ; then + unset vDNS[${i}] + fi + (( i++ )) + done + if [ ${#vDNS[*]} -gt 0 ] ; then + readonly FIN_DNS_SA="$( trim "${DYN_DNS_SA}" "${vDNS[*]}" )" + else + readonly FIN_DNS_SA="${DYN_DNS_SA}" + fi + logMessage "Aggregating ServerAddresses because running on OS X 10.4 or 10.5" + ;; + * ) + # Do nothing - in 10.6 and higher -- we don't aggregate our configurations, apparently + readonly FIN_DNS_SA="${DYN_DNS_SA}" + logMessage "Not aggregating ServerAddresses because running on OS X 10.6 or higher" + ;; + esac + fi + fi + + # SMB WINSAddresses (FIN_SMB_WA) are aggregated for 10.4 and 10.5 + if [ ${#vSMB[*]} -eq 0 ] ; then + readonly FIN_SMB_WA="${CUR_SMB_WA}" + else + if [ "${MAN_SMB_WA}" != "" ] ; then + logMessage "WARNING: Ignoring WINSAddresses '$DYN_SMB_WA' because WINSAddresses was set manually" + readonly FIN_SMB_WA="${MAN_SMB_WA}" + else + case "${OSVER}" in + 10.4 | 10.5 ) + # We need to remove duplicate SMB entries, so that our reference list matches MacOSX's + SSMB="$( echo "${DYN_SMB_WA}" | tr ' ' '\n' )" + (( i=0 )) + for n in "${vSMB[@]}" ; do + if echo "${SSMB}" | grep -q "${n}" ; then + unset vSMB[${i}] + fi + (( i++ )) + done + if [ ${#vSMB[*]} -gt 0 ] ; then + readonly FIN_SMB_WA="$( trim "${DYN_SMB_WA}" "${vSMB[*]}" )" + else + readonly FIN_SMB_WA="${DYN_SMB_WA}" + fi + logMessage "Aggregating WINSAddresses because running on OS X 10.4 or 10.5" + ;; + * ) + # Do nothing - in 10.6 and higher -- we don't aggregate our configurations, apparently + readonly FIN_SMB_WA="${DYN_SMB_WA}" + logMessage "Not aggregating WINSAddresses because running on OS X 10.6 or higher" + ;; + esac + fi + fi + + # DNS SearchDomains (FIN_DNS_SD) is treated specially + # + # OLD BEHAVIOR: + # if SearchDomains was not set manually, we set SearchDomains to the DomainName + # else + # In OS X 10.4-10.5, we add the DomainName to the end of any manual SearchDomains (unless it is already there) + # In OS X 10.6+, if SearchDomains was entered manually, we ignore the DomainName + # else we set SearchDomains to the DomainName + # + # NEW BEHAVIOR (done if ARG_PREPEND_DOMAIN_NAME is "true"): + # + # if SearchDomains was entered manually, we do nothing + # else we PREpend new SearchDomains (if any) to the existing SearchDomains (NOT replacing them) + # and PREpend DomainName to that + # + # (done if ARG_PREPEND_DOMAIN_NAME is "false" and there are new SearchDomains from DOMAIN-SEARCH): + # + # if SearchDomains was entered manually, we do nothing + # else we PREpend any new SearchDomains to the existing SearchDomains (NOT replacing them) + # + # This behavior is meant to behave like Linux with Network Manager and Windows + + if "${ARG_PREPEND_DOMAIN_NAME}" ; then + if [ "${MAN_DNS_SD}" = "" ] ; then + if [ "${DYN_DNS_SD}" != "" ] ; then + if ! echo "${CUR_DNS_SD}" | tr ' ' '\n' | grep -q "${DYN_DNS_SD}" ; then + logMessage "Prepending '${DYN_DNS_SD}' to search domains '${CUR_DNS_SD}' because the search domains were not set manually and 'Prepend domain name to search domains' was selected" + readonly TMP_DNS_SD="$( trim "${DYN_DNS_SD}" "${CUR_DNS_SD}" )" + else + logMessage "Not prepending '${DYN_DNS_SD}' to search domains '${CUR_DNS_SD}' because it is already there" + readonly TMP_DNS_SD="${CUR_DNS_SD}" + fi + else + readonly TMP_DNS_SD="${CUR_DNS_SD}" + fi + if [ "${FIN_DNS_DN}" != "" -a "${FIN_DNS_DN}" != "localdomain" ] ; then + if ! echo "${TMP_DNS_SD}" | tr ' ' '\n' | grep -q "${FIN_DNS_DN}" ; then + logMessage "Prepending '${FIN_DNS_DN}' to search domains '${TMP_DNS_SD}' because the search domains were not set manually and 'Prepend domain name to search domains' was selected" + readonly FIN_DNS_SD="$( trim "${FIN_DNS_DN}" "${TMP_DNS_SD}" )" + else + logMessage "Not prepending '${FIN_DNS_DN}' to search domains '${TMP_DNS_SD}' because it is already there" + readonly FIN_DNS_SD="${TMP_DNS_SD}" + fi + else + readonly FIN_DNS_SD="${TMP_DNS_SD}" + fi + else + if [ "${DYN_DNS_SD}" != "" ] ; then + logMessage "WARNING: Not prepending '${DYN_DNS_SD}' to search domains '${CUR_DNS_SD}' because the search domains were set manually" + fi + if [ "${FIN_DNS_DN}" != "" ] ; then + logMessage "WARNING: Not prepending domain '${FIN_DNS_DN}' to search domains '${CUR_DNS_SD}' because the search domains were set manually" + fi + readonly FIN_DNS_SD="${CUR_DNS_SD}" + fi + else + if [ "${DYN_DNS_SD}" != "" ] ; then + if [ "${MAN_DNS_SD}" = "" ] ; then + logMessage "Prepending '${DYN_DNS_SD}' to search domains '${CUR_DNS_SD}' because the search domains were not set manually but were set via OpenVPN and 'Prepend domain name to search domains' was not selected" + readonly FIN_DNS_SD="$( trim "${DYN_DNS_SD}" "${CUR_DNS_SD}" )" + else + logMessage "WARNING: Not prepending '${DYN_DNS_SD}' to search domains '${CUR_DNS_SD}' because the search domains were set manually" + readonly FIN_DNS_SD="${CUR_DNS_SD}" + fi + else + if [ "${FIN_DNS_DN}" != "" -a "${FIN_DNS_DN}" != "localdomain" ] ; then + case "${OSVER}" in + 10.4 | 10.5 ) + if ! echo "${MAN_DNS_SD}" | tr ' ' '\n' | grep -q "${FIN_DNS_DN}" ; then + logMessage "Appending '${FIN_DNS_DN}' to search domains '${CUR_DNS_SD}' that were set manually because running under OS X 10.4 or 10.5 and 'Prepend domain name to search domains' was not selected" + readonly FIN_DNS_SD="$( trim "${MAN_DNS_SD}" "${FIN_DNS_DN}" )" + else + logMessage "Not appending '${FIN_DNS_DN}' to search domains '${CUR_DNS_SD}' because it is already in the search domains that were set manually and 'Prepend domain name to search domains' was not selected" + readonly FIN_DNS_SD="${CUR_DNS_SD}" + fi + ;; + * ) + if [ "${MAN_DNS_SD}" = "" ] ; then + logMessage "Setting search domains to '${FIN_DNS_DN}' because running under OS X 10.6 or higher and the search domains were not set manually and 'Prepend domain name to search domains' was not selected" + readonly FIN_DNS_SD="${FIN_DNS_DN}" + else + logMessage "Not replacing search domains '${CUR_DNS_SD}' with '${FIN_DNS_DN}' because the search domains were set manually and 'Prepend domain name to search domains' was not selected" + readonly FIN_DNS_SD="${CUR_DNS_SD}" + fi + ;; + esac + else + readonly FIN_DNS_SD="${CUR_DNS_SD}" + fi + fi + fi + + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: FIN_DNS_DN = ${FIN_DNS_DN}; FIN_DNS_SA = ${FIN_DNS_SA}; FIN_DNS_SD = ${FIN_DNS_SD}" + logDebugMessage "DEBUG: FIN_SMB_NN = ${FIN_SMB_NN}; FIN_SMB_WG = ${FIN_SMB_WG}; FIN_SMB_WA = ${FIN_SMB_WA}" + +# Set up SKP_... variables to inhibit scutil from making some changes + + # SKP_DNS_... and SKP_SMB_... are used to comment out individual items that are not being set + if [ "${FIN_DNS_DN}" = "" -o "${FIN_DNS_DN}" = "${CUR_DNS_DN}" ] ; then + SKP_DNS_DN="#" + else + SKP_DNS_DN="" + fi + if [ "${FIN_DNS_SA}" = "" -o "${FIN_DNS_SA}" = "${CUR_DNS_SA}" ] ; then + SKP_DNS_SA="#" + else + SKP_DNS_SA="" + fi + if [ "${FIN_DNS_SD}" = "" -o "${FIN_DNS_SD}" = "${CUR_DNS_SD}" ] ; then + SKP_DNS_SD="#" + else + SKP_DNS_SD="" + fi + if [ "${FIN_SMB_NN}" = "" -o "${FIN_SMB_NN}" = "${CUR_SMB_NN}" ] ; then + SKP_SMB_NN="#" + else + SKP_SMB_NN="" + fi + if [ "${FIN_SMB_WG}" = "" -o "${FIN_SMB_WG}" = "${CUR_SMB_WG}" ] ; then + SKP_SMB_WG="#" + else + SKP_SMB_WG="" + fi + if [ "${FIN_SMB_WA}" = "" -o "${FIN_SMB_WA}" = "${CUR_SMB_WA}" ] ; then + SKP_SMB_WA="#" + else + SKP_SMB_WA="" + fi + + # if any DNS items should be set, set all that have values + if [ "${SKP_DNS_DN}${SKP_DNS_SA}${SKP_DNS_SD}" = "###" ] ; then + readonly SKP_DNS="#" + else + readonly SKP_DNS="" + if [ "${FIN_DNS_DN}" != "" ] ; then + SKP_DNS_DN="" + fi + if [ "${FIN_DNS_SA}" != "" ] ; then + SKP_DNS_SA="" + fi + if [ "${FIN_DNS_SD}" != "" ] ; then + SKP_DNS_SD="" + fi + fi + + # if any SMB items should be set, set all that have values + if [ "${SKP_SMB_NN}${SKP_SMB_WG}${SKP_SMB_WA}" = "###" ] ; then + readonly SKP_SMB="#" + else + readonly SKP_SMB="" + if [ "${FIN_SMB_NN}" != "" ] ; then + SKP_SMB_NN="" + fi + if [ "${FIN_SMB_WG}" != "" ] ; then + SKP_SMB_WG="" + fi + if [ "${FIN_SMB_WA}" != "" ] ; then + SKP_SMB_WA="" + fi + fi + + readonly SKP_DNS_SA SKP_DNS_SD SKP_DNS_DN + readonly SKP_SMB_NN SKP_SMB_WG SKP_SMB_WA + +# special-case fiddling: + + # in 10.8 and higher, ServerAddresses and SearchDomains must be set via the Setup: key in addition to the State: key + # in 10.7 if ServerAddresses or SearchDomains are manually set, ServerAddresses and SearchDomains must be similarly set with the Setup: key in addition to the State: key + # + # we pass a flag indicating whether we've done that to the other scripts in 'bAlsoUsingSetupKeys' + + case "${OSVER}" in + 10.4 | 10.5 | 10.6 ) + logDebugMessage "DEBUG: OS X 10.4-10.6, so will modify settings using only State:" + readonly SKP_SETUP_DNS="#" + readonly bAlsoUsingSetupKeys="false" + ;; + 10.7 ) + if [ "${MAN_DNS_SA}" = "" -a "${MAN_DNS_SD}" = "" ] ; then + logDebugMessage "DEBUG: OS X 10.7 and neither ServerAddresses nor SearchDomains were set manually, so will modify DNS settings using only State:" + readonly SKP_SETUP_DNS="#" + readonly bAlsoUsingSetupKeys="false" + else + logDebugMessage "DEBUG: OS X 10.7 and ServerAddresses or SearchDomains were set manually, so will modify DNS settings using Setup: in addition to State:" + readonly SKP_SETUP_DNS="" + readonly bAlsoUsingSetupKeys="true" + fi + ;; + * ) + logDebugMessage "DEBUG: OS X 10.8 or higher, so will modify DNS settings using Setup: in addition to State:" + readonly SKP_SETUP_DNS="" + readonly bAlsoUsingSetupKeys="true" + ;; + esac + + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: SKP_DNS = ${SKP_DNS}; SKP_DNS_SA = ${SKP_DNS_SA}; SKP_DNS_SD = ${SKP_DNS_SD}; SKP_DNS_DN = ${SKP_DNS_DN}" + logDebugMessage "DEBUG: SKP_SETUP_DNS = ${SKP_SETUP_DNS}" + logDebugMessage "DEBUG: SKP_SMB = ${SKP_SMB}; SKP_SMB_NN = ${SKP_SMB_NN}; SKP_SMB_WG = ${SKP_SMB_WG}; SKP_SMB_WA = ${SKP_SMB_WA}" + + set +e # "grep" will return error status (1) if no matches are found, so don't fail if not found + original_resolver_contents="$( grep -v '#' < /etc/resolv.conf )" + set -e # resume abort on error + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: /etc/resolve = ${original_resolver_contents}" + logDebugMessage "DEBUG:" + + set +e # scutil --dns will return error status in case dns is already down, so don't fail if no dns found + scutil_dns="$( scutil --dns)" + set -e # resume abort on error + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: scutil --dns BEFORE CHANGES = ${scutil_dns}" + logDebugMessage "DEBUG:" + + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: Configuration changes:" + logDebugMessage "DEBUG: ${SKP_DNS}${SKP_DNS_SA}ADD State: ServerAddresses ${FIN_DNS_SA}" + logDebugMessage "DEBUG: ${SKP_DNS}${SKP_DNS_SD}ADD State: SearchDomains ${FIN_DNS_SD}" + logDebugMessage "DEBUG: ${SKP_DNS}${SKP_DNS_DN}ADD State: DomainName ${FIN_DNS_DN}" + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: ${SKP_SETUP_DNS}${SKP_DNS}${SKP_DNS_SA}ADD Setup: ServerAddresses ${FIN_DNS_SA}" + logDebugMessage "DEBUG: ${SKP_SETUP_DNS}${SKP_DNS}${SKP_DNS_SD}ADD Setup: SearchDomains ${FIN_DNS_SD}" + logDebugMessage "DEBUG: ${SKP_SETUP_DNS}${SKP_DNS}${SKP_DNS_DN}ADD Setup: DomainName ${FIN_DNS_DN}" + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: ${SKP_SMB}${SKP_SMB_NN}ADD State: NetBIOSName ${FIN_SMB_NN}" + logDebugMessage "DEBUG: ${SKP_SMB}${SKP_SMB_WG}ADD State: Workgroup ${FIN_SMB_WG}" + logDebugMessage "DEBUG: ${SKP_SMB}${SKP_SMB_WA}ADD State: WINSAddresses ${FIN_SMB_WA}" + + # Save the openvpn process ID and the Network Primary Service ID, leasewather.plist path, logfile path, and optional arguments from Bitmask, + # then save old and new DNS and SMB settings + # PPID is a script variable (defined by bash itself) that contains the process ID of the parent of the process running the script (i.e., OpenVPN's process ID) + # config is an environmental variable set to the configuration path by OpenVPN prior to running this up script + + scutil <<-EOF > /dev/null + open + + # Store our variables for the other scripts (leasewatch, down, etc.) to use + d.init + # The '#' in the next line does NOT start a comment; it indicates to scutil that a number follows it (as opposed to a string or an array) + d.add PID # ${PPID} + d.add Service ${PSID} + d.add LeaseWatcherPlistPath "${LEASEWATCHER_PLIST_PATH}" + d.add RemoveLeaseWatcherPlist "${REMOVE_LEASEWATCHER_PLIST}" + d.add ScriptLogFile "${SCRIPT_LOG_FILE}" + d.add MonitorNetwork "${ARG_MONITOR_NETWORK_CONFIGURATION}" + d.add RestoreOnDNSReset "${ARG_RESTORE_ON_DNS_RESET}" + d.add RestoreOnWINSReset "${ARG_RESTORE_ON_WINS_RESET}" + d.add IgnoreOptionFlags "${ARG_IGNORE_OPTION_FLAGS}" + d.add IsTapInterface "${ARG_TAP}" + d.add FlushDNSCache "${ARG_FLUSH_DNS_CACHE}" + d.add ResetPrimaryInterface "${ARG_RESET_PRIMARY_INTERFACE_ON_DISCONNECT}" + d.add RouteGatewayIsDhcp "${bRouteGatewayIsDhcp}" + d.add bAlsoUsingSetupKeys "${bAlsoUsingSetupKeys}" + d.add TapDeviceHasBeenSetNone "false" + d.add TunnelDevice "$dev" + d.add RestoreIpv6Services "$ipv6_disabled_services_encoded" + set State:/Network/OpenVPN + + # Back up the device's current DNS and SMB configurations, + # Indicate 'no such key' by a dictionary with a single entry: "BitmaskNoSuchKey : true" + # If there isn't a key, "BitmaskNoSuchKey : true" won't be removed. + # If there is a key, "BitmaskNoSuchKey : true" will be removed and the key's contents will be used + + d.init + d.add BitmaskNoSuchKey true + get State:/Network/Service/${PSID}/DNS + set State:/Network/OpenVPN/OldDNS + + d.init + d.add BitmaskNoSuchKey true + get Setup:/Network/Service/${PSID}/DNS + set State:/Network/OpenVPN/OldDNSSetup + + d.init + d.add BitmaskNoSuchKey true + get State:/Network/Service/${PSID}/SMB + set State:/Network/OpenVPN/OldSMB + + # Initialize the new DNS map via State: + ${SKP_DNS}d.init + ${SKP_DNS}${SKP_DNS_SA}d.add ServerAddresses * ${FIN_DNS_SA} + ${SKP_DNS}${SKP_DNS_SD}d.add SearchDomains * ${FIN_DNS_SD} + ${SKP_DNS}${SKP_DNS_DN}d.add DomainName ${FIN_DNS_DN} + ${SKP_DNS}set State:/Network/Service/${PSID}/DNS + + # If necessary, initialize the new DNS map via Setup: also + ${SKP_SETUP_DNS}${SKP_DNS}d.init + ${SKP_SETUP_DNS}${SKP_DNS}${SKP_DNS_SA}d.add ServerAddresses * ${FIN_DNS_SA} + ${SKP_SETUP_DNS}${SKP_DNS}${SKP_DNS_SD}d.add SearchDomains * ${FIN_DNS_SD} + ${SKP_SETUP_DNS}${SKP_DNS}${SKP_DNS_DN}d.add DomainName ${FIN_DNS_DN} + ${SKP_SETUP_DNS}${SKP_DNS}set Setup:/Network/Service/${PSID}/DNS + + # Initialize the SMB map + ${SKP_SMB}d.init + ${SKP_SMB}${SKP_SMB_NN}d.add NetBIOSName ${FIN_SMB_NN} + ${SKP_SMB}${SKP_SMB_WG}d.add Workgroup ${FIN_SMB_WG} + ${SKP_SMB}${SKP_SMB_WA}d.add WINSAddresses * ${FIN_SMB_WA} + ${SKP_SMB}set State:/Network/Service/${PSID}/SMB + + quit +EOF + + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: Pause for configuration changes to be propagated to State:/Network/Global/DNS and .../SMB" + sleep 1 + + scutil <<-EOF > /dev/null + open + + # Initialize the maps that will be compared when a configuration change occurs + d.init + d.add BitmaskNoSuchKey true + get State:/Network/Global/DNS + set State:/Network/OpenVPN/DNS + + d.init + d.add BitmaskNoSuchKey true + get State:/Network/Global/SMB + set State:/Network/OpenVPN/SMB + + quit +EOF + + readonly NEW_DNS_SETUP_CONFIG="$( scutil <<-EOF | + open + show Setup:/Network/Service/${PSID}/DNS + quit +EOF +sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ' +)" + readonly NEW_SMB_SETUP_CONFIG="$( scutil <<-EOF | + open + show Setup:/Network/Service/${PSID}/SMB + quit +EOF +sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ' +)" + readonly NEW_DNS_STATE_CONFIG="$( scutil <<-EOF | + open + show State:/Network/Service/${PSID}/DNS + quit +EOF +sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ' +)" + readonly NEW_SMB_STATE_CONFIG="$( scutil <<-EOF | + open + show State:/Network/Service/${PSID}/SMB + quit +EOF +sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ' +)" + readonly NEW_DNS_GLOBAL_CONFIG="$( scutil <<-EOF | + open + show State:/Network/Global/DNS + quit +EOF +sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ' +)" + readonly NEW_SMB_GLOBAL_CONFIG="$( scutil <<-EOF | + open + show State:/Network/Global/SMB + quit +EOF +sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ' +)" + readonly EXPECTED_NEW_DNS_GLOBAL_CONFIG="$( scutil <<-EOF | + open + show State:/Network/OpenVPN/DNS + quit +EOF +sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ' +)" + readonly EXPECTED_NEW_SMB_GLOBAL_CONFIG="$( scutil <<-EOF | + open + show State:/Network/OpenVPN/SMB + quit +EOF +sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ' +)" + + + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: Configurations as read back after changes:" + logDebugMessage "DEBUG: State:/.../DNS = ${NEW_DNS_STATE_CONFIG}" + logDebugMessage "DEBUG: State:/.../SMB = ${NEW_SMB_STATE_CONFIG}" + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: Setup:/.../DNS = ${NEW_DNS_SETUP_CONFIG}" + logDebugMessage "DEBUG: Setup:/.../SMB = ${NEW_SMB_SETUP_CONFIG}" + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: State:/Network/Global/DNS = ${NEW_DNS_GLOBAL_CONFIG}" + logDebugMessage "DEBUG: State:/Network/Global/SMB = ${NEW_SMB_GLOBAL_CONFIG}" + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: Expected by process-network-changes:" + logDebugMessage "DEBUG: State:/Network/OpenVPN/DNS = ${EXPECTED_NEW_DNS_GLOBAL_CONFIG}" + logDebugMessage "DEBUG: State:/Network/OpenVPN/SMB = ${EXPECTED_NEW_SMB_GLOBAL_CONFIG}" + + set +e # "grep" will return error status (1) if no matches are found, so don't fail if not found + new_resolver_contents="$( grep -v '#' < /etc/resolv.conf )" + set -e # resume abort on error + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: /etc/resolve = ${new_resolver_contents}" + logDebugMessage "DEBUG:" + + set +e # scutil --dns will return error status in case dns is already down, so don't fail if no dns found + scutil_dns="$( scutil --dns )" + set -e # resume abort on error + logDebugMessage "DEBUG:" + logDebugMessage "DEBUG: scutil --dns AFTER CHANGES = ${scutil_dns}" + logDebugMessage "DEBUG:" + + logMessage "Saved the DNS and SMB configurations so they can be restored" + + logChange "${SKP_DNS}${SKP_DNS_SA}" "DNS ServerAddresses" "${FIN_DNS_SA}" "${CUR_DNS_SA}" + logChange "${SKP_DNS}${SKP_DNS_SD}" "DNS SearchDomains" "${FIN_DNS_SD}" "${CUR_DNS_SD}" + logChange "${SKP_DNS}${SKP_DNS_DN}" "DNS DomainName" "${FIN_DNS_DN}" "${CUR_DNS_DN}" + logChange "${SKP_SMB}${SKP_SMB_NN}" "SMB NetBIOSName" "${FIN_SMB_SA}" "${CUR_SMB_SA}" + logChange "${SKP_SMB}${SKP_SMB_WG}" "SMB Workgroup" "${FIN_SMB_WG}" "${CUR_SMB_WG}" + logChange "${SKP_SMB}${SKP_SMB_WA}" "SMB WINSAddresses" "${FIN_SMB_WA}" "${CUR_SMB_WA}" + + logDnsInfo "${MAN_DNS_SA}" "${FIN_DNS_SA}" + + flushDNSCache + + if ${ARG_MONITOR_NETWORK_CONFIGURATION} ; then + if [ "${ARG_IGNORE_OPTION_FLAGS:0:2}" = "-p" ] ; then + logMessage "Setting up to monitor system configuration with process-network-changes" + else + logMessage "Setting up to monitor system configuration with leasewatch" + fi + if [ "${LEASEWATCHER_TEMPLATE_PATH}" != "" ] ; then + sed -e "s|/Applications/Bitmask/.app/Contents/Resources|${TB_RESOURCES_PATH}|g" "${LEASEWATCHER_TEMPLATE_PATH}" > "${LEASEWATCHER_PLIST_PATH}" + fi + launchctl load "${LEASEWATCHER_PLIST_PATH}" + fi +} + +########################################################################################## +# Used for TAP device which does DHCP +configureDhcpDns() +{ + # whilst ipconfig will have created the neccessary Network Service keys, the DNS + # settings won't actually be used by OS X unless the SupplementalMatchDomains key + # is added + # ref. + # - is there a way to extract the domains from the SC dictionary and re-insert + # as SupplementalMatchDomains? i.e. not requiring the ipconfig domain_name call? + + # - wait until we get a lease before extracting the DNS domain name and merging into SC + # - despite it's name, ipconfig waitall doesn't (but maybe one day it will :-) + logDebugMessage "DEBUG: About to 'ipconfig waitall'" + ipconfig waitall + logDebugMessage "DEBUG: Completed 'ipconfig waitall'" + + unset test_domain_name + unset test_name_server + + set +e # We instruct bash NOT to exit on individual command errors, because if we need to wait longer these commands will fail + + # usually takes at least a few seconds to get a DHCP lease + sleep 3 + n=0 + while [ -z "$test_domain_name" -a -z "$test_name_server" -a $n -lt 5 ] + do + logMessage "Sleeping for $n seconds to wait for DHCP to finish setup." + sleep $n + n="$( expr $n + 1 )" + + if [ -z "$test_domain_name" ]; then + test_domain_name="$( ipconfig getoption "$dev" domain_name 2>/dev/null )" + fi + + if [ -z "$test_name_server" ]; then + test_name_server="$( ipconfig getoption "$dev" domain_name_server 2>/dev/null )" + fi + done + + logDebugMessage "DEBUG: Finished waiting for DHCP lease: test_domain_name = '$test_domain_name', test_name_server = '$test_name_server'" + + logDebugMessage "DEBUG: About to 'ipconfig getpacket $dev'" + sGetPacketOutput="$( ipconfig getpacket "$dev" )" + logDebugMessage "DEBUG: Completed 'ipconfig getpacket $dev'; sGetPacketOutput = $sGetPacketOutput" + + set -e # We instruct bash that it CAN again fail on individual errors + + unset aNameServers + unset aWinsServers + unset aSearchDomains + + nNameServerIndex=1 + nWinsServerIndex=1 + nSearchDomainIndex=1 + + if [ "$sGetPacketOutput" ]; then + sGetPacketOutput_FirstLine="$( echo "$sGetPacketOutput" | head -n 1 )" + logDebugMessage "DEBUG: sGetPacketOutput_FirstLine = $sGetPacketOutput_FirstLine" + + if [ "$sGetPacketOutput_FirstLine" == "op = BOOTREPLY" ]; then + set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors + + for tNameServer in $( echo "$sGetPacketOutput" | grep "domain_name_server" | grep -Eo "\{([0-9\.]+)(, [0-9\.]+)*\}" | grep -Eo "([0-9\.]+)" ); do + aNameServers[nNameServerIndex-1]="$( trim "$tNameServer" )" + let nNameServerIndex++ + done + + for tWINSServer in $( echo "$sGetPacketOutput" | grep "nb_over_tcpip_name_server" | grep -Eo "\{([0-9\.]+)(, [0-9\.]+)*\}" | grep -Eo "([0-9\.]+)" ); do + aWinsServers[nWinsServerIndex-1]="$( trim "$tWINSServer" )" + let nWinsServerIndex++ + done + + for tSearchDomain in $( echo "$sGetPacketOutput" | grep "search_domain" | grep -Eo "\{([-A-Za-z0-9\-\.]+)(, [-A-Za-z0-9\-\.]+)*\}" | grep -Eo "([-A-Za-z0-9\-\.]+)" ); do + aSearchDomains[nSearchDomainIndex-1]="$( trim "$tSearchDomain" )" + let nSearchDomainIndex++ + done + + sDomainName="$( echo "$sGetPacketOutput" | grep "domain_name " | grep -Eo ": [-A-Za-z0-9\-\.]+" | grep -Eo "[-A-Za-z0-9\-\.]+" )" + sDomainName="$( trim "$sDomainName" )" + + if [ ${#aNameServers[*]} -gt 0 -a "$sDomainName" ]; then + logMessage "Retrieved from DHCP/BOOTP packet: name server(s) [ ${aNameServers[@]} ], domain name [ $sDomainName ], search domain(s) [ ${aSearchDomains[@]} ] and SMB server(s) [ ${aWinsServers[@]} ]" + setDnsServersAndDomainName aNameServers[@] "$sDomainName" aWinsServers[@] aSearchDomains[@] + return 0 + elif [ ${#aNameServers[*]} -gt 0 ]; then + logMessage "Retrieved from DHCP/BOOTP packet: name server(s) [ ${aNameServers[@]} ], search domain(s) [ ${aSearchDomains[@]} ] and SMB server(s) [ ${aWinsServers[@]} ] and using default domain name [ $DEFAULT_DOMAIN_NAME ]" + setDnsServersAndDomainName aNameServers[@] "$DEFAULT_DOMAIN_NAME" aWinsServers[@] aSearchDomains[@] + return 0 + else + # Should we return 1 here and indicate an error, or attempt the old method? + logMessage "No useful information extracted from DHCP/BOOTP packet. Attempting legacy configuration." + fi + + set -e # We instruct bash that it CAN again fail on errors + else + # Should we return 1 here and indicate an error, or attempt the old method? + logMessage "No DHCP/BOOTP packet found on interface. Attempting legacy configuration." + fi + fi + + unset sDomainName + unset sNameServer + unset aNameServers + + set +e # We instruct bash NOT to exit on individual command errors, because if we need to wait longer these commands will fail + + logDebugMessage "DEBUG: About to 'ipconfig getoption $dev domain_name'" + sDomainName="$( ipconfig getoption "$dev" domain_name 2>/dev/null )" + logDebugMessage "DEBUG: Completed 'ipconfig getoption $dev domain_name'" + logDebugMessage "DEBUG: About to 'ipconfig getoption $dev domain_name_server'" + sNameServer="$( ipconfig getoption "$dev" domain_name_server 2>/dev/null )" + logDebugMessage "DEBUG: Completed 'ipconfig getoption $dev domain_name_server'" + + set -e # We instruct bash that it CAN again fail on individual errors + + sDomainName="$( trim "$sDomainName" )" + sNameServer="$( trim "$sNameServer" )" + + declare -a aWinsServers=( ) # Declare empty WINSServers array to avoid any useless error messages + declare -a aSearchDomains=( ) # Declare empty SearchDomains array to avoid any useless error messages + + if [ "$sDomainName" -a "$sNameServer" ]; then + aNameServers[0]=$sNameServer + logMessage "Retrieved OpenVPN (DHCP): name server [ $sNameServer ], domain name [ $sDomainName ], and no SMB servers or search domains" + setDnsServersAndDomainName aNameServers[@] "$sDomainName" aWinsServers[@] aSearchDomains[@] + elif [ "$sNameServer" ]; then + aNameServers[0]=$sNameServer + logMessage "Retrieved OpenVPN (DHCP): name server [ $sNameServer ] and no SMB servers or search domains, and using default domain name [ $DEFAULT_DOMAIN_NAME ]" + setDnsServersAndDomainName aNameServers[@] "$DEFAULT_DOMAIN_NAME" aWinsServers[@] aSearchDomains[@] + elif [ "$sDomainName" ]; then + logMessage "WARNING: Retrieved domain name [ $sDomainName ] but no name servers from OpenVPN via DHCP, which is not sufficient to make network/DNS configuration changes." + if ${ARG_MONITOR_NETWORK_CONFIGURATION} ; then + logMessage "WARNING: Will NOT monitor for other network configuration changes." + fi + logDnsInfoNoChanges + flushDNSCache + else + logMessage "WARNING: No DNS information received from OpenVPN via DHCP, so no network/DNS configuration changes need to be made." + if ${ARG_MONITOR_NETWORK_CONFIGURATION} ; then + logMessage "WARNING: Will NOT monitor for other network configuration changes." + fi + logDnsInfoNoChanges + flushDNSCache + fi + + return 0 +} + +########################################################################################## +# Configures using OpenVPN foreign_option_* instead of DHCP + +configureOpenVpnDns() +{ +# Description of foreign_option_ parameters (from OpenVPN 2.3-alpha_2 man page): +# +# DOMAIN name -- Set Connection-specific DNS Suffix. +# +# DOMAIN-SEARCH name -- Set Connection-specific DNS Search Address. Repeat this option to +# set additional search domains. (Bitmask-specific addition.) +# +# DNS addr -- Set primary domain name server address. Repeat this option to set +# secondary DNS server addresses. +# +# WINS addr -- Set primary WINS server address (NetBIOS over TCP/IP Name Server). +# Repeat this option to set secondary WINS server addresses. +# +# NBDD addr -- Set primary NBDD server address (NetBIOS over TCP/IP Datagram Distribution Server) +# Repeat this option to set secondary NBDD server addresses. +# +# NTP addr -- Set primary NTP server address (Network Time Protocol). Repeat this option +# to set secondary NTP server addresses. +# +# NBT type -- Set NetBIOS over TCP/IP Node type. Possible options: 1 = b-node +# (broadcasts), 2 = p-node (point-to-point name queries to a WINS server), 4 = m- +# node (broadcast then query name server), and 8 = h-node (query name server, then +# broadcast). +# +# NBS scope-id -- Set NetBIOS over TCP/IP Scope. A NetBIOS Scope ID provides an +# extended naming service for the NetBIOS over TCP/IP (Known as NBT) module. The +# primary purpose of a NetBIOS scope ID is to isolate NetBIOS traffic on a single +# network to only those nodes with the same NetBIOS scope ID. The NetBIOS scope ID +# is a character string that is appended to the NetBIOS name. The NetBIOS scope ID +# on two hosts must match, or the two hosts will not be able to communicate. The +# NetBIOS Scope ID also allows computers to use the same computer name, as they have +# different scope IDs. The Scope ID becomes a part of the NetBIOS name, making the +# name unique. (This description of NetBIOS scopes courtesy of NeonSurge@abyss.com) +# +#DISABLE-NBT -- Disable Netbios-over-TCP/IP. + + unset vForOptions + unset vOptions + unset aNameServers + unset aWinsServers + unset aSearchDomains + + nOptionIndex=1 + nNameServerIndex=1 + nWinsServerIndex=1 + nSearchDomainIndex=1 + + while vForOptions=foreign_option_$nOptionIndex; [ -n "${!vForOptions}" ]; do + vOptions[nOptionIndex-1]=${!vForOptions} + case ${vOptions[nOptionIndex-1]} in + *DOMAIN-SEARCH* ) + aSearchDomains[nSearchDomainIndex-1]="$( trim "${vOptions[nOptionIndex-1]//dhcp-option DOMAIN-SEARCH /}" )" + let nSearchDomainIndex++ + ;; + *DOMAIN* ) + sDomainName="$( trim "${vOptions[nOptionIndex-1]//dhcp-option DOMAIN /}" )" + ;; + *DNS* ) + aNameServers[nNameServerIndex-1]="$( trim "${vOptions[nOptionIndex-1]//dhcp-option DNS /}" )" + let nNameServerIndex++ + ;; + *WINS* ) + aWinsServers[nWinsServerIndex-1]="$( trim "${vOptions[nOptionIndex-1]//dhcp-option WINS /}" )" + let nWinsServerIndex++ + ;; + * ) + logMessage "WARNING: 'foreign_option_${nOptionIndex}' = '${vOptions[nOptionIndex-1]}' ignored" + ;; + esac + let nOptionIndex++ + done + + if [ ${#aNameServers[*]} -gt 0 -a "$sDomainName" ]; then + logMessage "Retrieved from OpenVPN: name server(s) [ ${aNameServers[@]} ], domain name [ $sDomainName ], search domain(s) [ ${aSearchDomains[@]} ], and SMB server(s) [ ${aWinsServers[@]} ]" + setDnsServersAndDomainName aNameServers[@] "$sDomainName" aWinsServers[@] aSearchDomains[@] + elif [ ${#aNameServers[*]} -gt 0 ]; then + logMessage "Retrieved from OpenVPN: name server(s) [ ${aNameServers[@]} ], search domain(s) [ ${aSearchDomains[@]} ] and SMB server(s) [ ${aWinsServers[@]} ] and using default domain name [ $DEFAULT_DOMAIN_NAME ]" + setDnsServersAndDomainName aNameServers[@] "$DEFAULT_DOMAIN_NAME" aWinsServers[@] aSearchDomains[@] + else + logMessage "WARNING: No DNS information received from OpenVPN, so no network configuration changes need to be made." + if ${ARG_MONITOR_NETWORK_CONFIGURATION} ; then + logMessage "WARNING: Will NOT monitor for other network configuration changes." + fi + logDnsInfoNoChanges + flushDNSCache + fi + + return 0 +} + +########################################################################################## +flushDNSCache() +{ + if ${ARG_FLUSH_DNS_CACHE} ; then + if [ "${OSVER}" = "10.4" ] ; then + + if [ -f /usr/sbin/lookupd ] ; then + set +e # we will catch errors from lookupd + /usr/sbin/lookupd -flushcache + if [ $? != 0 ] ; then + logMessage "WARNING: Unable to flush the DNS cache via lookupd" + else + logMessage "Flushed the DNS cache via lookupd" + fi + set -e # bash should again fail on errors + else + logMessage "WARNING: /usr/sbin/lookupd not present. Not flushing the DNS cache" + fi + + else + + if [ -f /usr/bin/dscacheutil ] ; then + set +e # we will catch errors from dscacheutil + /usr/bin/dscacheutil -flushcache + if [ $? != 0 ] ; then + logMessage "WARNING: Unable to flush the DNS cache via dscacheutil" + else + logMessage "Flushed the DNS cache via dscacheutil" + fi + set -e # bash should again fail on errors + else + logMessage "WARNING: /usr/bin/dscacheutil not present. Not flushing the DNS cache via dscacheutil" + fi + + if [ -f /usr/sbin/discoveryutil ] ; then + set +e # we will catch errors from discoveryutil + /usr/sbin/discoveryutil udnsflushcaches + if [ $? != 0 ] ; then + logMessage "WARNING: Unable to flush the DNS cache via discoveryutil udnsflushcaches" + else + logMessage "Flushed the DNS cache via discoveryutil udnsflushcaches" + fi + /usr/sbin/discoveryutil mdnsflushcache + if [ $? != 0 ] ; then + logMessage "WARNING: Unable to flush the DNS cache via discoveryutil mdnsflushcache" + else + logMessage "Flushed the DNS cache via discoveryutil mdnsflushcache" + fi + set -e # bash should again fail on errors + else + logMessage "/usr/sbin/discoveryutil not present. Not flushing the DNS cache via discoveryutil" + fi + + set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors + hands_off_ps="$( ps -ax | grep HandsOffDaemon | grep -v grep.HandsOffDaemon )" + set -e # We instruct bash that it CAN again fail on errors + if [ "${hands_off_ps}" = "" ] ; then + if [ -f /usr/bin/killall ] ; then + set +e # ignore errors if mDNSResponder isn't currently running + /usr/bin/killall -HUP mDNSResponder + if [ $? != 0 ] ; then + logMessage "mDNSResponder not running. Not notifying it that the DNS cache was flushed" + else + logMessage "Notified mDNSResponder that the DNS cache was flushed" + fi + set -e # bash should again fail on errors + else + logMessage "WARNING: /usr/bin/killall not present. Not notifying mDNSResponder that the DNS cache was flushed" + fi + else + logMessage "WARNING: Hands Off is running. Not notifying mDNSResponder that the DNS cache was flushed" + fi + + fi + fi +} + + +########################################################################################## +# log information about the DNS settings +# @param String Manual DNS_SA +# @param String New DNS_SA +logDnsInfo() { + + log_dns_info_manual_dns_sa="$1" + log_dns_info_new_dns_sa="$2" + + if [ "${log_dns_info_manual_dns_sa}" != "" ] ; then + logMessage "DNS servers '${log_dns_info_manual_dns_sa}' were set manually" + if [ "${log_dns_info_manual_dns_sa}" != "${log_dns_info_new_dns_sa}" ] ; then + logMessage "WARNING: that setting is being ignored by OS X; '${log_dns_info_new_dns_sa}' is being used." + fi + fi + + if [ "${log_dns_info_new_dns_sa}" != "" ] ; then + logMessage "DNS servers '${log_dns_info_new_dns_sa}' will be used for DNS queries when the VPN is active" + if [ "${log_dns_info_new_dns_sa}" == "127.0.0.1" ] ; then + logMessage "NOTE: DNS server 127.0.0.1 often is used inside virtual machines (e.g., 'VirtualBox', 'Parallels', or 'VMWare'). The actual VPN server may be specified by the host machine. This DNS server setting may cause DNS queries to fail or be intercepted or falsified. Specify only known public DNS servers or DNS servers located on the VPN network to avoid such problems." + else + set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors + serversContainLoopback="$( echo "${log_dns_info_new_dns_sa}" | grep "127.0.0.1" )" + set -e # We instruct bash that it CAN again fail on errors + if [ "${serversContainLoopback}" != "" ] ; then + logMessage "NOTE: DNS server 127.0.0.1 often is used inside virtual machines (e.g., 'VirtualBox', 'Parallels', or 'VMWare'). The actual VPN server may be specified by the host machine. If used, 127.0.0.1 may cause DNS queries to fail or be intercepted or falsified. Specify only known public DNS servers or DNS servers located on the VPN network to avoid such problems." + else + readonly knownPublicDnsServers="$( cat "${FREE_PUBLIC_DNS_SERVERS_LIST_PATH}" )" + knownDnsServerNotFound="true" + unknownDnsServerFound="false" + for server in ${log_dns_info_new_dns_sa} ; do + set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors + serverIsKnown="$( echo "${knownPublicDnsServers}" | grep "${server}" )" + set -e # We instruct bash that it CAN again fail on errors + if [ "${serverIsKnown}" != "" ] ; then + knownDnsServerNotFound="false" + else + unknownDnsServerFound="true" + fi + done + if ${knownDnsServerNotFound} ; then + logMessage "NOTE: The DNS servers do not include any free public DNS servers known to Bitmask. This may cause DNS queries to fail or be intercepted or falsified even if they are directed through the VPN. Specify only known public DNS servers or DNS servers located on the VPN network to avoid such problems." + else + if ${unknownDnsServerFound} ; then + logMessage "NOTE: The DNS servers include one or more free public DNS servers known to Bitmask and one or more DNS servers not known to Bitmask. If used, the DNS servers not known to Bitmask may cause DNS queries to fail or be intercepted or falsified even if they are directed through the VPN. Specify only known public DNS servers or DNS servers located on the VPN network to avoid such problems." + else + logMessage "The DNS servers include only free public DNS servers known to Bitmask." + fi + fi + fi + fi + else + logMessage "WARNING: There are no DNS servers in this computer's new network configuration. This computer or a DHCP server that this computer uses may be configured incorrectly." + fi +} + +logDnsInfoNoChanges() { +# log information about DNS settings if they are not changing + + set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors + + PSID="$( scutil <<-EOF | + open + show State:/Network/Global/IPv4 + quit +EOF +grep PrimaryService | sed -e 's/.*PrimaryService : //' +)" + + readonly LOGDNSINFO_MAN_DNS_CONFIG="$( scutil <<-EOF | + open + show Setup:/Network/Service/${PSID}/DNS + quit +EOF +sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ' +)" + + readonly LOGDNSINFO_CUR_DNS_CONFIG="$( scutil <<-EOF | + open + show State:/Network/Global/DNS + quit +EOF +sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ' +)" + + if echo "${LOGDNSINFO_MAN_DNS_CONFIG}" | grep -q "ServerAddresses" ; then + readonly LOGDNSINFO_MAN_DNS_SA="$( trim "$( echo "${LOGDNSINFO_MAN_DNS_CONFIG}" | sed -e 's/^.*ServerAddresses[^{]*{[[:space:]]*\([^}]*\)[[:space:]]*}.*$/\1/g' )" )" + else + readonly LOGDNSINFO_MAN_DNS_SA=""; + fi + + if echo "${LOGDNSINFO_CUR_DNS_CONFIG}" | grep -q "ServerAddresses" ; then + readonly LOGDNSINFO_CUR_DNS_SA="$( trim "$( echo "${LOGDNSINFO_CUR_DNS_CONFIG}" | sed -e 's/^.*ServerAddresses[^{]*{[[:space:]]*\([^}]*\)[[:space:]]*}.*$/\1/g' )" )" + else + readonly LOGDNSINFO_CUR_DNS_SA=""; + fi + + set -e # resume abort on error + + logDnsInfo "${LOGDNSINFO_MAN_DNS_SA}" "${LOGDNSINFO_CUR_DNS_SA}" +} + +########################################################################################## +# +# START OF SCRIPT +# +########################################################################################## + +trap "" TSTP +trap "" HUP +trap "" INT +export PATH="/bin:/sbin:/usr/sbin:/usr/bin" + +readonly OUR_NAME="$( basename "${0}" )" + +logMessage "**********************************************" +logMessage "Start of output from ${OUR_NAME}" + +# Process optional arguments (if any) for the script +# Each one begins with a "-" +# They come from Bitmask, and come first, before the OpenVPN arguments +# So we set ARG_ script variables to their values and shift them out of the argument list +# When we're done, only the OpenVPN arguments remain for the rest of the script to use +ARG_TAP="false" +ARG_WAIT_FOR_DHCP_IF_TAP="false" +ARG_RESTORE_ON_DNS_RESET="false" +ARG_FLUSH_DNS_CACHE="false" +ARG_IGNORE_OPTION_FLAGS="" +ARG_EXTRA_LOGGING="false" +ARG_MONITOR_NETWORK_CONFIGURATION="false" +ARG_DO_NO_USE_DEFAULT_DOMAIN="false" +ARG_PREPEND_DOMAIN_NAME="false" +ARG_RESET_PRIMARY_INTERFACE_ON_DISCONNECT="false" +ARG_TB_PATH="/Applications/Bitmask.app" +ARG_RESTORE_ON_WINS_RESET="false" +ARG_DISABLE_IPV6_ON_TUN="false" +ARG_ENABLE_IPV6_ON_TAP="false" + +# Handle the arguments we know about by setting ARG_ script variables to their values, then shift them out +while [ {$#} ] ; do + if [ "$1" = "-6" ] ; then # -6 = ARG_ENABLE_IPV6_ON_TAP (for TAP connections only) + ARG_ENABLE_IPV6_ON_TAP="true" + shift + elif [ "$1" = "-9" ] ; then # -9 = ARG_DISABLE_IPV6_ON_TUN (for TUN connections only) + ARG_DISABLE_IPV6_ON_TUN="true" + shift + elif [ "$1" = "-a" ] ; then # -a = ARG_TAP + ARG_TAP="true" + shift + elif [ "$1" = "-b" ] ; then # -b = ARG_WAIT_FOR_DHCP_IF_TAP + ARG_WAIT_FOR_DHCP_IF_TAP="true" + shift + elif [ "$1" = "-d" ] ; then # -d = ARG_RESTORE_ON_DNS_RESET + ARG_RESTORE_ON_DNS_RESET="true" + shift + elif [ "$1" = "-f" ] ; then # -f = ARG_FLUSH_DNS_CACHE + ARG_FLUSH_DNS_CACHE="true" + shift + elif [ "${1:0:2}" = "-i" ] ; then # -i arguments are for leasewatcher + ARG_IGNORE_OPTION_FLAGS="${1}" + shift + elif [ "$1" = "-l" ] ; then # -l = ARG_EXTRA_LOGGING + ARG_EXTRA_LOGGING="true" + shift + elif [ "$1" = "-m" ] ; then # -m = ARG_MONITOR_NETWORK_CONFIGURATION + ARG_MONITOR_NETWORK_CONFIGURATION="true" + shift + elif [ "$1" = "-n" ] ; then # -n = ARG_DO_NO_USE_DEFAULT_DOMAIN + ARG_DO_NO_USE_DEFAULT_DOMAIN="true" + shift + elif [ "$1" = "-p" ] ; then # -p = ARG_PREPEND_DOMAIN_NAME + ARG_PREPEND_DOMAIN_NAME="true" + shift + elif [ "${1:0:2}" = "-p" ] ; then # -p arguments are for process-network-changes + ARG_IGNORE_OPTION_FLAGS="${1}" + shift + elif [ "$1" = "-r" ] ; then # -r = ARG_RESET_PRIMARY_INTERFACE_ON_DISCONNECT + ARG_RESET_PRIMARY_INTERFACE_ON_DISCONNECT="true" + shift + elif [ "${1:0:2}" = "-t" ] ; then + ARG_TB_PATH="${1:2}" # -t path of Bitmask.app + shift + elif [ "$1" = "-w" ] ; then # -w = ARG_RESTORE_ON_WINS_RESET + ARG_RESTORE_ON_WINS_RESET="true" + shift + else + if [ "${1:0:1}" = "-" ] ; then # Shift out Bitmask arguments (they start with "-") that we don't understand + shift # so the rest of the script sees only the OpenVPN arguments + else + break + fi + fi +done + +readonly ARG_MONITOR_NETWORK_CONFIGURATION ARG_RESTORE_ON_DNS_RESET ARG_RESTORE_ON_WINS_RESET ARG_TAP ARG_PREPEND_DOMAIN_NAME ARG_FLUSH_DNS_CACHE ARG_RESET_PRIMARY_INTERFACE_ON_DISCONNECT ARG_IGNORE_OPTION_FLAGS + +# Note: The script log path name is constructed from the path of the regular config file, not the shadow copy +# if the config is shadow copy, e.g. /Library/Application Support/Bitmask/Users/Jonathan/Folder/Subfolder/config.ovpn +# then convert to regular config /Users/Jonathan/Library/Application Support/Bitmask/Configurations/Folder/Subfolder/config.ovpn +# to get the script log path +# Note: "/Users/..." works even if the home directory has a different path; it is used in the name of the log file, and is not used as a path to get to anything. +readonly TBALTPREFIX="/Library/Application Support/Bitmask/Users/" +readonly TBALTPREFIXLEN="${#TBALTPREFIX}" +readonly TBCONFIGSTART="${config:0:$TBALTPREFIXLEN}" +if [ "$TBCONFIGSTART" = "$TBALTPREFIX" ] ; then + readonly TBBASE="${config:$TBALTPREFIXLEN}" + readonly TBSUFFIX="${TBBASE#*/}" + readonly TBUSERNAME="${TBBASE%%/*}" + readonly TBCONFIG="/Users/$TBUSERNAME/Library/Application Support/Bitmask/Configurations/$TBSUFFIX" +else + readonly TBCONFIG="${config}" +fi + +readonly CONFIG_PATH_DASHES_SLASHES="$( echo "${TBCONFIG}" | sed -e 's/-/--/g' | sed -e 's/\//-S/g' )" +readonly SCRIPT_LOG_FILE="/Library/Application Support/Bitmask/Logs/${CONFIG_PATH_DASHES_SLASHES}.script.log" + +readonly TB_RESOURCES_PATH="${ARG_TB_PATH}/Contents/Resources" +readonly FREE_PUBLIC_DNS_SERVERS_LIST_PATH="${TB_RESOURCES_PATH}/FreePublicDnsServersList.txt" + +# These scripts use a launchd .plist to set up to monitor the network configuration. +# +# If Bitmask.app is located in /Applications, we load the launchd .plist directly from within the .app. +# +# If Bitmask.app is not located in /Applications (i.e., we are debugging), we create a modified version of the launchd .plist and use +# that modified copy in the 'launchctl load' command. (The modification is that the path to process-network-changes or leasewatch program +# in the .plist is changed to point to the copy of the program that is inside the running Bitmask.) +# +# The variables involved in this are set up here: +# +# LEASEWATCHER_PLIST_PATH is the path of the .plist to use in the 'launchctl load' command +# LEASEWATCHER_TEMPLATE_PATH is an empty string if we load the .plist directly from within the .app, +# or it is the path to the original .plist inside the .app which we copy and modify +# REMOVE_LEASEWATCHER_PLIST is "true" if a modified .plist was used and should be deleted after it is unloaded +# or "false' if the plist was loaded directly from the .app +# +# LEASEWATCHER_PLIST_PATH and REMOVE_LEASEWATCHER_PLIST are passed to the other scripts via the scutil State:/Network/OpenVPN mechanism + +if [ "${ARG_IGNORE_OPTION_FLAGS:0:2}" = "-p" ] ; then + readonly LEASEWATCHER_PLIST="ProcessNetworkChanges.plist" +else + readonly LEASEWATCHER_PLIST="LeaseWatch.plist" +fi +if [ "${ARG_TB_PATH}" = "/Applications/Bitmask.app" ] ; then + readonly LEASEWATCHER_PLIST_PATH="${TB_RESOURCES_PATH}/${LEASEWATCHER_PLIST}" + readonly LEASEWATCHER_TEMPLATE_PATH="" + readonly REMOVE_LEASEWATCHER_PLIST="false" +else + readonly LEASEWATCHER_PLIST_PATH="/Library/Application Support/Bitmask/${LEASEWATCHER_PLIST}" + readonly LEASEWATCHER_TEMPLATE_PATH="${TB_RESOURCES_PATH}/${LEASEWATCHER_PLIST}" + readonly REMOVE_LEASEWATCHER_PLIST="true" +fi + +set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors +readonly OSVER="$( sw_vers | grep 'ProductVersion:' | grep -o '10\.[0-9]*' )" +set -e # We instruct bash that it CAN again fail on errors + +if ${ARG_DO_NO_USE_DEFAULT_DOMAIN} ; then + readonly DEFAULT_DOMAIN_NAME="" +else + readonly DEFAULT_DOMAIN_NAME="openvpn" +fi + +bRouteGatewayIsDhcp="false" + +# We sleep to allow time for OS X to process network settings +sleep 2 + +EXIT_CODE=0 + +if ${ARG_TAP} ; then + + # IPv6 should be re-enabled only for TUN, not TAP + readonly ipv6_disabled_services="" + readonly ipv6_disabled_services_encoded="" + + # Still need to do: Look for route-gateway dhcp (TAP isn't always DHCP) + bRouteGatewayIsDhcp="false" + if [ -z "${route_vpn_gateway}" -o "$route_vpn_gateway" == "dhcp" -o "$route_vpn_gateway" == "DHCP" ]; then + bRouteGatewayIsDhcp="true" + fi + + if [ "$bRouteGatewayIsDhcp" == "true" ]; then + logDebugMessage "DEBUG: bRouteGatewayIsDhcp is TRUE" + if [ -z "$dev" ]; then + logMessage "ERROR: Cannot configure TAP interface for DHCP without \$dev being defined. Exiting." + # We don't create the "/tmp/bitmask-downscript-needs-to-be-run.txt" file, because the down script does NOT need to be run since we didn't do anything + logMessage "End of output from ${OUR_NAME}" + logMessage "**********************************************" + exit 1 + fi + + logDebugMessage "DEBUG: About to 'ipconfig set \"$dev\" DHCP" + ipconfig set "$dev" DHCP + logMessage "Did 'ipconfig set \"$dev\" DHCP'" + + if ${ARG_ENABLE_IPV6_ON_TAP} ; then + ipconfig set "$dev" AUTOMATIC-V6 + logMessage "Did 'ipconfig set \"$dev\" AUTOMATIC-V6'" + fi + + if ${ARG_WAIT_FOR_DHCP_IF_TAP} ; then + logMessage "Configuring tap DNS via DHCP synchronously" + configureDhcpDns + else + logMessage "Configuring tap DNS via DHCP asynchronously" + configureDhcpDns & # This must be run asynchronously; the DHCP lease will not complete until this script exits + EXIT_CODE=0 + fi + elif [ "$foreign_option_1" == "" ]; then + logMessage "NOTE: No network configuration changes need to be made." + if ${ARG_MONITOR_NETWORK_CONFIGURATION} ; then + logMessage "WARNING: Will NOT monitor for other network configuration changes." + fi + if ${ARG_ENABLE_IPV6_ON_TAP} ; then + logMessage "WARNING: Will NOT set up IPv6 on TAP device because it does not use DHCP." + fi + logDnsInfoNoChanges + flushDNSCache + else + if ${ARG_ENABLE_IPV6_ON_TAP} ; then + logMessage "WARNING: Will NOT set up IPv6 on TAP device because it does not use DHCP." + fi + logMessage "Configuring tap DNS via OpenVPN" + configureOpenVpnDns + EXIT_CODE=$? + fi +else + if [ "$foreign_option_1" == "" ]; then + logMessage "NOTE: No network configuration changes need to be made." + if ${ARG_MONITOR_NETWORK_CONFIGURATION} ; then + logMessage "WARNING: Will NOT monitor for other network configuration changes." + fi + if ${ARG_DISABLE_IPV6_ON_TUN} ; then + logMessage "WARNING: Will NOT disable IPv6 settings." + fi + logDnsInfoNoChanges + flushDNSCache + else + + ipv6_disabled_services="" + if ${ARG_DISABLE_IPV6_ON_TUN} ; then + ipv6_disabled_services="$( disable_ipv6 )" + if [ "$ipv6_disabled_services" != "" ] ; then + printf %s "$ipv6_disabled_services +" | \ + while IFS= read -r dipv6_service ; do + logMessage "Disabled IPv6 for '$dipv6_service'" + done + fi + fi + readonly ipv6_disabled_services + # Note '\n' is translated into '\t' so it is all on one line, because grep and sed only work with single lines + readonly ipv6_disabled_services_encoded="$( echo "$ipv6_disabled_services" | tr '\n' '\t' )" + + configureOpenVpnDns + EXIT_CODE=$? + fi +fi + +touch "/tmp/bitmask-downscript-needs-to-be-run.txt" + +logMessage "End of output from ${OUR_NAME}" +logMessage "**********************************************" + +exit $EXIT_CODE diff --git a/pkg/osx/daemon.py b/pkg/osx/daemon.py new file mode 100644 index 00000000..07810cf1 --- /dev/null +++ b/pkg/osx/daemon.py @@ -0,0 +1,926 @@ +# -*- coding: utf-8 -*- + +# daemon/daemon.py +# Part of ‘python-daemon’, an implementation of PEP 3143. +# +# Copyright © 2008–2015 Ben Finney +# Copyright © 2007–2008 Robert Niederreiter, Jens Klein +# Copyright © 2004–2005 Chad J. Schroeder +# Copyright © 2003 Clark Evans +# Copyright © 2002 Noah Spurrier +# Copyright © 2001 Jürgen Hermann +# +# This is free software: you may copy, modify, and/or distribute this work +# under the terms of the Apache License, version 2.0 as published by the +# Apache Software Foundation. +# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details. + +""" Daemon process behaviour. + """ + +from __future__ import (absolute_import, unicode_literals) + +import os +import sys +import resource +import errno +import signal +import socket +import atexit +try: + # Python 2 has both ‘str’ (bytes) and ‘unicode’ (text). + basestring = basestring + unicode = unicode +except NameError: + # Python 3 names the Unicode data type ‘str’. + basestring = str + unicode = str + + +class DaemonError(Exception): + """ Base exception class for errors from this module. """ + + def __init__(self, *args, **kwargs): + self._chain_from_context() + + super(DaemonError, self).__init__(*args, **kwargs) + + def _chain_from_context(self): + _chain_exception_from_existing_exception_context(self, as_cause=True) + + +class DaemonOSEnvironmentError(DaemonError, OSError): + """ Exception raised when daemon OS environment setup receives error. """ + + +class DaemonProcessDetachError(DaemonError, OSError): + """ Exception raised when process detach fails. """ + + +class DaemonContext: + """ Context for turning the current program into a daemon process. + + A `DaemonContext` instance represents the behaviour settings and + process context for the program when it becomes a daemon. The + behaviour and environment is customised by setting options on the + instance, before calling the `open` method. + + Each option can be passed as a keyword argument to the `DaemonContext` + constructor, or subsequently altered by assigning to an attribute on + the instance at any time prior to calling `open`. That is, for + options named `wibble` and `wubble`, the following invocation:: + + foo = daemon.DaemonContext(wibble=bar, wubble=baz) + foo.open() + + is equivalent to:: + + foo = daemon.DaemonContext() + foo.wibble = bar + foo.wubble = baz + foo.open() + + The following options are defined. + + `files_preserve` + :Default: ``None`` + + List of files that should *not* be closed when starting the + daemon. If ``None``, all open file descriptors will be closed. + + Elements of the list are file descriptors (as returned by a file + object's `fileno()` method) or Python `file` objects. Each + specifies a file that is not to be closed during daemon start. + + `chroot_directory` + :Default: ``None`` + + Full path to a directory to set as the effective root directory of + the process. If ``None``, specifies that the root directory is not + to be changed. + + `working_directory` + :Default: ``'/'`` + + Full path of the working directory to which the process should + change on daemon start. + + Since a filesystem cannot be unmounted if a process has its + current working directory on that filesystem, this should either + be left at default or set to a directory that is a sensible “home + directory” for the daemon while it is running. + + `umask` + :Default: ``0`` + + File access creation mask (“umask”) to set for the process on + daemon start. + + A daemon should not rely on the parent process's umask value, + which is beyond its control and may prevent creating a file with + the required access mode. So when the daemon context opens, the + umask is set to an explicit known value. + + If the conventional value of 0 is too open, consider setting a + value such as 0o022, 0o027, 0o077, or another specific value. + Otherwise, ensure the daemon creates every file with an + explicit access mode for the purpose. + + `pidfile` + :Default: ``None`` + + Context manager for a PID lock file. When the daemon context opens + and closes, it enters and exits the `pidfile` context manager. + + `detach_process` + :Default: ``None`` + + If ``True``, detach the process context when opening the daemon + context; if ``False``, do not detach. + + If unspecified (``None``) during initialisation of the instance, + this will be set to ``True`` by default, and ``False`` only if + detaching the process is determined to be redundant; for example, + in the case when the process was started by `init`, by `initd`, or + by `inetd`. + + `signal_map` + :Default: system-dependent + + Mapping from operating system signals to callback actions. + + The mapping is used when the daemon context opens, and determines + the action for each signal's signal handler: + + * A value of ``None`` will ignore the signal (by setting the + signal action to ``signal.SIG_IGN``). + + * A string value will be used as the name of an attribute on the + ``DaemonContext`` instance. The attribute's value will be used + as the action for the signal handler. + + * Any other value will be used as the action for the + signal handler. See the ``signal.signal`` documentation + for details of the signal handler interface. + + The default value depends on which signals are defined on the + running system. Each item from the list below whose signal is + actually defined in the ``signal`` module will appear in the + default map: + + * ``signal.SIGTTIN``: ``None`` + + * ``signal.SIGTTOU``: ``None`` + + * ``signal.SIGTSTP``: ``None`` + + * ``signal.SIGTERM``: ``'terminate'`` + + Depending on how the program will interact with its child + processes, it may need to specify a signal map that + includes the ``signal.SIGCHLD`` signal (received when a + child process exits). See the specific operating system's + documentation for more detail on how to determine what + circumstances dictate the need for signal handlers. + + `uid` + :Default: ``os.getuid()`` + + `gid` + :Default: ``os.getgid()`` + + The user ID (“UID”) value and group ID (“GID”) value to switch + the process to on daemon start. + + The default values, the real UID and GID of the process, will + relinquish any effective privilege elevation inherited by the + process. + + `prevent_core` + :Default: ``True`` + + If true, prevents the generation of core files, in order to avoid + leaking sensitive information from daemons run as `root`. + + `stdin` + :Default: ``None`` + + `stdout` + :Default: ``None`` + + `stderr` + :Default: ``None`` + + Each of `stdin`, `stdout`, and `stderr` is a file-like object + which will be used as the new file for the standard I/O stream + `sys.stdin`, `sys.stdout`, and `sys.stderr` respectively. The file + should therefore be open, with a minimum of mode 'r' in the case + of `stdin`, and mimimum of mode 'w+' in the case of `stdout` and + `stderr`. + + If the object has a `fileno()` method that returns a file + descriptor, the corresponding file will be excluded from being + closed during daemon start (that is, it will be treated as though + it were listed in `files_preserve`). + + If ``None``, the corresponding system stream is re-bound to the + file named by `os.devnull`. + + """ + + __metaclass__ = type + + def __init__( + self, + chroot_directory=None, + working_directory="/", + umask=0, + uid=None, + gid=None, + prevent_core=True, + detach_process=None, + files_preserve=None, + pidfile=None, + stdin=None, + stdout=None, + stderr=None, + signal_map=None, + ): + """ Set up a new instance. """ + self.chroot_directory = chroot_directory + self.working_directory = working_directory + self.umask = umask + self.prevent_core = prevent_core + self.files_preserve = files_preserve + self.pidfile = pidfile + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr + + if uid is None: + uid = os.getuid() + self.uid = uid + if gid is None: + gid = os.getgid() + self.gid = gid + + if detach_process is None: + detach_process = is_detach_process_context_required() + self.detach_process = detach_process + + if signal_map is None: + signal_map = make_default_signal_map() + self.signal_map = signal_map + + self._is_open = False + + @property + def is_open(self): + """ ``True`` if the instance is currently open. """ + return self._is_open + + def open(self): + """ Become a daemon process. + + :return: ``None``. + + Open the daemon context, turning the current program into a daemon + process. This performs the following steps: + + * If this instance's `is_open` property is true, return + immediately. This makes it safe to call `open` multiple times on + an instance. + + * If the `prevent_core` attribute is true, set the resource limits + for the process to prevent any core dump from the process. + + * If the `chroot_directory` attribute is not ``None``, set the + effective root directory of the process to that directory (via + `os.chroot`). + + This allows running the daemon process inside a “chroot gaol” + as a means of limiting the system's exposure to rogue behaviour + by the process. Note that the specified directory needs to + already be set up for this purpose. + + * Set the process UID and GID to the `uid` and `gid` attribute + values. + + * Close all open file descriptors. This excludes those listed in + the `files_preserve` attribute, and those that correspond to the + `stdin`, `stdout`, or `stderr` attributes. + + * Change current working directory to the path specified by the + `working_directory` attribute. + + * Reset the file access creation mask to the value specified by + the `umask` attribute. + + * If the `detach_process` option is true, detach the current + process into its own process group, and disassociate from any + controlling terminal. + + * Set signal handlers as specified by the `signal_map` attribute. + + * If any of the attributes `stdin`, `stdout`, `stderr` are not + ``None``, bind the system streams `sys.stdin`, `sys.stdout`, + and/or `sys.stderr` to the files represented by the + corresponding attributes. Where the attribute has a file + descriptor, the descriptor is duplicated (instead of re-binding + the name). + + * If the `pidfile` attribute is not ``None``, enter its context + manager. + + * Mark this instance as open (for the purpose of future `open` and + `close` calls). + + * Register the `close` method to be called during Python's exit + processing. + + When the function returns, the running program is a daemon + process. + + """ + if self.is_open: + return + + if self.chroot_directory is not None: + change_root_directory(self.chroot_directory) + + if self.prevent_core: + prevent_core_dump() + + change_file_creation_mask(self.umask) + change_working_directory(self.working_directory) + change_process_owner(self.uid, self.gid) + + if self.detach_process: + detach_process_context() + + signal_handler_map = self._make_signal_handler_map() + set_signal_handlers(signal_handler_map) + + exclude_fds = self._get_exclude_file_descriptors() + close_all_open_files(exclude=exclude_fds) + + redirect_stream(sys.stdin, self.stdin) + redirect_stream(sys.stdout, self.stdout) + redirect_stream(sys.stderr, self.stderr) + + if self.pidfile is not None: + self.pidfile.__enter__() + + self._is_open = True + + register_atexit_function(self.close) + + def __enter__(self): + """ Context manager entry point. """ + self.open() + return self + + def close(self): + """ Exit the daemon process context. + + :return: ``None``. + + Close the daemon context. This performs the following steps: + + * If this instance's `is_open` property is false, return + immediately. This makes it safe to call `close` multiple times + on an instance. + + * If the `pidfile` attribute is not ``None``, exit its context + manager. + + * Mark this instance as closed (for the purpose of future `open` + and `close` calls). + + """ + if not self.is_open: + return + + if self.pidfile is not None: + # Follow the interface for telling a context manager to exit, + # . + self.pidfile.__exit__(None, None, None) + + self._is_open = False + + def __exit__(self, exc_type, exc_value, traceback): + """ Context manager exit point. """ + self.close() + + def terminate(self, signal_number, stack_frame): + """ Signal handler for end-process signals. + + :param signal_number: The OS signal number received. + :param stack_frame: The frame object at the point the + signal was received. + :return: ``None``. + + Signal handler for the ``signal.SIGTERM`` signal. Performs the + following step: + + * Raise a ``SystemExit`` exception explaining the signal. + + """ + exception = SystemExit( + "Terminating on signal {signal_number!r}".format( + signal_number=signal_number)) + raise exception + + def _get_exclude_file_descriptors(self): + """ Get the set of file descriptors to exclude closing. + + :return: A set containing the file descriptors for the + files to be preserved. + + The file descriptors to be preserved are those from the + items in `files_preserve`, and also each of `stdin`, + `stdout`, and `stderr`. For each item: + + * If the item is ``None``, it is omitted from the return + set. + + * If the item's ``fileno()`` method returns a value, that + value is in the return set. + + * Otherwise, the item is in the return set verbatim. + + """ + files_preserve = self.files_preserve + if files_preserve is None: + files_preserve = [] + files_preserve.extend( + item for item in [self.stdin, self.stdout, self.stderr] + if hasattr(item, 'fileno')) + + exclude_descriptors = set() + for item in files_preserve: + if item is None: + continue + file_descriptor = _get_file_descriptor(item) + if file_descriptor is not None: + exclude_descriptors.add(file_descriptor) + else: + exclude_descriptors.add(item) + + return exclude_descriptors + + def _make_signal_handler(self, target): + """ Make the signal handler for a specified target object. + + :param target: A specification of the target for the + handler; see below. + :return: The value for use by `signal.signal()`. + + If `target` is ``None``, return ``signal.SIG_IGN``. If `target` + is a text string, return the attribute of this instance named + by that string. Otherwise, return `target` itself. + + """ + if target is None: + result = signal.SIG_IGN + elif isinstance(target, unicode): + name = target + result = getattr(self, name) + else: + result = target + + return result + + def _make_signal_handler_map(self): + """ Make the map from signals to handlers for this instance. + + :return: The constructed signal map for this instance. + + Construct a map from signal numbers to handlers for this + context instance, suitable for passing to + `set_signal_handlers`. + + """ + signal_handler_map = dict( + (signal_number, self._make_signal_handler(target)) + for (signal_number, target) in self.signal_map.items()) + return signal_handler_map + + +def _get_file_descriptor(obj): + """ Get the file descriptor, if the object has one. + + :param obj: The object expected to be a file-like object. + :return: The file descriptor iff the file supports it; otherwise + ``None``. + + The object may be a non-file object. It may also be a + file-like object with no support for a file descriptor. In + either case, return ``None``. + + """ + file_descriptor = None + if hasattr(obj, 'fileno'): + try: + file_descriptor = obj.fileno() + except ValueError: + # The item doesn't support a file descriptor. + pass + + return file_descriptor + + +def change_working_directory(directory): + """ Change the working directory of this process. + + :param directory: The target directory path. + :return: ``None``. + + """ + try: + os.chdir(directory) + except Exception as exc: + error = DaemonOSEnvironmentError( + "Unable to change working directory ({exc})".format(exc=exc)) + raise error + + +def change_root_directory(directory): + """ Change the root directory of this process. + + :param directory: The target directory path. + :return: ``None``. + + Set the current working directory, then the process root directory, + to the specified `directory`. Requires appropriate OS privileges + for this process. + + """ + try: + os.chdir(directory) + os.chroot(directory) + except Exception as exc: + error = DaemonOSEnvironmentError( + "Unable to change root directory ({exc})".format(exc=exc)) + raise error + + +def change_file_creation_mask(mask): + """ Change the file creation mask for this process. + + :param mask: The numeric file creation mask to set. + :return: ``None``. + + """ + try: + os.umask(mask) + except Exception as exc: + error = DaemonOSEnvironmentError( + "Unable to change file creation mask ({exc})".format(exc=exc)) + raise error + + +def change_process_owner(uid, gid): + """ Change the owning UID and GID of this process. + + :param uid: The target UID for the daemon process. + :param gid: The target GID for the daemon process. + :return: ``None``. + + Set the GID then the UID of the process (in that order, to avoid + permission errors) to the specified `gid` and `uid` values. + Requires appropriate OS privileges for this process. + + """ + try: + os.setgid(gid) + os.setuid(uid) + except Exception as exc: + error = DaemonOSEnvironmentError( + "Unable to change process owner ({exc})".format(exc=exc)) + raise error + + +def prevent_core_dump(): + """ Prevent this process from generating a core dump. + + :return: ``None``. + + Set the soft and hard limits for core dump size to zero. On Unix, + this entirely prevents the process from creating core dump. + + """ + core_resource = resource.RLIMIT_CORE + + try: + # Ensure the resource limit exists on this platform, by requesting + # its current value. + core_limit_prev = resource.getrlimit(core_resource) + except ValueError as exc: + error = DaemonOSEnvironmentError( + "System does not support RLIMIT_CORE resource limit" + " ({exc})".format(exc=exc)) + raise error + + # Set hard and soft limits to zero, i.e. no core dump at all. + core_limit = (0, 0) + resource.setrlimit(core_resource, core_limit) + + +def detach_process_context(): + """ Detach the process context from parent and session. + + :return: ``None``. + + Detach from the parent process and session group, allowing the + parent to exit while this process continues running. + + Reference: “Advanced Programming in the Unix Environment”, + section 13.3, by W. Richard Stevens, published 1993 by + Addison-Wesley. + + """ + + def fork_then_exit_parent(error_message): + """ Fork a child process, then exit the parent process. + + :param error_message: Message for the exception in case of a + detach failure. + :return: ``None``. + :raise DaemonProcessDetachError: If the fork fails. + + """ + try: + pid = os.fork() + if pid > 0: + os._exit(0) + except OSError as exc: + error = DaemonProcessDetachError( + "{message}: [{exc.errno:d}] {exc.strerror}".format( + message=error_message, exc=exc)) + raise error + + fork_then_exit_parent(error_message="Failed first fork") + os.setsid() + fork_then_exit_parent(error_message="Failed second fork") + + +def is_process_started_by_init(): + """ Determine whether the current process is started by `init`. + + :return: ``True`` iff the parent process is `init`; otherwise + ``False``. + + The `init` process is the one with process ID of 1. + + """ + result = False + + init_pid = 1 + if os.getppid() == init_pid: + result = True + + return result + + +def is_socket(fd): + """ Determine whether the file descriptor is a socket. + + :param fd: The file descriptor to interrogate. + :return: ``True`` iff the file descriptor is a socket; otherwise + ``False``. + + Query the socket type of `fd`. If there is no error, the file is a + socket. + + """ + result = False + + file_socket = socket.fromfd(fd, socket.AF_INET, socket.SOCK_RAW) + + try: + socket_type = file_socket.getsockopt( + socket.SOL_SOCKET, socket.SO_TYPE) + except socket.error as exc: + exc_errno = exc.args[0] + if exc_errno == errno.ENOTSOCK: + # Socket operation on non-socket. + pass + else: + # Some other socket error. + result = True + else: + # No error getting socket type. + result = True + + return result + + +def is_process_started_by_superserver(): + """ Determine whether the current process is started by the superserver. + + :return: ``True`` if this process was started by the internet + superserver; otherwise ``False``. + + The internet superserver creates a network socket, and + attaches it to the standard streams of the child process. If + that is the case for this process, return ``True``, otherwise + ``False``. + + """ + result = False + + stdin_fd = sys.__stdin__.fileno() + if is_socket(stdin_fd): + result = True + + return result + + +def is_detach_process_context_required(): + """ Determine whether detaching the process context is required. + + :return: ``True`` iff the process is already detached; otherwise + ``False``. + + The process environment is interrogated for the following: + + * Process was started by `init`; or + + * Process was started by `inetd`. + + If any of the above are true, the process is deemed to be already + detached. + + """ + result = True + if is_process_started_by_init() or is_process_started_by_superserver(): + result = False + + return result + + +def close_file_descriptor_if_open(fd): + """ Close a file descriptor if already open. + + :param fd: The file descriptor to close. + :return: ``None``. + + Close the file descriptor `fd`, suppressing an error in the + case the file was not open. + + """ + try: + os.close(fd) + except EnvironmentError as exc: + if exc.errno == errno.EBADF: + # File descriptor was not open. + pass + else: + error = DaemonOSEnvironmentError( + "Failed to close file descriptor {fd:d} ({exc})".format( + fd=fd, exc=exc)) + raise error + + +MAXFD = 2048 + +def get_maximum_file_descriptors(): + """ Get the maximum number of open file descriptors for this process. + + :return: The number (integer) to use as the maximum number of open + files for this process. + + The maximum is the process hard resource limit of maximum number of + open file descriptors. If the limit is “infinity”, a default value + of ``MAXFD`` is returned. + + """ + limits = resource.getrlimit(resource.RLIMIT_NOFILE) + result = limits[1] + if result == resource.RLIM_INFINITY: + result = MAXFD + return result + + +def close_all_open_files(exclude=set()): + """ Close all open file descriptors. + + :param exclude: Collection of file descriptors to skip when closing + files. + :return: ``None``. + + Closes every file descriptor (if open) of this process. If + specified, `exclude` is a set of file descriptors to *not* + close. + + """ + maxfd = get_maximum_file_descriptors() + for fd in reversed(range(maxfd)): + if fd not in exclude: + close_file_descriptor_if_open(fd) + + +def redirect_stream(system_stream, target_stream): + """ Redirect a system stream to a specified file. + + :param standard_stream: A file object representing a standard I/O + stream. + :param target_stream: The target file object for the redirected + stream, or ``None`` to specify the null device. + :return: ``None``. + + `system_stream` is a standard system stream such as + ``sys.stdout``. `target_stream` is an open file object that + should replace the corresponding system stream object. + + If `target_stream` is ``None``, defaults to opening the + operating system's null device and using its file descriptor. + + """ + if target_stream is None: + target_fd = os.open(os.devnull, os.O_RDWR) + else: + target_fd = target_stream.fileno() + os.dup2(target_fd, system_stream.fileno()) + + +def make_default_signal_map(): + """ Make the default signal map for this system. + + :return: A mapping from signal number to handler object. + + The signals available differ by system. The map will not contain + any signals not defined on the running system. + + """ + name_map = { + 'SIGTSTP': None, + 'SIGTTIN': None, + 'SIGTTOU': None, + 'SIGTERM': 'terminate', + } + signal_map = dict( + (getattr(signal, name), target) + for (name, target) in name_map.items() + if hasattr(signal, name)) + + return signal_map + + +def set_signal_handlers(signal_handler_map): + """ Set the signal handlers as specified. + + :param signal_handler_map: A map from signal number to handler + object. + :return: ``None``. + + See the `signal` module for details on signal numbers and signal + handlers. + + """ + for (signal_number, handler) in signal_handler_map.items(): + signal.signal(signal_number, handler) + + +def register_atexit_function(func): + """ Register a function for processing at program exit. + + :param func: A callable function expecting no arguments. + :return: ``None``. + + The function `func` is registered for a call with no arguments + at program exit. + + """ + atexit.register(func) + + +def _chain_exception_from_existing_exception_context(exc, as_cause=False): + """ Decorate the specified exception with the existing exception context. + + :param exc: The exception instance to decorate. + :param as_cause: If true, the existing context is declared to be + the cause of the exception. + :return: ``None``. + + :PEP:`344` describes syntax and attributes (`__traceback__`, + `__context__`, `__cause__`) for use in exception chaining. + + Python 2 does not have that syntax, so this function decorates + the exception with values from the current exception context. + + """ + (existing_exc_type, existing_exc, existing_traceback) = sys.exc_info() + if as_cause: + exc.__cause__ = existing_exc + else: + exc.__context__ = existing_exc + exc.__traceback__ = existing_traceback + + +# Local variables: +# coding: utf-8 +# mode: python +# End: +# vim: fileencoding=utf-8 filetype=python : diff --git a/pkg/osx/install/ProcessNetworkChanges.plist.template b/pkg/osx/install/ProcessNetworkChanges.plist.template deleted file mode 100644 index eaf54fcf..00000000 --- a/pkg/osx/install/ProcessNetworkChanges.plist.template +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Label - se.leap.openvpn.process-network-changes - ProgramArguments - - ${DIR}/process-network-changes - - WatchPaths - - /Library/Preferences/SystemConfiguration - - - diff --git a/pkg/osx/install/client.down.sh b/pkg/osx/install/client.down.sh deleted file mode 100755 index 52ba4de6..00000000 --- a/pkg/osx/install/client.down.sh +++ /dev/null @@ -1,148 +0,0 @@ -#!/bin/bash -e -# Note: must be bash; uses bash-specific tricks -# -# ****************************************************************************************************************** -# Based on the Tunnelblick script that just "does everything!" -# It handles TUN and TAP interfaces, -# pushed configurations and DHCP leases. :) -# -# This is the "Down" version of the script, executed after the connection is -# closed. -# -# Created by: Nick Williams (using original code and parts of old Tblk scripts) -# -# ****************************************************************************************************************** -# TODO: review and adapt version 3 of the clientX.down.sh - -trap "" TSTP -trap "" HUP -trap "" INT -export PATH="/bin:/sbin:/usr/sbin:/usr/bin" - -readonly LOG_MESSAGE_COMMAND=$(basename "${0}") - -# Quick check - is the configuration there? -if ! scutil -w State:/Network/OpenVPN &>/dev/null -t 1 ; then - # Configuration isn't there, so we forget it - echo "$(date '+%a %b %e %T %Y') *LEAPClient $LOG_MESSAGE_COMMAND: WARNING: No existing OpenVPN DNS configuration found; not tearing down anything; exiting." - exit 0 -fi - -# NOTE: This script does not use any arguments passed to it by OpenVPN, so it doesn't shift LEAPClient options out of the argument list - -# Get info saved by the up script -LEAPCLIENT_CONFIG="$(/usr/sbin/scutil <<-EOF - open - show State:/Network/OpenVPN - quit -EOF)" - -ARG_MONITOR_NETWORK_CONFIGURATION="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*MonitorNetwork :' | sed -e 's/^.*: //g')" -LEASEWATCHER_PLIST_PATH="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*LeaseWatcherPlistPath :' | sed -e 's/^.*: //g')" -PSID="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*Service :' | sed -e 's/^.*: //g')" -SCRIPT_LOG_FILE="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*ScriptLogFile :' | sed -e 's/^.*: //g')" -# Don't need: ARG_RESTORE_ON_DNS_RESET="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*RestoreOnDNSReset :' | sed -e 's/^.*: //g')" -# Don't need: ARG_RESTORE_ON_WINS_RESET="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*RestoreOnWINSReset :' | sed -e 's/^.*: //g')" -# Don't need: PROCESS="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*PID :' | sed -e 's/^.*: //g')" -# Don't need: ARG_IGNORE_OPTION_FLAGS="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*IgnoreOptionFlags :' | sed -e 's/^.*: //g')" -ARG_TAP="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*IsTapInterface :' | sed -e 's/^.*: //g')" -bRouteGatewayIsDhcp="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*RouteGatewayIsDhcp :' | sed -e 's/^.*: //g')" - -# @param String message - The message to log -logMessage() -{ - echo "$(date '+%a %b %e %T %Y') *LEAP CLient $LOG_MESSAGE_COMMAND: "${@} >> "${SCRIPT_LOG_FILE}" -} - -trim() -{ - echo ${@} -} - -if ${ARG_TAP} ; then - if [ "$bRouteGatewayIsDhcp" == "true" ]; then - if [ -z "$dev" ]; then - logMessage "Cannot configure TAP interface for DHCP without \$dev being defined. Device may not have disconnected properly." - else - set +e - ipconfig set "$dev" NONE 2>/dev/null - set -e - fi - fi -fi - -# Issue warning if the primary service ID has changed -PSID_CURRENT="$( (scutil | grep Service | sed -e 's/.*Service : //')<<- EOF - open - show State:/Network/OpenVPN - quit -EOF)" -if [ "${PSID}" != "${PSID_CURRENT}" ] ; then - logMessage "Ignoring change of Network Primary Service from ${PSID} to ${PSID_CURRENT}" -fi - -# Remove leasewatcher -if ${ARG_MONITOR_NETWORK_CONFIGURATION} ; then - launchctl unload "${LEASEWATCHER_PLIST_PATH}" - logMessage "Cancelled monitoring of system configuration changes" -fi - -# Restore configurations -DNS_OLD="$(/usr/sbin/scutil <<-EOF - open - show State:/Network/OpenVPN/OldDNS - quit -EOF)" -WINS_OLD="$(/usr/sbin/scutil <<-EOF - open - show State:/Network/OpenVPN/OldSMB - quit -EOF)" -TB_NO_SUCH_KEY=" { - LEAPClientNoSuchKey : true -}" - -if [ "${DNS_OLD}" = "${TB_NO_SUCH_KEY}" ] ; then - scutil <<- EOF - open - remove State:/Network/Service/${PSID}/DNS - quit -EOF -else - scutil <<- EOF - open - get State:/Network/OpenVPN/OldDNS - set State:/Network/Service/${PSID}/DNS - quit -EOF -fi - -if [ "${WINS_OLD}" = "${TB_NO_SUCH_KEY}" ] ; then - scutil <<- EOF - open - remove State:/Network/Service/${PSID}/SMB - quit -EOF -else - scutil <<- EOF - open - get State:/Network/OpenVPN/OldSMB - set State:/Network/Service/${PSID}/SMB - quit -EOF -fi - -logMessage "Restored the DNS and WINS configurations" - -# Remove our system configuration data -scutil <<- EOF - open - remove State:/Network/OpenVPN/SMB - remove State:/Network/OpenVPN/DNS - remove State:/Network/OpenVPN/OldSMB - remove State:/Network/OpenVPN/OldDNS - remove State:/Network/OpenVPN - quit -EOF - -exit 0 diff --git a/pkg/osx/install/client.up.sh b/pkg/osx/install/client.up.sh deleted file mode 100755 index be9814c2..00000000 --- a/pkg/osx/install/client.up.sh +++ /dev/null @@ -1,599 +0,0 @@ -#!/bin/bash -e -# Note: must be bash; uses bash-specific tricks -# -# ****************************************************************************************************************** -# Taken from the Tunnelblick script that "just does everything!" -# It handles TUN and TAP interfaces, -# pushed configurations, DHCP with DNS and WINS, and renewed DHCP leases. :) -# -# This is the "Up" version of the script, executed after the interface is -# initialized. -# -# Created by: Nick Williams (using original code and parts of old Tblk scripts) -# -# ****************************************************************************************************************** -# TODO: review and adapt revision 3 of the clientX-up.sh instead - -trap "" TSTP -trap "" HUP -trap "" INT -export PATH="/bin:/sbin:/usr/sbin:/usr/bin" - -# Process optional arguments (if any) for the script -# Each one begins with a "-" -# They come from the leap-client invocation, and come first, before the OpenVPN arguments -# So we set ARG_ script variables to their values and shift them out of the argument list -# When we're done, only the OpenVPN arguments remain for the rest of the script to use -ARG_MONITOR_NETWORK_CONFIGURATION="false" -ARG_RESTORE_ON_DNS_RESET="false" -ARG_RESTORE_ON_WINS_RESET="false" -ARG_TAP="false" -ARG_IGNORE_OPTION_FLAGS="" - -while [ {$#} ] ; do - if [ "$1" = "-m" ] ; then # Handle the arguments we know about - ARG_MONITOR_NETWORK_CONFIGURATION="true" # by setting ARG_ script variables to their values - shift # Then shift them out - elif [ "$1" = "-d" ] ; then - ARG_RESTORE_ON_DNS_RESET="true" - shift - elif [ "$1" = "-w" ] ; then - ARG_RESTORE_ON_WINS_RESET="true" - shift - elif [ "$1" = "-a" ] ; then - ARG_TAP="true" - shift - elif [ "${1:0:2}" = "-i" ] ; then - ARG_IGNORE_OPTION_FLAGS="${1}" - shift - elif [ "${1:0:2}" = "-a" ] ; then - ARG_IGNORE_OPTION_FLAGS="${1}" - shift - else - if [ "${1:0:1}" = "-" ] ; then # Shift out Tunnelblick arguments (they start with "-") that we don't understand - shift # so the rest of the script sees only the OpenVPN arguments - else - break - fi - fi -done - -readonly ARG_MONITOR_NETWORK_CONFIGURATION ARG_RESTORE_ON_DNS_RESET ARG_RESTORE_ON_WINS_RESET ARG_TAP ARG_IGNORE_OPTION_FLAGS - -# Note: The script log path name is constructed from the path of the regular config file, not the shadow copy -# if the config is shadow copy, e.g. /Library/Application Support/Tunnelblick/Users/Jonathan/Folder/Subfolder/config.ovpn -# then convert to regular config /Users/Jonathan/Library/Application Support/Tunnelblick/Configurations/Folder/Subfolder/config.ovpn -# to get the script log path -# Note: "/Users/..." works even if the home directory has a different path; it is used in the name of the log file, and is not used as a path to get to anything. -readonly TBALTPREFIX="/Library/Application Support/LEAP Client/Users/" -readonly TBALTPREFIXLEN="${#TBALTPREFIX}" -readonly TBCONFIGSTART="${config:0:$TBALTPREFIXLEN}" -if [ "$TBCONFIGSTART" = "$TBALTPREFIX" ] ; then - readonly TBBASE="${config:$TBALTPREFIXLEN}" - readonly TBSUFFIX="${TBBASE#*/}" - readonly TBUSERNAME="${TBBASE%%/*}" - readonly TBCONFIG="/Users/$TBUSERNAME/Library/Application Support/LEAP Client/Configurations/$TBSUFFIX" -else - readonly TBCONFIG="${config}" -fi - -readonly CONFIG_PATH_DASHES_SLASHES="$(echo "${TBCONFIG}" | sed -e 's/-/--/g' | sed -e 's/\//-S/g')" - -# XXX PUT LOGS SOMEWHERE BETTER -readonly SCRIPT_LOG_FILE="/Users/$LEAPUSER/.config/leap/logs/${CONFIG_PATH_DASHES_SLASHES}.script.log" -readonly TB_RESOURCE_PATH=$(dirname "${0}") - -LEASEWATCHER_PLIST_PATH="/Users/$LEAPUSER/.config/leap/logs/LeaseWatch.plist" - -readonly OSVER="$(sw_vers | grep 'ProductVersion:' | grep -o '10\.[0-9]*')" - -readonly DEFAULT_DOMAIN_NAME="openvpn" - -bRouteGatewayIsDhcp="false" - -# @param String message - The message to log -readonly LOG_MESSAGE_COMMAND=$(basename "${0}") -logMessage() -{ - echo "$(date '+%a %b %e %T %Y') *LEAP Client $LOG_MESSAGE_COMMAND: "${@} >> "${SCRIPT_LOG_FILE}" -} - -# @param String string - Content to trim -trim() -{ - echo ${@} -} - -# @param String[] dnsServers - The name servers to use -# @param String domainName - The domain name to use -# @param \optional String[] winsServers - The WINS servers to use -setDnsServersAndDomainName() -{ - declare -a vDNS=("${!1}") - domain=$2 - declare -a vWINS=("${!3}") - - set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors - - PSID=$( (scutil | grep PrimaryService | sed -e 's/.*PrimaryService : //')<<- EOF - open - show State:/Network/Global/IPv4 - quit -EOF ) - - STATIC_DNS_CONFIG="$( (scutil | sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ')<<- EOF - open - show Setup:/Network/Service/${PSID}/DNS - quit -EOF )" - if echo "${STATIC_DNS_CONFIG}" | grep -q "ServerAddresses" ; then - readonly STATIC_DNS="$(trim "$( echo "${STATIC_DNS_CONFIG}" | sed -e 's/^.*ServerAddresses[^{]*{[[:space:]]*\([^}]*\)[[:space:]]*}.*$/\1/g' )")" - fi - if echo "${STATIC_DNS_CONFIG}" | grep -q "SearchDomains" ; then - readonly STATIC_SEARCH="$(trim "$( echo "${STATIC_DNS_CONFIG}" | sed -e 's/^.*SearchDomains[^{]*{[[:space:]]*\([^}]*\)[[:space:]]*}.*$/\1/g' )")" - fi - - STATIC_WINS_CONFIG="$( (scutil | sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' ')<<- EOF - open - show Setup:/Network/Service/${PSID}/SMB - quit -EOF )" - STATIC_WINS_SERVERS="" - STATIC_WORKGROUP="" - STATIC_NETBIOSNAME="" - if echo "${STATIC_WINS_CONFIG}" | grep -q "WINSAddresses" ; then - STATIC_WINS_SERVERS="$(trim "$( echo "${STATIC_WINS_CONFIG}" | sed -e 's/^.*WINSAddresses[^{]*{[[:space:]]*\([^}]*\)[[:space:]]*}.*$/\1/g' )")" - fi - if echo "${STATIC_WINS_CONFIG}" | grep -q "Workgroup" ; then - STATIC_WORKGROUP="$(trim "$( echo "${STATIC_WINS_CONFIG}" | sed -e 's/^.*Workgroup : \([^[:space:]]*\).*$/\1/g' )")" - fi - if echo "${STATIC_WINS_CONFIG}" | grep -q "NetBIOSName" ; then - STATIC_NETBIOSNAME="$(trim "$( echo "${STATIC_WINS_CONFIG}" | sed -e 's/^.*NetBIOSName : \([^[:space:]]*\).*$/\1/g' )")" - fi - readonly STATIC_WINS_SERVERS STATIC_WORKGROUP STATIC_NETBIOSNAME - - if [ ${#vDNS[*]} -eq 0 ] ; then - DYN_DNS="false" - ALL_DNS="${STATIC_DNS}" - elif [ -n "${STATIC_DNS}" ] ; then - case "${OSVER}" in - 10.6 | 10.7 ) - # Do nothing - in 10.6 we don't aggregate our configurations, apparently - DYN_DNS="false" - ALL_DNS="${STATIC_DNS}" - ;; - 10.4 | 10.5 ) - DYN_DNS="true" - # We need to remove duplicate DNS entries, so that our reference list matches MacOSX's - SDNS="$(echo "${STATIC_DNS}" | tr ' ' '\n')" - (( i=0 )) - for n in "${vDNS[@]}" ; do - if echo "${SDNS}" | grep -q "${n}" ; then - unset vDNS[${i}] - fi - (( i++ )) - done - if [ ${#vDNS[*]} -gt 0 ] ; then - ALL_DNS="$(trim "${STATIC_DNS}" "${vDNS[*]}")" - else - DYN_DNS="false" - ALL_DNS="${STATIC_DNS}" - fi - ;; - esac - else - DYN_DNS="true" - ALL_DNS="$(trim "${vDNS[*]}")" - fi - readonly DYN_DNS ALL_DNS - - if [ ${#vWINS[*]} -eq 0 ] ; then - DYN_WINS="false" - ALL_WINS_SERVERS="${STATIC_WINS_SERVERS}" - elif [ -n "${STATIC_WINS_SERVERS}" ] ; then - case "${OSVER}" in - 10.6 | 10.7 ) - # Do nothing - in 10.6 we don't aggregate our configurations, apparently - DYN_WINS="false" - ALL_WINS_SERVERS="${STATIC_WINS_SERVERS}" - ;; - 10.4 | 10.5 ) - DYN_WINS="true" - # We need to remove duplicate WINS entries, so that our reference list matches MacOSX's - SWINS="$(echo "${STATIC_WINS_SERVERS}" | tr ' ' '\n')" - (( i=0 )) - for n in "${vWINS[@]}" ; do - if echo "${SWINS}" | grep -q "${n}" ; then - unset vWINS[${i}] - fi - (( i++ )) - done - if [ ${#vWINS[*]} -gt 0 ] ; then - ALL_WINS_SERVERS="$(trim "${STATIC_WINS_SERVERS}" "${vWINS[*]}")" - else - DYN_WINS="false" - ALL_WINS_SERVERS="${STATIC_WINS_SERVERS}" - fi - ;; - esac - else - DYN_WINS="true" - ALL_WINS_SERVERS="$(trim "${vWINS[*]}")" - fi - readonly DYN_WINS ALL_WINS_SERVERS - - # We double-check that our search domain isn't already on the list - SEARCH_DOMAIN="${domain}" - case "${OSVER}" in - 10.6 | 10.7 ) - # Do nothing - in 10.6 we don't aggregate our configurations, apparently - if [ -n "${STATIC_SEARCH}" ] ; then - ALL_SEARCH="${STATIC_SEARCH}" - SEARCH_DOMAIN="" - else - ALL_SEARCH="${SEARCH_DOMAIN}" - fi - ;; - 10.4 | 10.5 ) - if echo "${STATIC_SEARCH}" | tr ' ' '\n' | grep -q "${SEARCH_DOMAIN}" ; then - SEARCH_DOMAIN="" - fi - if [ -z "${SEARCH_DOMAIN}" ] ; then - ALL_SEARCH="${STATIC_SEARCH}" - else - ALL_SEARCH="$(trim "${STATIC_SEARCH}" "${SEARCH_DOMAIN}")" - fi - ;; - esac - readonly SEARCH_DOMAIN ALL_SEARCH - - if ! ${DYN_DNS} ; then - NO_DNS="#" - fi - if ! ${DYN_WINS} ; then - NO_WS="#" - fi - if [ -z "${SEARCH_DOMAIN}" ] ; then - NO_SEARCH="#" - fi - if [ -z "${STATIC_WORKGROUP}" ] ; then - NO_WG="#" - fi - if [ -z "${STATIC_NETBIOSNAME}" ] ; then - NO_NB="#" - fi - if [ -z "${ALL_DNS}" ] ; then - AGG_DNS="#" - fi - if [ -z "${ALL_SEARCH}" ] ; then - AGG_SEARCH="#" - fi - if [ -z "${ALL_WINS_SERVERS}" ] ; then - AGG_WINS="#" - fi - - # Now, do the aggregation - # Save the openvpn process ID and the Network Primary Service ID, leasewather.plist path, logfile path, and optional arguments from LEAP Client, - # then save old and new DNS and WINS settings - # PPID is a bash-script variable that contains the process ID of the parent of the process running the script (i.e., OpenVPN's process ID) - # config is an environmental variable set to the configuration path by OpenVPN prior to running this up script - logMessage "Up to two 'No such key' warnings are normal and may be ignored" - - # If DNS is manually set, it overrides the DHCP setting, which isn't reflected in 'State:/Network/Service/${PSID}/DNS' - if echo "${STATIC_DNS_CONFIG}" | grep -q "ServerAddresses" ; then - CORRECT_OLD_DNS_KEY="Setup:" - else - CORRECT_OLD_DNS_KEY="State:" - fi - - # If WINS is manually set, it overrides the DHCP setting, which isn't reflected in 'State:/Network/Service/${PSID}/DNS' - if echo "${STATIC_WINS_CONFIG}" | grep -q "WINSAddresses" ; then - CORRECT_OLD_WINS_KEY="Setup:" - else - CORRECT_OLD_WINS_KEY="State:" - fi - - # If we are not expecting any WINS value, add to the expected WINS setup - NO_NOSUCH_KEY_WINS="#" - if [ "${NO_NB}" = "#" -a "${AGG_WINS}" = "#" -a "${NO_WG}" = "#" ] ; then - NO_NOSUCH_KEY_WINS="" - fi - readonly NO_NOSUCH_KEY_WINS - - set -e # We instruct bash that it CAN again fail on errors - - scutil <<- EOF - open - d.init - d.add PID # ${PPID} - d.add Service ${PSID} - d.add LeaseWatcherPlistPath "${LEASEWATCHER_PLIST_PATH}" - d.add ScriptLogFile "${SCRIPT_LOG_FILE}" - d.add MonitorNetwork "${ARG_MONITOR_NETWORK_CONFIGURATION}" - d.add RestoreOnDNSReset "${ARG_RESTORE_ON_DNS_RESET}" - d.add RestoreOnWINSReset "${ARG_RESTORE_ON_WINS_RESET}" - d.add IgnoreOptionFlags "${ARG_IGNORE_OPTION_FLAGS}" - d.add IsTapInterface "${ARG_TAP}" - d.add RouteGatewayIsDhcp "${bRouteGatewayIsDhcp}" - set State:/Network/OpenVPN - - # First, back up the device's current DNS and WINS configurations - # Indicate 'no such key' by a dictionary with a single entry: "LEAPClientNoSuchKey : true" - d.init - d.add LEAPClientNoSuchKey true - get ${CORRECT_OLD_DNS_KEY}/Network/Service/${PSID}/DNS - set State:/Network/OpenVPN/OldDNS - - d.init - d.add LEAPClientNoSuchKey true - get ${CORRECT_OLD_WINS_KEY}/Network/Service/${PSID}/SMB - set State:/Network/OpenVPN/OldSMB - - # Second, initialize the new DNS map - d.init - ${NO_DNS}d.add ServerAddresses * ${vDNS[*]} - ${NO_SEARCH}d.add SearchDomains * ${SEARCH_DOMAIN} - d.add DomainName ${domain} - set State:/Network/Service/${PSID}/DNS - - # Third, initialize the WINS map - d.init - ${NO_NB}d.add NetBIOSName ${STATIC_NETBIOSNAME} - ${NO_WS}d.add WINSAddresses * ${vWINS[*]} - ${NO_WG}d.add Workgroup ${STATIC_WORKGROUP} - set State:/Network/Service/${PSID}/SMB - - # Now, initialize the maps that will be compared against the system-generated map - # which means that we will have to aggregate configurations of statically-configured - # nameservers, and statically-configured search domains - d.init - ${AGG_DNS}d.add ServerAddresses * ${ALL_DNS} - ${AGG_SEARCH}d.add SearchDomains * ${ALL_SEARCH} - d.add DomainName ${domain} - set State:/Network/OpenVPN/DNS - - d.init - ${NO_NB}d.add NetBIOSName ${STATIC_NETBIOSNAME} - ${AGG_WINS}d.add WINSAddresses * ${ALL_WINS_SERVERS} - ${NO_WG}d.add Workgroup ${STATIC_WORKGROUP} - ${NO_NOSUCH_KEY_WINS}d.add LEAPClientNoSuchKey true - set State:/Network/OpenVPN/SMB - - # We are done - quit -EOF - - logMessage "Saved the DNS and WINS configurations for later use" - - if ${ARG_MONITOR_NETWORK_CONFIGURATION} ; then - if [ "${ARG_IGNORE_OPTION_FLAGS:0:2}" = "-a" ] ; then - # Generate an updated plist with the path for process-network-changes - readonly LEASEWATCHER_TEMPLATE_PATH="$(dirname "${0}")/ProcessNetworkChanges.plist.template" - sed -e "s|\${DIR}|$(dirname "${0}")|g" "${LEASEWATCHER_TEMPLATE_PATH}" > "${LEASEWATCHER_PLIST_PATH}" - launchctl load "${LEASEWATCHER_PLIST_PATH}" - logMessage "Set up to monitor system configuration with process-network-changes" - else - # Generate an updated plist with the path for leasewatch - readonly LEASEWATCHER_TEMPLATE_PATH="$(dirname "${0}")/LeaseWatch.plist.template" - sed -e "s|\${DIR}|$(dirname "${0}")|g" "${LEASEWATCHER_TEMPLATE_PATH}" > "${LEASEWATCHER_PLIST_PATH}" - launchctl load "${LEASEWATCHER_PLIST_PATH}" - logMessage "Set up to monitor system configuration with leasewatch" - fi - fi -} - -configureDhcpDns() -{ - # whilst ipconfig will have created the neccessary Network Service keys, the DNS - # settings won't actually be used by OS X unless the SupplementalMatchDomains key - # is added - # ref. - # - is there a way to extract the domains from the SC dictionary and re-insert - # as SupplementalMatchDomains? i.e. not requiring the ipconfig domain_name call? - - # - wait until we get a lease before extracting the DNS domain name and merging into SC - # - despite it's name, ipconfig waitall doesn't (but maybe one day it will :-) - ipconfig waitall - - unset test_domain_name - unset test_name_server - - set +e # We instruct bash NOT to exit on individual command errors, because if we need to wait longer these commands will fail - - # usually takes at least a few seconds to get a DHCP lease - sleep 3 - n=0 - while [ -z "$test_domain_name" -a -z "$test_name_server" -a $n -lt 5 ] - do - logMessage "Sleeping for $n seconds to wait for DHCP to finish setup." - sleep $n - n=`expr $n + 1` - - if [ -z "$test_domain_name" ]; then - test_domain_name=`ipconfig getoption $dev domain_name 2>/dev/null` - fi - - if [ -z "$test_name_server" ]; then - test_name_server=`ipconfig getoption $dev domain_name_server 2>/dev/null` - fi - done - - sGetPacketOutput=`ipconfig getpacket $dev` - - set -e # We instruct bash that it CAN again fail on individual errors - - #echo "`date` test_domain_name = $test_domain_name, test_name_server = $test_name_server, sGetPacketOutput = $sGetPacketOutput" - - unset aNameServers - unset aWinsServers - - nNameServerIndex=1 - nWinsServerIndex=1 - - if [ "$sGetPacketOutput" ]; then - sGetPacketOutput_FirstLine=`echo "$sGetPacketOutput"|head -n 1` - #echo $sGetPacketOutput_FirstLine - - if [ "$sGetPacketOutput_FirstLine" == "op = BOOTREPLY" ]; then - set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors - - for tNameServer in `echo "$sGetPacketOutput"|grep "domain_name_server"|grep -Eo "\{([0-9\.]+)(, [0-9\.]+)*\}"|grep -Eo "([0-9\.]+)"`; do - aNameServers[nNameServerIndex-1]="$(trim "$tNameServer")" - let nNameServerIndex++ - done - - for tWINSServer in `echo "$sGetPacketOutput"|grep "nb_over_tcpip_name_server"|grep -Eo "\{([0-9\.]+)(, [0-9\.]+)*\}"|grep -Eo "([0-9\.]+)"`; do - aWinsServers[nWinsServerIndex-1]="$(trim "$tWINSServer")" - let nWinsServerIndex++ - done - - sDomainName=`echo "$sGetPacketOutput"|grep "domain_name "|grep -Eo ": [-A-Za-z0-9\-\.]+"|grep -Eo "[-A-Za-z0-9\-\.]+"` - sDomainName="$(trim "$sDomainName")" - - if [ ${#aNameServers[*]} -gt 0 -a "$sDomainName" ]; then - logMessage "Retrieved name server(s) [ ${aNameServers[@]} ], domain name [ $sDomainName ], and WINS server(s) [ ${aWinsServers[@]} ]" - setDnsServersAndDomainName aNameServers[@] "$sDomainName" aWinsServers[@] - return 0 - elif [ ${#aNameServers[*]} -gt 0 ]; then - logMessage "Retrieved name server(s) [ ${aNameServers[@]} ] and WINS server(s) [ ${aWinsServers[@]} ] and using default domain name [ $DEFAULT_DOMAIN_NAME ]" - setDnsServersAndDomainName aNameServers[@] "$DEFAULT_DOMAIN_NAME" aWinsServers[@] - return 0 - else - # Should we return 1 here and indicate an error, or attempt the old method? - logMessage "No useful information extracted from DHCP/BOOTP packet. Attempting legacy configuration." - fi - - set -e # We instruct bash that it CAN again fail on errors - else - # Should we return 1 here and indicate an error, or attempt the old method? - logMessage "No DHCP/BOOTP packet found on interface. Attempting legacy configuration." - fi - fi - - unset sDomainName - unset sNameServer - unset aNameServers - - sDomainName=`ipconfig getoption $dev domain_name 2>/dev/null` - sNameServer=`ipconfig getoption $dev domain_name_server 2>/dev/null` - - sDomainName="$(trim "$sDomainName")" - sNameServer="$(trim "$sNameServer")" - - declare -a aWinsServers=( ) # Declare empty WINS array to avoid any useless error messages - - if [ "$sDomainName" -a "$sNameServer" ]; then - aNameServers[0]=$sNameServer - logMessage "Retrieved name server [ $sNameServer ], domain name [ $sDomainName ], and no WINS servers" - setDnsServersAndDomainName aNameServers[@] "$sDomainName" aWinsServers[@] - elif [ "$sNameServer" ]; then - aNameServers[0]=$sNameServer - logMessage "Retrieved name server [ $sNameServer ] and no WINS servers, and using default domain name [ $DEFAULT_DOMAIN_NAME ]" - setDnsServersAndDomainName aNameServers[@] "$DEFAULT_DOMAIN_NAME" aWinsServers[@] - elif [ "$sDomainName" ]; then - logMessage "WARNING: Retrieved domain name [ $sDomainName ] but no name servers from OpenVPN (DHCP), which is not sufficient to make network/DNS configuration changes." - if ${ARG_MONITOR_NETWORK_CONFIGURATION} ; then - logMessage "Will NOT monitor for other network configuration changes." - fi - else - logMessage "WARNING: No DNS information received from OpenVPN (DHCP), so no network/DNS configuration changes need to be made." - if ${ARG_MONITOR_NETWORK_CONFIGURATION} ; then - logMessage "Will NOT monitor for other network configuration changes." - fi - fi - - return 0 -} - -configureOpenVpnDns() -{ - unset vForOptions - unset vOptions - unset aNameServers - unset aWinsServers - - nOptionIndex=1 - nNameServerIndex=1 - nWinsServerIndex=1 - - while vForOptions=foreign_option_$nOptionIndex; [ -n "${!vForOptions}" ]; do - vOptions[nOptionIndex-1]=${!vForOptions} - case ${vOptions[nOptionIndex-1]} in - *DOMAIN* ) - sDomainName="$(trim "${vOptions[nOptionIndex-1]//dhcp-option DOMAIN /}")" - ;; - *DNS* ) - aNameServers[nNameServerIndex-1]="$(trim "${vOptions[nOptionIndex-1]//dhcp-option DNS /}")" - let nNameServerIndex++ - ;; - *WINS* ) - aWinsServers[nWinsServerIndex-1]="$(trim "${vOptions[nOptionIndex-1]//dhcp-option WINS /}")" - let nWinsServerIndex++ - ;; - * ) - logMessage "Unknown: 'foreign_option_${nOptionIndex}' = '${vOptions[nOptionIndex-1]}'" - ;; - esac - let nOptionIndex++ - done - - if [ ${#aNameServers[*]} -gt 0 -a "$sDomainName" ]; then - logMessage "Retrieved name server(s) [ ${aNameServers[@]} ], domain name [ $sDomainName ], and WINS server(s) [ ${aWinsServers[@]} ]" - setDnsServersAndDomainName aNameServers[@] "$sDomainName" aWinsServers[@] - elif [ ${#aNameServers[*]} -gt 0 ]; then - logMessage "Retrieved name server(s) [ ${aNameServers[@]} ] and WINS server(s) [ ${aWinsServers[@]} ] and using default domain name [ $DEFAULT_DOMAIN_NAME ]" - setDnsServersAndDomainName aNameServers[@] "$DEFAULT_DOMAIN_NAME" aWinsServers[@] - else - # Should we maybe just return 1 here to indicate an error? Does this mean that something bad has happened? - logMessage "No DNS information recieved from OpenVPN, so no network configuration changes need to be made." - if ${ARG_MONITOR_NETWORK_CONFIGURATION} ; then - logMessage "Will NOT monitor for other network configuration changes." - fi - fi - - return 0 -} - -# We sleep here to allow time for OS X to process network settings -sleep 2 - -EXIT_CODE=0 - -if ${ARG_TAP} ; then - # Still need to do: Look for route-gateway dhcp (TAP isn't always DHCP) - bRouteGatewayIsDhcp="false" - if [ -z "${route_vpn_gateway}" -o "$route_vpn_gateway" == "dhcp" -o "$route_vpn_gateway" == "DHCP" ]; then - bRouteGatewayIsDhcp="true" - fi - - if [ "$bRouteGatewayIsDhcp" == "true" ]; then - if [ -z "$dev" ]; then - logMessage "Cannot configure TAP interface for DHCP without \$dev being defined. Exiting." - exit 1 - fi - - ipconfig set "$dev" DHCP - - configureDhcpDns & - elif [ "$foreign_option_1" == "" ]; then - logMessage "No network configuration changes need to be made." - if ${ARG_MONITOR_NETWORK_CONFIGURATION} ; then - logMessage "Will NOT monitor for other network configuration changes." - fi - else - configureOpenVpnDns - EXIT_CODE=$? - fi -else - if [ "$foreign_option_1" == "" ]; then - logMessage "No network configuration changes need to be made." - if ${ARG_MONITOR_NETWORK_CONFIGURATION} ; then - logMessage "Will NOT monitor for other network configuration changes." - fi - else - configureOpenVpnDns - EXIT_CODE=$? - fi -fi - -exit $EXIT_CODE diff --git a/pkg/osx/install/install-leapc.sh b/pkg/osx/install/install-leapc.sh deleted file mode 100755 index e47abb7c..00000000 --- a/pkg/osx/install/install-leapc.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -# Bitmask Installer Script. -# -# Copyright (C) 2013 LEAP Encryption Access Project -# -# This file is part of LEAP Client, as -# available from http://leap.se/. This file is free software; -# you can redistribute it and/or modify it under the terms of the GNU -# General Public License (GPL) as published by the Free Software -# Foundation, in version 2 as it comes in the "COPYING" file of the -# LEAP Client distribution. LEAP Client is distributed in the -# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. -# - -set -e - -destlibs=/opt/local/lib -leapdir=/Applications/LEAP\ Client.app -leaplibs=${leapdir}/Contents/MacOS -tunstartup=/Library/StartupItems/tun/tun - -echo "Installing Bitmask in /Applications..." -cp -r "LEAP Client.app" /Applications - -echo "Copying openvpn binary..." -cp -r openvpn.leap /usr/bin - -echo "Installing tun/tap drivers..." -test -f $tunstartup && $tunstartup stop - -test -d /Library/Extensions || mkdir -p /Library/Extensions -test -d /Library/StartupItems || mkdir -p /Library/StartupItems - -cp -r Extensions/* /Library/Extensions -cp -r StartupItems/* /Library/StartupItems - -echo "Loading tun/tap kernel extension..." - -$tunstartup start - -echo "Installation Finished!" diff --git a/pkg/osx/install/leap-installer.platypus b/pkg/osx/install/leap-installer.platypus deleted file mode 100644 index 9150961e..00000000 --- a/pkg/osx/install/leap-installer.platypus +++ /dev/null @@ -1,90 +0,0 @@ - - - - - AcceptsFiles - - AcceptsText - - Authentication - - Author - Kali Yuga - BundledFiles - - Creator - Platypus-4.7 - DeclareService - - Destination - MyPlatypusApp.app - DestinationOverride - - DevelopmentVersion - - DocIcon - - Droppable - - ExecutablePath - /opt/local/share/platypus/ScriptExec - FileTypes - - **** - fold - - IconPath - - Identifier - se.leap.LEAPClientInstaller - Interpreter - /bin/sh - InterpreterArgs - - Name - LEAPClient Installer - NibPath - /opt/local/share/platypus/MainMenu.nib - OptimizeApplication - - Output - Progress Bar - RemainRunning - - Role - Viewer - ScriptArgs - - ScriptPath - ./install/install-leapc.sh - Secure - - ShowInDock - - StatusItemDisplayType - Text - StatusItemIcon - - - StatusItemTitle - MyPlatypusApp - Suffixes - - * - - TextBackground - #ffffff - TextEncoding - 4 - TextFont - Monaco - TextForeground - #000000 - TextSize - 10 - UseXMLPlistFormat - - Version - 1.0 - - diff --git a/pkg/osx/pidfile.py b/pkg/osx/pidfile.py new file mode 100644 index 00000000..4517ee0e --- /dev/null +++ b/pkg/osx/pidfile.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +# daemon/pidfile.py +# Part of ‘python-daemon’, an implementation of PEP 3143. +# +# Copyright © 2008–2015 Ben Finney +# +# This is free software: you may copy, modify, and/or distribute this work +# under the terms of the Apache License, version 2.0 as published by the +# Apache Software Foundation. +# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details. + +""" Lockfile behaviour implemented via Unix PID files. + """ + +from __future__ import (absolute_import, unicode_literals) + +from lockfile.pidlockfile import PIDLockFile + + +class TimeoutPIDLockFile(PIDLockFile, object): + """ Lockfile with default timeout, implemented as a Unix PID file. + + This uses the ``PIDLockFile`` implementation, with the + following changes: + + * The `acquire_timeout` parameter to the initialiser will be + used as the default `timeout` parameter for the `acquire` + method. + + """ + + def __init__(self, path, acquire_timeout=None, *args, **kwargs): + """ Set up the parameters of a TimeoutPIDLockFile. + + :param path: Filesystem path to the PID file. + :param acquire_timeout: Value to use by default for the + `acquire` call. + :return: ``None``. + + """ + self.acquire_timeout = acquire_timeout + super(TimeoutPIDLockFile, self).__init__(path, *args, **kwargs) + + def acquire(self, timeout=None, *args, **kwargs): + """ Acquire the lock. + + :param timeout: Specifies the timeout; see below for valid + values. + :return: ``None``. + + The `timeout` defaults to the value set during + initialisation with the `acquire_timeout` parameter. It is + passed to `PIDLockFile.acquire`; see that method for + details. + + """ + if timeout is None: + timeout = self.acquire_timeout + super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs) + + +# Local variables: +# coding: utf-8 +# mode: python +# End: +# vim: fileencoding=utf-8 filetype=python : diff --git a/pkg/osx/post-inst.sh b/pkg/osx/post-inst.sh new file mode 100755 index 00000000..2fc719f0 --- /dev/null +++ b/pkg/osx/post-inst.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +# Post-Instalation script + +cp se.leap.bitmask-helper.plist /Library/LaunchDaemons/ +launchctl load /Library/LaunchDaemons/se.leap.bitmask-helper.plist diff --git a/pkg/osx/pre-inst.sh b/pkg/osx/pre-inst.sh new file mode 100755 index 00000000..b6d17f20 --- /dev/null +++ b/pkg/osx/pre-inst.sh @@ -0,0 +1,2 @@ +#!/bin/sh +launchctl unload /Library/LaunchDaemons/se.leap.bitmask-helper.plist diff --git a/pkg/osx/runner.py b/pkg/osx/runner.py new file mode 100644 index 00000000..6973cf1c --- /dev/null +++ b/pkg/osx/runner.py @@ -0,0 +1,324 @@ +# -*- coding: utf-8 -*- + +# daemon/runner.py +# Part of ‘python-daemon’, an implementation of PEP 3143. +# +# Copyright © 2009–2015 Ben Finney +# Copyright © 2007–2008 Robert Niederreiter, Jens Klein +# Copyright © 2003 Clark Evans +# Copyright © 2002 Noah Spurrier +# Copyright © 2001 Jürgen Hermann +# +# This is free software: you may copy, modify, and/or distribute this work +# under the terms of the Apache License, version 2.0 as published by the +# Apache Software Foundation. +# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details. + +""" Daemon runner library. + """ + +from __future__ import (absolute_import, unicode_literals) + +import sys +import os +import signal +import errno +try: + # Python 3 standard library. + ProcessLookupError +except NameError: + # No such class in Python 2. + ProcessLookupError = NotImplemented + +import lockfile + +from . import pidfile +from .daemon import (basestring, unicode) +from .daemon import DaemonContext +from .daemon import _chain_exception_from_existing_exception_context + + +class DaemonRunnerError(Exception): + """ Abstract base class for errors from DaemonRunner. """ + + def __init__(self, *args, **kwargs): + self._chain_from_context() + + super(DaemonRunnerError, self).__init__(*args, **kwargs) + + def _chain_from_context(self): + _chain_exception_from_existing_exception_context(self, as_cause=True) + + +class DaemonRunnerInvalidActionError(DaemonRunnerError, ValueError): + """ Raised when specified action for DaemonRunner is invalid. """ + + def _chain_from_context(self): + # This exception is normally not caused by another. + _chain_exception_from_existing_exception_context(self, as_cause=False) + + +class DaemonRunnerStartFailureError(DaemonRunnerError, RuntimeError): + """ Raised when failure starting DaemonRunner. """ + + +class DaemonRunnerStopFailureError(DaemonRunnerError, RuntimeError): + """ Raised when failure stopping DaemonRunner. """ + + +class DaemonRunner: + """ Controller for a callable running in a separate background process. + + The first command-line argument is the action to take: + + * 'start': Become a daemon and call `app.run()`. + * 'stop': Exit the daemon process specified in the PID file. + * 'restart': Stop, then start. + + """ + + __metaclass__ = type + + start_message = "started with pid {pid:d}" + + def __init__(self, app): + """ Set up the parameters of a new runner. + + :param app: The application instance; see below. + :return: ``None``. + + The `app` argument must have the following attributes: + + * `stdin_path`, `stdout_path`, `stderr_path`: Filesystem paths + to open and replace the existing `sys.stdin`, `sys.stdout`, + `sys.stderr`. + + * `pidfile_path`: Absolute filesystem path to a file that will + be used as the PID file for the daemon. If ``None``, no PID + file will be used. + + * `pidfile_timeout`: Used as the default acquisition timeout + value supplied to the runner's PID lock file. + + * `run`: Callable that will be invoked when the daemon is + started. + + """ + self.parse_args() + self.app = app + self.daemon_context = DaemonContext() + self.daemon_context.stdin = open(app.stdin_path, 'rt') + self.daemon_context.stdout = open(app.stdout_path, 'w+t') + self.daemon_context.stderr = open( + app.stderr_path, 'w+t', buffering=0) + + self.pidfile = None + if app.pidfile_path is not None: + self.pidfile = make_pidlockfile( + app.pidfile_path, app.pidfile_timeout) + self.daemon_context.pidfile = self.pidfile + + def _usage_exit(self, argv): + """ Emit a usage message, then exit. + + :param argv: The command-line arguments used to invoke the + program, as a sequence of strings. + :return: ``None``. + + """ + progname = os.path.basename(argv[0]) + usage_exit_code = 2 + action_usage = "|".join(self.action_funcs.keys()) + message = "usage: {progname} {usage}".format( + progname=progname, usage=action_usage) + emit_message(message) + sys.exit(usage_exit_code) + + def parse_args(self, argv=None): + """ Parse command-line arguments. + + :param argv: The command-line arguments used to invoke the + program, as a sequence of strings. + + :return: ``None``. + + The parser expects the first argument as the program name, the + second argument as the action to perform. + + If the parser fails to parse the arguments, emit a usage + message and exit the program. + + """ + if argv is None: + argv = sys.argv + + min_args = 2 + if len(argv) < min_args: + self._usage_exit(argv) + + self.action = unicode(argv[1]) + if self.action not in self.action_funcs: + self._usage_exit(argv) + + def _start(self): + """ Open the daemon context and run the application. + + :return: ``None``. + :raises DaemonRunnerStartFailureError: If the PID file cannot + be locked by this process. + + """ + if is_pidfile_stale(self.pidfile): + self.pidfile.break_lock() + + try: + self.daemon_context.open() + except lockfile.AlreadyLocked: + error = DaemonRunnerStartFailureError( + "PID file {pidfile.path!r} already locked".format( + pidfile=self.pidfile)) + raise error + + pid = os.getpid() + message = self.start_message.format(pid=pid) + emit_message(message) + + self.app.run() + + def _terminate_daemon_process(self): + """ Terminate the daemon process specified in the current PID file. + + :return: ``None``. + :raises DaemonRunnerStopFailureError: If terminating the daemon + fails with an OS error. + + """ + pid = self.pidfile.read_pid() + try: + os.kill(pid, signal.SIGTERM) + except OSError as exc: + error = DaemonRunnerStopFailureError( + "Failed to terminate {pid:d}: {exc}".format( + pid=pid, exc=exc)) + raise error + + def _stop(self): + """ Exit the daemon process specified in the current PID file. + + :return: ``None``. + :raises DaemonRunnerStopFailureError: If the PID file is not + already locked. + + """ + if not self.pidfile.is_locked(): + error = DaemonRunnerStopFailureError( + "PID file {pidfile.path!r} not locked".format( + pidfile=self.pidfile)) + raise error + + if is_pidfile_stale(self.pidfile): + self.pidfile.break_lock() + else: + self._terminate_daemon_process() + + def _restart(self): + """ Stop, then start. + """ + self._stop() + self._start() + + action_funcs = { + 'start': _start, + 'stop': _stop, + 'restart': _restart, + } + + def _get_action_func(self): + """ Get the function for the specified action. + + :return: The function object corresponding to the specified + action. + :raises DaemonRunnerInvalidActionError: if the action is + unknown. + + The action is specified by the `action` attribute, which is set + during `parse_args`. + + """ + try: + func = self.action_funcs[self.action] + except KeyError: + error = DaemonRunnerInvalidActionError( + "Unknown action: {action!r}".format( + action=self.action)) + raise error + return func + + def do_action(self): + """ Perform the requested action. + + :return: ``None``. + + The action is specified by the `action` attribute, which is set + during `parse_args`. + + """ + func = self._get_action_func() + func(self) + + +def emit_message(message, stream=None): + """ Emit a message to the specified stream (default `sys.stderr`). """ + if stream is None: + stream = sys.stderr + stream.write("{message}\n".format(message=message)) + stream.flush() + + +def make_pidlockfile(path, acquire_timeout): + """ Make a PIDLockFile instance with the given filesystem path. """ + if not isinstance(path, basestring): + error = ValueError("Not a filesystem path: {path!r}".format( + path=path)) + raise error + if not os.path.isabs(path): + error = ValueError("Not an absolute path: {path!r}".format( + path=path)) + raise error + lockfile = pidfile.TimeoutPIDLockFile(path, acquire_timeout) + + return lockfile + + +def is_pidfile_stale(pidfile): + """ Determine whether a PID file is stale. + + :return: ``True`` iff the PID file is stale; otherwise ``False``. + + The PID file is “stale” if its contents are valid but do not + match the PID of a currently-running process. + + """ + result = False + + pidfile_pid = pidfile.read_pid() + if pidfile_pid is not None: + try: + os.kill(pidfile_pid, signal.SIG_DFL) + except ProcessLookupError: + # The specified PID does not exist. + result = True + except OSError as exc: + if exc.errno == errno.ESRCH: + # Under Python 2, process lookup error is an OSError. + # The specified PID does not exist. + result = True + + return result + + +# Local variables: +# coding: utf-8 +# mode: python +# End: +# vim: fileencoding=utf-8 filetype=python : diff --git a/pkg/osx/se.leap.bitmask-helper.plist b/pkg/osx/se.leap.bitmask-helper.plist new file mode 100644 index 00000000..4428f131 --- /dev/null +++ b/pkg/osx/se.leap.bitmask-helper.plist @@ -0,0 +1,29 @@ + + + + + StandardOutPath + bitmask-helper.log + StandardErrorPath + bitmask-helper-err.log + GroupName + daemon + KeepAlive + + SuccessfulExit + + + Label + se.leap.bitmask-helper + ProgramArguments + + /Applications/Bitmask.app/Contents/Resources/bitmask-helper/bitmask-helper + + RunAtLoad + + WorkingDirectory + /Applications/Bitmask.app/Contents/Resources/bitmask-helper/ + SessionCreate + + + diff --git a/pkg/pyinst/bitmask.spec.orig b/pkg/pyinst/bitmask.spec.orig new file mode 100644 index 00000000..617104b9 --- /dev/null +++ b/pkg/pyinst/bitmask.spec.orig @@ -0,0 +1,38 @@ +# -*- mode: python -*- + +block_cipher = None + + +a = Analysis([os.path.join('bitmask.py')], + hiddenimports=[ + 'zope.interface', 'zope.proxy', + 'PySide.QtCore', 'PySide.QtGui'], + hookspath=None, + runtime_hooks=None, + excludes=None, + cipher=block_cipher) +pyz = PYZ(a.pure, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + exclude_binaries=True, + name='bitmask', + debug=False, + strip=False, + upx=True, + console=False ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + name='bitmask') +if sys.platform.startswith("darwin"): + app = BUNDLE(coll, + name=os.path.join( + 'dist', 'Bitmask.app'), + appname='Bitmask', + version='0.9.0rc2', + icon='pkg/osx/bitmask.icns', + bundle_identifier='bitmask-0.9.0rc2') diff --git a/pkg/pyinst/cryptography/osrandom_engine.c b/pkg/pyinst/cryptography/osrandom_engine.c new file mode 100644 index 00000000..27894712 --- /dev/null +++ b/pkg/pyinst/cryptography/osrandom_engine.c @@ -0,0 +1,167 @@ +static const char *Cryptography_osrandom_engine_id = "osrandom"; +static const char *Cryptography_osrandom_engine_name = "osrandom_engine"; + +#if defined(_WIN32) +static HCRYPTPROV hCryptProv = 0; + +static int osrandom_init(ENGINE *e) { + if (hCryptProv > 0) { + return 1; + } + if (CryptAcquireContext(&hCryptProv, NULL, NULL, + PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + return 1; + } else { + return 0; + } +} + +static int osrandom_rand_bytes(unsigned char *buffer, int size) { + if (hCryptProv == 0) { + return 0; + } + + if (!CryptGenRandom(hCryptProv, (DWORD)size, buffer)) { + ERR_put_error( + ERR_LIB_RAND, 0, ERR_R_RAND_LIB, "osrandom_engine.py", 0 + ); + return 0; + } + return 1; +} + +static int osrandom_finish(ENGINE *e) { + if (CryptReleaseContext(hCryptProv, 0)) { + hCryptProv = 0; + return 1; + } else { + return 0; + } +} + +static int osrandom_rand_status(void) { + if (hCryptProv == 0) { + return 0; + } else { + return 1; + } +} +#else +static int urandom_fd = -1; + +static int osrandom_finish(ENGINE *e); + +static int osrandom_init(ENGINE *e) { + if (urandom_fd > -1) { + return 1; + } + urandom_fd = open("/dev/urandom", O_RDONLY); + if (urandom_fd > -1) { + int flags = fcntl(urandom_fd, F_GETFD); + if (flags == -1) { + osrandom_finish(e); + return 0; + } else if (fcntl(urandom_fd, F_SETFD, flags | FD_CLOEXEC) == -1) { + osrandom_finish(e); + return 0; + } + return 1; + } else { + return 0; + } +} + +static int osrandom_rand_bytes(unsigned char *buffer, int size) { + ssize_t n; + while (size > 0) { + do { + n = read(urandom_fd, buffer, (size_t)size); + } while (n < 0 && errno == EINTR); + if (n <= 0) { + ERR_put_error( + ERR_LIB_RAND, 0, ERR_R_RAND_LIB, "osrandom_engine.py", 0 + ); + return 0; + } + buffer += n; + size -= n; + } + return 1; +} + +static int osrandom_finish(ENGINE *e) { + int n; + do { + n = close(urandom_fd); + } while (n < 0 && errno == EINTR); + urandom_fd = -1; + if (n < 0) { + return 0; + } else { + return 1; + } +} + +static int osrandom_rand_status(void) { + if (urandom_fd == -1) { + return 0; + } else { + return 1; + } +} +#endif + +/* This replicates the behavior of the OpenSSL FIPS RNG, which returns a + -1 in the event that there is an error when calling RAND_pseudo_bytes. */ +static int osrandom_pseudo_rand_bytes(unsigned char *buffer, int size) { + int res = osrandom_rand_bytes(buffer, size); + if (res == 0) { + return -1; + } else { + return res; + } +} + +static RAND_METHOD osrandom_rand = { + NULL, + osrandom_rand_bytes, + NULL, + NULL, + osrandom_pseudo_rand_bytes, + osrandom_rand_status, +}; + +/* Returns 1 if successfully added, 2 if engine has previously been added, + and 0 for error. */ +int Cryptography_add_osrandom_engine(void) { + ENGINE *e; + e = ENGINE_by_id(Cryptography_osrandom_engine_id); + if (e != NULL) { + ENGINE_free(e); + return 2; + } else { + ERR_clear_error(); + } + + e = ENGINE_new(); + if (e == NULL) { + return 0; + } + if(!ENGINE_set_id(e, Cryptography_osrandom_engine_id) || + !ENGINE_set_name(e, Cryptography_osrandom_engine_name) || + !ENGINE_set_RAND(e, &osrandom_rand) || + !ENGINE_set_init_function(e, osrandom_init) || + !ENGINE_set_finish_function(e, osrandom_finish)) { + ENGINE_free(e); + return 0; + } + if (!ENGINE_add(e)) { + ENGINE_free(e); + return 0; + } + if (!ENGINE_free(e)) { + return 0; + } + + return 1; +} diff --git a/pkg/pyinst/cryptography/osrandom_engine.h b/pkg/pyinst/cryptography/osrandom_engine.h new file mode 100644 index 00000000..11a3159e --- /dev/null +++ b/pkg/pyinst/cryptography/osrandom_engine.h @@ -0,0 +1,6 @@ +#ifdef _WIN32 +#include +#else +#include +#include +#endif diff --git a/src/leap/bitmask/_components.py b/src/leap/bitmask/_components.py index 9d6f3f59..9be0e6bc 100644 --- a/src/leap/bitmask/_components.py +++ b/src/leap/bitmask/_components.py @@ -2,5 +2,5 @@ Enabled Modules in Bitmask. Change these values for builds of the client with only one module enabled. """ -HAS_EIP = False +HAS_EIP = True HAS_MAIL = True diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 03a92c88..acb562c7 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -647,7 +647,7 @@ class EIP(object): :param domain: the domain for the provider to check :type domain: str """ - if not LinuxPolicyChecker.is_up(): + if IS_LINUX and not LinuxPolicyChecker.is_up(): logger.error("No polkit agent running.") return False diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py index 17fc11c2..42d9576b 100644 --- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py +++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py @@ -20,6 +20,7 @@ Darwin VPN launcher implementation. import commands import getpass import os +import socket import sys from leap.bitmask.logs.utils import get_logger @@ -34,17 +35,47 @@ class EIPNoTunKextLoaded(VPNLauncherException): pass +class DarwinHelperCommand(object): + + SOCKET_ADDR = '/tmp/bitmask-helper.socket' + + def __init__(self): + pass + + def _connect(self): + self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + self._sock.connect(self.SOCKET_ADDR) + except socket.error, msg: + raise RuntimeError(msg) + + def send(self, cmd, args=''): + # TODO check cmd is in allowed list + self._connect() + sock = self._sock + data = "" + + command = cmd + ' ' + args + '/CMD' + + try: + sock.sendall(command) + while '\n' not in data: + data += sock.recv(32) + finally: + sock.close() + + return data + + class DarwinVPNLauncher(VPNLauncher): """ VPN launcher for the Darwin Platform """ - COCOASUDO = "cocoasudo" - # XXX need the good old magic translate for these strings - # (look for magic in 0.2.0 release) - SUDO_MSG = ("Bitmask needs administrative privileges to run " - "Encrypted Internet.") - INSTALL_MSG = ("\"Bitmask needs administrative privileges to install " - "missing scripts and fix permissions.\"") + UP_SCRIPT = None + DOWN_SCRIPT = None + + # TODO -- move this to bitmask-helper + # Hardcode the installation path for OSX for security, openvpn is # run as root @@ -56,14 +87,9 @@ class DarwinVPNLauncher(VPNLauncher): INSTALL_PATH_ESCAPED,) OPENVPN_BIN_PATH = "%s/Contents/Resources/%s" % (INSTALL_PATH, OPENVPN_BIN) - - UP_SCRIPT = "%s/client.up.sh" % (OPENVPN_PATH,) - DOWN_SCRIPT = "%s/client.down.sh" % (OPENVPN_PATH,) - OPENVPN_DOWN_PLUGIN = '%s/openvpn-down-root.so' % (OPENVPN_PATH,) - - UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT, OPENVPN_DOWN_PLUGIN) OTHER_FILES = [] + # TODO deprecate ------------------------------------------------ @classmethod def cmd_for_missing_scripts(kls, frompath): """ @@ -87,7 +113,7 @@ class DarwinVPNLauncher(VPNLauncher): :returns: True if kext is loaded, False otherwise. :rtype: bool """ - return bool(commands.getoutput('kextstat | grep "leap.tun"')) + return bool(commands.getoutput('kextstat | grep "foo.tun"')) @classmethod def _get_icon_path(kls): @@ -101,6 +127,8 @@ class DarwinVPNLauncher(VPNLauncher): return os.path.join(resources_path, "bitmask.tiff") + + # TODO deprecate --------------------------------------------------------- @classmethod def get_cocoasudo_ovpn_cmd(kls): """ @@ -120,6 +148,7 @@ class DarwinVPNLauncher(VPNLauncher): return kls.COCOASUDO, args + # TODO deprecate --------------------------------------------------------- @classmethod def get_cocoasudo_installmissing_cmd(kls): """ @@ -171,12 +200,6 @@ class DarwinVPNLauncher(VPNLauncher): # we use `super` in order to send the class to use command = super(DarwinVPNLauncher, kls).get_vpn_command( eipconfig, providerconfig, socket_host, socket_port, openvpn_verb) - - cocoa, cargs = kls.get_cocoasudo_ovpn_cmd() - cargs.extend(command) - command = cargs - command.insert(0, cocoa) - command.extend(['--setenv', "LEAPUSER", getpass.getuser()]) return command diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index c48f857c..16dfd9cf 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -29,7 +29,7 @@ from leap.bitmask.config import flags from leap.bitmask.logs.utils import get_logger from leap.bitmask.backend.settings import Settings, GATEWAY_AUTOMATIC from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.platform_init import IS_LINUX +from leap.bitmask.platform_init import IS_LINUX, IS_MAC 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 @@ -286,8 +286,8 @@ class VPNLauncher(object): :rtype: list """ # FIXME - # XXX remove method when we ditch UPDOWN in osx and win too - if IS_LINUX: + # XXX remove method when we ditch UPDOWN in win too + if IS_LINUX or IS_MAC: return [] else: leap_assert(kls.UPDOWN_FILES is not None, @@ -308,7 +308,7 @@ class VPNLauncher(object): """ leap_assert(kls.OTHER_FILES is not None, "Need to define OTHER_FILES for this particular " - "auncher before calling this method") + "launcher before calling this method") other = force_eval(kls.OTHER_FILES) file_exist = partial(_has_other_files, warn=False) diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 586b50f5..6d18a599 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -23,6 +23,7 @@ import shutil import socket import subprocess import sys +import time from itertools import chain, repeat @@ -41,6 +42,7 @@ from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.logs.utils import get_logger from leap.bitmask.services.eip import get_vpn_launcher from leap.bitmask.services.eip import linuxvpnlauncher +from leap.bitmask.services.eip import darwinvpnlauncher from leap.bitmask.services.eip.eipconfig import EIPConfig from leap.bitmask.services.eip.udstelnet import UDSTelnet from leap.bitmask.util import first, force_eval @@ -145,7 +147,7 @@ class VPN(object): demand. """ TERMINATE_MAXTRIES = 10 - TERMINATE_WAIT = 1 # secs + TERMINATE_WAIT = 2 # secs OPENVPN_VERB = "openvpn_verb" @@ -173,7 +175,7 @@ class VPN(object): :param kwargs: kwargs to be passed to the VPNProcess :type kwargs: dict """ - logger.debug('VPN: start') + logger.debug('VPN: start ---------------------------------------------------') self._user_stopped = False self._stop_pollers() kwargs['openvpn_verb'] = self._openvpn_verb @@ -181,22 +183,6 @@ class VPN(object): restart = kwargs.pop('restart', False) - # start the main vpn subprocess - vpnproc = VPNProcess(*args, **kwargs) - - if vpnproc.get_openvpn_process(): - logger.info("Another vpn process is running. Will try to stop it.") - vpnproc.stop_if_already_running() - - # we try to bring the firewall up - if IS_LINUX: - gateways = vpnproc.getGateways() - firewall_up = self._launch_firewall(gateways, - restart=restart) - if not restart and not firewall_up: - logger.error("Could not bring firewall up, " - "aborting openvpn launch.") - return # FIXME it would be good to document where the # errors here are catched, since we currently handle them @@ -211,18 +197,56 @@ class VPN(object): # the ping-pong to the frontend, and without adding any logical checks # in the frontend. We should just communicate UI changes to frontend, # and abstract us away from anything else. - try: + + # TODO factor this out to the platform-launchers + + if IS_LINUX: + # start the main vpn subprocess + vpnproc = VPNProcess(*args, **kwargs) cmd = vpnproc.getCommand() - except Exception as e: - logger.error("Error while getting vpn command... {0!r}".format(e)) - raise + + if vpnproc.get_openvpn_process(): + logger.info("Another vpn process is running. Will try to stop it.") + vpnproc.stop_if_already_running() + + # we try to bring the firewall up + gateways = vpnproc.getGateways() + firewall_up = self._launch_firewall_linux( + gateways, restart=restart) + if not restart and not firewall_up: + logger.error("Could not bring firewall up, " + "aborting openvpn launch.") + return + + if IS_MAC: + # start the main vpn subprocess + vpnproc = VPNCanary(*args, **kwargs) + + # we try to bring the firewall up + gateways = vpnproc.getGateways() + firewall_up = self._launch_firewall_osx( + gateways, restart=restart) + if not restart and not firewall_up: + logger.error("Could not bring firewall up, " + "aborting openvpn launch.") + return + + helper = darwinvpnlauncher.DarwinHelperCommand() + cmd = vpnproc.getVPNCommand() + result = helper.send('openvpn_start %s' % ' '.join(cmd)) + + # TODO Windows version -- should be similar to osx. env = os.environ for key, val in vpnproc.vpn_env.items(): env[key] = val - reactor.spawnProcess(vpnproc, cmd[0], cmd, env) + cmd = vpnproc.getCommand() + running_proc = reactor.spawnProcess(vpnproc, cmd[0], cmd, env) + vpnproc.pid = running_proc.pid self._vpnproc = vpnproc + + # add pollers for status and state # this could be extended to a collection of @@ -233,9 +257,9 @@ class VPN(object): self._pollers.extend(poll_list) self._start_pollers() - def _launch_firewall(self, gateways, restart=False): + def _launch_firewall_linux(self, gateways, restart=False): """ - Launch the firewall using the privileged wrapper. + Launch the firewall using the privileged wrapper (linux). :param gateways: :type gateways: list @@ -254,40 +278,63 @@ class VPN(object): exitCode = subprocess.call(cmd + gateways) return True if exitCode is 0 else False + def _launch_firewall_osx(self, gateways, restart=False): + cmd = 'firewall_start %s' % ' '.join(gateways) + helper = darwinvpnlauncher.DarwinHelperCommand() + result = helper.send(cmd) + return True + + # TODO -- write LINUX/OSX VERSION too ------------------------------------ def is_fw_down(self): """ Return whether the firewall is down or not. :rtype: bool """ - 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() + if IS_LINUX: + 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() + + if IS_MAC: + cmd = 'firewall_isup' + helper = darwinvpnlauncher.DarwinHelperCommand() + result = helper.send(cmd) + return True + def tear_down_firewall(self): """ Tear the firewall down using the privileged wrapper. """ if IS_MAC: - # We don't support Mac so far + cmd = 'firewall_stop' + helper = darwinvpnlauncher.DarwinHelperCommand() + result = helper.send(cmd) return True - BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) - exitCode = subprocess.call(["pkexec", - BM_ROOT, "firewall", "stop"]) - return True if exitCode is 0 else False + + if IS_LINUX: + BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) + exitCode = subprocess.call(["pkexec", + BM_ROOT, "firewall", "stop"]) + return True if exitCode is 0 else False def bitmask_root_vpn_down(self): """ Bring openvpn down using the privileged wrapper. """ if IS_MAC: - # We don't support Mac so far + cmd = 'openvpn_stop' + helper = darwinvpnlauncher.DarwinHelperCommand() + result = helper.send(cmd) return True - BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) - exitCode = subprocess.call(["pkexec", - BM_ROOT, "openvpn", "stop"]) - return True if exitCode is 0 else False + + if IS_LINUX: + BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) + exitCode = subprocess.call(["pkexec", + BM_ROOT, "openvpn", "stop"]) + return True if exitCode is 0 else False def _kill_if_left_alive(self, tries=0): """ @@ -297,18 +344,18 @@ class VPN(object): :param tries: counter of tries, used in recursion :type tries: int """ + # we try to tear the firewall down + if (IS_LINUX or IS_MAC) and self._user_stopped: + logger.debug('trying to bring firewall down...') + firewall_down = self.tear_down_firewall() + if firewall_down: + logger.debug("Firewall down") + else: + logger.warning("Could not tear firewall down") + while tries < self.TERMINATE_MAXTRIES: if self._vpnproc.transport.pid is None: logger.debug("Process has been happily terminated.") - - # we try to tear the firewall down - if IS_LINUX and self._user_stopped: - firewall_down = self.tear_down_firewall() - if firewall_down: - logger.debug("Firewall down") - else: - logger.warning("Could not tear firewall down") - return else: logger.debug("Process did not die, waiting...") @@ -813,6 +860,8 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): programmatically. """ + pid = None + def __init__(self, eipconfig, providerconfig, socket_host, socket_port, signaler, openvpn_verb): """ @@ -861,7 +910,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): self._vpn_observer = VPNObserver(signaler) self.is_restart = False - # processProtocol methods + # ProcessProtocol methods def connectionMade(self): """ @@ -893,8 +942,11 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): .. seeAlso: `http://twistedmatrix.com/documents/13.0.0/api/twisted.internet.protocol.ProcessProtocol.html` # noqa """ exit_code = reason.value.exitCode + if isinstance(exit_code, int): logger.debug("processExited, status %d" % (exit_code,)) + else: + exit_code = 0 self._signaler.signal( self._signaler.eip_process_finished, exit_code) self._alive = False @@ -976,3 +1028,41 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): self.transport.signalProcess('KILL') except internet_error.ProcessExitedAlready: logger.debug('Process Exited Already') + + +class VPNCanary(VPNProcess): + + """ + This is a Canary Process that does not run openvpn itself, but it's + notified by the privileged process when the process dies. + + This is an ugly workaround to allow the qt signals and the processprotocol + to live happily together until we refactor EIP out of the qt model + completely. + """ + + def connectionMade(self): + VPNProcess.connectionMade(self) + reactor.callLater(2, self.registerPID) + + def registerPID(self): + helper = darwinvpnlauncher.DarwinHelperCommand() + cmd = 'openvpn_set_watcher %s' % self.pid + result = helper.send(cmd) + + def killProcess(self): + helper = darwinvpnlauncher.DarwinHelperCommand() + cmd = 'openvpn_force_stop' + result = helper.send(cmd) + + def getVPNCommand(self): + return VPNProcess.getCommand(self) + + def getCommand(self): + canary = '''import sys, signal, time +def receive_signal(signum, stack): + sys.exit() +signal.signal(signal.SIGTERM, receive_signal) +while True: + time.sleep(60)''' + return ['python', '-c', '%s' % canary] -- cgit v1.2.3 From 9affaaacb18598fc98be669ef1c086b0afe4ad91 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 11 Feb 2016 13:34:46 -0800 Subject: [refactor] cleanup helper usage to adapt to new one also cleanups build process --- pkg/osx/__init__.py | 2 +- pkg/osx/_metadata.py | 28 ++++---- pkg/osx/daemon.py | 75 +++++++++++----------- pkg/osx/pidfile.py | 4 +- pkg/osx/runner.py | 40 ++++++------ src/leap/bitmask/services/eip/darwinvpnlauncher.py | 4 +- src/leap/bitmask/services/eip/vpnprocess.py | 19 +++--- 7 files changed, 87 insertions(+), 85 deletions(-) diff --git a/pkg/osx/__init__.py b/pkg/osx/__init__.py index 4731a6ef..77ff624b 100644 --- a/pkg/osx/__init__.py +++ b/pkg/osx/__init__.py @@ -41,7 +41,7 @@ from __future__ import (absolute_import, unicode_literals) from .daemon import DaemonContext - + # Local variables: # coding: utf-8 # mode: python diff --git a/pkg/osx/_metadata.py b/pkg/osx/_metadata.py index 6d22a2b7..88843df7 100644 --- a/pkg/osx/_metadata.py +++ b/pkg/osx/_metadata.py @@ -21,10 +21,11 @@ import datetime import pkg_resources - + distribution_name = "python-daemon" version_info_filename = "version_info.json" + def get_distribution_version_info(filename=version_info_filename): """ Get the version info from the installed distribution. @@ -37,10 +38,10 @@ def get_distribution_version_info(filename=version_info_filename): """ version_info = { - 'release_date': "UNKNOWN", - 'version': "UNKNOWN", - 'maintainer': "UNKNOWN", - } + 'release_date': "UNKNOWN", + 'version': "UNKNOWN", + 'maintainer': "UNKNOWN", + } try: distribution = pkg_resources.get_distribution(distribution_name) @@ -58,12 +59,13 @@ version_info = get_distribution_version_info() version_installed = version_info['version'] - + rfc822_person_regex = re.compile( - "^(?P[^<]+) <(?P[^>]+)>$") + "^(?P[^<]+) <(?P[^>]+)>$") ParsedPerson = collections.namedtuple('ParsedPerson', ['name', 'email']) + def parse_person_field(value): """ Parse a person field into name and email address. @@ -80,18 +82,18 @@ def parse_person_field(value): if len(value): if match is not None: result = ParsedPerson( - name=match.group('name'), - email=match.group('email')) + name=match.group('name'), + email=match.group('email')) else: result = ParsedPerson(name=value, email=None) - return result + return result author_name = "Ben Finney" author_email = "ben+python@benfinney.id.au" author = "{name} <{email}>".format(name=author_name, email=author_email) - + class YearRange: """ A range of years spanning a period. """ @@ -140,11 +142,11 @@ build_date = version_info['release_date'] copyright_year_range = make_year_range(copyright_year_begin, build_date) copyright = "Copyright © {year_range} {author} and others".format( - year_range=copyright_year_range, author=author) + year_range=copyright_year_range, author=author) license = "Apache-2" url = "https://alioth.debian.org/projects/python-daemon/" - + # Local variables: # coding: utf-8 # mode: python diff --git a/pkg/osx/daemon.py b/pkg/osx/daemon.py index 07810cf1..7ca8770e 100644 --- a/pkg/osx/daemon.py +++ b/pkg/osx/daemon.py @@ -36,7 +36,7 @@ except NameError: basestring = str unicode = str - + class DaemonError(Exception): """ Base exception class for errors from this module. """ @@ -56,7 +56,7 @@ class DaemonOSEnvironmentError(DaemonError, OSError): class DaemonProcessDetachError(DaemonError, OSError): """ Exception raised when process detach fails. """ - + class DaemonContext: """ Context for turning the current program into a daemon process. @@ -245,7 +245,7 @@ class DaemonContext: stdout=None, stderr=None, signal_map=None, - ): + ): """ Set up a new instance. """ self.chroot_directory = chroot_directory self.working_directory = working_directory @@ -427,8 +427,8 @@ class DaemonContext: """ exception = SystemExit( - "Terminating on signal {signal_number!r}".format( - signal_number=signal_number)) + "Terminating on signal {signal_number!r}".format( + signal_number=signal_number)) raise exception def _get_exclude_file_descriptors(self): @@ -454,8 +454,8 @@ class DaemonContext: if files_preserve is None: files_preserve = [] files_preserve.extend( - item for item in [self.stdin, self.stdout, self.stderr] - if hasattr(item, 'fileno')) + item for item in [self.stdin, self.stdout, self.stderr] + if hasattr(item, 'fileno')) exclude_descriptors = set() for item in files_preserve: @@ -502,8 +502,8 @@ class DaemonContext: """ signal_handler_map = dict( - (signal_number, self._make_signal_handler(target)) - for (signal_number, target) in self.signal_map.items()) + (signal_number, self._make_signal_handler(target)) + for (signal_number, target) in self.signal_map.items()) return signal_handler_map @@ -529,7 +529,7 @@ def _get_file_descriptor(obj): return file_descriptor - + def change_working_directory(directory): """ Change the working directory of this process. @@ -541,7 +541,7 @@ def change_working_directory(directory): os.chdir(directory) except Exception as exc: error = DaemonOSEnvironmentError( - "Unable to change working directory ({exc})".format(exc=exc)) + "Unable to change working directory ({exc})".format(exc=exc)) raise error @@ -561,7 +561,7 @@ def change_root_directory(directory): os.chroot(directory) except Exception as exc: error = DaemonOSEnvironmentError( - "Unable to change root directory ({exc})".format(exc=exc)) + "Unable to change root directory ({exc})".format(exc=exc)) raise error @@ -576,7 +576,7 @@ def change_file_creation_mask(mask): os.umask(mask) except Exception as exc: error = DaemonOSEnvironmentError( - "Unable to change file creation mask ({exc})".format(exc=exc)) + "Unable to change file creation mask ({exc})".format(exc=exc)) raise error @@ -597,10 +597,10 @@ def change_process_owner(uid, gid): os.setuid(uid) except Exception as exc: error = DaemonOSEnvironmentError( - "Unable to change process owner ({exc})".format(exc=exc)) + "Unable to change process owner ({exc})".format(exc=exc)) raise error - + def prevent_core_dump(): """ Prevent this process from generating a core dump. @@ -618,15 +618,15 @@ def prevent_core_dump(): core_limit_prev = resource.getrlimit(core_resource) except ValueError as exc: error = DaemonOSEnvironmentError( - "System does not support RLIMIT_CORE resource limit" - " ({exc})".format(exc=exc)) + "System does not support RLIMIT_CORE resource limit" + " ({exc})".format(exc=exc)) raise error # Set hard and soft limits to zero, i.e. no core dump at all. core_limit = (0, 0) resource.setrlimit(core_resource, core_limit) - + def detach_process_context(): """ Detach the process context from parent and session. @@ -656,15 +656,15 @@ def detach_process_context(): os._exit(0) except OSError as exc: error = DaemonProcessDetachError( - "{message}: [{exc.errno:d}] {exc.strerror}".format( - message=error_message, exc=exc)) + "{message}: [{exc.errno:d}] {exc.strerror}".format( + message=error_message, exc=exc)) raise error fork_then_exit_parent(error_message="Failed first fork") os.setsid() fork_then_exit_parent(error_message="Failed second fork") - + def is_process_started_by_init(): """ Determine whether the current process is started by `init`. @@ -700,7 +700,7 @@ def is_socket(fd): try: socket_type = file_socket.getsockopt( - socket.SOL_SOCKET, socket.SO_TYPE) + socket.SOL_SOCKET, socket.SO_TYPE) except socket.error as exc: exc_errno = exc.args[0] if exc_errno == errno.ENOTSOCK: @@ -759,7 +759,7 @@ def is_detach_process_context_required(): return result - + def close_file_descriptor_if_open(fd): """ Close a file descriptor if already open. @@ -778,13 +778,14 @@ def close_file_descriptor_if_open(fd): pass else: error = DaemonOSEnvironmentError( - "Failed to close file descriptor {fd:d} ({exc})".format( - fd=fd, exc=exc)) + "Failed to close file descriptor {fd:d} ({exc})".format( + fd=fd, exc=exc)) raise error MAXFD = 2048 + def get_maximum_file_descriptors(): """ Get the maximum number of open file descriptors for this process. @@ -820,7 +821,7 @@ def close_all_open_files(exclude=set()): if fd not in exclude: close_file_descriptor_if_open(fd) - + def redirect_stream(system_stream, target_stream): """ Redirect a system stream to a specified file. @@ -844,7 +845,7 @@ def redirect_stream(system_stream, target_stream): target_fd = target_stream.fileno() os.dup2(target_fd, system_stream.fileno()) - + def make_default_signal_map(): """ Make the default signal map for this system. @@ -855,15 +856,15 @@ def make_default_signal_map(): """ name_map = { - 'SIGTSTP': None, - 'SIGTTIN': None, - 'SIGTTOU': None, - 'SIGTERM': 'terminate', - } + 'SIGTSTP': None, + 'SIGTTIN': None, + 'SIGTTOU': None, + 'SIGTERM': 'terminate', + } signal_map = dict( - (getattr(signal, name), target) - for (name, target) in name_map.items() - if hasattr(signal, name)) + (getattr(signal, name), target) + for (name, target) in name_map.items() + if hasattr(signal, name)) return signal_map @@ -895,7 +896,7 @@ def register_atexit_function(func): """ atexit.register(func) - + def _chain_exception_from_existing_exception_context(exc, as_cause=False): """ Decorate the specified exception with the existing exception context. @@ -918,7 +919,7 @@ def _chain_exception_from_existing_exception_context(exc, as_cause=False): exc.__context__ = existing_exc exc.__traceback__ = existing_traceback - + # Local variables: # coding: utf-8 # mode: python diff --git a/pkg/osx/pidfile.py b/pkg/osx/pidfile.py index 4517ee0e..68f7b2ac 100644 --- a/pkg/osx/pidfile.py +++ b/pkg/osx/pidfile.py @@ -17,7 +17,7 @@ from __future__ import (absolute_import, unicode_literals) from lockfile.pidlockfile import PIDLockFile - + class TimeoutPIDLockFile(PIDLockFile, object): """ Lockfile with default timeout, implemented as a Unix PID file. @@ -59,7 +59,7 @@ class TimeoutPIDLockFile(PIDLockFile, object): timeout = self.acquire_timeout super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs) - + # Local variables: # coding: utf-8 # mode: python diff --git a/pkg/osx/runner.py b/pkg/osx/runner.py index 6973cf1c..de9025d3 100644 --- a/pkg/osx/runner.py +++ b/pkg/osx/runner.py @@ -37,7 +37,7 @@ from .daemon import (basestring, unicode) from .daemon import DaemonContext from .daemon import _chain_exception_from_existing_exception_context - + class DaemonRunnerError(Exception): """ Abstract base class for errors from DaemonRunner. """ @@ -65,7 +65,7 @@ class DaemonRunnerStartFailureError(DaemonRunnerError, RuntimeError): class DaemonRunnerStopFailureError(DaemonRunnerError, RuntimeError): """ Raised when failure stopping DaemonRunner. """ - + class DaemonRunner: """ Controller for a callable running in a separate background process. @@ -110,12 +110,12 @@ class DaemonRunner: self.daemon_context.stdin = open(app.stdin_path, 'rt') self.daemon_context.stdout = open(app.stdout_path, 'w+t') self.daemon_context.stderr = open( - app.stderr_path, 'w+t', buffering=0) + app.stderr_path, 'w+t', buffering=0) self.pidfile = None if app.pidfile_path is not None: self.pidfile = make_pidlockfile( - app.pidfile_path, app.pidfile_timeout) + app.pidfile_path, app.pidfile_timeout) self.daemon_context.pidfile = self.pidfile def _usage_exit(self, argv): @@ -130,7 +130,7 @@ class DaemonRunner: usage_exit_code = 2 action_usage = "|".join(self.action_funcs.keys()) message = "usage: {progname} {usage}".format( - progname=progname, usage=action_usage) + progname=progname, usage=action_usage) emit_message(message) sys.exit(usage_exit_code) @@ -175,8 +175,8 @@ class DaemonRunner: self.daemon_context.open() except lockfile.AlreadyLocked: error = DaemonRunnerStartFailureError( - "PID file {pidfile.path!r} already locked".format( - pidfile=self.pidfile)) + "PID file {pidfile.path!r} already locked".format( + pidfile=self.pidfile)) raise error pid = os.getpid() @@ -198,8 +198,8 @@ class DaemonRunner: os.kill(pid, signal.SIGTERM) except OSError as exc: error = DaemonRunnerStopFailureError( - "Failed to terminate {pid:d}: {exc}".format( - pid=pid, exc=exc)) + "Failed to terminate {pid:d}: {exc}".format( + pid=pid, exc=exc)) raise error def _stop(self): @@ -212,8 +212,8 @@ class DaemonRunner: """ if not self.pidfile.is_locked(): error = DaemonRunnerStopFailureError( - "PID file {pidfile.path!r} not locked".format( - pidfile=self.pidfile)) + "PID file {pidfile.path!r} not locked".format( + pidfile=self.pidfile)) raise error if is_pidfile_stale(self.pidfile): @@ -228,10 +228,10 @@ class DaemonRunner: self._start() action_funcs = { - 'start': _start, - 'stop': _stop, - 'restart': _restart, - } + 'start': _start, + 'stop': _stop, + 'restart': _restart, + } def _get_action_func(self): """ Get the function for the specified action. @@ -249,8 +249,8 @@ class DaemonRunner: func = self.action_funcs[self.action] except KeyError: error = DaemonRunnerInvalidActionError( - "Unknown action: {action!r}".format( - action=self.action)) + "Unknown action: {action!r}".format( + action=self.action)) raise error return func @@ -279,11 +279,11 @@ def make_pidlockfile(path, acquire_timeout): """ Make a PIDLockFile instance with the given filesystem path. """ if not isinstance(path, basestring): error = ValueError("Not a filesystem path: {path!r}".format( - path=path)) + path=path)) raise error if not os.path.isabs(path): error = ValueError("Not an absolute path: {path!r}".format( - path=path)) + path=path)) raise error lockfile = pidfile.TimeoutPIDLockFile(path, acquire_timeout) @@ -316,7 +316,7 @@ def is_pidfile_stale(pidfile): return result - + # Local variables: # coding: utf-8 # mode: python diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py index 42d9576b..f1d17698 100644 --- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py +++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py @@ -54,7 +54,7 @@ class DarwinHelperCommand(object): self._connect() sock = self._sock data = "" - + command = cmd + ' ' + args + '/CMD' try: @@ -76,7 +76,6 @@ class DarwinVPNLauncher(VPNLauncher): # TODO -- move this to bitmask-helper - # Hardcode the installation path for OSX for security, openvpn is # run as root INSTALL_PATH = "/Applications/Bitmask.app/" @@ -127,7 +126,6 @@ class DarwinVPNLauncher(VPNLauncher): return os.path.join(resources_path, "bitmask.tiff") - # TODO deprecate --------------------------------------------------------- @classmethod def get_cocoasudo_ovpn_cmd(kls): diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 6d18a599..de8d92f3 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -175,7 +175,8 @@ class VPN(object): :param kwargs: kwargs to be passed to the VPNProcess :type kwargs: dict """ - logger.debug('VPN: start ---------------------------------------------------') + logger.debug( + 'VPN: start ---------------------------------------------------') self._user_stopped = False self._stop_pollers() kwargs['openvpn_verb'] = self._openvpn_verb @@ -183,7 +184,6 @@ class VPN(object): restart = kwargs.pop('restart', False) - # FIXME it would be good to document where the # errors here are catched, since we currently handle them # at the frontend layer. This *should* move to be handled entirely @@ -206,7 +206,8 @@ class VPN(object): cmd = vpnproc.getCommand() if vpnproc.get_openvpn_process(): - logger.info("Another vpn process is running. Will try to stop it.") + logger.info( + "Another vpn process is running. Will try to stop it.") vpnproc.stop_if_already_running() # we try to bring the firewall up @@ -245,8 +246,6 @@ class VPN(object): running_proc = reactor.spawnProcess(vpnproc, cmd[0], cmd, env) vpnproc.pid = running_proc.pid self._vpnproc = vpnproc - - # add pollers for status and state # this could be extended to a collection of @@ -292,7 +291,8 @@ class VPN(object): :rtype: bool """ if IS_LINUX: - BM_ROOT = force_eval(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() @@ -303,7 +303,6 @@ class VPN(object): result = helper.send(cmd) return True - def tear_down_firewall(self): """ Tear the firewall down using the privileged wrapper. @@ -315,7 +314,8 @@ class VPN(object): return True if IS_LINUX: - BM_ROOT = force_eval(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 @@ -331,7 +331,8 @@ class VPN(object): return True if IS_LINUX: - BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) + BM_ROOT = force_eval( + linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) exitCode = subprocess.call(["pkexec", BM_ROOT, "openvpn", "stop"]) return True if exitCode is 0 else False -- cgit v1.2.3 From d01772d0a83535bf45fa43786213a9a0fcb232bc Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 11 Feb 2016 13:34:46 -0800 Subject: [refactor] cleanup helper usage to adapt to new one also cleanups build process --- Makefile | 73 +----------- openvpn/openvpn.sh | 123 +++++++++++++++++++++ pkg/deps.mk | 28 +++++ pkg/osx/Makefile | 51 --------- pkg/osx/bitmask-wrapper | 3 - pkg/osx/install/tun.kext/Info.plist | 36 ------ pkg/osx/post-inst.sh | 4 +- pkg/pyinst/pyinst-build.mk | 34 ++++-- pkg/tools/profile.mk | 23 ++++ src/leap/bitmask/services/eip/darwinvpnlauncher.py | 5 +- 10 files changed, 212 insertions(+), 168 deletions(-) create mode 100755 openvpn/openvpn.sh create mode 100644 pkg/deps.mk delete mode 100644 pkg/osx/Makefile delete mode 100755 pkg/osx/bitmask-wrapper delete mode 100644 pkg/osx/install/tun.kext/Info.plist create mode 100644 pkg/tools/profile.mk diff --git a/Makefile b/Makefile index fdfb0381..84497607 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,8 @@ LRELE = lrelease # pyinst dist dir DIST = dist/bitmask/ +DIST_OSX = dist/Bitmask.app/ +DIST_OSX_RES = dist/Bitmask.app/Contents/Resources/ NEXT_VERSION = $(shell cat pkg/next-version) DIST_VERSION = dist/bitmask-$(NEXT_VERSION)/ GIT_COMMIT = $(shell git rev-parse HEAD) @@ -95,78 +97,11 @@ manpages: apidocs: @sphinx-apidoc -o docs/api src/leap/bitmask -do_cprofile: - python -m cProfile -o bitmask.cprofile src/leap/bitmask/app.py --debug -N - -view_cprofile: - cprofilev bitmask.cprofile - -mailprofile: - gprof2dot -f pstats /tmp/leap_mail_profile.pstats -n 0.2 -e 0.2 | dot -Tpdf -o /tmp/leap_mail_profile.pdf - -do_lineprof: - LEAP_PROFILE_IMAPCMD=1 LEAP_MAIL_MANHOLE=1 kernprof.py -l src/leap/bitmask/app.py --debug - -do_lineprof_offline: - LEAP_PROFILE_IMAPCMD=1 LEAP_MAIL_MANHOLE=1 kernprof.py -l src/leap/bitmask/app.py --offline --debug -N - -view_lineprof: - @python -m line_profiler app.py.lprof | $(EDITOR) - - -resource_graph: - #./pkg/scripts/monitor_resource.zsh `ps aux | grep app.py | head -1 | awk '{print $$2}'` $(RESOURCE_TIME) - ./pkg/scripts/monitor_resource.zsh `pgrep bitmask` $(RESOURCE_TIME) - display bitmask-resources.png - -get_wheels: - pip install --upgrade setuptools - pip install --upgrade pip - pip install wheel - -gather_wheels: - pip wheel --wheel-dir=../wheelhouse pyzmq --build-option "--zmq=bundled" - # because fuck u1db externals, that's why... - pip wheel --wheel-dir=../wheelhouse --allow-external dirspec --allow-unverified dirspec --allow-external u1db --allow-unverified u1db -r pkg/requirements.pip - -install_wheel: - # if it's the first time, you'll need to get_wheels first - pip install --pre --use-wheel --no-index --find-links=../wheelhouse -r pkg/requirements.pip - -gather_deps: - pipdeptree | pkg/scripts/filter-bitmask-deps - -install_base_deps: - for repo in leap_pycommon keymanager leap_mail soledad/common soledad/client; do cd $(CURDIR)/../$$repo && pkg/pip_install_requirements.sh; done - pkg/pip_install_requirements.sh - -pull_leapdeps: - for repo in $(LEAP_REPOS); do cd $(CURDIR)/../$$repo && git pull; done - -checkout_leapdeps_develop: - for repo in $(LEAP_REPOS); do cd $(CURDIR)/../$$repo && git checkout develop; done - git checkout develop - +include pkg/deps.mk +include pkg/tools/profile.mk include pkg/sumo-tarballs.mk include pkg/pyinst/pyinst-build.mk include pkg/branding/branding.mk -pyinst_osx: pyinst - mv dist/Bitmask.app/Contents/MacOS/bitmask dist/Bitmask.app/Contents/MacOS/bitmask-app - cp pkg/osx/bitmask-wrapper dist/Bitmask.app/Contents/MacOS/bitmask - mkdir -p dist/Bitmask.app/Contents/Resources/bitmask-helper - cp pkg/osx/client.up.sh dist/Bitmask.app/Contents/Resources/ - cp pkg/osx/client.down.sh dist/Bitmask.app/Contents/Resources/ - cp pkg/osx/bitmask-helper dist/Bitmask.app/Contents/Resources/bitmask-helper/ - cp pkg/osx/bitmask.pf.conf dist/Bitmask.app/Contents/Resources/bitmask-helper/ - cp pkg/osx/se.leap.bitmask-helper.plist dist/Bitmask.app/Contents/Resources/bitmask-helper/ - cp pkg/osx/post-inst.sh dist/Bitmask.app/Contents/Resources/bitmask-helper/ - cp pkg/osx/daemon.py dist/Bitmask.app/Contents/Resources/bitmask-helper/ - # XXX hack... this contains the gpg binary (brew), but we need to build it from sources. - cp -r src/leap/bitmask/util/apps dist/Bitmask.app/Contents/MacOS/ - # XXX hack... this contains the openvpn binary (brew), but we need to build it from sources. - cp -r src/leap/bitmask/util/openvpn.leap dist/Bitmask.app/Contents/Resources/ - # XXX this should be taken care of by pyinstaller data collector - cp $(VIRTUAL_ENV)/lib/python2.7/site-packages/leap/common/cacert.pem dist/Bitmask.app/Contents/MacOS/ - clean : $(RM) $(COMPILED_UI) $(COMPILED_RESOURCES) $(COMPILED_UI:.py=.pyc) $(COMPILED_RESOURCES:.py=.pyc) diff --git a/openvpn/openvpn.sh b/openvpn/openvpn.sh new file mode 100755 index 00000000..db63c987 --- /dev/null +++ b/openvpn/openvpn.sh @@ -0,0 +1,123 @@ +#!/bin/bash + +set -e +set -x + +mkdir -p ~/openvpn && cd ~/openvpn + +BASE=`pwd` +SRC=$BASE/src +WGET="wget --prefer-family=IPv4" +DEST=$BASE/stuff +LDFLAGS="-L$DEST/lib -Wl" +CPPFLAGS="-I$DEST/include" +CFLAGS="-O3" +CXXFLAGS=$CFLAGS +CONFIGURE="./configure --prefix=/stuff" +MAKE="make -j2" +mkdir -p $SRC + +######## #################################################################### +# ZLIB # #################################################################### +######## #################################################################### + +mkdir $SRC/zlib && cd $SRC/zlib + +if [ ! -f zlib-1.2.8.tar.gz ]; then + $WGET http://zlib.net/zlib-1.2.8.tar.gz +fi +tar zxvf zlib-1.2.8.tar.gz +cd zlib-1.2.8 + +LDFLAGS=$LDFLAGS \ +CPPFLAGS=$CPPFLAGS \ +CFLAGS=$CFLAGS \ +CXXFLAGS=$CXXFLAGS \ +./configure \ +--prefix=/stuff + +$MAKE +make install DESTDIR=$BASE + +########### ################################################################# +# OPENSSL # ################################################################# +########### ################################################################# + +#mkdir -p $SRC/openssl && cd $SRC/openssl +#if [ ! -f openssl-1.0.2f.tar.gz ]; then +# $WGET https://www.openssl.org/source/openssl-1.0.2f.tar.gz +#fi +#tar zxvf openssl-1.0.2f.tar.gz +#cd openssl-1.0.2f + +#./Configure darwin64-x86_64-cc \ +#-Wl \ +#--prefix=/opts zlib \ +#--with-zlib-lib=$DEST/lib \ +#--with-zlib-include=$DEST/include + +#$MAKE +#make install INSTALLTOP=$DEST OPENSSLDIR=$DEST/ssl + +############ ################################################################# +# POLARSSL # ################################################################# +############ ################################################################# + +mkdir -p $SRC/polarssl && cd $SRC/polarssl +if [ ! -f polarssl-1.3.9-gpl.tgz ]; then + $WGET https://tls.mbed.org/download/polarssl-1.3.9-gpl.tgz +fi +tar zxvf polarssl-1.3.9-gpl.tgz +cd polarssl-1.3.9 +mkdir build +cd build +cmake .. +$MAKE +make install DESTDIR=$BASE + +######## #################################################################### +# LZO2 # #################################################################### +######## #################################################################### + +mkdir $SRC/lzo2 && cd $SRC/lzo2 +if [ ! -f lzo-2.09.tar.gz ]; then + $WGET http://www.oberhumer.com/opensource/lzo/download/lzo-2.09.tar.gz +fi +tar zxvf lzo-2.09.tar.gz +cd lzo-2.09 + +LDFLAGS=$LDFLAGS \ +CPPFLAGS=$CPPFLAGS \ +CFLAGS=$CFLAGS \ +CXXFLAGS=$CXXFLAGS \ +$CONFIGURE + +$MAKE +make install DESTDIR=$BASE + +########### ################################################################# +# OPENVPN # ################################################################# +########### ################################################################# + +mkdir $SRC/openvpn && cd $SRC/openvpn +if [ ! -f openvpn-2.3.10.tar.gz ]; then + $WGET http://swupdate.openvpn.org/community/releases/openvpn-2.3.10.tar.gz +fi +tar zxvf openvpn-2.3.10.tar.gz +cd openvpn-2.3.10 + +# OPENSSL_SSL_LIBS=$DEST/lib/ + +POLARSSL_CFLAGS=-I$DEST/usr/local/include \ +POLARSSL_LIBS=$DEST/lib/libpolarssl.a \ +LDFLAGS=$LDFLAGS \ +CPPFLAGS=$CPPFLAGS \ +CFLAGS=$CFLAGS \ +CXXFLAGS=$CXXFLAGS \ +$CONFIGURE \ +--disable-plugin-auth-pam \ +--enable-password-save \ +--with-crypto-library=polarssl + +$MAKE LIBS="-all-static -lssl -lcrypto -lz -llzo2" +make install DESTDIR=$BASE/openvpn diff --git a/pkg/deps.mk b/pkg/deps.mk new file mode 100644 index 00000000..26bed466 --- /dev/null +++ b/pkg/deps.mk @@ -0,0 +1,28 @@ +get_wheels: + pip install --upgrade setuptools + pip install --upgrade pip + pip install wheel + +gather_wheels: + pip wheel --wheel-dir=../wheelhouse pyzmq --build-option "--zmq=bundled" + # because fuck u1db externals, that's why... + pip wheel --wheel-dir=../wheelhouse --allow-external dirspec --allow-unverified dirspec --allow-external u1db --allow-unverified u1db -r pkg/requirements.pip + +install_wheel: + # if it's the first time, you'll need to get_wheels first + pip install --pre --use-wheel --no-index --find-links=../wheelhouse -r pkg/requirements.pip + +gather_deps: + pipdeptree | pkg/scripts/filter-bitmask-deps + +install_base_deps: + for repo in leap_pycommon keymanager leap_mail soledad/common soledad/client; do cd $(CURDIR)/../$$repo && pkg/pip_install_requirements.sh; done + pkg/pip_install_requirements.sh + +pull_leapdeps: + for repo in $(LEAP_REPOS); do cd $(CURDIR)/../$$repo && git pull; done + +checkout_leapdeps_develop: + for repo in $(LEAP_REPOS); do cd $(CURDIR)/../$$repo && git checkout develop; done + git checkout develop + diff --git a/pkg/osx/Makefile b/pkg/osx/Makefile deleted file mode 100644 index 15dfb810..00000000 --- a/pkg/osx/Makefile +++ /dev/null @@ -1,51 +0,0 @@ -OSX = dist/LEAP\ Client.app/Contents/MacOS/ -GITC = `git rev-parse --short HEAD` -DMG = "dist/leap-client-$(GITC).dmg" -INST = "dist/LEAP Client installer.app" -INSTR = "dist/LEAP Client installer.app/Contents/Resources" - -pkg : check-env dist tuntap installer dmg - -dist : - ~/pyinstaller/pyinstaller.py -w -s leap-client.spec - cp -r /opt/local/Library/Frameworks/QtGui.framework/Versions/4/Resources/qt_menu.nib "dist/LEAP Client.app/Contents/Resources" - cp Info.plist "dist/LEAP Client.app/Contents/Info.plist" - cp ../../data/images/leap-client.icns "dist/LEAP Client.app/Contents/Resources/icon-windowed.icns" - -trim: - #XXX this should go properly in pyinstaller spec excludes, but going quick'n'dirty - #XXX adapt to PySide - rm $(OSX)QtSvg $(OSX)QtXml $(OSX)QtNetwork $(OSX)QtOpenGL $(OSX)Qt3Support $(OSX)QtSql - -tuntap: - ./build_tuntaposx clean && ./build_tuntaposx - -installer: - #XXX need to fix some paths there (binary, etc) - platypus -P install/leap-installer.platypus -y $(INST) - # build tuntaposx kernel extension - mkdir $(INSTR)/StartupItems - mkdir $(INSTR)/Extensions - cp -r dist/tun.kext $(INSTR)/Extensions - cp -r dist/tuntaposx/StartupItems/* $(INSTR)/StartupItems - cp install/tun.kext/Info.plist $(INSTR)/Extensions/tun.kext/Contents/ - #copy the binary that we have previously built (not yet) - cp ../../openvpn/build/openvpn.leap $(INSTR) - #copy startup scripts - cp install/client.up.sh $(INSTR) - cp install/client.down.sh $(INSTR) - cp install/ProcessNetworkChanges.plist.template $(INSTR) - #Finally, copy application bundle... - cp -r "dist/LEAP Client.app" $(INSTR) - -dmg : - rm -f $(DMG) - hdiutil create -format UDBZ -srcfolder $(INST) $(DMG) - -check-env: -ifndef VIRTUAL_ENV - $(error WHAT DO YOU THINK VIRTUALENV IS FOR??!! Please go get into one..) -endif - -clean : - rm -rf dist/ build/ diff --git a/pkg/osx/bitmask-wrapper b/pkg/osx/bitmask-wrapper deleted file mode 100755 index 240fc186..00000000 --- a/pkg/osx/bitmask-wrapper +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -exec $DIR/bitmask-app --debug --danger diff --git a/pkg/osx/install/tun.kext/Info.plist b/pkg/osx/install/tun.kext/Info.plist deleted file mode 100644 index fb69ba85..00000000 --- a/pkg/osx/install/tun.kext/Info.plist +++ /dev/null @@ -1,36 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - tun - CFBundleIdentifier - leap.tun - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - tun - CFBundlePackageType - KEXT - CFBundleShortVersionString - 20120120 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - OSBundleLibraries - - com.apple.kpi.mach - 8.0 - com.apple.kpi.bsd - 8.0 - com.apple.kpi.libkern - 8.0 - com.apple.kpi.unsupported - 8.0 - - - - diff --git a/pkg/osx/post-inst.sh b/pkg/osx/post-inst.sh index 2fc719f0..03dc4d2f 100755 --- a/pkg/osx/post-inst.sh +++ b/pkg/osx/post-inst.sh @@ -1,6 +1,8 @@ #!/bin/sh -# Post-Instalation script +# Bitmask Post-Instalation script cp se.leap.bitmask-helper.plist /Library/LaunchDaemons/ launchctl load /Library/LaunchDaemons/se.leap.bitmask-helper.plist +cp tuntap_20150118.pkg /tmp/ +open /tmp/tuntap_20150118.pkg diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index 9595ad50..835c793c 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -12,8 +12,9 @@ pyinst: freeze-ver hash-binaries reset-ver: git checkout -- src/leap/bitmask/_version.py -pyinst-hacks: - cp ../leap_common/src/leap/common/cacert.pem $(DIST) +pyinst-hacks-linux: + # XXX this should be taken care of by pyinstaller data collector + cp $(VIRTUAL_ENV)/lib/python2.7/site-packages/leap/common/cacert.pem $(DIST) mkdir -p $(DIST)pysqlcipher mkdir -p $(DIST)pixelated mkdir -p $(DIST)twisted/web @@ -22,15 +23,17 @@ pyinst-hacks: cp -r $(VIRTUAL_ENV)/lib/python2.7/site-packages/pixelated/assets/ $(DIST)pixelated cp -r $(VIRTUAL_ENV)/lib/python2.7/site-packages/twisted/web/failure.xhtml $(DIST)twisted/web/ +pyinst-hacks-osx: + # XXX this should be taken care of by pyinstaller data collector + cp $(VIRTUAL_ENV)/lib/python2.7/site-packages/leap/common/cacert.pem $(DIST_OSX)Contents/MacOS/ + # XXX need the rest??? + pyinst-trim: rm -f $(DIST)libQtOpenGL.so.4 rm -f $(DIST)libQtSql.so.4 rm -f $(DIST)libQt3Support.so.4 rm -f $(DIST)libaudio.so.2 rm -f $(DIST)libnvidia-* - #rm -f dist/bitmask/libgstvideo-1.0.so.0 - #rm -f dist/bitmask/libgstaudio0.0.so.0 - #rm -f dist/bitmask/libgstreamer-1.0.so.0 pyinst-cleanup: rm -rf $(DIST)config @@ -44,7 +47,7 @@ pyinst-distribution-data: cp pkg/PixelatedWebmail.README $(DIST_VERSION) cp LICENSE $(DIST_VERSION) -pyinst-linux-helpers: +pyinst-helpers-linux: mkdir -p $(DIST_VERSION)apps/eip/files cp $(LEAP_BUILD_DIR)openvpn $(DIST_VERSION)apps/eip/files/leap-openvpn cp pkg/linux/bitmask-root $(DIST_VERSION)apps/eip/files/ @@ -53,6 +56,21 @@ pyinst-linux-helpers: mkdir -p $(DIST_VERSION)apps/mail cp $(LEAP_BUILD_DIR)gpg $(DIST_VERSION)apps/mail +pyinst-helpers-osx: + mkdir -p $(DIST_OSX_RES)bitmask-helper + cp pkg/osx/client.up.sh $(DIST_OSX_RES) + cp pkg/osx/client.down.sh $(DIST_OSX_RES) + cp pkg/osx/bitmask-helper $(DIST_OSX_RES)bitmask-helper/ + cp pkg/osx/bitmask.pf.conf $(DIST_OSX_RES)bitmask-helper/ + cp pkg/osx/se.leap.bitmask-helper.$(DIST_OSX_RES)bitmask-helper/ + cp pkg/osx/post-inst.sh $(DIST_OSX_RES)bitmask-helper/ + cp pkg/osx/daemon.py $(DIST_OSX_RES)bitmask-helper/ + cp /opt/homebrew-cask/Caskroom/tuntap/20150118/tuntap_20150118.pkg $(DIST_OSX_RES) + # TODO get from the path the build script places it + cp ~/leap/openvpn.leap.polarssl $(DIST_OSX_RES)openvpn.leap + # TODO this contains the gpg binary (brew), but we need to compile it statically from sources. + cp -r src/leap/bitmask/util/apps $(DIST_OSX)Contents/MacOS/ + pyinst-tar: cd dist/ && tar cvzf Bitmask.$(NEXT_VERSION).tar.gz bitmask-$(NEXT_VERSION) @@ -62,7 +80,9 @@ pyinst-sign: pyinst-upload: rsync --rsh='ssh' -avztlpog --progress --partial dist/Bitmask.$(NEXT_VERSION).* salmon.leap.se:./ -pyinst-linux: pyinst reset-ver pyinst-hacks pyinst-trim pyinst-cleanup pyinst-distribution-data pyinst-linux-helpers pyinst-tar +pyinst-linux: pyinst reset-ver pyinst-hacks-linux pyinst-trim pyinst-cleanup pyinst-distribution-data pyinst-helpers-linux pyinst-tar + +pyinst-osx: pyinst reset-ver pyinst-hacks-osx pyinst-helpers-osx clean_pkg: rm -rf build dist diff --git a/pkg/tools/profile.mk b/pkg/tools/profile.mk new file mode 100644 index 00000000..8d45c01a --- /dev/null +++ b/pkg/tools/profile.mk @@ -0,0 +1,23 @@ +do_cprofile: + python -m cProfile -o bitmask.cprofile src/leap/bitmask/app.py --debug -N + +view_cprofile: + cprofilev bitmask.cprofile + +mailprofile: + gprof2dot -f pstats /tmp/leap_mail_profile.pstats -n 0.2 -e 0.2 | dot -Tpdf -o /tmp/leap_mail_profile.pdf + +do_lineprof: + LEAP_PROFILE_IMAPCMD=1 LEAP_MAIL_MANHOLE=1 kernprof.py -l src/leap/bitmask/app.py --debug + +do_lineprof_offline: + LEAP_PROFILE_IMAPCMD=1 LEAP_MAIL_MANHOLE=1 kernprof.py -l src/leap/bitmask/app.py --offline --debug -N + +view_lineprof: + @python -m line_profiler app.py.lprof | $(EDITOR) - + +resource_graph: + #./pkg/scripts/monitor_resource.zsh `ps aux | grep app.py | head -1 | awk '{print $$2}'` $(RESOURCE_TIME) + ./pkg/scripts/monitor_resource.zsh `pgrep bitmask` $(RESOURCE_TIME) + display bitmask-resources.png + diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py index f1d17698..94161192 100644 --- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py +++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py @@ -112,7 +112,10 @@ class DarwinVPNLauncher(VPNLauncher): :returns: True if kext is loaded, False otherwise. :rtype: bool """ - return bool(commands.getoutput('kextstat | grep "foo.tun"')) + loaded = bool(commands.getoutput('kextstat | grep "net.sf.tuntaposx.tun"')) + if not loaded: + logger.error("tuntaposx extension not loaded!") + return loaded @classmethod def _get_icon_path(kls): -- cgit v1.2.3 From 354a0a3cc24545dda6c4ccaa04eb060fa269cdeb Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 13 Apr 2016 15:35:28 -0700 Subject: [pkg] add gnugp build script besides: * moved openvpn and gnupg folders to top-level thirdparty * renamed jaromil's script to .zsh.old (we need to have it handy for compiling on windows) --- openvpn/README | 6 -- openvpn/Sources | 4 - openvpn/build.zsh | 191 --------------------------------------- openvpn/openvpn.sh | 123 ------------------------- thirdparty/gnupg/build_gnupg.sh | 68 ++++++++++++++ thirdparty/openvpn/README | 6 ++ thirdparty/openvpn/Sources | 4 + thirdparty/openvpn/build.zsh.old | 191 +++++++++++++++++++++++++++++++++++++++ thirdparty/openvpn/openvpn.sh | 123 +++++++++++++++++++++++++ 9 files changed, 392 insertions(+), 324 deletions(-) delete mode 100644 openvpn/README delete mode 100644 openvpn/Sources delete mode 100755 openvpn/build.zsh delete mode 100755 openvpn/openvpn.sh create mode 100755 thirdparty/gnupg/build_gnupg.sh create mode 100644 thirdparty/openvpn/README create mode 100644 thirdparty/openvpn/Sources create mode 100755 thirdparty/openvpn/build.zsh.old create mode 100755 thirdparty/openvpn/openvpn.sh diff --git a/openvpn/README b/openvpn/README deleted file mode 100644 index bf2205c2..00000000 --- a/openvpn/README +++ /dev/null @@ -1,6 +0,0 @@ -OpenVPN binary, build scripts -Works using a GCC minGW32 cross-compiler on Debian/Ubuntu -Produces a working MS Windows executable -openvpn.exe: PE32 executable (DLL) (console) Intel 80386, for MS Windows -goes smooth for the 99%, might still need some slapping the flags around now and then - -jrml diff --git a/openvpn/Sources b/openvpn/Sources deleted file mode 100644 index e2fe7bb3..00000000 --- a/openvpn/Sources +++ /dev/null @@ -1,4 +0,0 @@ -lzo -2.06 .tar.gz -opensc -0.12.2 .tar.gz -openssl -1.0.1c .tar.gz -polarssl -1.1.4 .tgz diff --git a/openvpn/build.zsh b/openvpn/build.zsh deleted file mode 100755 index b36717c1..00000000 --- a/openvpn/build.zsh +++ /dev/null @@ -1,191 +0,0 @@ -#!/bin/zsh -# -# Copyright (C) 2012 Denis Roio -# -# This source code is free software; you can redistribute it and/or -# modify it under the terms of the GNU Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This source code 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. -# Please refer to the GNU Public License for more details. -# -# You should have received a copy of the GNU Public License along with -# this source code; if not, write to: -# Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - - -REPO="http://files.dyne.org/leap/openvpn/sources" -TOPSRC=`pwd` -QUIET=0 -DEBUG=0 - - -autoload colors; colors -# standard output message routines -# it's always useful to wrap them, in case we change behaviour later -notice() { if [[ $QUIET == 0 ]]; then print "$fg_bold[green][*]$fg_no_bold[default] $1" >&2; fi } -error() { if [[ $QUIET == 0 ]]; then print "$fg[red][!]$fg[default] $1" >&2; fi } -func() { if [[ $DEBUG == 1 ]]; then print "$fg[blue][D]$fg[default] $1" >&2; fi } -act() { - if [[ $QUIET == 0 ]]; then - if [ "$1" = "-n" ]; then - print -n "$fg_bold[white] . $fg_no_bold[default] $2" >&2; - else - print "$fg_bold[white] . $fg_no_bold[default] $1" >&2; - fi - fi -} - -{ test "$1" = "clean" } && { - notice "Cleaning up all build in ${TOPSRC}" - for src in `cat Sources | awk ' -/^#/ {next} -/^./ { print $1 }'`; do - { test "$src" != "" } && { rm -rf "${src}" } - done - act "Done." - return 0 -} - -os="`uname -s`" -target="$1" -notice "OpenVPN build on $os for $target in ${TOPSRC}" - -prepare_sources() { - notice "Preparing sources" - # look for a file names "Sources", download and decompress entries - # format of file: name version compression (complete filename when merged) - { test -r Sources } || { - error "Sources not found, nothing to build here" - return 1 - } - for src in `cat Sources | awk ' -/^#/ {next} -/^./ { print $1 ";" $2 ";" $3 }'`; do - name="${src[(ws:;:)1]}" - ver="${src[(ws:;:)2]}" - arch="${src[(ws:;:)3]}" - file="${name}${ver}${arch}" - func "preparing source for ${name}${ver}" - - { test "$1" != "" } && { - test "$1" != "$name" } && { - continue } - - # download the file - { test -r ${file} } || { - act "downloading ${file}" - curl ${REPO}/${file} -o ${file} - } - # decompress the file - { test -r ${name} } || { - act "decompressing ${name}" - case $arch in - ## BARE SOURCE - .tar.gz) tar xfz ${file}; mv ${name}${ver} ${name} ;; - .tar.bz2) tar xfj ${file}; mv ${name}${ver} ${name} ;; - .tgz) tar xfz ${file}; mv ${name}${ver} ${name} ;; - *) error "compression not supported: $arch" - esac - } - act "${name} source ready" - done -} - -act "Downloading sources" - -# git clone latest openvpn -{ test -r openvpn } || { git clone https://github.com/OpenVPN/openvpn.git } - -case "$os" in - Darwin) - prepare_sources lzo - prepare_sources polarssl - ;; - Linux) # Cross-compile for Win32 - prepare_sources lzo - prepare_sources opensc - prepare_sources openssl - # tap windows - { test -r tap-windows } || { git clone https://github.com/OpenVPN/tap-windows.git } - ;; -esac - -notice "Sources ready, now compiling..." -LOG="`pwd`/build.log"; touch ${LOG} -act "logs saved in build.log" - -case "$target" in - osx) - { test -r polarssl/library/libpolarssl.a } || { - act "building PolarSSL..." - pushd polarssl - CC=clang cmake . >> ${LOG} - make -C library clean - cat CMakeCache.txt | awk ' -/^CMAKE_C_COMPILER/ { print "CMAKE_C_COMPILER:FILEPATH=/usr/bin/clang"; next } -/^CMAKE_BUILD_TYPE/ { print $1 "Release"; next } -/^CMAKE_C_FLAGS:STRING/ { print "CMAKE_C_FLAGS:STRING=-arch x86_64 -arch i386"; next } -{ print $0 } -' > CMakeCache.leap - cp CMakeCache.leap CMakeCache.txt - make -C library >> ${LOG} - popd - act "done." - } - - act "building OpenVPN" - pushd openvpn - CC=clang CFLAGS="-arch x86_64 -arch i386" \ - LZO_LIBS="/opt/local/lib/liblzo2.a" LZO_CFLAGS="-I/opt/local/include" \ - POLARSSL_CFLAGS="-I${TOPSRC}/polarssl/include" \ - POLARSSL_LIBS="${TOPSRC}/polarssl/library/libpolarssl.a" \ - ./configure --with-crypto-library=polarssl >> ${LOG} - make src/openvpn/openvpn - popd - act "done." - ;; - - win32) - { test -r lzo/src/liblzo2.la } || { pushd lzo - act "building LZO lib" - ./configure --host=i586-mingw32msvc >> ${LOG} - make >> ${LOG}; popd } - # openssl - { test -r openssl/libssl.a } || { - act "building OpenSSL lib" - pushd openssl - ./Configure --cross-compile-prefix=i586-mingw32msvc- mingw >> ${LOG} - make ${LOG}; popd } - - pushd openvpn - act "building latest OpenVPN" - { test -r configure } || { - sed -i -e 's/-municode//' src/openvpn/Makefile.am - autoreconf -i >> ${LOG} - } - CFLAGS="-I/usr/i586-mingw32msvc/include/ddk -D_WIN32_WINNT=0x0501" \ - LZO_LIBS="${TOPSRC}/lzo/src/liblzo2.la" \ - LZO_CFLAGS="-I${TOPSRC}/lzo/include" \ - TAP_CFLAGS="-I${TOPSRC}/tap-windows/src" \ - OPENSSL_SSL_CFLAGS="-I${TOPSRC}/openssl/include" \ - OPENSSL_CRYPTO_CFLAGS="-I${TOPSRC}/openssl/crypto" \ - OPENSSL_SSL_LIBS="${TOPSRC}/openssl/libssl.a" \ - OPENSSL_CRYPTO_LIBS="${TOPSRC}/openssl/libcrypto.a" \ - ./configure --host=i586-mingw32msvc >> ${LOG} - make >> ${LOG} - popd - - act "If OpenVPN build reports a final error on linkage, it might be due to a libtool bug" - act "(something like undefined reference to _WinMain@16)" - act "You need to go inside openvpn/src/openvpn and issue the last compile line manually" - act "adding an flat '-shared' at the end of it, then do 'cp .libs/openvpn.exe .'" - act "Happy hacking." - ;; - *) - error "Unknown target: $target" - ;; -esac diff --git a/openvpn/openvpn.sh b/openvpn/openvpn.sh deleted file mode 100755 index db63c987..00000000 --- a/openvpn/openvpn.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/bash - -set -e -set -x - -mkdir -p ~/openvpn && cd ~/openvpn - -BASE=`pwd` -SRC=$BASE/src -WGET="wget --prefer-family=IPv4" -DEST=$BASE/stuff -LDFLAGS="-L$DEST/lib -Wl" -CPPFLAGS="-I$DEST/include" -CFLAGS="-O3" -CXXFLAGS=$CFLAGS -CONFIGURE="./configure --prefix=/stuff" -MAKE="make -j2" -mkdir -p $SRC - -######## #################################################################### -# ZLIB # #################################################################### -######## #################################################################### - -mkdir $SRC/zlib && cd $SRC/zlib - -if [ ! -f zlib-1.2.8.tar.gz ]; then - $WGET http://zlib.net/zlib-1.2.8.tar.gz -fi -tar zxvf zlib-1.2.8.tar.gz -cd zlib-1.2.8 - -LDFLAGS=$LDFLAGS \ -CPPFLAGS=$CPPFLAGS \ -CFLAGS=$CFLAGS \ -CXXFLAGS=$CXXFLAGS \ -./configure \ ---prefix=/stuff - -$MAKE -make install DESTDIR=$BASE - -########### ################################################################# -# OPENSSL # ################################################################# -########### ################################################################# - -#mkdir -p $SRC/openssl && cd $SRC/openssl -#if [ ! -f openssl-1.0.2f.tar.gz ]; then -# $WGET https://www.openssl.org/source/openssl-1.0.2f.tar.gz -#fi -#tar zxvf openssl-1.0.2f.tar.gz -#cd openssl-1.0.2f - -#./Configure darwin64-x86_64-cc \ -#-Wl \ -#--prefix=/opts zlib \ -#--with-zlib-lib=$DEST/lib \ -#--with-zlib-include=$DEST/include - -#$MAKE -#make install INSTALLTOP=$DEST OPENSSLDIR=$DEST/ssl - -############ ################################################################# -# POLARSSL # ################################################################# -############ ################################################################# - -mkdir -p $SRC/polarssl && cd $SRC/polarssl -if [ ! -f polarssl-1.3.9-gpl.tgz ]; then - $WGET https://tls.mbed.org/download/polarssl-1.3.9-gpl.tgz -fi -tar zxvf polarssl-1.3.9-gpl.tgz -cd polarssl-1.3.9 -mkdir build -cd build -cmake .. -$MAKE -make install DESTDIR=$BASE - -######## #################################################################### -# LZO2 # #################################################################### -######## #################################################################### - -mkdir $SRC/lzo2 && cd $SRC/lzo2 -if [ ! -f lzo-2.09.tar.gz ]; then - $WGET http://www.oberhumer.com/opensource/lzo/download/lzo-2.09.tar.gz -fi -tar zxvf lzo-2.09.tar.gz -cd lzo-2.09 - -LDFLAGS=$LDFLAGS \ -CPPFLAGS=$CPPFLAGS \ -CFLAGS=$CFLAGS \ -CXXFLAGS=$CXXFLAGS \ -$CONFIGURE - -$MAKE -make install DESTDIR=$BASE - -########### ################################################################# -# OPENVPN # ################################################################# -########### ################################################################# - -mkdir $SRC/openvpn && cd $SRC/openvpn -if [ ! -f openvpn-2.3.10.tar.gz ]; then - $WGET http://swupdate.openvpn.org/community/releases/openvpn-2.3.10.tar.gz -fi -tar zxvf openvpn-2.3.10.tar.gz -cd openvpn-2.3.10 - -# OPENSSL_SSL_LIBS=$DEST/lib/ - -POLARSSL_CFLAGS=-I$DEST/usr/local/include \ -POLARSSL_LIBS=$DEST/lib/libpolarssl.a \ -LDFLAGS=$LDFLAGS \ -CPPFLAGS=$CPPFLAGS \ -CFLAGS=$CFLAGS \ -CXXFLAGS=$CXXFLAGS \ -$CONFIGURE \ ---disable-plugin-auth-pam \ ---enable-password-save \ ---with-crypto-library=polarssl - -$MAKE LIBS="-all-static -lssl -lcrypto -lz -llzo2" -make install DESTDIR=$BASE/openvpn diff --git a/thirdparty/gnupg/build_gnupg.sh b/thirdparty/gnupg/build_gnupg.sh new file mode 100755 index 00000000..e125b684 --- /dev/null +++ b/thirdparty/gnupg/build_gnupg.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env sh + +# ---------------------------------------------------------- +# Compile gnupg binary, to distribute with Bitmask bundles. +# ---------------------------------------------------------- +# You will need to import the keys for the gnupg developers into your keyring, +# see https://www.gnupg.org/download/integrity_check.html +# and https://www.gnupg.org/signature_key.html + +# For osx specific details, see: +# http://macgpg.sourceforge.net/docs/howto-build-gpg-osx.txt.asc +# osx doesn't allow to build static binaries, see: +# http://stackoverflow.com/questions/5259249/creating-static-mac-os-x-c-build + +set -e +set -x + +gnupg_version="gnupg-1.4.20" +url="ftp://ftp.gnupg.org/gcrypt/gnupg/$gnupg_version.tar.bz2" + +platform='unknown' +unamestr=`uname` +if [[ "$unamestr" == 'Linux' ]]; then + platform='linux' +elif [[ "$unamestr" == 'Darwin' ]]; then + platform='osx' +fi + +function prepare_source() +{ + wget -c $url -O $gnupg_version.tar.bz2; + wget -c $url.sig -O $gnupg_version.tar.bz2.sig; + #gpg --verify $gnupg_version.tar.bz2.sig $gnupg_version.tar.bz2; + tar -xjf $gnupg_version.tar.bz2; + cd $gnupg_version; +} + + +function build_static_gpg() +{ + ./configure CFLAGS="-static"; + make; +} + +function build_gpg() +{ + ./configure; + make; +} + +function copy_to_builddir() +{ + mkdir -p ~/leap_thirdparty_build + cp g10/gpg ~/leap_thirdparty_build +} + +function main() +{ + if [[ $platform == 'linux' ]]; then + (prepare_source; build_static_gpg; copy_to_builddir) + elif [[ $platform == 'osx' ]]; then + (prepare_source; build_gpg; copy_to_builddir) + fi + +} + +main "$@" + diff --git a/thirdparty/openvpn/README b/thirdparty/openvpn/README new file mode 100644 index 00000000..bf2205c2 --- /dev/null +++ b/thirdparty/openvpn/README @@ -0,0 +1,6 @@ +OpenVPN binary, build scripts +Works using a GCC minGW32 cross-compiler on Debian/Ubuntu +Produces a working MS Windows executable +openvpn.exe: PE32 executable (DLL) (console) Intel 80386, for MS Windows +goes smooth for the 99%, might still need some slapping the flags around now and then + -jrml diff --git a/thirdparty/openvpn/Sources b/thirdparty/openvpn/Sources new file mode 100644 index 00000000..e2fe7bb3 --- /dev/null +++ b/thirdparty/openvpn/Sources @@ -0,0 +1,4 @@ +lzo -2.06 .tar.gz +opensc -0.12.2 .tar.gz +openssl -1.0.1c .tar.gz +polarssl -1.1.4 .tgz diff --git a/thirdparty/openvpn/build.zsh.old b/thirdparty/openvpn/build.zsh.old new file mode 100755 index 00000000..b36717c1 --- /dev/null +++ b/thirdparty/openvpn/build.zsh.old @@ -0,0 +1,191 @@ +#!/bin/zsh +# +# Copyright (C) 2012 Denis Roio +# +# This source code is free software; you can redistribute it and/or +# modify it under the terms of the GNU Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This source code 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. +# Please refer to the GNU Public License for more details. +# +# You should have received a copy of the GNU Public License along with +# this source code; if not, write to: +# Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +REPO="http://files.dyne.org/leap/openvpn/sources" +TOPSRC=`pwd` +QUIET=0 +DEBUG=0 + + +autoload colors; colors +# standard output message routines +# it's always useful to wrap them, in case we change behaviour later +notice() { if [[ $QUIET == 0 ]]; then print "$fg_bold[green][*]$fg_no_bold[default] $1" >&2; fi } +error() { if [[ $QUIET == 0 ]]; then print "$fg[red][!]$fg[default] $1" >&2; fi } +func() { if [[ $DEBUG == 1 ]]; then print "$fg[blue][D]$fg[default] $1" >&2; fi } +act() { + if [[ $QUIET == 0 ]]; then + if [ "$1" = "-n" ]; then + print -n "$fg_bold[white] . $fg_no_bold[default] $2" >&2; + else + print "$fg_bold[white] . $fg_no_bold[default] $1" >&2; + fi + fi +} + +{ test "$1" = "clean" } && { + notice "Cleaning up all build in ${TOPSRC}" + for src in `cat Sources | awk ' +/^#/ {next} +/^./ { print $1 }'`; do + { test "$src" != "" } && { rm -rf "${src}" } + done + act "Done." + return 0 +} + +os="`uname -s`" +target="$1" +notice "OpenVPN build on $os for $target in ${TOPSRC}" + +prepare_sources() { + notice "Preparing sources" + # look for a file names "Sources", download and decompress entries + # format of file: name version compression (complete filename when merged) + { test -r Sources } || { + error "Sources not found, nothing to build here" + return 1 + } + for src in `cat Sources | awk ' +/^#/ {next} +/^./ { print $1 ";" $2 ";" $3 }'`; do + name="${src[(ws:;:)1]}" + ver="${src[(ws:;:)2]}" + arch="${src[(ws:;:)3]}" + file="${name}${ver}${arch}" + func "preparing source for ${name}${ver}" + + { test "$1" != "" } && { + test "$1" != "$name" } && { + continue } + + # download the file + { test -r ${file} } || { + act "downloading ${file}" + curl ${REPO}/${file} -o ${file} + } + # decompress the file + { test -r ${name} } || { + act "decompressing ${name}" + case $arch in + ## BARE SOURCE + .tar.gz) tar xfz ${file}; mv ${name}${ver} ${name} ;; + .tar.bz2) tar xfj ${file}; mv ${name}${ver} ${name} ;; + .tgz) tar xfz ${file}; mv ${name}${ver} ${name} ;; + *) error "compression not supported: $arch" + esac + } + act "${name} source ready" + done +} + +act "Downloading sources" + +# git clone latest openvpn +{ test -r openvpn } || { git clone https://github.com/OpenVPN/openvpn.git } + +case "$os" in + Darwin) + prepare_sources lzo + prepare_sources polarssl + ;; + Linux) # Cross-compile for Win32 + prepare_sources lzo + prepare_sources opensc + prepare_sources openssl + # tap windows + { test -r tap-windows } || { git clone https://github.com/OpenVPN/tap-windows.git } + ;; +esac + +notice "Sources ready, now compiling..." +LOG="`pwd`/build.log"; touch ${LOG} +act "logs saved in build.log" + +case "$target" in + osx) + { test -r polarssl/library/libpolarssl.a } || { + act "building PolarSSL..." + pushd polarssl + CC=clang cmake . >> ${LOG} + make -C library clean + cat CMakeCache.txt | awk ' +/^CMAKE_C_COMPILER/ { print "CMAKE_C_COMPILER:FILEPATH=/usr/bin/clang"; next } +/^CMAKE_BUILD_TYPE/ { print $1 "Release"; next } +/^CMAKE_C_FLAGS:STRING/ { print "CMAKE_C_FLAGS:STRING=-arch x86_64 -arch i386"; next } +{ print $0 } +' > CMakeCache.leap + cp CMakeCache.leap CMakeCache.txt + make -C library >> ${LOG} + popd + act "done." + } + + act "building OpenVPN" + pushd openvpn + CC=clang CFLAGS="-arch x86_64 -arch i386" \ + LZO_LIBS="/opt/local/lib/liblzo2.a" LZO_CFLAGS="-I/opt/local/include" \ + POLARSSL_CFLAGS="-I${TOPSRC}/polarssl/include" \ + POLARSSL_LIBS="${TOPSRC}/polarssl/library/libpolarssl.a" \ + ./configure --with-crypto-library=polarssl >> ${LOG} + make src/openvpn/openvpn + popd + act "done." + ;; + + win32) + { test -r lzo/src/liblzo2.la } || { pushd lzo + act "building LZO lib" + ./configure --host=i586-mingw32msvc >> ${LOG} + make >> ${LOG}; popd } + # openssl + { test -r openssl/libssl.a } || { + act "building OpenSSL lib" + pushd openssl + ./Configure --cross-compile-prefix=i586-mingw32msvc- mingw >> ${LOG} + make ${LOG}; popd } + + pushd openvpn + act "building latest OpenVPN" + { test -r configure } || { + sed -i -e 's/-municode//' src/openvpn/Makefile.am + autoreconf -i >> ${LOG} + } + CFLAGS="-I/usr/i586-mingw32msvc/include/ddk -D_WIN32_WINNT=0x0501" \ + LZO_LIBS="${TOPSRC}/lzo/src/liblzo2.la" \ + LZO_CFLAGS="-I${TOPSRC}/lzo/include" \ + TAP_CFLAGS="-I${TOPSRC}/tap-windows/src" \ + OPENSSL_SSL_CFLAGS="-I${TOPSRC}/openssl/include" \ + OPENSSL_CRYPTO_CFLAGS="-I${TOPSRC}/openssl/crypto" \ + OPENSSL_SSL_LIBS="${TOPSRC}/openssl/libssl.a" \ + OPENSSL_CRYPTO_LIBS="${TOPSRC}/openssl/libcrypto.a" \ + ./configure --host=i586-mingw32msvc >> ${LOG} + make >> ${LOG} + popd + + act "If OpenVPN build reports a final error on linkage, it might be due to a libtool bug" + act "(something like undefined reference to _WinMain@16)" + act "You need to go inside openvpn/src/openvpn and issue the last compile line manually" + act "adding an flat '-shared' at the end of it, then do 'cp .libs/openvpn.exe .'" + act "Happy hacking." + ;; + *) + error "Unknown target: $target" + ;; +esac diff --git a/thirdparty/openvpn/openvpn.sh b/thirdparty/openvpn/openvpn.sh new file mode 100755 index 00000000..db63c987 --- /dev/null +++ b/thirdparty/openvpn/openvpn.sh @@ -0,0 +1,123 @@ +#!/bin/bash + +set -e +set -x + +mkdir -p ~/openvpn && cd ~/openvpn + +BASE=`pwd` +SRC=$BASE/src +WGET="wget --prefer-family=IPv4" +DEST=$BASE/stuff +LDFLAGS="-L$DEST/lib -Wl" +CPPFLAGS="-I$DEST/include" +CFLAGS="-O3" +CXXFLAGS=$CFLAGS +CONFIGURE="./configure --prefix=/stuff" +MAKE="make -j2" +mkdir -p $SRC + +######## #################################################################### +# ZLIB # #################################################################### +######## #################################################################### + +mkdir $SRC/zlib && cd $SRC/zlib + +if [ ! -f zlib-1.2.8.tar.gz ]; then + $WGET http://zlib.net/zlib-1.2.8.tar.gz +fi +tar zxvf zlib-1.2.8.tar.gz +cd zlib-1.2.8 + +LDFLAGS=$LDFLAGS \ +CPPFLAGS=$CPPFLAGS \ +CFLAGS=$CFLAGS \ +CXXFLAGS=$CXXFLAGS \ +./configure \ +--prefix=/stuff + +$MAKE +make install DESTDIR=$BASE + +########### ################################################################# +# OPENSSL # ################################################################# +########### ################################################################# + +#mkdir -p $SRC/openssl && cd $SRC/openssl +#if [ ! -f openssl-1.0.2f.tar.gz ]; then +# $WGET https://www.openssl.org/source/openssl-1.0.2f.tar.gz +#fi +#tar zxvf openssl-1.0.2f.tar.gz +#cd openssl-1.0.2f + +#./Configure darwin64-x86_64-cc \ +#-Wl \ +#--prefix=/opts zlib \ +#--with-zlib-lib=$DEST/lib \ +#--with-zlib-include=$DEST/include + +#$MAKE +#make install INSTALLTOP=$DEST OPENSSLDIR=$DEST/ssl + +############ ################################################################# +# POLARSSL # ################################################################# +############ ################################################################# + +mkdir -p $SRC/polarssl && cd $SRC/polarssl +if [ ! -f polarssl-1.3.9-gpl.tgz ]; then + $WGET https://tls.mbed.org/download/polarssl-1.3.9-gpl.tgz +fi +tar zxvf polarssl-1.3.9-gpl.tgz +cd polarssl-1.3.9 +mkdir build +cd build +cmake .. +$MAKE +make install DESTDIR=$BASE + +######## #################################################################### +# LZO2 # #################################################################### +######## #################################################################### + +mkdir $SRC/lzo2 && cd $SRC/lzo2 +if [ ! -f lzo-2.09.tar.gz ]; then + $WGET http://www.oberhumer.com/opensource/lzo/download/lzo-2.09.tar.gz +fi +tar zxvf lzo-2.09.tar.gz +cd lzo-2.09 + +LDFLAGS=$LDFLAGS \ +CPPFLAGS=$CPPFLAGS \ +CFLAGS=$CFLAGS \ +CXXFLAGS=$CXXFLAGS \ +$CONFIGURE + +$MAKE +make install DESTDIR=$BASE + +########### ################################################################# +# OPENVPN # ################################################################# +########### ################################################################# + +mkdir $SRC/openvpn && cd $SRC/openvpn +if [ ! -f openvpn-2.3.10.tar.gz ]; then + $WGET http://swupdate.openvpn.org/community/releases/openvpn-2.3.10.tar.gz +fi +tar zxvf openvpn-2.3.10.tar.gz +cd openvpn-2.3.10 + +# OPENSSL_SSL_LIBS=$DEST/lib/ + +POLARSSL_CFLAGS=-I$DEST/usr/local/include \ +POLARSSL_LIBS=$DEST/lib/libpolarssl.a \ +LDFLAGS=$LDFLAGS \ +CPPFLAGS=$CPPFLAGS \ +CFLAGS=$CFLAGS \ +CXXFLAGS=$CXXFLAGS \ +$CONFIGURE \ +--disable-plugin-auth-pam \ +--enable-password-save \ +--with-crypto-library=polarssl + +$MAKE LIBS="-all-static -lssl -lcrypto -lz -llzo2" +make install DESTDIR=$BASE/openvpn -- cgit v1.2.3 From ec04bb1ec723f3f9a97ecc6d9daa00a5a8f86609 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 13 Apr 2016 15:42:27 -0700 Subject: [pkg] update pyinst makefile to pick our custom built gpg --- pkg/pyinst/pyinst-build.mk | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index 835c793c..4668d3f0 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -58,6 +58,7 @@ pyinst-helpers-linux: pyinst-helpers-osx: mkdir -p $(DIST_OSX_RES)bitmask-helper + mkdir $(DIST_OSX)Contents/MacOS/apps/mail cp pkg/osx/client.up.sh $(DIST_OSX_RES) cp pkg/osx/client.down.sh $(DIST_OSX_RES) cp pkg/osx/bitmask-helper $(DIST_OSX_RES)bitmask-helper/ @@ -66,10 +67,9 @@ pyinst-helpers-osx: cp pkg/osx/post-inst.sh $(DIST_OSX_RES)bitmask-helper/ cp pkg/osx/daemon.py $(DIST_OSX_RES)bitmask-helper/ cp /opt/homebrew-cask/Caskroom/tuntap/20150118/tuntap_20150118.pkg $(DIST_OSX_RES) - # TODO get from the path the build script places it - cp ~/leap/openvpn.leap.polarssl $(DIST_OSX_RES)openvpn.leap - # TODO this contains the gpg binary (brew), but we need to compile it statically from sources. - cp -r src/leap/bitmask/util/apps $(DIST_OSX)Contents/MacOS/ + # TODO make the build script put it there + cp ~/leap_thirdparty_build/openvpn.leap.polarssl $(DIST_OSX_RES)openvpn.leap + cp ~/leap_thirdparty_build/gpg $(DIST_OSX)Contents/MacOS/apps/mail/ pyinst-tar: cd dist/ && tar cvzf Bitmask.$(NEXT_VERSION).tar.gz bitmask-$(NEXT_VERSION) -- cgit v1.2.3 From 1194fa5779cbac2e1c4f891c64bbf2d912a6cbd0 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 13 Apr 2016 15:46:58 -0700 Subject: [bug] check if helper.plist file exist b4 unloading it - Fixes: #7944 - Releases: 0.9.2 --- pkg/osx/post-inst.sh | 1 - pkg/osx/pre-inst.sh | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/osx/post-inst.sh b/pkg/osx/post-inst.sh index 03dc4d2f..f88ea97a 100755 --- a/pkg/osx/post-inst.sh +++ b/pkg/osx/post-inst.sh @@ -1,5 +1,4 @@ #!/bin/sh - # Bitmask Post-Instalation script cp se.leap.bitmask-helper.plist /Library/LaunchDaemons/ diff --git a/pkg/osx/pre-inst.sh b/pkg/osx/pre-inst.sh index b6d17f20..1651a221 100755 --- a/pkg/osx/pre-inst.sh +++ b/pkg/osx/pre-inst.sh @@ -1,2 +1,3 @@ #!/bin/sh -launchctl unload /Library/LaunchDaemons/se.leap.bitmask-helper.plist +# Bitmask Post-Instalation script +[[ -f /Library/LaunchDaemons/se.leap.bitmask-helper.plist ]] && launchctl unload /Library/LaunchDaemons/se.leap.bitmask-helper.plist -- cgit v1.2.3 From ab6746623c8907e710c053cf73db036d9c432964 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 14 Apr 2016 17:01:34 -0700 Subject: [bug] fix typos in osx build --- pkg/pyinst/pyinst-build.mk | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index 4668d3f0..a414bd4e 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -1,7 +1,7 @@ freeze-ver: cp pkg/version-template src/leap/bitmask/_version.py - sed -i 's/^version_version\(.*\)/version_version = "$(NEXT_VERSION)"/' src/leap/bitmask/_version.py - sed -i 's/^full_revisionid\(.*\)/full_revisionid = "$(GIT_COMMIT)"/' src/leap/bitmask/_version.py + sed -i ' ' 's/^version_version\(.*\)/version_version = "$(NEXT_VERSION)"/' src/leap/bitmask/_version.py + sed -i ' ' "s/^full_revisionid\(.*\)/full_revisionid='$(GIT_COMMIT)'/" src/leap/bitmask/_version.py hash-binaries: OPENVPN_BIN=$(LEAP_BUILD_DIR)openvpn BITMASK_ROOT=pkg/linux/bitmask-root python setup.py hash_binaries @@ -58,12 +58,12 @@ pyinst-helpers-linux: pyinst-helpers-osx: mkdir -p $(DIST_OSX_RES)bitmask-helper - mkdir $(DIST_OSX)Contents/MacOS/apps/mail + mkdir -p $(DIST_OSX)Contents/MacOS/apps/mail cp pkg/osx/client.up.sh $(DIST_OSX_RES) cp pkg/osx/client.down.sh $(DIST_OSX_RES) cp pkg/osx/bitmask-helper $(DIST_OSX_RES)bitmask-helper/ cp pkg/osx/bitmask.pf.conf $(DIST_OSX_RES)bitmask-helper/ - cp pkg/osx/se.leap.bitmask-helper.$(DIST_OSX_RES)bitmask-helper/ + cp pkg/osx/se.leap.bitmask-helper.plist $(DIST_OSX_RES)bitmask-helper/ cp pkg/osx/post-inst.sh $(DIST_OSX_RES)bitmask-helper/ cp pkg/osx/daemon.py $(DIST_OSX_RES)bitmask-helper/ cp /opt/homebrew-cask/Caskroom/tuntap/20150118/tuntap_20150118.pkg $(DIST_OSX_RES) -- cgit v1.2.3 From b91263cba4078a7c4d19de0c31060cb7564ae410 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sun, 17 Apr 2016 08:52:26 -0700 Subject: [bug] enable first page in wizard - Resolves: #8041 - Releases: 0.9.2 --- src/leap/bitmask/gui/mainwindow.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 168de8ed..20909038 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -448,6 +448,9 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): # Refer to http://www.themacaque.com/?p=1067 for funny details. self._wizard.show() if IS_MAC: + # XXX hack. For some reason, there's a signal that doesn't arrive + # on time, so that the next button is disabled. See #8041 + self._wizard.page(self._wizard.INTRO_PAGE).set_completed() self._wizard.raise_() self._settings.set_skip_first_run(True) -- cgit v1.2.3 From 2cb8887119ff1dade6e3a4d9368654a6b67576a4 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sun, 17 Apr 2016 12:47:57 -0700 Subject: [refactor] re-add wrapper, cd needed --- pkg/osx/bitmask-wrapper | 3 +++ pkg/pyinst/bitmask.spec | 7 ++++--- pkg/pyinst/pyinst-build.mk | 3 +++ 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100755 pkg/osx/bitmask-wrapper diff --git a/pkg/osx/bitmask-wrapper b/pkg/osx/bitmask-wrapper new file mode 100755 index 00000000..240fc186 --- /dev/null +++ b/pkg/osx/bitmask-wrapper @@ -0,0 +1,3 @@ +#!/bin/sh +DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +exec $DIR/bitmask-app --debug --danger diff --git a/pkg/pyinst/bitmask.spec b/pkg/pyinst/bitmask.spec index 149093eb..afee9dd1 100644 --- a/pkg/pyinst/bitmask.spec +++ b/pkg/pyinst/bitmask.spec @@ -3,10 +3,11 @@ import sys block_cipher = None +# TODO remove QtWebKit for bundles that don't ship pixelated??? a = Analysis(['bitmask.py'], hiddenimports=[ 'zope.interface', 'zope.proxy', - 'PySide.QtCore', 'PySide.QtGui', + 'PySide.QtCore', 'PySide.QtGui', 'PySide.QtWebKit', 'pysqlcipher', 'service_identity', 'leap.common', 'leap.bitmask' ], @@ -50,6 +51,6 @@ if sys.platform.startswith("darwin"): name=os.path.join( 'dist', 'Bitmask.app'), appname='Bitmask', - version='0.9.0rc4', + version='0.9.0.alpha7', icon='pkg/osx/bitmask.icns', - bundle_identifier='bitmask-0.9.0rc4') + bundle_identifier='bitmask-0.9.0alpha7') diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index a414bd4e..b96e5dab 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -25,7 +25,10 @@ pyinst-hacks-linux: pyinst-hacks-osx: # XXX this should be taken care of by pyinstaller data collector + cp $(VIRTUAL_ENV)/lib/python2.7/site-packages/leap/common/cacert.pem $(DIST) cp $(VIRTUAL_ENV)/lib/python2.7/site-packages/leap/common/cacert.pem $(DIST_OSX)Contents/MacOS/ + mv $(DIST_OSX)Contents/MacOS/bitmask $(DIST_OSX)Contents/MacOS/bitmask-app + cp pkg/osx/bitmask-wrapper $(DIST_OSX)Contents/MacOS/bitmask # XXX need the rest??? pyinst-trim: -- cgit v1.2.3 From c36e86275fe1c9ca4d6932c5103f91f5c2473b2e Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sun, 17 Apr 2016 12:50:59 -0700 Subject: [bug] avoid osx hanging --- pkg/pyinst/bitmask.spec | 3 ++- src/leap/bitmask/app.py | 25 +++++++++++++++---------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/pkg/pyinst/bitmask.spec b/pkg/pyinst/bitmask.spec index afee9dd1..8c6561cf 100644 --- a/pkg/pyinst/bitmask.spec +++ b/pkg/pyinst/bitmask.spec @@ -51,6 +51,7 @@ if sys.platform.startswith("darwin"): name=os.path.join( 'dist', 'Bitmask.app'), appname='Bitmask', - version='0.9.0.alpha7', + # TODO get this from ../next-version.txt + version='0.9.0.rc1', icon='pkg/osx/bitmask.icns', bundle_identifier='bitmask-0.9.0alpha7') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 9412ccd7..31dba157 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -46,7 +46,15 @@ import os import platform import sys -import psutil + +if platform.system() == "Darwin": + # XXX please ignore pep8 complains, this needs to be executed + # early. + # We need to tune maximum number of files, due to zmq usage + # we hit the limit. + import resource + resource.setrlimit(resource.RLIMIT_NOFILE, (4096, 10240)) + from leap.bitmask import __version__ as VERSION from leap.bitmask.backend.backend_proxy import BackendProxy @@ -66,13 +74,7 @@ from leap.mail import __version__ as MAIL_VERSION import codecs codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) - -if platform.system() == "Darwin": - # We need to tune maximum number of files, due to zmq usage - # we hit the limit. - import resource - resource.setrlimit(resource.RLIMIT_NOFILE, (4096, 10240)) - +import psutil def qt_hack_ubuntu(): """Export two env vars to avoid gui corruption, see #8028""" @@ -172,8 +174,11 @@ def start_app(): } flags.STANDALONE = opts.standalone - if getattr(sys, 'frozen', False): - flags.STANDALONE = True + + if platform.system() != 'Darwin': + # XXX this hangs the OSX bundles. + if getattr(sys, 'frozen', False): + flags.STANDALONE = True flags.OFFLINE = opts.offline flags.MAIL_LOGFILE = opts.mail_log_file -- cgit v1.2.3 From 0a1c3299650098027c810f6d66ba10d653a00ba5 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sun, 17 Apr 2016 14:24:49 -0700 Subject: [bug] remove --danger flag from osx build phew! --- pkg/osx/bitmask-wrapper | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/osx/bitmask-wrapper b/pkg/osx/bitmask-wrapper index 240fc186..c861380b 100755 --- a/pkg/osx/bitmask-wrapper +++ b/pkg/osx/bitmask-wrapper @@ -1,3 +1,3 @@ #!/bin/sh DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -exec $DIR/bitmask-app --debug --danger +exec $DIR/bitmask-app --debug -- cgit v1.2.3 From 265b3b28af70eef466ac5f4b4ae815bbabd2b92f Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 18 Apr 2016 12:46:57 -0700 Subject: [bug] fix missing import --- pkg/osx/bitmask-helper | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/osx/bitmask-helper b/pkg/osx/bitmask-helper index c22d33d9..a1a3e86a 100755 --- a/pkg/osx/bitmask-helper +++ b/pkg/osx/bitmask-helper @@ -41,6 +41,7 @@ To see the loaded rules: """ import os +import re import socket import signal import subprocess -- cgit v1.2.3 From 431d23021b5ac25c5a723e30698eb7465ad0949b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 18 Apr 2016 12:48:39 -0700 Subject: [pkg] remove cryptography hack, not needed with pyinst>3.0 --- pkg/pyinst/cryptography/osrandom_engine.c | 167 ------------------------------ pkg/pyinst/cryptography/osrandom_engine.h | 6 -- 2 files changed, 173 deletions(-) delete mode 100644 pkg/pyinst/cryptography/osrandom_engine.c delete mode 100644 pkg/pyinst/cryptography/osrandom_engine.h diff --git a/pkg/pyinst/cryptography/osrandom_engine.c b/pkg/pyinst/cryptography/osrandom_engine.c deleted file mode 100644 index 27894712..00000000 --- a/pkg/pyinst/cryptography/osrandom_engine.c +++ /dev/null @@ -1,167 +0,0 @@ -static const char *Cryptography_osrandom_engine_id = "osrandom"; -static const char *Cryptography_osrandom_engine_name = "osrandom_engine"; - -#if defined(_WIN32) -static HCRYPTPROV hCryptProv = 0; - -static int osrandom_init(ENGINE *e) { - if (hCryptProv > 0) { - return 1; - } - if (CryptAcquireContext(&hCryptProv, NULL, NULL, - PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { - return 1; - } else { - return 0; - } -} - -static int osrandom_rand_bytes(unsigned char *buffer, int size) { - if (hCryptProv == 0) { - return 0; - } - - if (!CryptGenRandom(hCryptProv, (DWORD)size, buffer)) { - ERR_put_error( - ERR_LIB_RAND, 0, ERR_R_RAND_LIB, "osrandom_engine.py", 0 - ); - return 0; - } - return 1; -} - -static int osrandom_finish(ENGINE *e) { - if (CryptReleaseContext(hCryptProv, 0)) { - hCryptProv = 0; - return 1; - } else { - return 0; - } -} - -static int osrandom_rand_status(void) { - if (hCryptProv == 0) { - return 0; - } else { - return 1; - } -} -#else -static int urandom_fd = -1; - -static int osrandom_finish(ENGINE *e); - -static int osrandom_init(ENGINE *e) { - if (urandom_fd > -1) { - return 1; - } - urandom_fd = open("/dev/urandom", O_RDONLY); - if (urandom_fd > -1) { - int flags = fcntl(urandom_fd, F_GETFD); - if (flags == -1) { - osrandom_finish(e); - return 0; - } else if (fcntl(urandom_fd, F_SETFD, flags | FD_CLOEXEC) == -1) { - osrandom_finish(e); - return 0; - } - return 1; - } else { - return 0; - } -} - -static int osrandom_rand_bytes(unsigned char *buffer, int size) { - ssize_t n; - while (size > 0) { - do { - n = read(urandom_fd, buffer, (size_t)size); - } while (n < 0 && errno == EINTR); - if (n <= 0) { - ERR_put_error( - ERR_LIB_RAND, 0, ERR_R_RAND_LIB, "osrandom_engine.py", 0 - ); - return 0; - } - buffer += n; - size -= n; - } - return 1; -} - -static int osrandom_finish(ENGINE *e) { - int n; - do { - n = close(urandom_fd); - } while (n < 0 && errno == EINTR); - urandom_fd = -1; - if (n < 0) { - return 0; - } else { - return 1; - } -} - -static int osrandom_rand_status(void) { - if (urandom_fd == -1) { - return 0; - } else { - return 1; - } -} -#endif - -/* This replicates the behavior of the OpenSSL FIPS RNG, which returns a - -1 in the event that there is an error when calling RAND_pseudo_bytes. */ -static int osrandom_pseudo_rand_bytes(unsigned char *buffer, int size) { - int res = osrandom_rand_bytes(buffer, size); - if (res == 0) { - return -1; - } else { - return res; - } -} - -static RAND_METHOD osrandom_rand = { - NULL, - osrandom_rand_bytes, - NULL, - NULL, - osrandom_pseudo_rand_bytes, - osrandom_rand_status, -}; - -/* Returns 1 if successfully added, 2 if engine has previously been added, - and 0 for error. */ -int Cryptography_add_osrandom_engine(void) { - ENGINE *e; - e = ENGINE_by_id(Cryptography_osrandom_engine_id); - if (e != NULL) { - ENGINE_free(e); - return 2; - } else { - ERR_clear_error(); - } - - e = ENGINE_new(); - if (e == NULL) { - return 0; - } - if(!ENGINE_set_id(e, Cryptography_osrandom_engine_id) || - !ENGINE_set_name(e, Cryptography_osrandom_engine_name) || - !ENGINE_set_RAND(e, &osrandom_rand) || - !ENGINE_set_init_function(e, osrandom_init) || - !ENGINE_set_finish_function(e, osrandom_finish)) { - ENGINE_free(e); - return 0; - } - if (!ENGINE_add(e)) { - ENGINE_free(e); - return 0; - } - if (!ENGINE_free(e)) { - return 0; - } - - return 1; -} diff --git a/pkg/pyinst/cryptography/osrandom_engine.h b/pkg/pyinst/cryptography/osrandom_engine.h deleted file mode 100644 index 11a3159e..00000000 --- a/pkg/pyinst/cryptography/osrandom_engine.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifdef _WIN32 -#include -#else -#include -#include -#endif -- cgit v1.2.3 From 21bc3df30c7e280241472c58d74a96a6c84188eb Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 18 Apr 2016 12:54:18 -0700 Subject: [pkg] move embedded copy of daemon to its own folder --- pkg/osx/_metadata.py | 154 -------- pkg/osx/daemon.py | 927 -------------------------------------------- pkg/osx/daemon/_metadata.py | 154 ++++++++ pkg/osx/daemon/daemon.py | 927 ++++++++++++++++++++++++++++++++++++++++++++ pkg/osx/daemon/pidfile.py | 67 ++++ pkg/osx/daemon/runner.py | 324 ++++++++++++++++ pkg/osx/pidfile.py | 67 ---- pkg/osx/runner.py | 324 ---------------- pkg/pyinst/pyinst-build.mk | 6 +- 9 files changed, 1475 insertions(+), 1475 deletions(-) delete mode 100644 pkg/osx/_metadata.py delete mode 100644 pkg/osx/daemon.py create mode 100644 pkg/osx/daemon/_metadata.py create mode 100644 pkg/osx/daemon/daemon.py create mode 100644 pkg/osx/daemon/pidfile.py create mode 100644 pkg/osx/daemon/runner.py delete mode 100644 pkg/osx/pidfile.py delete mode 100644 pkg/osx/runner.py diff --git a/pkg/osx/_metadata.py b/pkg/osx/_metadata.py deleted file mode 100644 index 88843df7..00000000 --- a/pkg/osx/_metadata.py +++ /dev/null @@ -1,154 +0,0 @@ -# -*- coding: utf-8 -*- - -# daemon/_metadata.py -# Part of ‘python-daemon’, an implementation of PEP 3143. -# -# Copyright © 2008–2015 Ben Finney -# -# This is free software: you may copy, modify, and/or distribute this work -# under the terms of the Apache License, version 2.0 as published by the -# Apache Software Foundation. -# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details. - -""" Package metadata for the ‘python-daemon’ distribution. """ - -from __future__ import (absolute_import, unicode_literals) - -import json -import re -import collections -import datetime - -import pkg_resources - - -distribution_name = "python-daemon" -version_info_filename = "version_info.json" - - -def get_distribution_version_info(filename=version_info_filename): - """ Get the version info from the installed distribution. - - :param filename: Base filename of the version info resource. - :return: The version info as a mapping of fields. If the - distribution is not available, the mapping is empty. - - The version info is stored as a metadata file in the - distribution. - - """ - version_info = { - 'release_date': "UNKNOWN", - 'version': "UNKNOWN", - 'maintainer': "UNKNOWN", - } - - try: - distribution = pkg_resources.get_distribution(distribution_name) - except pkg_resources.DistributionNotFound: - distribution = None - - if distribution is not None: - if distribution.has_metadata(version_info_filename): - content = distribution.get_metadata(version_info_filename) - version_info = json.loads(content) - - return version_info - -version_info = get_distribution_version_info() - -version_installed = version_info['version'] - - -rfc822_person_regex = re.compile( - "^(?P[^<]+) <(?P[^>]+)>$") - -ParsedPerson = collections.namedtuple('ParsedPerson', ['name', 'email']) - - -def parse_person_field(value): - """ Parse a person field into name and email address. - - :param value: The text value specifying a person. - :return: A 2-tuple (name, email) for the person's details. - - If the `value` does not match a standard person with email - address, the `email` item is ``None``. - - """ - result = (None, None) - - match = rfc822_person_regex.match(value) - if len(value): - if match is not None: - result = ParsedPerson( - name=match.group('name'), - email=match.group('email')) - else: - result = ParsedPerson(name=value, email=None) - - return result - -author_name = "Ben Finney" -author_email = "ben+python@benfinney.id.au" -author = "{name} <{email}>".format(name=author_name, email=author_email) - - -class YearRange: - """ A range of years spanning a period. """ - - def __init__(self, begin, end=None): - self.begin = begin - self.end = end - - def __unicode__(self): - text = "{range.begin:04d}".format(range=self) - if self.end is not None: - if self.end > self.begin: - text = "{range.begin:04d}–{range.end:04d}".format(range=self) - return text - - __str__ = __unicode__ - - -def make_year_range(begin_year, end_date=None): - """ Construct the year range given a start and possible end date. - - :param begin_date: The beginning year (text) for the range. - :param end_date: The end date (text, ISO-8601 format) for the - range, or a non-date token string. - :return: The range of years as a `YearRange` instance. - - If the `end_date` is not a valid ISO-8601 date string, the - range has ``None`` for the end year. - - """ - begin_year = int(begin_year) - - try: - end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d") - except (TypeError, ValueError): - # Specified end_date value is not a valid date. - end_year = None - else: - end_year = end_date.year - - year_range = YearRange(begin=begin_year, end=end_year) - - return year_range - -copyright_year_begin = "2001" -build_date = version_info['release_date'] -copyright_year_range = make_year_range(copyright_year_begin, build_date) - -copyright = "Copyright © {year_range} {author} and others".format( - year_range=copyright_year_range, author=author) -license = "Apache-2" -url = "https://alioth.debian.org/projects/python-daemon/" - - -# Local variables: -# coding: utf-8 -# mode: python -# End: -# vim: fileencoding=utf-8 filetype=python : diff --git a/pkg/osx/daemon.py b/pkg/osx/daemon.py deleted file mode 100644 index 7ca8770e..00000000 --- a/pkg/osx/daemon.py +++ /dev/null @@ -1,927 +0,0 @@ -# -*- coding: utf-8 -*- - -# daemon/daemon.py -# Part of ‘python-daemon’, an implementation of PEP 3143. -# -# Copyright © 2008–2015 Ben Finney -# Copyright © 2007–2008 Robert Niederreiter, Jens Klein -# Copyright © 2004–2005 Chad J. Schroeder -# Copyright © 2003 Clark Evans -# Copyright © 2002 Noah Spurrier -# Copyright © 2001 Jürgen Hermann -# -# This is free software: you may copy, modify, and/or distribute this work -# under the terms of the Apache License, version 2.0 as published by the -# Apache Software Foundation. -# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details. - -""" Daemon process behaviour. - """ - -from __future__ import (absolute_import, unicode_literals) - -import os -import sys -import resource -import errno -import signal -import socket -import atexit -try: - # Python 2 has both ‘str’ (bytes) and ‘unicode’ (text). - basestring = basestring - unicode = unicode -except NameError: - # Python 3 names the Unicode data type ‘str’. - basestring = str - unicode = str - - -class DaemonError(Exception): - """ Base exception class for errors from this module. """ - - def __init__(self, *args, **kwargs): - self._chain_from_context() - - super(DaemonError, self).__init__(*args, **kwargs) - - def _chain_from_context(self): - _chain_exception_from_existing_exception_context(self, as_cause=True) - - -class DaemonOSEnvironmentError(DaemonError, OSError): - """ Exception raised when daemon OS environment setup receives error. """ - - -class DaemonProcessDetachError(DaemonError, OSError): - """ Exception raised when process detach fails. """ - - -class DaemonContext: - """ Context for turning the current program into a daemon process. - - A `DaemonContext` instance represents the behaviour settings and - process context for the program when it becomes a daemon. The - behaviour and environment is customised by setting options on the - instance, before calling the `open` method. - - Each option can be passed as a keyword argument to the `DaemonContext` - constructor, or subsequently altered by assigning to an attribute on - the instance at any time prior to calling `open`. That is, for - options named `wibble` and `wubble`, the following invocation:: - - foo = daemon.DaemonContext(wibble=bar, wubble=baz) - foo.open() - - is equivalent to:: - - foo = daemon.DaemonContext() - foo.wibble = bar - foo.wubble = baz - foo.open() - - The following options are defined. - - `files_preserve` - :Default: ``None`` - - List of files that should *not* be closed when starting the - daemon. If ``None``, all open file descriptors will be closed. - - Elements of the list are file descriptors (as returned by a file - object's `fileno()` method) or Python `file` objects. Each - specifies a file that is not to be closed during daemon start. - - `chroot_directory` - :Default: ``None`` - - Full path to a directory to set as the effective root directory of - the process. If ``None``, specifies that the root directory is not - to be changed. - - `working_directory` - :Default: ``'/'`` - - Full path of the working directory to which the process should - change on daemon start. - - Since a filesystem cannot be unmounted if a process has its - current working directory on that filesystem, this should either - be left at default or set to a directory that is a sensible “home - directory” for the daemon while it is running. - - `umask` - :Default: ``0`` - - File access creation mask (“umask”) to set for the process on - daemon start. - - A daemon should not rely on the parent process's umask value, - which is beyond its control and may prevent creating a file with - the required access mode. So when the daemon context opens, the - umask is set to an explicit known value. - - If the conventional value of 0 is too open, consider setting a - value such as 0o022, 0o027, 0o077, or another specific value. - Otherwise, ensure the daemon creates every file with an - explicit access mode for the purpose. - - `pidfile` - :Default: ``None`` - - Context manager for a PID lock file. When the daemon context opens - and closes, it enters and exits the `pidfile` context manager. - - `detach_process` - :Default: ``None`` - - If ``True``, detach the process context when opening the daemon - context; if ``False``, do not detach. - - If unspecified (``None``) during initialisation of the instance, - this will be set to ``True`` by default, and ``False`` only if - detaching the process is determined to be redundant; for example, - in the case when the process was started by `init`, by `initd`, or - by `inetd`. - - `signal_map` - :Default: system-dependent - - Mapping from operating system signals to callback actions. - - The mapping is used when the daemon context opens, and determines - the action for each signal's signal handler: - - * A value of ``None`` will ignore the signal (by setting the - signal action to ``signal.SIG_IGN``). - - * A string value will be used as the name of an attribute on the - ``DaemonContext`` instance. The attribute's value will be used - as the action for the signal handler. - - * Any other value will be used as the action for the - signal handler. See the ``signal.signal`` documentation - for details of the signal handler interface. - - The default value depends on which signals are defined on the - running system. Each item from the list below whose signal is - actually defined in the ``signal`` module will appear in the - default map: - - * ``signal.SIGTTIN``: ``None`` - - * ``signal.SIGTTOU``: ``None`` - - * ``signal.SIGTSTP``: ``None`` - - * ``signal.SIGTERM``: ``'terminate'`` - - Depending on how the program will interact with its child - processes, it may need to specify a signal map that - includes the ``signal.SIGCHLD`` signal (received when a - child process exits). See the specific operating system's - documentation for more detail on how to determine what - circumstances dictate the need for signal handlers. - - `uid` - :Default: ``os.getuid()`` - - `gid` - :Default: ``os.getgid()`` - - The user ID (“UID”) value and group ID (“GID”) value to switch - the process to on daemon start. - - The default values, the real UID and GID of the process, will - relinquish any effective privilege elevation inherited by the - process. - - `prevent_core` - :Default: ``True`` - - If true, prevents the generation of core files, in order to avoid - leaking sensitive information from daemons run as `root`. - - `stdin` - :Default: ``None`` - - `stdout` - :Default: ``None`` - - `stderr` - :Default: ``None`` - - Each of `stdin`, `stdout`, and `stderr` is a file-like object - which will be used as the new file for the standard I/O stream - `sys.stdin`, `sys.stdout`, and `sys.stderr` respectively. The file - should therefore be open, with a minimum of mode 'r' in the case - of `stdin`, and mimimum of mode 'w+' in the case of `stdout` and - `stderr`. - - If the object has a `fileno()` method that returns a file - descriptor, the corresponding file will be excluded from being - closed during daemon start (that is, it will be treated as though - it were listed in `files_preserve`). - - If ``None``, the corresponding system stream is re-bound to the - file named by `os.devnull`. - - """ - - __metaclass__ = type - - def __init__( - self, - chroot_directory=None, - working_directory="/", - umask=0, - uid=None, - gid=None, - prevent_core=True, - detach_process=None, - files_preserve=None, - pidfile=None, - stdin=None, - stdout=None, - stderr=None, - signal_map=None, - ): - """ Set up a new instance. """ - self.chroot_directory = chroot_directory - self.working_directory = working_directory - self.umask = umask - self.prevent_core = prevent_core - self.files_preserve = files_preserve - self.pidfile = pidfile - self.stdin = stdin - self.stdout = stdout - self.stderr = stderr - - if uid is None: - uid = os.getuid() - self.uid = uid - if gid is None: - gid = os.getgid() - self.gid = gid - - if detach_process is None: - detach_process = is_detach_process_context_required() - self.detach_process = detach_process - - if signal_map is None: - signal_map = make_default_signal_map() - self.signal_map = signal_map - - self._is_open = False - - @property - def is_open(self): - """ ``True`` if the instance is currently open. """ - return self._is_open - - def open(self): - """ Become a daemon process. - - :return: ``None``. - - Open the daemon context, turning the current program into a daemon - process. This performs the following steps: - - * If this instance's `is_open` property is true, return - immediately. This makes it safe to call `open` multiple times on - an instance. - - * If the `prevent_core` attribute is true, set the resource limits - for the process to prevent any core dump from the process. - - * If the `chroot_directory` attribute is not ``None``, set the - effective root directory of the process to that directory (via - `os.chroot`). - - This allows running the daemon process inside a “chroot gaol” - as a means of limiting the system's exposure to rogue behaviour - by the process. Note that the specified directory needs to - already be set up for this purpose. - - * Set the process UID and GID to the `uid` and `gid` attribute - values. - - * Close all open file descriptors. This excludes those listed in - the `files_preserve` attribute, and those that correspond to the - `stdin`, `stdout`, or `stderr` attributes. - - * Change current working directory to the path specified by the - `working_directory` attribute. - - * Reset the file access creation mask to the value specified by - the `umask` attribute. - - * If the `detach_process` option is true, detach the current - process into its own process group, and disassociate from any - controlling terminal. - - * Set signal handlers as specified by the `signal_map` attribute. - - * If any of the attributes `stdin`, `stdout`, `stderr` are not - ``None``, bind the system streams `sys.stdin`, `sys.stdout`, - and/or `sys.stderr` to the files represented by the - corresponding attributes. Where the attribute has a file - descriptor, the descriptor is duplicated (instead of re-binding - the name). - - * If the `pidfile` attribute is not ``None``, enter its context - manager. - - * Mark this instance as open (for the purpose of future `open` and - `close` calls). - - * Register the `close` method to be called during Python's exit - processing. - - When the function returns, the running program is a daemon - process. - - """ - if self.is_open: - return - - if self.chroot_directory is not None: - change_root_directory(self.chroot_directory) - - if self.prevent_core: - prevent_core_dump() - - change_file_creation_mask(self.umask) - change_working_directory(self.working_directory) - change_process_owner(self.uid, self.gid) - - if self.detach_process: - detach_process_context() - - signal_handler_map = self._make_signal_handler_map() - set_signal_handlers(signal_handler_map) - - exclude_fds = self._get_exclude_file_descriptors() - close_all_open_files(exclude=exclude_fds) - - redirect_stream(sys.stdin, self.stdin) - redirect_stream(sys.stdout, self.stdout) - redirect_stream(sys.stderr, self.stderr) - - if self.pidfile is not None: - self.pidfile.__enter__() - - self._is_open = True - - register_atexit_function(self.close) - - def __enter__(self): - """ Context manager entry point. """ - self.open() - return self - - def close(self): - """ Exit the daemon process context. - - :return: ``None``. - - Close the daemon context. This performs the following steps: - - * If this instance's `is_open` property is false, return - immediately. This makes it safe to call `close` multiple times - on an instance. - - * If the `pidfile` attribute is not ``None``, exit its context - manager. - - * Mark this instance as closed (for the purpose of future `open` - and `close` calls). - - """ - if not self.is_open: - return - - if self.pidfile is not None: - # Follow the interface for telling a context manager to exit, - # . - self.pidfile.__exit__(None, None, None) - - self._is_open = False - - def __exit__(self, exc_type, exc_value, traceback): - """ Context manager exit point. """ - self.close() - - def terminate(self, signal_number, stack_frame): - """ Signal handler for end-process signals. - - :param signal_number: The OS signal number received. - :param stack_frame: The frame object at the point the - signal was received. - :return: ``None``. - - Signal handler for the ``signal.SIGTERM`` signal. Performs the - following step: - - * Raise a ``SystemExit`` exception explaining the signal. - - """ - exception = SystemExit( - "Terminating on signal {signal_number!r}".format( - signal_number=signal_number)) - raise exception - - def _get_exclude_file_descriptors(self): - """ Get the set of file descriptors to exclude closing. - - :return: A set containing the file descriptors for the - files to be preserved. - - The file descriptors to be preserved are those from the - items in `files_preserve`, and also each of `stdin`, - `stdout`, and `stderr`. For each item: - - * If the item is ``None``, it is omitted from the return - set. - - * If the item's ``fileno()`` method returns a value, that - value is in the return set. - - * Otherwise, the item is in the return set verbatim. - - """ - files_preserve = self.files_preserve - if files_preserve is None: - files_preserve = [] - files_preserve.extend( - item for item in [self.stdin, self.stdout, self.stderr] - if hasattr(item, 'fileno')) - - exclude_descriptors = set() - for item in files_preserve: - if item is None: - continue - file_descriptor = _get_file_descriptor(item) - if file_descriptor is not None: - exclude_descriptors.add(file_descriptor) - else: - exclude_descriptors.add(item) - - return exclude_descriptors - - def _make_signal_handler(self, target): - """ Make the signal handler for a specified target object. - - :param target: A specification of the target for the - handler; see below. - :return: The value for use by `signal.signal()`. - - If `target` is ``None``, return ``signal.SIG_IGN``. If `target` - is a text string, return the attribute of this instance named - by that string. Otherwise, return `target` itself. - - """ - if target is None: - result = signal.SIG_IGN - elif isinstance(target, unicode): - name = target - result = getattr(self, name) - else: - result = target - - return result - - def _make_signal_handler_map(self): - """ Make the map from signals to handlers for this instance. - - :return: The constructed signal map for this instance. - - Construct a map from signal numbers to handlers for this - context instance, suitable for passing to - `set_signal_handlers`. - - """ - signal_handler_map = dict( - (signal_number, self._make_signal_handler(target)) - for (signal_number, target) in self.signal_map.items()) - return signal_handler_map - - -def _get_file_descriptor(obj): - """ Get the file descriptor, if the object has one. - - :param obj: The object expected to be a file-like object. - :return: The file descriptor iff the file supports it; otherwise - ``None``. - - The object may be a non-file object. It may also be a - file-like object with no support for a file descriptor. In - either case, return ``None``. - - """ - file_descriptor = None - if hasattr(obj, 'fileno'): - try: - file_descriptor = obj.fileno() - except ValueError: - # The item doesn't support a file descriptor. - pass - - return file_descriptor - - -def change_working_directory(directory): - """ Change the working directory of this process. - - :param directory: The target directory path. - :return: ``None``. - - """ - try: - os.chdir(directory) - except Exception as exc: - error = DaemonOSEnvironmentError( - "Unable to change working directory ({exc})".format(exc=exc)) - raise error - - -def change_root_directory(directory): - """ Change the root directory of this process. - - :param directory: The target directory path. - :return: ``None``. - - Set the current working directory, then the process root directory, - to the specified `directory`. Requires appropriate OS privileges - for this process. - - """ - try: - os.chdir(directory) - os.chroot(directory) - except Exception as exc: - error = DaemonOSEnvironmentError( - "Unable to change root directory ({exc})".format(exc=exc)) - raise error - - -def change_file_creation_mask(mask): - """ Change the file creation mask for this process. - - :param mask: The numeric file creation mask to set. - :return: ``None``. - - """ - try: - os.umask(mask) - except Exception as exc: - error = DaemonOSEnvironmentError( - "Unable to change file creation mask ({exc})".format(exc=exc)) - raise error - - -def change_process_owner(uid, gid): - """ Change the owning UID and GID of this process. - - :param uid: The target UID for the daemon process. - :param gid: The target GID for the daemon process. - :return: ``None``. - - Set the GID then the UID of the process (in that order, to avoid - permission errors) to the specified `gid` and `uid` values. - Requires appropriate OS privileges for this process. - - """ - try: - os.setgid(gid) - os.setuid(uid) - except Exception as exc: - error = DaemonOSEnvironmentError( - "Unable to change process owner ({exc})".format(exc=exc)) - raise error - - -def prevent_core_dump(): - """ Prevent this process from generating a core dump. - - :return: ``None``. - - Set the soft and hard limits for core dump size to zero. On Unix, - this entirely prevents the process from creating core dump. - - """ - core_resource = resource.RLIMIT_CORE - - try: - # Ensure the resource limit exists on this platform, by requesting - # its current value. - core_limit_prev = resource.getrlimit(core_resource) - except ValueError as exc: - error = DaemonOSEnvironmentError( - "System does not support RLIMIT_CORE resource limit" - " ({exc})".format(exc=exc)) - raise error - - # Set hard and soft limits to zero, i.e. no core dump at all. - core_limit = (0, 0) - resource.setrlimit(core_resource, core_limit) - - -def detach_process_context(): - """ Detach the process context from parent and session. - - :return: ``None``. - - Detach from the parent process and session group, allowing the - parent to exit while this process continues running. - - Reference: “Advanced Programming in the Unix Environment”, - section 13.3, by W. Richard Stevens, published 1993 by - Addison-Wesley. - - """ - - def fork_then_exit_parent(error_message): - """ Fork a child process, then exit the parent process. - - :param error_message: Message for the exception in case of a - detach failure. - :return: ``None``. - :raise DaemonProcessDetachError: If the fork fails. - - """ - try: - pid = os.fork() - if pid > 0: - os._exit(0) - except OSError as exc: - error = DaemonProcessDetachError( - "{message}: [{exc.errno:d}] {exc.strerror}".format( - message=error_message, exc=exc)) - raise error - - fork_then_exit_parent(error_message="Failed first fork") - os.setsid() - fork_then_exit_parent(error_message="Failed second fork") - - -def is_process_started_by_init(): - """ Determine whether the current process is started by `init`. - - :return: ``True`` iff the parent process is `init`; otherwise - ``False``. - - The `init` process is the one with process ID of 1. - - """ - result = False - - init_pid = 1 - if os.getppid() == init_pid: - result = True - - return result - - -def is_socket(fd): - """ Determine whether the file descriptor is a socket. - - :param fd: The file descriptor to interrogate. - :return: ``True`` iff the file descriptor is a socket; otherwise - ``False``. - - Query the socket type of `fd`. If there is no error, the file is a - socket. - - """ - result = False - - file_socket = socket.fromfd(fd, socket.AF_INET, socket.SOCK_RAW) - - try: - socket_type = file_socket.getsockopt( - socket.SOL_SOCKET, socket.SO_TYPE) - except socket.error as exc: - exc_errno = exc.args[0] - if exc_errno == errno.ENOTSOCK: - # Socket operation on non-socket. - pass - else: - # Some other socket error. - result = True - else: - # No error getting socket type. - result = True - - return result - - -def is_process_started_by_superserver(): - """ Determine whether the current process is started by the superserver. - - :return: ``True`` if this process was started by the internet - superserver; otherwise ``False``. - - The internet superserver creates a network socket, and - attaches it to the standard streams of the child process. If - that is the case for this process, return ``True``, otherwise - ``False``. - - """ - result = False - - stdin_fd = sys.__stdin__.fileno() - if is_socket(stdin_fd): - result = True - - return result - - -def is_detach_process_context_required(): - """ Determine whether detaching the process context is required. - - :return: ``True`` iff the process is already detached; otherwise - ``False``. - - The process environment is interrogated for the following: - - * Process was started by `init`; or - - * Process was started by `inetd`. - - If any of the above are true, the process is deemed to be already - detached. - - """ - result = True - if is_process_started_by_init() or is_process_started_by_superserver(): - result = False - - return result - - -def close_file_descriptor_if_open(fd): - """ Close a file descriptor if already open. - - :param fd: The file descriptor to close. - :return: ``None``. - - Close the file descriptor `fd`, suppressing an error in the - case the file was not open. - - """ - try: - os.close(fd) - except EnvironmentError as exc: - if exc.errno == errno.EBADF: - # File descriptor was not open. - pass - else: - error = DaemonOSEnvironmentError( - "Failed to close file descriptor {fd:d} ({exc})".format( - fd=fd, exc=exc)) - raise error - - -MAXFD = 2048 - - -def get_maximum_file_descriptors(): - """ Get the maximum number of open file descriptors for this process. - - :return: The number (integer) to use as the maximum number of open - files for this process. - - The maximum is the process hard resource limit of maximum number of - open file descriptors. If the limit is “infinity”, a default value - of ``MAXFD`` is returned. - - """ - limits = resource.getrlimit(resource.RLIMIT_NOFILE) - result = limits[1] - if result == resource.RLIM_INFINITY: - result = MAXFD - return result - - -def close_all_open_files(exclude=set()): - """ Close all open file descriptors. - - :param exclude: Collection of file descriptors to skip when closing - files. - :return: ``None``. - - Closes every file descriptor (if open) of this process. If - specified, `exclude` is a set of file descriptors to *not* - close. - - """ - maxfd = get_maximum_file_descriptors() - for fd in reversed(range(maxfd)): - if fd not in exclude: - close_file_descriptor_if_open(fd) - - -def redirect_stream(system_stream, target_stream): - """ Redirect a system stream to a specified file. - - :param standard_stream: A file object representing a standard I/O - stream. - :param target_stream: The target file object for the redirected - stream, or ``None`` to specify the null device. - :return: ``None``. - - `system_stream` is a standard system stream such as - ``sys.stdout``. `target_stream` is an open file object that - should replace the corresponding system stream object. - - If `target_stream` is ``None``, defaults to opening the - operating system's null device and using its file descriptor. - - """ - if target_stream is None: - target_fd = os.open(os.devnull, os.O_RDWR) - else: - target_fd = target_stream.fileno() - os.dup2(target_fd, system_stream.fileno()) - - -def make_default_signal_map(): - """ Make the default signal map for this system. - - :return: A mapping from signal number to handler object. - - The signals available differ by system. The map will not contain - any signals not defined on the running system. - - """ - name_map = { - 'SIGTSTP': None, - 'SIGTTIN': None, - 'SIGTTOU': None, - 'SIGTERM': 'terminate', - } - signal_map = dict( - (getattr(signal, name), target) - for (name, target) in name_map.items() - if hasattr(signal, name)) - - return signal_map - - -def set_signal_handlers(signal_handler_map): - """ Set the signal handlers as specified. - - :param signal_handler_map: A map from signal number to handler - object. - :return: ``None``. - - See the `signal` module for details on signal numbers and signal - handlers. - - """ - for (signal_number, handler) in signal_handler_map.items(): - signal.signal(signal_number, handler) - - -def register_atexit_function(func): - """ Register a function for processing at program exit. - - :param func: A callable function expecting no arguments. - :return: ``None``. - - The function `func` is registered for a call with no arguments - at program exit. - - """ - atexit.register(func) - - -def _chain_exception_from_existing_exception_context(exc, as_cause=False): - """ Decorate the specified exception with the existing exception context. - - :param exc: The exception instance to decorate. - :param as_cause: If true, the existing context is declared to be - the cause of the exception. - :return: ``None``. - - :PEP:`344` describes syntax and attributes (`__traceback__`, - `__context__`, `__cause__`) for use in exception chaining. - - Python 2 does not have that syntax, so this function decorates - the exception with values from the current exception context. - - """ - (existing_exc_type, existing_exc, existing_traceback) = sys.exc_info() - if as_cause: - exc.__cause__ = existing_exc - else: - exc.__context__ = existing_exc - exc.__traceback__ = existing_traceback - - -# Local variables: -# coding: utf-8 -# mode: python -# End: -# vim: fileencoding=utf-8 filetype=python : diff --git a/pkg/osx/daemon/_metadata.py b/pkg/osx/daemon/_metadata.py new file mode 100644 index 00000000..88843df7 --- /dev/null +++ b/pkg/osx/daemon/_metadata.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- + +# daemon/_metadata.py +# Part of ‘python-daemon’, an implementation of PEP 3143. +# +# Copyright © 2008–2015 Ben Finney +# +# This is free software: you may copy, modify, and/or distribute this work +# under the terms of the Apache License, version 2.0 as published by the +# Apache Software Foundation. +# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details. + +""" Package metadata for the ‘python-daemon’ distribution. """ + +from __future__ import (absolute_import, unicode_literals) + +import json +import re +import collections +import datetime + +import pkg_resources + + +distribution_name = "python-daemon" +version_info_filename = "version_info.json" + + +def get_distribution_version_info(filename=version_info_filename): + """ Get the version info from the installed distribution. + + :param filename: Base filename of the version info resource. + :return: The version info as a mapping of fields. If the + distribution is not available, the mapping is empty. + + The version info is stored as a metadata file in the + distribution. + + """ + version_info = { + 'release_date': "UNKNOWN", + 'version': "UNKNOWN", + 'maintainer': "UNKNOWN", + } + + try: + distribution = pkg_resources.get_distribution(distribution_name) + except pkg_resources.DistributionNotFound: + distribution = None + + if distribution is not None: + if distribution.has_metadata(version_info_filename): + content = distribution.get_metadata(version_info_filename) + version_info = json.loads(content) + + return version_info + +version_info = get_distribution_version_info() + +version_installed = version_info['version'] + + +rfc822_person_regex = re.compile( + "^(?P[^<]+) <(?P[^>]+)>$") + +ParsedPerson = collections.namedtuple('ParsedPerson', ['name', 'email']) + + +def parse_person_field(value): + """ Parse a person field into name and email address. + + :param value: The text value specifying a person. + :return: A 2-tuple (name, email) for the person's details. + + If the `value` does not match a standard person with email + address, the `email` item is ``None``. + + """ + result = (None, None) + + match = rfc822_person_regex.match(value) + if len(value): + if match is not None: + result = ParsedPerson( + name=match.group('name'), + email=match.group('email')) + else: + result = ParsedPerson(name=value, email=None) + + return result + +author_name = "Ben Finney" +author_email = "ben+python@benfinney.id.au" +author = "{name} <{email}>".format(name=author_name, email=author_email) + + +class YearRange: + """ A range of years spanning a period. """ + + def __init__(self, begin, end=None): + self.begin = begin + self.end = end + + def __unicode__(self): + text = "{range.begin:04d}".format(range=self) + if self.end is not None: + if self.end > self.begin: + text = "{range.begin:04d}–{range.end:04d}".format(range=self) + return text + + __str__ = __unicode__ + + +def make_year_range(begin_year, end_date=None): + """ Construct the year range given a start and possible end date. + + :param begin_date: The beginning year (text) for the range. + :param end_date: The end date (text, ISO-8601 format) for the + range, or a non-date token string. + :return: The range of years as a `YearRange` instance. + + If the `end_date` is not a valid ISO-8601 date string, the + range has ``None`` for the end year. + + """ + begin_year = int(begin_year) + + try: + end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d") + except (TypeError, ValueError): + # Specified end_date value is not a valid date. + end_year = None + else: + end_year = end_date.year + + year_range = YearRange(begin=begin_year, end=end_year) + + return year_range + +copyright_year_begin = "2001" +build_date = version_info['release_date'] +copyright_year_range = make_year_range(copyright_year_begin, build_date) + +copyright = "Copyright © {year_range} {author} and others".format( + year_range=copyright_year_range, author=author) +license = "Apache-2" +url = "https://alioth.debian.org/projects/python-daemon/" + + +# Local variables: +# coding: utf-8 +# mode: python +# End: +# vim: fileencoding=utf-8 filetype=python : diff --git a/pkg/osx/daemon/daemon.py b/pkg/osx/daemon/daemon.py new file mode 100644 index 00000000..7ca8770e --- /dev/null +++ b/pkg/osx/daemon/daemon.py @@ -0,0 +1,927 @@ +# -*- coding: utf-8 -*- + +# daemon/daemon.py +# Part of ‘python-daemon’, an implementation of PEP 3143. +# +# Copyright © 2008–2015 Ben Finney +# Copyright © 2007–2008 Robert Niederreiter, Jens Klein +# Copyright © 2004–2005 Chad J. Schroeder +# Copyright © 2003 Clark Evans +# Copyright © 2002 Noah Spurrier +# Copyright © 2001 Jürgen Hermann +# +# This is free software: you may copy, modify, and/or distribute this work +# under the terms of the Apache License, version 2.0 as published by the +# Apache Software Foundation. +# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details. + +""" Daemon process behaviour. + """ + +from __future__ import (absolute_import, unicode_literals) + +import os +import sys +import resource +import errno +import signal +import socket +import atexit +try: + # Python 2 has both ‘str’ (bytes) and ‘unicode’ (text). + basestring = basestring + unicode = unicode +except NameError: + # Python 3 names the Unicode data type ‘str’. + basestring = str + unicode = str + + +class DaemonError(Exception): + """ Base exception class for errors from this module. """ + + def __init__(self, *args, **kwargs): + self._chain_from_context() + + super(DaemonError, self).__init__(*args, **kwargs) + + def _chain_from_context(self): + _chain_exception_from_existing_exception_context(self, as_cause=True) + + +class DaemonOSEnvironmentError(DaemonError, OSError): + """ Exception raised when daemon OS environment setup receives error. """ + + +class DaemonProcessDetachError(DaemonError, OSError): + """ Exception raised when process detach fails. """ + + +class DaemonContext: + """ Context for turning the current program into a daemon process. + + A `DaemonContext` instance represents the behaviour settings and + process context for the program when it becomes a daemon. The + behaviour and environment is customised by setting options on the + instance, before calling the `open` method. + + Each option can be passed as a keyword argument to the `DaemonContext` + constructor, or subsequently altered by assigning to an attribute on + the instance at any time prior to calling `open`. That is, for + options named `wibble` and `wubble`, the following invocation:: + + foo = daemon.DaemonContext(wibble=bar, wubble=baz) + foo.open() + + is equivalent to:: + + foo = daemon.DaemonContext() + foo.wibble = bar + foo.wubble = baz + foo.open() + + The following options are defined. + + `files_preserve` + :Default: ``None`` + + List of files that should *not* be closed when starting the + daemon. If ``None``, all open file descriptors will be closed. + + Elements of the list are file descriptors (as returned by a file + object's `fileno()` method) or Python `file` objects. Each + specifies a file that is not to be closed during daemon start. + + `chroot_directory` + :Default: ``None`` + + Full path to a directory to set as the effective root directory of + the process. If ``None``, specifies that the root directory is not + to be changed. + + `working_directory` + :Default: ``'/'`` + + Full path of the working directory to which the process should + change on daemon start. + + Since a filesystem cannot be unmounted if a process has its + current working directory on that filesystem, this should either + be left at default or set to a directory that is a sensible “home + directory” for the daemon while it is running. + + `umask` + :Default: ``0`` + + File access creation mask (“umask”) to set for the process on + daemon start. + + A daemon should not rely on the parent process's umask value, + which is beyond its control and may prevent creating a file with + the required access mode. So when the daemon context opens, the + umask is set to an explicit known value. + + If the conventional value of 0 is too open, consider setting a + value such as 0o022, 0o027, 0o077, or another specific value. + Otherwise, ensure the daemon creates every file with an + explicit access mode for the purpose. + + `pidfile` + :Default: ``None`` + + Context manager for a PID lock file. When the daemon context opens + and closes, it enters and exits the `pidfile` context manager. + + `detach_process` + :Default: ``None`` + + If ``True``, detach the process context when opening the daemon + context; if ``False``, do not detach. + + If unspecified (``None``) during initialisation of the instance, + this will be set to ``True`` by default, and ``False`` only if + detaching the process is determined to be redundant; for example, + in the case when the process was started by `init`, by `initd`, or + by `inetd`. + + `signal_map` + :Default: system-dependent + + Mapping from operating system signals to callback actions. + + The mapping is used when the daemon context opens, and determines + the action for each signal's signal handler: + + * A value of ``None`` will ignore the signal (by setting the + signal action to ``signal.SIG_IGN``). + + * A string value will be used as the name of an attribute on the + ``DaemonContext`` instance. The attribute's value will be used + as the action for the signal handler. + + * Any other value will be used as the action for the + signal handler. See the ``signal.signal`` documentation + for details of the signal handler interface. + + The default value depends on which signals are defined on the + running system. Each item from the list below whose signal is + actually defined in the ``signal`` module will appear in the + default map: + + * ``signal.SIGTTIN``: ``None`` + + * ``signal.SIGTTOU``: ``None`` + + * ``signal.SIGTSTP``: ``None`` + + * ``signal.SIGTERM``: ``'terminate'`` + + Depending on how the program will interact with its child + processes, it may need to specify a signal map that + includes the ``signal.SIGCHLD`` signal (received when a + child process exits). See the specific operating system's + documentation for more detail on how to determine what + circumstances dictate the need for signal handlers. + + `uid` + :Default: ``os.getuid()`` + + `gid` + :Default: ``os.getgid()`` + + The user ID (“UID”) value and group ID (“GID”) value to switch + the process to on daemon start. + + The default values, the real UID and GID of the process, will + relinquish any effective privilege elevation inherited by the + process. + + `prevent_core` + :Default: ``True`` + + If true, prevents the generation of core files, in order to avoid + leaking sensitive information from daemons run as `root`. + + `stdin` + :Default: ``None`` + + `stdout` + :Default: ``None`` + + `stderr` + :Default: ``None`` + + Each of `stdin`, `stdout`, and `stderr` is a file-like object + which will be used as the new file for the standard I/O stream + `sys.stdin`, `sys.stdout`, and `sys.stderr` respectively. The file + should therefore be open, with a minimum of mode 'r' in the case + of `stdin`, and mimimum of mode 'w+' in the case of `stdout` and + `stderr`. + + If the object has a `fileno()` method that returns a file + descriptor, the corresponding file will be excluded from being + closed during daemon start (that is, it will be treated as though + it were listed in `files_preserve`). + + If ``None``, the corresponding system stream is re-bound to the + file named by `os.devnull`. + + """ + + __metaclass__ = type + + def __init__( + self, + chroot_directory=None, + working_directory="/", + umask=0, + uid=None, + gid=None, + prevent_core=True, + detach_process=None, + files_preserve=None, + pidfile=None, + stdin=None, + stdout=None, + stderr=None, + signal_map=None, + ): + """ Set up a new instance. """ + self.chroot_directory = chroot_directory + self.working_directory = working_directory + self.umask = umask + self.prevent_core = prevent_core + self.files_preserve = files_preserve + self.pidfile = pidfile + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr + + if uid is None: + uid = os.getuid() + self.uid = uid + if gid is None: + gid = os.getgid() + self.gid = gid + + if detach_process is None: + detach_process = is_detach_process_context_required() + self.detach_process = detach_process + + if signal_map is None: + signal_map = make_default_signal_map() + self.signal_map = signal_map + + self._is_open = False + + @property + def is_open(self): + """ ``True`` if the instance is currently open. """ + return self._is_open + + def open(self): + """ Become a daemon process. + + :return: ``None``. + + Open the daemon context, turning the current program into a daemon + process. This performs the following steps: + + * If this instance's `is_open` property is true, return + immediately. This makes it safe to call `open` multiple times on + an instance. + + * If the `prevent_core` attribute is true, set the resource limits + for the process to prevent any core dump from the process. + + * If the `chroot_directory` attribute is not ``None``, set the + effective root directory of the process to that directory (via + `os.chroot`). + + This allows running the daemon process inside a “chroot gaol” + as a means of limiting the system's exposure to rogue behaviour + by the process. Note that the specified directory needs to + already be set up for this purpose. + + * Set the process UID and GID to the `uid` and `gid` attribute + values. + + * Close all open file descriptors. This excludes those listed in + the `files_preserve` attribute, and those that correspond to the + `stdin`, `stdout`, or `stderr` attributes. + + * Change current working directory to the path specified by the + `working_directory` attribute. + + * Reset the file access creation mask to the value specified by + the `umask` attribute. + + * If the `detach_process` option is true, detach the current + process into its own process group, and disassociate from any + controlling terminal. + + * Set signal handlers as specified by the `signal_map` attribute. + + * If any of the attributes `stdin`, `stdout`, `stderr` are not + ``None``, bind the system streams `sys.stdin`, `sys.stdout`, + and/or `sys.stderr` to the files represented by the + corresponding attributes. Where the attribute has a file + descriptor, the descriptor is duplicated (instead of re-binding + the name). + + * If the `pidfile` attribute is not ``None``, enter its context + manager. + + * Mark this instance as open (for the purpose of future `open` and + `close` calls). + + * Register the `close` method to be called during Python's exit + processing. + + When the function returns, the running program is a daemon + process. + + """ + if self.is_open: + return + + if self.chroot_directory is not None: + change_root_directory(self.chroot_directory) + + if self.prevent_core: + prevent_core_dump() + + change_file_creation_mask(self.umask) + change_working_directory(self.working_directory) + change_process_owner(self.uid, self.gid) + + if self.detach_process: + detach_process_context() + + signal_handler_map = self._make_signal_handler_map() + set_signal_handlers(signal_handler_map) + + exclude_fds = self._get_exclude_file_descriptors() + close_all_open_files(exclude=exclude_fds) + + redirect_stream(sys.stdin, self.stdin) + redirect_stream(sys.stdout, self.stdout) + redirect_stream(sys.stderr, self.stderr) + + if self.pidfile is not None: + self.pidfile.__enter__() + + self._is_open = True + + register_atexit_function(self.close) + + def __enter__(self): + """ Context manager entry point. """ + self.open() + return self + + def close(self): + """ Exit the daemon process context. + + :return: ``None``. + + Close the daemon context. This performs the following steps: + + * If this instance's `is_open` property is false, return + immediately. This makes it safe to call `close` multiple times + on an instance. + + * If the `pidfile` attribute is not ``None``, exit its context + manager. + + * Mark this instance as closed (for the purpose of future `open` + and `close` calls). + + """ + if not self.is_open: + return + + if self.pidfile is not None: + # Follow the interface for telling a context manager to exit, + # . + self.pidfile.__exit__(None, None, None) + + self._is_open = False + + def __exit__(self, exc_type, exc_value, traceback): + """ Context manager exit point. """ + self.close() + + def terminate(self, signal_number, stack_frame): + """ Signal handler for end-process signals. + + :param signal_number: The OS signal number received. + :param stack_frame: The frame object at the point the + signal was received. + :return: ``None``. + + Signal handler for the ``signal.SIGTERM`` signal. Performs the + following step: + + * Raise a ``SystemExit`` exception explaining the signal. + + """ + exception = SystemExit( + "Terminating on signal {signal_number!r}".format( + signal_number=signal_number)) + raise exception + + def _get_exclude_file_descriptors(self): + """ Get the set of file descriptors to exclude closing. + + :return: A set containing the file descriptors for the + files to be preserved. + + The file descriptors to be preserved are those from the + items in `files_preserve`, and also each of `stdin`, + `stdout`, and `stderr`. For each item: + + * If the item is ``None``, it is omitted from the return + set. + + * If the item's ``fileno()`` method returns a value, that + value is in the return set. + + * Otherwise, the item is in the return set verbatim. + + """ + files_preserve = self.files_preserve + if files_preserve is None: + files_preserve = [] + files_preserve.extend( + item for item in [self.stdin, self.stdout, self.stderr] + if hasattr(item, 'fileno')) + + exclude_descriptors = set() + for item in files_preserve: + if item is None: + continue + file_descriptor = _get_file_descriptor(item) + if file_descriptor is not None: + exclude_descriptors.add(file_descriptor) + else: + exclude_descriptors.add(item) + + return exclude_descriptors + + def _make_signal_handler(self, target): + """ Make the signal handler for a specified target object. + + :param target: A specification of the target for the + handler; see below. + :return: The value for use by `signal.signal()`. + + If `target` is ``None``, return ``signal.SIG_IGN``. If `target` + is a text string, return the attribute of this instance named + by that string. Otherwise, return `target` itself. + + """ + if target is None: + result = signal.SIG_IGN + elif isinstance(target, unicode): + name = target + result = getattr(self, name) + else: + result = target + + return result + + def _make_signal_handler_map(self): + """ Make the map from signals to handlers for this instance. + + :return: The constructed signal map for this instance. + + Construct a map from signal numbers to handlers for this + context instance, suitable for passing to + `set_signal_handlers`. + + """ + signal_handler_map = dict( + (signal_number, self._make_signal_handler(target)) + for (signal_number, target) in self.signal_map.items()) + return signal_handler_map + + +def _get_file_descriptor(obj): + """ Get the file descriptor, if the object has one. + + :param obj: The object expected to be a file-like object. + :return: The file descriptor iff the file supports it; otherwise + ``None``. + + The object may be a non-file object. It may also be a + file-like object with no support for a file descriptor. In + either case, return ``None``. + + """ + file_descriptor = None + if hasattr(obj, 'fileno'): + try: + file_descriptor = obj.fileno() + except ValueError: + # The item doesn't support a file descriptor. + pass + + return file_descriptor + + +def change_working_directory(directory): + """ Change the working directory of this process. + + :param directory: The target directory path. + :return: ``None``. + + """ + try: + os.chdir(directory) + except Exception as exc: + error = DaemonOSEnvironmentError( + "Unable to change working directory ({exc})".format(exc=exc)) + raise error + + +def change_root_directory(directory): + """ Change the root directory of this process. + + :param directory: The target directory path. + :return: ``None``. + + Set the current working directory, then the process root directory, + to the specified `directory`. Requires appropriate OS privileges + for this process. + + """ + try: + os.chdir(directory) + os.chroot(directory) + except Exception as exc: + error = DaemonOSEnvironmentError( + "Unable to change root directory ({exc})".format(exc=exc)) + raise error + + +def change_file_creation_mask(mask): + """ Change the file creation mask for this process. + + :param mask: The numeric file creation mask to set. + :return: ``None``. + + """ + try: + os.umask(mask) + except Exception as exc: + error = DaemonOSEnvironmentError( + "Unable to change file creation mask ({exc})".format(exc=exc)) + raise error + + +def change_process_owner(uid, gid): + """ Change the owning UID and GID of this process. + + :param uid: The target UID for the daemon process. + :param gid: The target GID for the daemon process. + :return: ``None``. + + Set the GID then the UID of the process (in that order, to avoid + permission errors) to the specified `gid` and `uid` values. + Requires appropriate OS privileges for this process. + + """ + try: + os.setgid(gid) + os.setuid(uid) + except Exception as exc: + error = DaemonOSEnvironmentError( + "Unable to change process owner ({exc})".format(exc=exc)) + raise error + + +def prevent_core_dump(): + """ Prevent this process from generating a core dump. + + :return: ``None``. + + Set the soft and hard limits for core dump size to zero. On Unix, + this entirely prevents the process from creating core dump. + + """ + core_resource = resource.RLIMIT_CORE + + try: + # Ensure the resource limit exists on this platform, by requesting + # its current value. + core_limit_prev = resource.getrlimit(core_resource) + except ValueError as exc: + error = DaemonOSEnvironmentError( + "System does not support RLIMIT_CORE resource limit" + " ({exc})".format(exc=exc)) + raise error + + # Set hard and soft limits to zero, i.e. no core dump at all. + core_limit = (0, 0) + resource.setrlimit(core_resource, core_limit) + + +def detach_process_context(): + """ Detach the process context from parent and session. + + :return: ``None``. + + Detach from the parent process and session group, allowing the + parent to exit while this process continues running. + + Reference: “Advanced Programming in the Unix Environment”, + section 13.3, by W. Richard Stevens, published 1993 by + Addison-Wesley. + + """ + + def fork_then_exit_parent(error_message): + """ Fork a child process, then exit the parent process. + + :param error_message: Message for the exception in case of a + detach failure. + :return: ``None``. + :raise DaemonProcessDetachError: If the fork fails. + + """ + try: + pid = os.fork() + if pid > 0: + os._exit(0) + except OSError as exc: + error = DaemonProcessDetachError( + "{message}: [{exc.errno:d}] {exc.strerror}".format( + message=error_message, exc=exc)) + raise error + + fork_then_exit_parent(error_message="Failed first fork") + os.setsid() + fork_then_exit_parent(error_message="Failed second fork") + + +def is_process_started_by_init(): + """ Determine whether the current process is started by `init`. + + :return: ``True`` iff the parent process is `init`; otherwise + ``False``. + + The `init` process is the one with process ID of 1. + + """ + result = False + + init_pid = 1 + if os.getppid() == init_pid: + result = True + + return result + + +def is_socket(fd): + """ Determine whether the file descriptor is a socket. + + :param fd: The file descriptor to interrogate. + :return: ``True`` iff the file descriptor is a socket; otherwise + ``False``. + + Query the socket type of `fd`. If there is no error, the file is a + socket. + + """ + result = False + + file_socket = socket.fromfd(fd, socket.AF_INET, socket.SOCK_RAW) + + try: + socket_type = file_socket.getsockopt( + socket.SOL_SOCKET, socket.SO_TYPE) + except socket.error as exc: + exc_errno = exc.args[0] + if exc_errno == errno.ENOTSOCK: + # Socket operation on non-socket. + pass + else: + # Some other socket error. + result = True + else: + # No error getting socket type. + result = True + + return result + + +def is_process_started_by_superserver(): + """ Determine whether the current process is started by the superserver. + + :return: ``True`` if this process was started by the internet + superserver; otherwise ``False``. + + The internet superserver creates a network socket, and + attaches it to the standard streams of the child process. If + that is the case for this process, return ``True``, otherwise + ``False``. + + """ + result = False + + stdin_fd = sys.__stdin__.fileno() + if is_socket(stdin_fd): + result = True + + return result + + +def is_detach_process_context_required(): + """ Determine whether detaching the process context is required. + + :return: ``True`` iff the process is already detached; otherwise + ``False``. + + The process environment is interrogated for the following: + + * Process was started by `init`; or + + * Process was started by `inetd`. + + If any of the above are true, the process is deemed to be already + detached. + + """ + result = True + if is_process_started_by_init() or is_process_started_by_superserver(): + result = False + + return result + + +def close_file_descriptor_if_open(fd): + """ Close a file descriptor if already open. + + :param fd: The file descriptor to close. + :return: ``None``. + + Close the file descriptor `fd`, suppressing an error in the + case the file was not open. + + """ + try: + os.close(fd) + except EnvironmentError as exc: + if exc.errno == errno.EBADF: + # File descriptor was not open. + pass + else: + error = DaemonOSEnvironmentError( + "Failed to close file descriptor {fd:d} ({exc})".format( + fd=fd, exc=exc)) + raise error + + +MAXFD = 2048 + + +def get_maximum_file_descriptors(): + """ Get the maximum number of open file descriptors for this process. + + :return: The number (integer) to use as the maximum number of open + files for this process. + + The maximum is the process hard resource limit of maximum number of + open file descriptors. If the limit is “infinity”, a default value + of ``MAXFD`` is returned. + + """ + limits = resource.getrlimit(resource.RLIMIT_NOFILE) + result = limits[1] + if result == resource.RLIM_INFINITY: + result = MAXFD + return result + + +def close_all_open_files(exclude=set()): + """ Close all open file descriptors. + + :param exclude: Collection of file descriptors to skip when closing + files. + :return: ``None``. + + Closes every file descriptor (if open) of this process. If + specified, `exclude` is a set of file descriptors to *not* + close. + + """ + maxfd = get_maximum_file_descriptors() + for fd in reversed(range(maxfd)): + if fd not in exclude: + close_file_descriptor_if_open(fd) + + +def redirect_stream(system_stream, target_stream): + """ Redirect a system stream to a specified file. + + :param standard_stream: A file object representing a standard I/O + stream. + :param target_stream: The target file object for the redirected + stream, or ``None`` to specify the null device. + :return: ``None``. + + `system_stream` is a standard system stream such as + ``sys.stdout``. `target_stream` is an open file object that + should replace the corresponding system stream object. + + If `target_stream` is ``None``, defaults to opening the + operating system's null device and using its file descriptor. + + """ + if target_stream is None: + target_fd = os.open(os.devnull, os.O_RDWR) + else: + target_fd = target_stream.fileno() + os.dup2(target_fd, system_stream.fileno()) + + +def make_default_signal_map(): + """ Make the default signal map for this system. + + :return: A mapping from signal number to handler object. + + The signals available differ by system. The map will not contain + any signals not defined on the running system. + + """ + name_map = { + 'SIGTSTP': None, + 'SIGTTIN': None, + 'SIGTTOU': None, + 'SIGTERM': 'terminate', + } + signal_map = dict( + (getattr(signal, name), target) + for (name, target) in name_map.items() + if hasattr(signal, name)) + + return signal_map + + +def set_signal_handlers(signal_handler_map): + """ Set the signal handlers as specified. + + :param signal_handler_map: A map from signal number to handler + object. + :return: ``None``. + + See the `signal` module for details on signal numbers and signal + handlers. + + """ + for (signal_number, handler) in signal_handler_map.items(): + signal.signal(signal_number, handler) + + +def register_atexit_function(func): + """ Register a function for processing at program exit. + + :param func: A callable function expecting no arguments. + :return: ``None``. + + The function `func` is registered for a call with no arguments + at program exit. + + """ + atexit.register(func) + + +def _chain_exception_from_existing_exception_context(exc, as_cause=False): + """ Decorate the specified exception with the existing exception context. + + :param exc: The exception instance to decorate. + :param as_cause: If true, the existing context is declared to be + the cause of the exception. + :return: ``None``. + + :PEP:`344` describes syntax and attributes (`__traceback__`, + `__context__`, `__cause__`) for use in exception chaining. + + Python 2 does not have that syntax, so this function decorates + the exception with values from the current exception context. + + """ + (existing_exc_type, existing_exc, existing_traceback) = sys.exc_info() + if as_cause: + exc.__cause__ = existing_exc + else: + exc.__context__ = existing_exc + exc.__traceback__ = existing_traceback + + +# Local variables: +# coding: utf-8 +# mode: python +# End: +# vim: fileencoding=utf-8 filetype=python : diff --git a/pkg/osx/daemon/pidfile.py b/pkg/osx/daemon/pidfile.py new file mode 100644 index 00000000..68f7b2ac --- /dev/null +++ b/pkg/osx/daemon/pidfile.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +# daemon/pidfile.py +# Part of ‘python-daemon’, an implementation of PEP 3143. +# +# Copyright © 2008–2015 Ben Finney +# +# This is free software: you may copy, modify, and/or distribute this work +# under the terms of the Apache License, version 2.0 as published by the +# Apache Software Foundation. +# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details. + +""" Lockfile behaviour implemented via Unix PID files. + """ + +from __future__ import (absolute_import, unicode_literals) + +from lockfile.pidlockfile import PIDLockFile + + +class TimeoutPIDLockFile(PIDLockFile, object): + """ Lockfile with default timeout, implemented as a Unix PID file. + + This uses the ``PIDLockFile`` implementation, with the + following changes: + + * The `acquire_timeout` parameter to the initialiser will be + used as the default `timeout` parameter for the `acquire` + method. + + """ + + def __init__(self, path, acquire_timeout=None, *args, **kwargs): + """ Set up the parameters of a TimeoutPIDLockFile. + + :param path: Filesystem path to the PID file. + :param acquire_timeout: Value to use by default for the + `acquire` call. + :return: ``None``. + + """ + self.acquire_timeout = acquire_timeout + super(TimeoutPIDLockFile, self).__init__(path, *args, **kwargs) + + def acquire(self, timeout=None, *args, **kwargs): + """ Acquire the lock. + + :param timeout: Specifies the timeout; see below for valid + values. + :return: ``None``. + + The `timeout` defaults to the value set during + initialisation with the `acquire_timeout` parameter. It is + passed to `PIDLockFile.acquire`; see that method for + details. + + """ + if timeout is None: + timeout = self.acquire_timeout + super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs) + + +# Local variables: +# coding: utf-8 +# mode: python +# End: +# vim: fileencoding=utf-8 filetype=python : diff --git a/pkg/osx/daemon/runner.py b/pkg/osx/daemon/runner.py new file mode 100644 index 00000000..de9025d3 --- /dev/null +++ b/pkg/osx/daemon/runner.py @@ -0,0 +1,324 @@ +# -*- coding: utf-8 -*- + +# daemon/runner.py +# Part of ‘python-daemon’, an implementation of PEP 3143. +# +# Copyright © 2009–2015 Ben Finney +# Copyright © 2007–2008 Robert Niederreiter, Jens Klein +# Copyright © 2003 Clark Evans +# Copyright © 2002 Noah Spurrier +# Copyright © 2001 Jürgen Hermann +# +# This is free software: you may copy, modify, and/or distribute this work +# under the terms of the Apache License, version 2.0 as published by the +# Apache Software Foundation. +# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details. + +""" Daemon runner library. + """ + +from __future__ import (absolute_import, unicode_literals) + +import sys +import os +import signal +import errno +try: + # Python 3 standard library. + ProcessLookupError +except NameError: + # No such class in Python 2. + ProcessLookupError = NotImplemented + +import lockfile + +from . import pidfile +from .daemon import (basestring, unicode) +from .daemon import DaemonContext +from .daemon import _chain_exception_from_existing_exception_context + + +class DaemonRunnerError(Exception): + """ Abstract base class for errors from DaemonRunner. """ + + def __init__(self, *args, **kwargs): + self._chain_from_context() + + super(DaemonRunnerError, self).__init__(*args, **kwargs) + + def _chain_from_context(self): + _chain_exception_from_existing_exception_context(self, as_cause=True) + + +class DaemonRunnerInvalidActionError(DaemonRunnerError, ValueError): + """ Raised when specified action for DaemonRunner is invalid. """ + + def _chain_from_context(self): + # This exception is normally not caused by another. + _chain_exception_from_existing_exception_context(self, as_cause=False) + + +class DaemonRunnerStartFailureError(DaemonRunnerError, RuntimeError): + """ Raised when failure starting DaemonRunner. """ + + +class DaemonRunnerStopFailureError(DaemonRunnerError, RuntimeError): + """ Raised when failure stopping DaemonRunner. """ + + +class DaemonRunner: + """ Controller for a callable running in a separate background process. + + The first command-line argument is the action to take: + + * 'start': Become a daemon and call `app.run()`. + * 'stop': Exit the daemon process specified in the PID file. + * 'restart': Stop, then start. + + """ + + __metaclass__ = type + + start_message = "started with pid {pid:d}" + + def __init__(self, app): + """ Set up the parameters of a new runner. + + :param app: The application instance; see below. + :return: ``None``. + + The `app` argument must have the following attributes: + + * `stdin_path`, `stdout_path`, `stderr_path`: Filesystem paths + to open and replace the existing `sys.stdin`, `sys.stdout`, + `sys.stderr`. + + * `pidfile_path`: Absolute filesystem path to a file that will + be used as the PID file for the daemon. If ``None``, no PID + file will be used. + + * `pidfile_timeout`: Used as the default acquisition timeout + value supplied to the runner's PID lock file. + + * `run`: Callable that will be invoked when the daemon is + started. + + """ + self.parse_args() + self.app = app + self.daemon_context = DaemonContext() + self.daemon_context.stdin = open(app.stdin_path, 'rt') + self.daemon_context.stdout = open(app.stdout_path, 'w+t') + self.daemon_context.stderr = open( + app.stderr_path, 'w+t', buffering=0) + + self.pidfile = None + if app.pidfile_path is not None: + self.pidfile = make_pidlockfile( + app.pidfile_path, app.pidfile_timeout) + self.daemon_context.pidfile = self.pidfile + + def _usage_exit(self, argv): + """ Emit a usage message, then exit. + + :param argv: The command-line arguments used to invoke the + program, as a sequence of strings. + :return: ``None``. + + """ + progname = os.path.basename(argv[0]) + usage_exit_code = 2 + action_usage = "|".join(self.action_funcs.keys()) + message = "usage: {progname} {usage}".format( + progname=progname, usage=action_usage) + emit_message(message) + sys.exit(usage_exit_code) + + def parse_args(self, argv=None): + """ Parse command-line arguments. + + :param argv: The command-line arguments used to invoke the + program, as a sequence of strings. + + :return: ``None``. + + The parser expects the first argument as the program name, the + second argument as the action to perform. + + If the parser fails to parse the arguments, emit a usage + message and exit the program. + + """ + if argv is None: + argv = sys.argv + + min_args = 2 + if len(argv) < min_args: + self._usage_exit(argv) + + self.action = unicode(argv[1]) + if self.action not in self.action_funcs: + self._usage_exit(argv) + + def _start(self): + """ Open the daemon context and run the application. + + :return: ``None``. + :raises DaemonRunnerStartFailureError: If the PID file cannot + be locked by this process. + + """ + if is_pidfile_stale(self.pidfile): + self.pidfile.break_lock() + + try: + self.daemon_context.open() + except lockfile.AlreadyLocked: + error = DaemonRunnerStartFailureError( + "PID file {pidfile.path!r} already locked".format( + pidfile=self.pidfile)) + raise error + + pid = os.getpid() + message = self.start_message.format(pid=pid) + emit_message(message) + + self.app.run() + + def _terminate_daemon_process(self): + """ Terminate the daemon process specified in the current PID file. + + :return: ``None``. + :raises DaemonRunnerStopFailureError: If terminating the daemon + fails with an OS error. + + """ + pid = self.pidfile.read_pid() + try: + os.kill(pid, signal.SIGTERM) + except OSError as exc: + error = DaemonRunnerStopFailureError( + "Failed to terminate {pid:d}: {exc}".format( + pid=pid, exc=exc)) + raise error + + def _stop(self): + """ Exit the daemon process specified in the current PID file. + + :return: ``None``. + :raises DaemonRunnerStopFailureError: If the PID file is not + already locked. + + """ + if not self.pidfile.is_locked(): + error = DaemonRunnerStopFailureError( + "PID file {pidfile.path!r} not locked".format( + pidfile=self.pidfile)) + raise error + + if is_pidfile_stale(self.pidfile): + self.pidfile.break_lock() + else: + self._terminate_daemon_process() + + def _restart(self): + """ Stop, then start. + """ + self._stop() + self._start() + + action_funcs = { + 'start': _start, + 'stop': _stop, + 'restart': _restart, + } + + def _get_action_func(self): + """ Get the function for the specified action. + + :return: The function object corresponding to the specified + action. + :raises DaemonRunnerInvalidActionError: if the action is + unknown. + + The action is specified by the `action` attribute, which is set + during `parse_args`. + + """ + try: + func = self.action_funcs[self.action] + except KeyError: + error = DaemonRunnerInvalidActionError( + "Unknown action: {action!r}".format( + action=self.action)) + raise error + return func + + def do_action(self): + """ Perform the requested action. + + :return: ``None``. + + The action is specified by the `action` attribute, which is set + during `parse_args`. + + """ + func = self._get_action_func() + func(self) + + +def emit_message(message, stream=None): + """ Emit a message to the specified stream (default `sys.stderr`). """ + if stream is None: + stream = sys.stderr + stream.write("{message}\n".format(message=message)) + stream.flush() + + +def make_pidlockfile(path, acquire_timeout): + """ Make a PIDLockFile instance with the given filesystem path. """ + if not isinstance(path, basestring): + error = ValueError("Not a filesystem path: {path!r}".format( + path=path)) + raise error + if not os.path.isabs(path): + error = ValueError("Not an absolute path: {path!r}".format( + path=path)) + raise error + lockfile = pidfile.TimeoutPIDLockFile(path, acquire_timeout) + + return lockfile + + +def is_pidfile_stale(pidfile): + """ Determine whether a PID file is stale. + + :return: ``True`` iff the PID file is stale; otherwise ``False``. + + The PID file is “stale” if its contents are valid but do not + match the PID of a currently-running process. + + """ + result = False + + pidfile_pid = pidfile.read_pid() + if pidfile_pid is not None: + try: + os.kill(pidfile_pid, signal.SIG_DFL) + except ProcessLookupError: + # The specified PID does not exist. + result = True + except OSError as exc: + if exc.errno == errno.ESRCH: + # Under Python 2, process lookup error is an OSError. + # The specified PID does not exist. + result = True + + return result + + +# Local variables: +# coding: utf-8 +# mode: python +# End: +# vim: fileencoding=utf-8 filetype=python : diff --git a/pkg/osx/pidfile.py b/pkg/osx/pidfile.py deleted file mode 100644 index 68f7b2ac..00000000 --- a/pkg/osx/pidfile.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- - -# daemon/pidfile.py -# Part of ‘python-daemon’, an implementation of PEP 3143. -# -# Copyright © 2008–2015 Ben Finney -# -# This is free software: you may copy, modify, and/or distribute this work -# under the terms of the Apache License, version 2.0 as published by the -# Apache Software Foundation. -# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details. - -""" Lockfile behaviour implemented via Unix PID files. - """ - -from __future__ import (absolute_import, unicode_literals) - -from lockfile.pidlockfile import PIDLockFile - - -class TimeoutPIDLockFile(PIDLockFile, object): - """ Lockfile with default timeout, implemented as a Unix PID file. - - This uses the ``PIDLockFile`` implementation, with the - following changes: - - * The `acquire_timeout` parameter to the initialiser will be - used as the default `timeout` parameter for the `acquire` - method. - - """ - - def __init__(self, path, acquire_timeout=None, *args, **kwargs): - """ Set up the parameters of a TimeoutPIDLockFile. - - :param path: Filesystem path to the PID file. - :param acquire_timeout: Value to use by default for the - `acquire` call. - :return: ``None``. - - """ - self.acquire_timeout = acquire_timeout - super(TimeoutPIDLockFile, self).__init__(path, *args, **kwargs) - - def acquire(self, timeout=None, *args, **kwargs): - """ Acquire the lock. - - :param timeout: Specifies the timeout; see below for valid - values. - :return: ``None``. - - The `timeout` defaults to the value set during - initialisation with the `acquire_timeout` parameter. It is - passed to `PIDLockFile.acquire`; see that method for - details. - - """ - if timeout is None: - timeout = self.acquire_timeout - super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs) - - -# Local variables: -# coding: utf-8 -# mode: python -# End: -# vim: fileencoding=utf-8 filetype=python : diff --git a/pkg/osx/runner.py b/pkg/osx/runner.py deleted file mode 100644 index de9025d3..00000000 --- a/pkg/osx/runner.py +++ /dev/null @@ -1,324 +0,0 @@ -# -*- coding: utf-8 -*- - -# daemon/runner.py -# Part of ‘python-daemon’, an implementation of PEP 3143. -# -# Copyright © 2009–2015 Ben Finney -# Copyright © 2007–2008 Robert Niederreiter, Jens Klein -# Copyright © 2003 Clark Evans -# Copyright © 2002 Noah Spurrier -# Copyright © 2001 Jürgen Hermann -# -# This is free software: you may copy, modify, and/or distribute this work -# under the terms of the Apache License, version 2.0 as published by the -# Apache Software Foundation. -# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details. - -""" Daemon runner library. - """ - -from __future__ import (absolute_import, unicode_literals) - -import sys -import os -import signal -import errno -try: - # Python 3 standard library. - ProcessLookupError -except NameError: - # No such class in Python 2. - ProcessLookupError = NotImplemented - -import lockfile - -from . import pidfile -from .daemon import (basestring, unicode) -from .daemon import DaemonContext -from .daemon import _chain_exception_from_existing_exception_context - - -class DaemonRunnerError(Exception): - """ Abstract base class for errors from DaemonRunner. """ - - def __init__(self, *args, **kwargs): - self._chain_from_context() - - super(DaemonRunnerError, self).__init__(*args, **kwargs) - - def _chain_from_context(self): - _chain_exception_from_existing_exception_context(self, as_cause=True) - - -class DaemonRunnerInvalidActionError(DaemonRunnerError, ValueError): - """ Raised when specified action for DaemonRunner is invalid. """ - - def _chain_from_context(self): - # This exception is normally not caused by another. - _chain_exception_from_existing_exception_context(self, as_cause=False) - - -class DaemonRunnerStartFailureError(DaemonRunnerError, RuntimeError): - """ Raised when failure starting DaemonRunner. """ - - -class DaemonRunnerStopFailureError(DaemonRunnerError, RuntimeError): - """ Raised when failure stopping DaemonRunner. """ - - -class DaemonRunner: - """ Controller for a callable running in a separate background process. - - The first command-line argument is the action to take: - - * 'start': Become a daemon and call `app.run()`. - * 'stop': Exit the daemon process specified in the PID file. - * 'restart': Stop, then start. - - """ - - __metaclass__ = type - - start_message = "started with pid {pid:d}" - - def __init__(self, app): - """ Set up the parameters of a new runner. - - :param app: The application instance; see below. - :return: ``None``. - - The `app` argument must have the following attributes: - - * `stdin_path`, `stdout_path`, `stderr_path`: Filesystem paths - to open and replace the existing `sys.stdin`, `sys.stdout`, - `sys.stderr`. - - * `pidfile_path`: Absolute filesystem path to a file that will - be used as the PID file for the daemon. If ``None``, no PID - file will be used. - - * `pidfile_timeout`: Used as the default acquisition timeout - value supplied to the runner's PID lock file. - - * `run`: Callable that will be invoked when the daemon is - started. - - """ - self.parse_args() - self.app = app - self.daemon_context = DaemonContext() - self.daemon_context.stdin = open(app.stdin_path, 'rt') - self.daemon_context.stdout = open(app.stdout_path, 'w+t') - self.daemon_context.stderr = open( - app.stderr_path, 'w+t', buffering=0) - - self.pidfile = None - if app.pidfile_path is not None: - self.pidfile = make_pidlockfile( - app.pidfile_path, app.pidfile_timeout) - self.daemon_context.pidfile = self.pidfile - - def _usage_exit(self, argv): - """ Emit a usage message, then exit. - - :param argv: The command-line arguments used to invoke the - program, as a sequence of strings. - :return: ``None``. - - """ - progname = os.path.basename(argv[0]) - usage_exit_code = 2 - action_usage = "|".join(self.action_funcs.keys()) - message = "usage: {progname} {usage}".format( - progname=progname, usage=action_usage) - emit_message(message) - sys.exit(usage_exit_code) - - def parse_args(self, argv=None): - """ Parse command-line arguments. - - :param argv: The command-line arguments used to invoke the - program, as a sequence of strings. - - :return: ``None``. - - The parser expects the first argument as the program name, the - second argument as the action to perform. - - If the parser fails to parse the arguments, emit a usage - message and exit the program. - - """ - if argv is None: - argv = sys.argv - - min_args = 2 - if len(argv) < min_args: - self._usage_exit(argv) - - self.action = unicode(argv[1]) - if self.action not in self.action_funcs: - self._usage_exit(argv) - - def _start(self): - """ Open the daemon context and run the application. - - :return: ``None``. - :raises DaemonRunnerStartFailureError: If the PID file cannot - be locked by this process. - - """ - if is_pidfile_stale(self.pidfile): - self.pidfile.break_lock() - - try: - self.daemon_context.open() - except lockfile.AlreadyLocked: - error = DaemonRunnerStartFailureError( - "PID file {pidfile.path!r} already locked".format( - pidfile=self.pidfile)) - raise error - - pid = os.getpid() - message = self.start_message.format(pid=pid) - emit_message(message) - - self.app.run() - - def _terminate_daemon_process(self): - """ Terminate the daemon process specified in the current PID file. - - :return: ``None``. - :raises DaemonRunnerStopFailureError: If terminating the daemon - fails with an OS error. - - """ - pid = self.pidfile.read_pid() - try: - os.kill(pid, signal.SIGTERM) - except OSError as exc: - error = DaemonRunnerStopFailureError( - "Failed to terminate {pid:d}: {exc}".format( - pid=pid, exc=exc)) - raise error - - def _stop(self): - """ Exit the daemon process specified in the current PID file. - - :return: ``None``. - :raises DaemonRunnerStopFailureError: If the PID file is not - already locked. - - """ - if not self.pidfile.is_locked(): - error = DaemonRunnerStopFailureError( - "PID file {pidfile.path!r} not locked".format( - pidfile=self.pidfile)) - raise error - - if is_pidfile_stale(self.pidfile): - self.pidfile.break_lock() - else: - self._terminate_daemon_process() - - def _restart(self): - """ Stop, then start. - """ - self._stop() - self._start() - - action_funcs = { - 'start': _start, - 'stop': _stop, - 'restart': _restart, - } - - def _get_action_func(self): - """ Get the function for the specified action. - - :return: The function object corresponding to the specified - action. - :raises DaemonRunnerInvalidActionError: if the action is - unknown. - - The action is specified by the `action` attribute, which is set - during `parse_args`. - - """ - try: - func = self.action_funcs[self.action] - except KeyError: - error = DaemonRunnerInvalidActionError( - "Unknown action: {action!r}".format( - action=self.action)) - raise error - return func - - def do_action(self): - """ Perform the requested action. - - :return: ``None``. - - The action is specified by the `action` attribute, which is set - during `parse_args`. - - """ - func = self._get_action_func() - func(self) - - -def emit_message(message, stream=None): - """ Emit a message to the specified stream (default `sys.stderr`). """ - if stream is None: - stream = sys.stderr - stream.write("{message}\n".format(message=message)) - stream.flush() - - -def make_pidlockfile(path, acquire_timeout): - """ Make a PIDLockFile instance with the given filesystem path. """ - if not isinstance(path, basestring): - error = ValueError("Not a filesystem path: {path!r}".format( - path=path)) - raise error - if not os.path.isabs(path): - error = ValueError("Not an absolute path: {path!r}".format( - path=path)) - raise error - lockfile = pidfile.TimeoutPIDLockFile(path, acquire_timeout) - - return lockfile - - -def is_pidfile_stale(pidfile): - """ Determine whether a PID file is stale. - - :return: ``True`` iff the PID file is stale; otherwise ``False``. - - The PID file is “stale” if its contents are valid but do not - match the PID of a currently-running process. - - """ - result = False - - pidfile_pid = pidfile.read_pid() - if pidfile_pid is not None: - try: - os.kill(pidfile_pid, signal.SIG_DFL) - except ProcessLookupError: - # The specified PID does not exist. - result = True - except OSError as exc: - if exc.errno == errno.ESRCH: - # Under Python 2, process lookup error is an OSError. - # The specified PID does not exist. - result = True - - return result - - -# Local variables: -# coding: utf-8 -# mode: python -# End: -# vim: fileencoding=utf-8 filetype=python : diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index b96e5dab..756185bf 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -68,11 +68,11 @@ pyinst-helpers-osx: cp pkg/osx/bitmask.pf.conf $(DIST_OSX_RES)bitmask-helper/ cp pkg/osx/se.leap.bitmask-helper.plist $(DIST_OSX_RES)bitmask-helper/ cp pkg/osx/post-inst.sh $(DIST_OSX_RES)bitmask-helper/ - cp pkg/osx/daemon.py $(DIST_OSX_RES)bitmask-helper/ + cp pkg/osx/daemon/daemon.py $(DIST_OSX_RES)bitmask-helper/ cp /opt/homebrew-cask/Caskroom/tuntap/20150118/tuntap_20150118.pkg $(DIST_OSX_RES) # TODO make the build script put it there - cp ~/leap_thirdparty_build/openvpn.leap.polarssl $(DIST_OSX_RES)openvpn.leap - cp ~/leap_thirdparty_build/gpg $(DIST_OSX)Contents/MacOS/apps/mail/ + cp $(LEAP_BUILD_DIR)openvpn.leap.polarssl $(DIST_OSX_RES)openvpn.leap + cp $(LEAP_BUILD_DIR)gpg $(DIST_OSX)Contents/MacOS/apps/mail/ pyinst-tar: cd dist/ && tar cvzf Bitmask.$(NEXT_VERSION).tar.gz bitmask-$(NEXT_VERSION) -- cgit v1.2.3 From e709476718d7a0aa4e80663a6a55b2931d221796 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 18 Apr 2016 12:58:13 -0700 Subject: [bug] fix BSD sed syntax --- pkg/pyinst/pyinst-build.mk | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index 756185bf..92a1cbb3 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -1,4 +1,9 @@ freeze-ver: + cp pkg/version-template src/leap/bitmask/_version.py + sed -i 's/^version_version\(.*\)/version_version = "$(NEXT_VERSION)"/' src/leap/bitmask/_version.py + sed -i "s/^full_revisionid\(.*\)/full_revisionid='$(GIT_COMMIT)'/" src/leap/bitmask/_version.py + +freeze-ver-osx: cp pkg/version-template src/leap/bitmask/_version.py sed -i ' ' 's/^version_version\(.*\)/version_version = "$(NEXT_VERSION)"/' src/leap/bitmask/_version.py sed -i ' ' "s/^full_revisionid\(.*\)/full_revisionid='$(GIT_COMMIT)'/" src/leap/bitmask/_version.py @@ -9,6 +14,9 @@ hash-binaries: pyinst: freeze-ver hash-binaries pyinstaller -y pkg/pyinst/bitmask.spec +pyinst_osx: freeze-ver-osx hash-binaries + pyinstaller -y pkg/pyinst/bitmask.spec + reset-ver: git checkout -- src/leap/bitmask/_version.py @@ -85,7 +93,7 @@ pyinst-upload: pyinst-linux: pyinst reset-ver pyinst-hacks-linux pyinst-trim pyinst-cleanup pyinst-distribution-data pyinst-helpers-linux pyinst-tar -pyinst-osx: pyinst reset-ver pyinst-hacks-osx pyinst-helpers-osx +pyinst-osx: pyinst_osx reset-ver pyinst-hacks-osx pyinst-helpers-osx clean_pkg: rm -rf build dist -- cgit v1.2.3 From 9c3a713ff33c87af03b31ec9ad018a6ca3dfbb97 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 18 Apr 2016 16:22:02 -0400 Subject: [style] autopep8 --- src/leap/bitmask/app.py | 1 + src/leap/bitmask/services/eip/darwinvpnlauncher.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 31dba157..e5189e23 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -76,6 +76,7 @@ codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) import psutil + def qt_hack_ubuntu(): """Export two env vars to avoid gui corruption, see #8028""" os.environ['QT_GRAPHICSSYSTEM'] = 'native' diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py index 94161192..e697b118 100644 --- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py +++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py @@ -112,7 +112,8 @@ class DarwinVPNLauncher(VPNLauncher): :returns: True if kext is loaded, False otherwise. :rtype: bool """ - loaded = bool(commands.getoutput('kextstat | grep "net.sf.tuntaposx.tun"')) + loaded = bool(commands.getoutput( + 'kextstat | grep "net.sf.tuntaposx.tun"')) if not loaded: logger.error("tuntaposx extension not loaded!") return loaded -- cgit v1.2.3 From 60aed8f499f6581e6568c6f66090da1aa8ac2fe0 Mon Sep 17 00:00:00 2001 From: Paixu Aabuizia Date: Wed, 3 Feb 2016 21:40:30 +0100 Subject: [bug] vindows initializer does not return state causing the application to always quit - log the init_platform failure to the critical log - return True/False in initializer - remove implementation to install driver and display informative message instead --- src/leap/bitmask/gui/mainwindow.py | 1 + src/leap/bitmask/platform_init/initializers.py | 39 ++++++++------------------ 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 20909038..cde44f7b 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -302,6 +302,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._mail_conductor.connect_mail_signals(self._mail_status) if not init_platform(): + logger.critical('init_platform failed, quitting application.') self.quit() return diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index e8d48e4a..751762df 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -257,36 +257,19 @@ def WindowsInitializer(): if not _windows_has_tap_device(): msg = QtGui.QMessageBox() msg.setWindowTitle(msg.tr("TAP Driver")) - msg.setText(msg.tr("Bitmask needs to install the necessary drivers " - "for Encrypted Internet to work. Would you like to " - "proceed?")) + msg.setText(msg.tr("Bitmask needs a TAP Driver installed " + "for Encrypted Internet to work. Please reinstall " + "bitmask-client to proceed.")) msg.setInformativeText(msg.tr("Encrypted Internet uses VPN, which " "needs a TAP device installed and none " - "has been found. This will ask for " - "administrative privileges.")) - msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) - msg.setDefaultButton(QtGui.QMessageBox.Yes) - ret = msg.exec_() - - if ret == QtGui.QMessageBox.Yes: - # XXX should do this only if executed inside bundle. - # Let's assume it's the only way it's gonna be executed under win - # by now. - driver_path = os.path.join(os.getcwd(), - "apps", - "eip", - "tap_driver") - dev_installer = os.path.join(driver_path, - "devcon.exe") - if os.path.isfile(dev_installer) and \ - stat.S_IXUSR & os.stat(dev_installer)[stat.ST_MODE] != 0: - inf_path = os.path.join(driver_path, - "OemWin2k.inf") - cmd = [dev_installer, "install", inf_path, "tap0901"] - ret = subprocess.call(cmd, stdout=subprocess.PIPE, shell=True) - else: - logger.error("Tried to install TAP driver, but the installer " - "is not found or not executable") + "has been found. The bitmask-client " + "installer prompts for installing the " + "TAP Driver.")) + msg.setStandardButtons(QtGui.QMessageBox.Ok) + logger.error('TAP Drivers not installed') + msg.exec_() + return False + return True # # Darwin initializer functions -- cgit v1.2.3 From cea88f895f3fe2fadd2a59f9301e84cb13020dd4 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 4 Mar 2016 11:44:18 -0400 Subject: [cmd] fix build cmdclass --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 8f38ca7c..44e55709 100755 --- a/setup.py +++ b/setup.py @@ -32,7 +32,6 @@ from distutils.command.build import build as _build from setuptools import Command from setuptools.command.develop import develop as _develop - from pkg import utils import versioneer -- cgit v1.2.3 From 6fd1c73db49c5e1e08cf7963017470511fef0059 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 4 Mar 2016 11:44:41 -0400 Subject: [feature] add bitmask_cli entrypoint --- setup.py | 9 +- src/leap/bitmask/cli/__init__.py | 0 src/leap/bitmask/cli/bitmask_cli.py | 341 ++++++++++++++++++++++++++++++++++++ 3 files changed, 346 insertions(+), 4 deletions(-) create mode 100644 src/leap/bitmask/cli/__init__.py create mode 100755 src/leap/bitmask/cli/bitmask_cli.py diff --git a/setup.py b/setup.py index 44e55709..3e720971 100755 --- a/setup.py +++ b/setup.py @@ -170,9 +170,6 @@ else: reqfiles=["pkg/requirements-leap.pip"]) -leap_launcher = 'bitmask=leap.bitmask.app:start_app' - - def copy_reqs(path, withsrc=False): # add a copy of the processed requirements to the package _reqpath = ('leap', 'bitmask', 'util', 'reqs.txt') @@ -480,6 +477,10 @@ if IS_LINUX: extra_options = {} +gui_launcher = 'bitmask=leap.bitmask.app:start_app' +bitmask_cli = 'bitmask_cli=leap.bitmask.cli.bitmask_cli:main' + + setup( name="leap.bitmask", package_dir={"": "src"}, @@ -515,7 +516,7 @@ setup( zip_safe=False, platforms="all", entry_points={ - 'console_scripts': [leap_launcher] + 'console_scripts': [gui_launcher, bitmask_cli] }, **extra_options ) diff --git a/src/leap/bitmask/cli/__init__.py b/src/leap/bitmask/cli/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/leap/bitmask/cli/bitmask_cli.py b/src/leap/bitmask/cli/bitmask_cli.py new file mode 100755 index 00000000..c5bb1b15 --- /dev/null +++ b/src/leap/bitmask/cli/bitmask_cli.py @@ -0,0 +1,341 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# bitmask_cli +# Copyright (C) 2015, 2016 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 . +""" +Bitmask Command Line interface: zmq client. +""" +import json +import sys +import getpass +import argparse + +from colorama import init as color_init +from colorama import Fore +from twisted.internet import reactor +from txzmq import ZmqEndpoint, ZmqEndpointType +from txzmq import ZmqFactory, ZmqREQConnection +from txzmq import ZmqRequestTimeoutError + +from leap.bitmask.core import ENDPOINT + + +class BitmaskCLI(object): + + def __init__(self): + parser = argparse.ArgumentParser( + usage='''bitmask_cli [] + +Controls the Bitmask application. + +SERVICE COMMANDS: + + user Handles Bitmask accounts + mail Bitmask Encrypted Mail + eip Encrypted Internet Proxy + keys Bitmask Keymanager + +GENERAL COMMANDS: + + version prints version number and exit + launch launch the Bitmask backend daemon + shutdown shutdown Bitmask backend daemon + status displays general status about the running Bitmask services + debug show some debug info about bitmask-core + + +''', epilog=("Use 'bitmask_cli --help' to learn more " + "about each command.")) + parser.add_argument('command', help='Subcommand to run') + + # parse_args defaults to [1:] for args, but you need to + # exclude the rest of the args too, or validation will fail + args = parser.parse_args(sys.argv[1:2]) + self.args = args + self.subargs = None + + if not hasattr(self, args.command): + print 'Unrecognized command' + parser.print_help() + exit(1) + + # use dispatch pattern to invoke method with same name + getattr(self, args.command)() + + def user(self): + parser = argparse.ArgumentParser( + description=('Handles Bitmask accounts: creation, authentication ' + 'and modification'), + prog='bitmask_cli user') + parser.add_argument('username', nargs='?', + help='username ID, in the form ') + parser.add_argument('--create', action='store_true', + help='register a new user, if possible') + parser.add_argument('--authenticate', action='store_true', + help='logs in against the provider') + parser.add_argument('--logout', action='store_true', + help='ends any active session with the provider') + parser.add_argument('--active', action='store_true', + help='shows the active user, if any') + # now that we're inside a subcommand, ignore the first + # TWO argvs, ie the command (bitmask_cli) and the subcommand (user) + args = parser.parse_args(sys.argv[2:]) + self.subargs = args + + def mail(self): + parser = argparse.ArgumentParser( + description='Bitmask Encrypted Mail service', + prog='bitmask_cli mail') + parser.add_argument('--start', action='store_true', + help='tries to start the mail service') + parser.add_argument('--stop', action='store_true', + help='stops the mail service if running') + parser.add_argument('--status', action='store_true', + help='displays status about the mail service') + parser.add_argument('--enable', action='store_true') + parser.add_argument('--disable', action='store_true') + parser.add_argument('--get-imap-token', action='store_true', + help='returns token for the IMAP service') + parser.add_argument('--get-smtp-token', action='store_true', + help='returns token for the SMTP service') + parser.add_argument('--get-smtp-certificate', action='store_true', + help='downloads a new smtp certificate') + parser.add_argument('--check-smtp-certificate', action='store_true', + help='downloads a new smtp certificate ' + '(NOT IMPLEMENTED)') + + args = parser.parse_args(sys.argv[2:]) + self.subargs = args + + def eip(self): + parser = argparse.ArgumentParser( + description='Encrypted Internet Proxy service', + prog='bitmask_cli eip') + parser.add_argument('--start', action='store_true', + help='Start service') + parser.add_argument('--stop', action='store_true', help='Stop service') + parser.add_argument('--status', action='store_true', + help='Display status about service') + parser.add_argument('--enable', action='store_true') + parser.add_argument('--disable', action='store_true') + args = parser.parse_args(sys.argv[2:]) + self.subargs = args + + def keys(self): + parser = argparse.ArgumentParser( + description='Bitmask Keymanager management service', + prog='bitmask_cli keys') + parser.add_argument('--status', action='store_true', + help='Display status about service') + parser.add_argument('--list-keys', action='store_true', + help='List all known keys') + parser.add_argument('--export-key', action='store_true', + help='Export the given key') + args = parser.parse_args(sys.argv[2:]) + self.subargs = args + + # Single commands + + def launch(self): + pass + + def shutdown(self): + pass + + def status(self): + pass + + def version(self): + pass + + def debug(self): + pass + + +def get_zmq_connection(): + zf = ZmqFactory() + e = ZmqEndpoint(ZmqEndpointType.connect, ENDPOINT) + return ZmqREQConnection(zf, e) + + +def error(msg, stop=False): + print Fore.RED + "[!] %s" % msg + Fore.RESET + if stop: + reactor.stop() + else: + sys.exit(1) + + +def timeout_handler(failure, stop_reactor=True): + # TODO ---- could try to launch the bitmask daemon here and retry + + if failure.trap(ZmqRequestTimeoutError) == ZmqRequestTimeoutError: + print (Fore.RED + "[ERROR] Timeout contacting the bitmask daemon. " + "Is it running?" + Fore.RESET) + reactor.stop() + + +def do_print_result(stuff): + obj = json.loads(stuff[0]) + if not obj['error']: + print Fore.GREEN + '%s' % obj['result'] + Fore.RESET + else: + print Fore.RED + 'ERROR:' + '%s' % obj['error'] + Fore.RESET + + +def send_command(cli): + + args = cli.args + subargs = cli.subargs + cb = do_print_result + + cmd = args.command + + if cmd == 'launch': + # XXX careful! Should see if the process in PID is running, + # avoid launching again. + import commands + commands.getoutput('bitmaskd') + reactor.stop() + return + + elif cmd == 'version': + do_print_result([json.dumps( + {'result': 'bitmask_cli: 0.0.1', + 'error': None})]) + data = ('version',) + + elif cmd == 'status': + data = ('status',) + + elif cmd == 'shutdown': + data = ('shutdown',) + + elif cmd == 'debug': + data = ('stats',) + + elif cmd == 'user': + username = subargs.username + if username and '@' not in username: + error("Username ID must be in the form ", + stop=True) + return + if not username: + username = '' + + # TODO check that ONLY ONE FLAG is True + # TODO check that AT LEAST ONE FLAG is True + + passwd = getpass.getpass() + data = ['user'] + + if subargs.active: + data += ['active', '', ''] + + elif subargs.create: + data += ['signup', username, passwd] + + elif subargs.authenticate: + data += ['authenticate', username, passwd] + + elif subargs.logout: + data += ['logout', username, passwd] + + else: + error('Use bitmask_cli user --help to see available subcommands') + return + + elif cmd == 'mail': + data = ['mail'] + + if subargs.status: + data += ['status'] + + elif subargs.enable: + data += ['enable'] + + elif subargs.disable: + data += ['disable'] + + elif subargs.get_imap_token: + data += ['get_imap_token'] + + elif subargs.get_smtp_token: + data += ['get_smtp_token'] + + elif subargs.get_smtp_certificate: + data += ['get_smtp_certificate'] + + else: + error('Use bitmask_cli mail --help to see available subcommands') + return + + elif cmd == 'eip': + data = ['eip'] + + if subargs.status: + data += ['status'] + + elif subargs.enable: + data += ['enable'] + + elif subargs.disable: + data += ['disable'] + + elif subargs.start: + data += ['start'] + + elif subargs.stop: + data += ['stop'] + + else: + error('Use bitmask_cli eip --help to see available subcommands', + stop=True) + return + + elif cmd == 'keys': + data = ['keys'] + + if subargs.status: + data += ['status'] + + elif subargs.list_keys: + data += ['list_keys'] + + elif subargs.export_key: + data += ['export_keys'] + + else: + error('Use bitmask_cli keys --help to see available subcommands', + stop=True) + return + + s = get_zmq_connection() + + d = s.sendMsg(*data, timeout=20) + d.addCallback(cb) + d.addCallback(lambda x: reactor.stop()) + d.addErrback(timeout_handler) + + +def main(): + color_init() + cli = BitmaskCLI() + reactor.callWhenRunning(reactor.callLater, 0, send_command, cli) + reactor.run() + +if __name__ == "__main__": + main() -- cgit v1.2.3 From f4503842fdc288fb6336211099ad0a8d01165df3 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 4 Mar 2016 12:02:26 -0400 Subject: [feature] landing of bitmask.core --- .gitignore | 1 - src/leap/bitmask/core/__init__.py | 2 + src/leap/bitmask/core/_zmq.py | 68 ++++ src/leap/bitmask/core/bitmaskd.tac | 11 + src/leap/bitmask/core/configurable.py | 262 +++++++++++++ src/leap/bitmask/core/dispatcher.py | 196 ++++++++++ src/leap/bitmask/core/launcher.py | 37 ++ src/leap/bitmask/core/mail_services.py | 672 +++++++++++++++++++++++++++++++++ src/leap/bitmask/core/service.py | 174 +++++++++ src/leap/bitmask/core/uuid_map.py | 115 ++++++ src/leap/bitmask/core/web/__init__.py | 0 src/leap/bitmask/core/web/index.html | 70 ++++ src/leap/bitmask/core/web/root.py | 0 src/leap/bitmask/core/websocket.py | 98 +++++ 14 files changed, 1705 insertions(+), 1 deletion(-) create mode 100644 src/leap/bitmask/core/__init__.py create mode 100644 src/leap/bitmask/core/_zmq.py create mode 100644 src/leap/bitmask/core/bitmaskd.tac create mode 100644 src/leap/bitmask/core/configurable.py create mode 100644 src/leap/bitmask/core/dispatcher.py create mode 100644 src/leap/bitmask/core/launcher.py create mode 100644 src/leap/bitmask/core/mail_services.py create mode 100644 src/leap/bitmask/core/service.py create mode 100644 src/leap/bitmask/core/uuid_map.py create mode 100644 src/leap/bitmask/core/web/__init__.py create mode 100644 src/leap/bitmask/core/web/index.html create mode 100644 src/leap/bitmask/core/web/root.py create mode 100644 src/leap/bitmask/core/websocket.py diff --git a/.gitignore b/.gitignore index 35c5d4a9..eb7280a5 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ ui_*.py !.gitattributes bin/ build/ -core dist/ docs/_build docs/covhtml diff --git a/src/leap/bitmask/core/__init__.py b/src/leap/bitmask/core/__init__.py new file mode 100644 index 00000000..bda4b8d0 --- /dev/null +++ b/src/leap/bitmask/core/__init__.py @@ -0,0 +1,2 @@ +APPNAME = "bitmask.core" +ENDPOINT = "ipc:///tmp/%s.sock" % APPNAME diff --git a/src/leap/bitmask/core/_zmq.py b/src/leap/bitmask/core/_zmq.py new file mode 100644 index 00000000..a656fc65 --- /dev/null +++ b/src/leap/bitmask/core/_zmq.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# _zmq.py +# Copyright (C) 2015 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 . +""" +ZMQ REQ-REP Dispatcher. +""" + +from twisted.application import service +from twisted.internet import reactor +from twisted.python import log + +from txzmq import ZmqEndpoint, ZmqEndpointType +from txzmq import ZmqFactory, ZmqREPConnection + +from leap.bitmask.core import ENDPOINT +from leap.bitmask.core.dispatcher import CommandDispatcher + + +class ZMQServerService(service.Service): + + def __init__(self, core): + self._core = core + + def startService(self): + zf = ZmqFactory() + e = ZmqEndpoint(ZmqEndpointType.bind, ENDPOINT) + + self._conn = _DispatcherREPConnection(zf, e, self._core) + reactor.callWhenRunning(self._conn.do_greet) + service.Service.startService(self) + + def stopService(self): + service.Service.stopService(self) + + +class _DispatcherREPConnection(ZmqREPConnection): + + def __init__(self, zf, e, core): + ZmqREPConnection.__init__(self, zf, e) + self.dispatcher = CommandDispatcher(core) + + def gotMessage(self, msgId, *parts): + + r = self.dispatcher.dispatch(parts) + r.addCallback(self.defer_reply, msgId) + + def defer_reply(self, response, msgId): + reactor.callLater(0, self.reply, msgId, str(response)) + + def log_err(self, failure, msgId): + log.err(failure) + self.defer_reply("ERROR: %r" % failure, msgId) + + def do_greet(self): + log.msg('starting ZMQ dispatcher') diff --git a/src/leap/bitmask/core/bitmaskd.tac b/src/leap/bitmask/core/bitmaskd.tac new file mode 100644 index 00000000..3c9b1d8b --- /dev/null +++ b/src/leap/bitmask/core/bitmaskd.tac @@ -0,0 +1,11 @@ +# Service composition for bitmask-core. +# Run as: twistd -n -y bitmaskd.tac +# +from twisted.application import service + +from leap.bitmask.core.service import BitmaskBackend + + +bb = BitmaskBackend() +application = service.Application("bitmaskd") +bb.setServiceParent(application) diff --git a/src/leap/bitmask/core/configurable.py b/src/leap/bitmask/core/configurable.py new file mode 100644 index 00000000..3b97916d --- /dev/null +++ b/src/leap/bitmask/core/configurable.py @@ -0,0 +1,262 @@ +# -*- coding: utf-8 -*- +# configurable.py +# Copyright (C) 2015, 2016 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 . +""" +Configurable Backend for Bitmask Service. +""" +import ConfigParser +import locale +import os +import re +import sys + +from twisted.application import service +from twisted.python import log + +from leap.common import files + + +class MissingConfigEntry(Exception): + """ + A required config entry was not found. + """ + + +class ConfigurableService(service.MultiService): + + config_file = u"bitmaskd.cfg" + service_names = ('mail', 'eip', 'zmq', 'web') + + def __init__(self, basedir='~/.config/leap'): + service.MultiService.__init__(self) + + path = os.path.abspath(os.path.expanduser(basedir)) + if not os.path.isdir(path): + files.mkdir_p(path) + self.basedir = path + + # creates self.config + self.read_config() + + def get_config(self, section, option, default=None, boolean=False): + try: + if boolean: + return self.config.getboolean(section, option) + + item = self.config.get(section, option) + return item + + except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): + if default is None: + fn = os.path.join(self.basedir, self.config_file) + raise MissingConfigEntry("%s is missing the [%s]%s entry" + % (_quote_output(fn), + section, option)) + return default + + def set_config(self, section, option, value): + if not self.config.has_section(section): + self.config.add_section(section) + self.config.set(section, option, value) + self.save_config() + self.read_config() + assert self.config.get(section, option) == value + + def read_config(self): + self.config = ConfigParser.SafeConfigParser() + bitmaskd_cfg = self._get_config_path() + + if not os.path.isfile(bitmaskd_cfg): + self._create_default_config(bitmaskd_cfg) + + try: + with open(bitmaskd_cfg, "rb") as f: + self.config.readfp(f) + except EnvironmentError: + if os.path.exists(bitmaskd_cfg): + raise + + def save_config(self): + bitmaskd_cfg = self._get_config_path() + with open(bitmaskd_cfg, 'wb') as f: + self.config.write(f) + + def _create_default_config(self, path): + with open(path, 'w') as outf: + outf.write(DEFAULT_CONFIG) + + def _get_config_path(self): + return os.path.join(self.basedir, self.config_file) + + +DEFAULT_CONFIG = """ +[services] +mail = True +eip = True +zmq = True +web = False +""" + + +def canonical_encoding(encoding): + if encoding is None: + log.msg("Warning: falling back to UTF-8 encoding.", level=log.WEIRD) + encoding = 'utf-8' + encoding = encoding.lower() + if encoding == "cp65001": + encoding = 'utf-8' + elif (encoding == "us-ascii" or encoding == "646" or encoding == + "ansi_x3.4-1968"): + encoding = 'ascii' + + return encoding + + +def check_encoding(encoding): + # sometimes Python returns an encoding name that it doesn't support for + # conversion fail early if this happens + try: + u"test".encode(encoding) + except (LookupError, AttributeError): + raise AssertionError( + "The character encoding '%s' is not supported for conversion." % ( + encoding,)) + +filesystem_encoding = None +io_encoding = None +is_unicode_platform = False + + +def _reload(): + global filesystem_encoding, io_encoding, is_unicode_platform + + filesystem_encoding = canonical_encoding(sys.getfilesystemencoding()) + check_encoding(filesystem_encoding) + + if sys.platform == 'win32': + # On Windows we install UTF-8 stream wrappers for sys.stdout and + # sys.stderr, and reencode the arguments as UTF-8 (see + # scripts/runner.py). + io_encoding = 'utf-8' + else: + ioenc = None + if hasattr(sys.stdout, 'encoding'): + ioenc = sys.stdout.encoding + if ioenc is None: + try: + ioenc = locale.getpreferredencoding() + except Exception: + pass # work around + io_encoding = canonical_encoding(ioenc) + + check_encoding(io_encoding) + + is_unicode_platform = sys.platform in ["win32", "darwin"] + +_reload() + + +def _quote_output(s, quotemarks=True, quote_newlines=None, encoding=None): + """ + Encode either a Unicode string or a UTF-8-encoded bytestring for + representation on stdout or stderr, tolerating errors. If 'quotemarks' is + True, the string is always quoted; otherwise, it is quoted only if + necessary to avoid ambiguity or control bytes in the output. (Newlines are + counted as control bytes iff quote_newlines is True.) + + Quoting may use either single or double quotes. Within single quotes, all + characters stand for themselves, and ' will not appear. Within double + quotes, Python-compatible backslash escaping is used. + + If not explicitly given, quote_newlines is True when quotemarks is True. + """ + assert isinstance(s, (str, unicode)) + if quote_newlines is None: + quote_newlines = quotemarks + + if isinstance(s, str): + try: + s = s.decode('utf-8') + except UnicodeDecodeError: + return 'b"%s"' % ( + ESCAPABLE_8BIT.sub( + lambda m: _str_escape(m, quote_newlines), s),) + + must_double_quote = (quote_newlines and MUST_DOUBLE_QUOTE_NL or + MUST_DOUBLE_QUOTE) + if must_double_quote.search(s) is None: + try: + out = s.encode(encoding or io_encoding) + if quotemarks or out.startswith('"'): + return "'%s'" % (out,) + else: + return out + except (UnicodeDecodeError, UnicodeEncodeError): + pass + + escaped = ESCAPABLE_UNICODE.sub( + lambda m: _unicode_escape(m, quote_newlines), s) + return '"%s"' % ( + escaped.encode(encoding or io_encoding, 'backslashreplace'),) + + +def _unicode_escape(m, quote_newlines): + u = m.group(0) + if u == u'"' or u == u'$' or u == u'`' or u == u'\\': + return u'\\' + u + elif u == u'\n' and not quote_newlines: + return u + if len(u) == 2: + codepoint = ( + ord(u[0]) - 0xD800) * 0x400 + ord(u[1]) - 0xDC00 + 0x10000 + else: + codepoint = ord(u) + if codepoint > 0xFFFF: + return u'\\U%08x' % (codepoint,) + elif codepoint > 0xFF: + return u'\\u%04x' % (codepoint,) + else: + return u'\\x%02x' % (codepoint,) + + +def _str_escape(m, quote_newlines): + c = m.group(0) + if c == '"' or c == '$' or c == '`' or c == '\\': + return '\\' + c + elif c == '\n' and not quote_newlines: + return c + else: + return '\\x%02x' % (ord(c),) + +MUST_DOUBLE_QUOTE_NL = re.compile( + ur'[^\x20-\x26\x28-\x7E\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFC]', + re.DOTALL) +MUST_DOUBLE_QUOTE = re.compile( + ur'[^\n\x20-\x26\x28-\x7E\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFC]', + re.DOTALL) + +ESCAPABLE_8BIT = re.compile( + r'[^ !#\x25-\x5B\x5D-\x5F\x61-\x7E]', + re.DOTALL) + +# if we must double-quote, then we have to escape ", $ and `, but need not +# escape ' + +ESCAPABLE_UNICODE = re.compile( + ur'([\uD800-\uDBFF][\uDC00-\uDFFF])|' # valid surrogate pairs + ur'[^ !#\x25-\x5B\x5D-\x5F\x61-\x7E\u00A0-\uD7FF' + ur'\uE000-\uFDCF\uFDF0-\uFFFC]', + re.DOTALL) diff --git a/src/leap/bitmask/core/dispatcher.py b/src/leap/bitmask/core/dispatcher.py new file mode 100644 index 00000000..4d7e1813 --- /dev/null +++ b/src/leap/bitmask/core/dispatcher.py @@ -0,0 +1,196 @@ +# -*- coding: utf-8 -*- +# dispatcher.py +# Copyright (C) 2016 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 . +""" +Command dispatcher. +""" +import json + +from twisted.internet import defer +from twisted.python import failure, log + + +# TODO implement sub-classes to dispatch subcommands (user, mail). + + +class CommandDispatcher(object): + + def __init__(self, core): + + self.core = core + + def _get_service(self, name): + + try: + return self.core.getServiceNamed(name) + except KeyError: + return None + + def dispatch(self, msg): + cmd = msg[0] + + _method = getattr(self, 'do_' + cmd.upper(), None) + + if not _method: + return defer.fail(failure.Failure(RuntimeError('No such command'))) + + return defer.maybeDeferred(_method, *msg) + + def do_STATS(self, *parts): + return _format_result(self.core.do_stats()) + + def do_VERSION(self, *parts): + return _format_result(self.core.do_version()) + + def do_STATUS(self, *parts): + return _format_result(self.core.do_status()) + + def do_SHUTDOWN(self, *parts): + return _format_result(self.core.do_shutdown()) + + def do_USER(self, *parts): + + subcmd = parts[1] + user, password = parts[2], parts[3] + + bf = self._get_service('bonafide') + + if subcmd == 'authenticate': + d = bf.do_authenticate(user, password) + + elif subcmd == 'signup': + d = bf.do_signup(user, password) + + elif subcmd == 'logout': + d = bf.do_logout(user, password) + + elif subcmd == 'active': + d = bf.do_get_active_user() + + d.addCallbacks(_format_result, _format_error) + return d + + def do_EIP(self, *parts): + subcmd = parts[1] + eip_label = 'eip' + + if subcmd == 'enable': + return _format_result( + self.core.do_enable_service(eip_label)) + + eip = self._get_service(eip_label) + if not eip: + return _format_result('eip: disabled') + + if subcmd == 'status': + return _format_result(eip.do_status()) + + elif subcmd == 'disable': + return _format_result( + self.core.do_disable_service(eip_label)) + + elif subcmd == 'start': + # TODO --- attempt to get active provider + # TODO or catch the exception and send error + provider = parts[2] + d = eip.do_start(provider) + d.addCallbacks(_format_result, _format_error) + return d + + elif subcmd == 'stop': + d = eip.do_stop() + d.addCallbacks(_format_result, _format_error) + return d + + def do_MAIL(self, *parts): + + subcmd = parts[1] + mail_label = 'mail' + + if subcmd == 'enable': + return _format_result( + self.core.do_enable_service(mail_label)) + + m = self._get_service(mail_label) + bf = self._get_service('bonafide') + + if not m: + return _format_result('mail: disabled') + + if subcmd == 'status': + return _format_result(m.do_status()) + + elif subcmd == 'disable': + return _format_result(self.core.do_disable_service(mail_label)) + + elif subcmd == 'get_imap_token': + d = m.get_imap_token() + d.addCallbacks(_format_result, _format_error) + return d + + elif subcmd == 'get_smtp_token': + d = m.get_smtp_token() + d.addCallbacks(_format_result, _format_error) + return d + + elif subcmd == 'get_smtp_certificate': + # TODO move to mail service + # TODO should ask for confirmation? like --force or something, + # if we already have a valid one. or better just refuse if cert + # exists. + # TODO how should we pass the userid?? + # - Keep an 'active' user in bonafide (last authenticated) + # (doing it now) + # - Get active user from Mail Service (maybe preferred?) + # - Have a command/method to set 'active' user. + + @defer.inlineCallbacks + def save_cert(cert_data): + userid, cert_str = cert_data + cert_path = yield m.do_get_smtp_cert_path(userid) + with open(cert_path, 'w') as outf: + outf.write(cert_str) + defer.returnValue('certificate saved to %s' % cert_path) + + d = bf.do_get_smtp_cert() + d.addCallback(save_cert) + d.addCallbacks(_format_result, _format_error) + return d + + def do_KEYS(self, *parts): + subcmd = parts[1] + + keymanager_label = 'keymanager' + km = self._get_service(keymanager_label) + bf = self._get_service('bonafide') + + if not km: + return _format_result('keymanager: disabled') + + if subcmd == 'list_keys': + d = bf.do_get_active_user() + d.addCallback(km.do_list_keys) + d.addCallbacks(_format_result, _format_error) + return d + + +def _format_result(result): + return json.dumps({'error': None, 'result': result}) + + +def _format_error(failure): + log.err(failure) + return json.dumps({'error': failure.value.message, 'result': None}) diff --git a/src/leap/bitmask/core/launcher.py b/src/leap/bitmask/core/launcher.py new file mode 100644 index 00000000..7d658017 --- /dev/null +++ b/src/leap/bitmask/core/launcher.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# launcher.py +# Copyright (C) 2016 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 . +""" +Run bitmask daemon. +""" +from twisted.scripts.twistd import run +from os.path import join, dirname +from sys import argv + +from leap.bitmask import core + + +def run_bitmaskd(): + # TODO --- configure where to put the logs... (get --logfile, --logdir + # from the bitmask_cli + argv[1:] = [ + '-y', join(dirname(core.__file__), "bitmaskd.tac"), + '--pidfile', '/tmp/bitmaskd.pid', + '--logfile', '/tmp/bitmaskd.log', + '--umask=0022', + ] + print '[+] launching bitmaskd...' + run() diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py new file mode 100644 index 00000000..9858d498 --- /dev/null +++ b/src/leap/bitmask/core/mail_services.py @@ -0,0 +1,672 @@ +""" +Mail services. + +This is quite moving work still. +This should be moved to the different packages when it stabilizes. +""" +import json +import os +from glob import glob +from collections import defaultdict +from collections import namedtuple + +from twisted.application import service +from twisted.internet import defer +from twisted.python import log + +from leap.bonafide import config +from leap.common.service_hooks import HookableService +from leap.keymanager import KeyManager, openpgp +from leap.keymanager.errors import KeyNotFound +from leap.soledad.client.api import Soledad +from leap.mail.constants import INBOX_NAME +from leap.mail.mail import Account +from leap.mail.imap.service import imap +from leap.mail.incoming.service import IncomingMail, INCOMING_CHECK_PERIOD +from leap.mail import smtp + +from leap.bitmask.core.uuid_map import UserMap + + +class Container(object): + + def __init__(self): + self._instances = defaultdict(None) + + def get_instance(self, key): + return self._instances.get(key, None) + + +class ImproperlyConfigured(Exception): + pass + + +def get_all_soledad_uuids(): + return [os.path.split(p)[-1].split('.db')[0] for p in + glob(os.path.expanduser('~/.config/leap/soledad/*.db'))] + # FIXME do not hardcode basedir + + +class SoledadContainer(Container): + + def __init__(self, basedir='~/.config/leap'): + # FIXME do not hardcode basedir + self._basedir = os.path.expanduser(basedir) + self._usermap = UserMap() + super(SoledadContainer, self).__init__() + + def add_instance(self, userid, passphrase, uuid=None, token=None): + + if not uuid: + bootstrapped_uuid = self._usermap.lookup_uuid(userid, passphrase) + uuid = bootstrapped_uuid + if not uuid: + return + else: + self._usermap.add(userid, uuid, passphrase) + + user, provider = userid.split('@') + + soledad_path = os.path.join(self._basedir, 'soledad') + soledad_url = _get_soledad_uri(self._basedir, provider) + cert_path = _get_ca_cert_path(self._basedir, provider) + + soledad = self._create_soledad_instance( + uuid, passphrase, soledad_path, soledad_url, + cert_path, token) + + self._instances[userid] = soledad + + data = {'user': userid, 'uuid': uuid, 'token': token, + 'soledad': soledad} + self.service.trigger_hook('on_new_soledad_instance', **data) + + def _create_soledad_instance(self, uuid, passphrase, basedir, server_url, + cert_file, token): + # setup soledad info + secrets_path = os.path.join( + basedir, '%s.secret' % uuid) + local_db_path = os.path.join( + basedir, '%s.db' % uuid) + + if token is None: + syncable = False + token = '' + else: + syncable = True + + return Soledad( + uuid, + unicode(passphrase), + secrets_path=secrets_path, + local_db_path=local_db_path, + server_url=server_url, + cert_file=cert_file, + auth_token=token, + defer_encryption=True, + syncable=syncable) + + def set_remote_auth_token(self, userid, token): + self.get_instance(userid).token = token + + def set_syncable(self, userid, state): + # TODO should check that there's a token! + self.get_instance(userid).set_syncable(bool(state)) + + def sync(self, userid): + self.get_instance(userid).sync() + + +def _get_provider_from_full_userid(userid): + _, provider_id = config.get_username_and_provider(userid) + return config.Provider(provider_id) + + +def is_service_ready(service, provider): + """ + Returns True when the following conditions are met: + - Provider offers that service. + - We have the config files for the service. + - The service is enabled. + """ + has_service = provider.offers_service(service) + has_config = provider.has_config_for_service(service) + is_enabled = provider.is_service_enabled(service) + return has_service and has_config and is_enabled + + +class SoledadService(service.Service, HookableService): + + subscribed_to_hooks = ('on_bonafide_auth', 'on_passphrase_entry') + + def __init__(self, basedir): + service.Service.__init__(self) + self._basedir = basedir + + def startService(self): + log.msg('Starting Soledad Service') + self._container = SoledadContainer() + self._container.service = self + super(SoledadService, self).startService() + + # hooks + + def hook_on_passphrase_entry(self, **kw): + userid = kw.get('username') + provider = _get_provider_from_full_userid(userid) + provider.callWhenReady(self._hook_on_passphrase_entry, provider, **kw) + + def _hook_on_passphrase_entry(self, provider, **kw): + if is_service_ready('mx', provider): + userid = kw.get('username') + password = kw.get('password') + uuid = kw.get('uuid') + container = self._container + log.msg("on_passphrase_entry: New Soledad Instance: %s" % userid) + if not container.get_instance(userid): + container.add_instance(userid, password, uuid=uuid, token=None) + else: + log.msg('Service MX is not ready...') + + def hook_on_bonafide_auth(self, **kw): + userid = kw['username'] + provider = _get_provider_from_full_userid(userid) + provider.callWhenReady(self._hook_on_bonafide_auth, provider, **kw) + + def _hook_on_bonafide_auth(self, provider, **kw): + if provider.offers_service('mx'): + userid = kw['username'] + password = kw['password'] + token = kw['token'] + uuid = kw['uuid'] + + container = self._container + if container.get_instance(userid): + log.msg("Passing a new SRP Token to Soledad: %s" % userid) + container.set_remote_auth_token(userid, token) + container.set_syncable(userid, True) + else: + log.msg("Adding a new Soledad Instance: %s" % userid) + container.add_instance( + userid, password, uuid=uuid, token=token) + + +class KeymanagerContainer(Container): + + def __init__(self, basedir): + self._basedir = os.path.expanduser(basedir) + super(KeymanagerContainer, self).__init__() + + def add_instance(self, userid, token, uuid, soledad): + + keymanager = self._create_keymanager_instance( + userid, token, uuid, soledad) + + d = self._get_or_generate_keys(keymanager, userid) + d.addCallback(self._on_keymanager_ready_cb, userid, soledad) + return d + + def set_remote_auth_token(self, userid, token): + self.get_instance(userid)._token = token + + def _on_keymanager_ready_cb(self, keymanager, userid, soledad): + # TODO use onready-deferreds instead + self._instances[userid] = keymanager + + log.msg("Adding Keymanager instance for: %s" % userid) + data = {'userid': userid, 'soledad': soledad, 'keymanager': keymanager} + self.service.trigger_hook('on_new_keymanager_instance', **data) + + def _get_or_generate_keys(self, keymanager, userid): + + def if_not_found_generate(failure): + # TODO -------------- should ONLY generate if INITIAL_SYNC_DONE. + # ie: put callback on_soledad_first_sync_ready ----------------- + # -------------------------------------------------------------- + failure.trap(KeyNotFound) + log.msg("Core: Key not found. Generating key for %s" % (userid,)) + d = keymanager.gen_key(openpgp.OpenPGPKey) + d.addCallbacks(send_key, log_key_error("generating")) + return d + + def send_key(ignored): + # ---------------------------------------------------------------- + # It might be the case that we have generated a key-pair + # but this hasn't been successfully uploaded. How do we know that? + # XXX Should this be a method of bonafide instead? + # ----------------------------------------------------------------- + d = keymanager.send_key(openpgp.OpenPGPKey) + d.addCallbacks( + lambda _: log.msg( + "Key generated successfully for %s" % userid), + log_key_error("sending")) + return d + + def log_key_error(step): + def log_error(failure): + log.err("Error while %s key!" % step) + log.err(failure) + return failure + return log_error + + d = keymanager.get_key( + userid, openpgp.OpenPGPKey, private=True, fetch_remote=False) + d.addErrback(if_not_found_generate) + d.addCallback(lambda _: keymanager) + return d + + def _create_keymanager_instance(self, userid, token, uuid, soledad): + user, provider = userid.split('@') + nickserver_uri = self._get_nicknym_uri(provider) + + cert_path = _get_ca_cert_path(self._basedir, provider) + api_uri = self._get_api_uri(provider) + + if not token: + token = self.service.tokens.get(userid) + + km_args = (userid, nickserver_uri, soledad) + km_kwargs = { + "token": token, "uid": uuid, + "api_uri": api_uri, "api_version": "1", + "ca_cert_path": cert_path, + "gpgbinary": "/usr/bin/gpg" + } + keymanager = KeyManager(*km_args, **km_kwargs) + return keymanager + + def _get_api_uri(self, provider): + # TODO get this from service.json (use bonafide service) + api_uri = "https://api.{provider}:4430".format( + provider=provider) + return api_uri + + def _get_nicknym_uri(self, provider): + return 'https://nicknym.{provider}:6425'.format( + provider=provider) + + +class KeymanagerService(service.Service, HookableService): + + subscribed_to_hooks = ('on_new_soledad_instance', 'on_bonafide_auth') + + def __init__(self, basedir='~/.config/leap'): + service.Service.__init__(self) + self._basedir = basedir + + def startService(self): + log.msg('Starting Keymanager Service') + self._container = KeymanagerContainer(self._basedir) + self._container.service = self + self.tokens = {} + super(KeymanagerService, self).startService() + + # hooks + + def hook_on_new_soledad_instance(self, **kw): + container = self._container + user = kw['user'] + token = kw['token'] + uuid = kw['uuid'] + soledad = kw['soledad'] + if not container.get_instance(user): + log.msg('Adding a new Keymanager instance for %s' % user) + if not token: + token = self.tokens.get(user) + container.add_instance(user, token, uuid, soledad) + + def hook_on_bonafide_auth(self, **kw): + userid = kw['username'] + provider = _get_provider_from_full_userid(userid) + provider.callWhenReady(self._hook_on_bonafide_auth, provider, **kw) + + def _hook_on_bonafide_auth(self, provider, **kw): + if provider.offers_service('mx'): + userid = kw['username'] + token = kw['token'] + + container = self._container + if container.get_instance(userid): + log.msg('Passing a new SRP Token to Keymanager: %s' % userid) + container.set_remote_auth_token(userid, token) + else: + log.msg('storing the keymanager token...') + self.tokens[userid] = token + + # commands + + def do_list_keys(self, userid): + km = self._container.get_instance(userid) + d = km.get_all_keys() + d.addCallback( + lambda keys: [ + (key.uids, key.fingerprint) for key in keys]) + return d + + +class StandardMailService(service.MultiService, HookableService): + """ + A collection of Services. + + This is the parent service, that launches 3 different services that expose + Encrypted Mail Capabilities on specific ports: + + - SMTP service, on port 2013 + - IMAP service, on port 1984 + - The IncomingMail Service, which doesn't listen on any port, but + watches and processes the Incoming Queue and saves the processed mail + into the matching INBOX. + """ + + name = 'mail' + + # TODO factor out Mail Service to inside mail package. + + subscribed_to_hooks = ('on_new_keymanager_instance',) + + def __init__(self, basedir): + self._basedir = basedir + self._soledad_sessions = {} + self._keymanager_sessions = {} + self._sendmail_opts = {} + self._imap_tokens = {} + self._smtp_tokens = {} + self._active_user = None + super(StandardMailService, self).__init__() + self.initializeChildrenServices() + + def initializeChildrenServices(self): + self.addService(IMAPService(self._soledad_sessions)) + self.addService(IncomingMailService(self)) + self.addService(SMTPService( + self._soledad_sessions, self._keymanager_sessions, + self._sendmail_opts)) + + def startService(self): + log.msg('Starting Mail Service...') + super(StandardMailService, self).startService() + + def stopService(self): + super(StandardMailService, self).stopService() + + def startInstance(self, userid, soledad, keymanager): + username, provider = userid.split('@') + + self._soledad_sessions[userid] = soledad + self._keymanager_sessions[userid] = keymanager + + sendmail_opts = _get_sendmail_opts(self._basedir, provider, username) + self._sendmail_opts[userid] = sendmail_opts + + incoming = self.getServiceNamed('incoming_mail') + incoming.startInstance(userid) + + def registerIMAPToken(token): + self._imap_tokens[userid] = token + self._active_user = userid + return token + + def registerSMTPToken(token): + self._smtp_tokens[userid] = token + return token + + d = soledad.get_or_create_service_token('imap') + d.addCallback(registerIMAPToken) + d.addCallback( + lambda _: soledad.get_or_create_service_token('smtp')) + d.addCallback(registerSMTPToken) + return d + + def stopInstance(self): + pass + + # hooks + + def hook_on_new_keymanager_instance(self, **kw): + # XXX we can specify this as a waterfall, or just AND the two + # conditions. + userid = kw['userid'] + soledad = kw['soledad'] + keymanager = kw['keymanager'] + + # TODO --- only start instance if "autostart" is True. + self.startInstance(userid, soledad, keymanager) + + # commands + + def do_status(self): + return 'mail: %s' % 'running' if self.running else 'disabled' + + def get_imap_token(self): + active_user = self._active_user + if not active_user: + return defer.succeed('NO ACTIVE USER') + token = self._imap_tokens.get(active_user) + return defer.succeed("IMAP TOKEN (%s): %s" % (active_user, token)) + + def get_smtp_token(self): + active_user = self._active_user + if not active_user: + return defer.succeed('NO ACTIVE USER') + token = self._smtp_tokens.get(active_user) + return defer.succeed("SMTP TOKEN (%s): %s" % (active_user, token)) + + def do_get_smtp_cert_path(self, userid): + username, provider = userid.split('@') + return _get_smtp_client_cert_path(self._basedir, provider, username) + + # access to containers + + def get_soledad_session(self, userid): + return self._soledad_sessions.get(userid) + + def get_keymanager_session(self, userid): + return self._keymanager_sessions.get(userid) + + +class IMAPService(service.Service): + + name = 'imap' + + def __init__(self, soledad_sessions): + port, factory = imap.run_service(soledad_sessions) + + self._port = port + self._factory = factory + self._soledad_sessions = soledad_sessions + super(IMAPService, self).__init__() + + def startService(self): + log.msg('Starting IMAP Service') + super(IMAPService, self).startService() + + def stopService(self): + self._port.stopListening() + self._factory.doStop() + super(IMAPService, self).stopService() + + +class SMTPService(service.Service): + + name = 'smtp' + + def __init__(self, soledad_sessions, keymanager_sessions, sendmail_opts, + basedir='~/.config/leap'): + + self._basedir = os.path.expanduser(basedir) + port, factory = smtp.run_service( + soledad_sessions, keymanager_sessions, sendmail_opts) + self._port = port + self._factory = factory + self._soledad_sessions = soledad_sessions + self._keymanager_sessions = keymanager_sessions + self._sendmail_opts = sendmail_opts + super(SMTPService, self).__init__() + + def startService(self): + log.msg('Starting SMTP Service') + super(SMTPService, self).startService() + + def stopService(self): + # TODO cleanup all instances + super(SMTPService, self).stopService() + + +class IncomingMailService(service.Service): + + name = 'incoming_mail' + + def __init__(self, mail_service): + super(IncomingMailService, self).__init__() + self._mail = mail_service + self._instances = {} + + def startService(self): + log.msg('Starting IncomingMail Service') + super(IncomingMailService, self).startService() + + def stopService(self): + super(IncomingMailService, self).stopService() + + # Individual accounts + + # TODO IncomingMail *IS* already a service. + # I think we should better model the current Service + # as a startInstance inside a container, and get this + # multi-tenant service inside the leap.mail.incoming.service. + # ... or just simply make it a multiService and set per-user + # instances as Child of this parent. + + def startInstance(self, userid): + soledad = self._mail.get_soledad_session(userid) + keymanager = self._mail.get_keymanager_session(userid) + + log.msg('Starting Incoming Mail instance for %s' % userid) + self._start_incoming_mail_instance( + keymanager, soledad, userid) + + def stopInstance(self, userid): + # TODO toggle offline! + pass + + def _start_incoming_mail_instance(self, keymanager, soledad, + userid, start_sync=True): + + def setUpIncomingMail(inbox): + incoming_mail = IncomingMail( + keymanager, soledad, + inbox, userid, + check_period=INCOMING_CHECK_PERIOD) + return incoming_mail + + def registerInstance(incoming_instance): + self._instances[userid] = incoming_instance + if start_sync: + incoming_instance.startService() + + acc = Account(soledad) + d = acc.callWhenReady( + lambda _: acc.get_collection_by_mailbox(INBOX_NAME)) + d.addCallback(setUpIncomingMail) + d.addCallback(registerInstance) + d.addErrback(log.err) + return d + +# -------------------------------------------------------------------- +# +# config utilities. should be moved to bonafide +# + +SERVICES = ('soledad', 'smtp', 'eip') + + +Provider = namedtuple( + 'Provider', ['hostname', 'ip_address', 'location', 'port']) + +SendmailOpts = namedtuple( + 'SendmailOpts', ['cert', 'key', 'hostname', 'port']) + + +def _get_ca_cert_path(basedir, provider): + path = os.path.join( + basedir, 'providers', provider, 'keys', 'ca', 'cacert.pem') + return path + + +def _get_sendmail_opts(basedir, provider, username): + cert = _get_smtp_client_cert_path(basedir, provider, username) + key = cert + prov = _get_provider_for_service('smtp', basedir, provider) + hostname = prov.hostname + port = prov.port + opts = SendmailOpts(cert, key, hostname, port) + return opts + + +def _get_smtp_client_cert_path(basedir, provider, username): + path = os.path.join( + basedir, 'providers', provider, 'keys', 'client', 'stmp_%s.pem' % + username) + return path + + +def _get_config_for_service(service, basedir, provider): + if service not in SERVICES: + raise ImproperlyConfigured('Tried to use an unknown service') + + config_path = os.path.join( + basedir, 'providers', provider, '%s-service.json' % service) + try: + with open(config_path) as config: + config = json.loads(config.read()) + except IOError: + # FIXME might be that the provider DOES NOT offer this service! + raise ImproperlyConfigured( + 'could not open config file %s' % config_path) + else: + return config + + +def first(xs): + return xs[0] + + +def _pick_server(config, strategy=first): + """ + Picks a server from a list of possible choices. + The service files have a . + This implementation just picks the FIRST available server. + """ + servers = config['hosts'].keys() + choice = config['hosts'][strategy(servers)] + return choice + + +def _get_subdict(d, keys): + return {key: d.get(key) for key in keys} + + +def _get_provider_for_service(service, basedir, provider): + + if service not in SERVICES: + raise ImproperlyConfigured('Tried to use an unknown service') + + config = _get_config_for_service(service, basedir, provider) + p = _pick_server(config) + attrs = _get_subdict(p, ('hostname', 'ip_address', 'location', 'port')) + provider = Provider(**attrs) + return provider + + +def _get_smtp_uri(basedir, provider): + prov = _get_provider_for_service('smtp', basedir, provider) + url = 'https://{hostname}:{port}'.format( + hostname=prov.hostname, port=prov.port) + return url + + +def _get_soledad_uri(basedir, provider): + prov = _get_provider_for_service('soledad', basedir, provider) + url = 'https://{hostname}:{port}'.format( + hostname=prov.hostname, port=prov.port) + return url diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py new file mode 100644 index 00000000..4c18ab5d --- /dev/null +++ b/src/leap/bitmask/core/service.py @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- +# service.py +# Copyright (C) 2015 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 . +""" +Bitmask-core Service. +""" +import resource + +from twisted.internet import reactor +from twisted.python import log + +from leap.bonafide.service import BonafideService + +from leap.bitmask.core import configurable +from leap.bitmask.core import mail_services +from leap.bitmask.core import _zmq +from leap.bitmask.core import websocket +from leap.bitmask.core._version import get_versions + +from leap.common.events import server as event_server +from leap.vpn import EIPService + + +class BitmaskBackend(configurable.ConfigurableService): + + def __init__(self, basedir='~/.config/leap'): + + configurable.ConfigurableService.__init__(self, basedir) + + def enabled(service): + return self.get_config('services', service, False, boolean=True) + + on_start = reactor.callWhenRunning + + on_start(self.init_events) + on_start(self.init_bonafide) + + if enabled('mail'): + on_start(self.init_soledad) + on_start(self.init_keymanager) + on_start(self.init_mail) + + if enabled('eip'): + on_start(self.init_eip) + + if enabled('zmq'): + on_start(self.init_zmq) + + if enabled('web'): + on_start(self.init_web) + + def init_events(self): + event_server.ensure_server() + + def init_bonafide(self): + bf = BonafideService(self.basedir) + bf.setName("bonafide") + bf.setServiceParent(self) + # TODO ---- these hooks should be activated only if + # (1) we have enabled that service + # (2) provider offers this service + bf.register_hook('on_passphrase_entry', listener='soledad') + bf.register_hook('on_bonafide_auth', listener='soledad') + bf.register_hook('on_bonafide_auth', listener='keymanager') + + def init_soledad(self): + service = mail_services.SoledadService + sol = self._maybe_start_service( + 'soledad', service, self.basedir) + if sol: + sol.register_hook( + 'on_new_soledad_instance', listener='keymanager') + + def init_keymanager(self): + service = mail_services.KeymanagerService + km = self._maybe_start_service( + 'keymanager', service, self.basedir) + if km: + km.register_hook('on_new_keymanager_instance', listener='mail') + + def init_mail(self): + service = mail_services.StandardMailService + self._maybe_start_service('mail', service, self.basedir) + + def init_eip(self): + self._maybe_start_service('eip', EIPService) + + def init_zmq(self): + zs = _zmq.ZMQServerService(self) + zs.setServiceParent(self) + + def init_web(self): + ws = websocket.WebSocketsDispatcherService(self) + ws.setServiceParent(self) + + def _maybe_start_service(self, label, klass, *args, **kw): + try: + self.getServiceNamed(label) + except KeyError: + service = klass(*args, **kw) + service.setName(label) + service.setServiceParent(self) + return service + + # General commands for the BitmaskBackend Core Service + + def do_stats(self): + log.msg('BitmaskCore Service STATS') + mem = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss + return 'BitmaskCore: [Mem usage: %s KB]' % (mem / 1024) + + def do_status(self): + # we may want to make this tuple a class member + services = ('soledad', 'keymanager', 'mail', 'eip') + + status_messages = [] + for name in services: + status = 'stopped' + try: + if self.getServiceNamed(name).running: + status = "running" + except KeyError: + pass + status_messages.append("[{}: {}]".format(name, status)) + + return " ".join(status_messages) + + def do_version(self): + version = get_versions()['version'] + return 'BitmaskCore: %s' % version + + def do_shutdown(self): + self.stopService() + reactor.callLater(1, reactor.stop) + return 'shutting down...' + + def do_enable_service(self, service): + assert service in self.service_names + self.set_config('services', service, 'True') + + if service == 'mail': + self.init_soledad() + self.init_keymanager() + self.init_mail() + + elif service == 'eip': + self.init_eip() + + elif service == 'zmq': + self.init_zmq() + + elif service == 'web': + self.init_web() + + return 'ok' + + def do_disable_service(self, service): + assert service in self.service_names + # TODO -- should stop also? + self.set_config('services', service, 'False') + return 'ok' diff --git a/src/leap/bitmask/core/uuid_map.py b/src/leap/bitmask/core/uuid_map.py new file mode 100644 index 00000000..5edc7216 --- /dev/null +++ b/src/leap/bitmask/core/uuid_map.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +# uuid_map.py +# Copyright (C) 2015,2016 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 . +""" +UUID Map: a persistent mapping between user-ids and uuids. +""" + +import base64 +import os +import re + +import scrypt + +from leap.common.config import get_path_prefix + + +MAP_PATH = os.path.join(get_path_prefix(), 'leap', 'uuids') + + +class UserMap(object): + + """ + A persistent mapping between user-ids and uuids. + """ + + # TODO Add padding to the encrypted string + + def __init__(self): + self._d = {} + self._lines = set([]) + if os.path.isfile(MAP_PATH): + self.load() + + def add(self, userid, uuid, passwd): + """ + Add a new userid-uuid mapping, and encrypt the record with the user + password. + """ + self._add_to_cache(userid, uuid) + self._lines.add(_encode_uuid_map(userid, uuid, passwd)) + self.dump() + + def _add_to_cache(self, userid, uuid): + self._d[userid] = uuid + + def load(self): + """ + Load a mapping from a default file. + """ + with open(MAP_PATH, 'r') as infile: + lines = infile.readlines() + self._lines = set(lines) + + def dump(self): + """ + Dump the mapping to a default file. + """ + with open(MAP_PATH, 'w') as out: + out.write('\n'.join(self._lines)) + + def lookup_uuid(self, userid, passwd=None): + """ + Lookup the uuid for a given userid. + + If no password is given, try to lookup on cache. + Else, try to decrypt all the records that we know about with the + passed password. + """ + if not passwd: + return self._d.get(userid) + + for line in self._lines: + guess = _decode_uuid_line(line, passwd) + if guess: + record_userid, uuid = guess + if record_userid == userid: + self._add_to_cache(userid, uuid) + return uuid + + def lookup_userid(self, uuid): + """ + Get the userid for the given uuid from cache. + """ + rev_d = {v: k for (k, v) in self._d.items()} + return rev_d.get(uuid) + + +def _encode_uuid_map(userid, uuid, passwd): + data = 'userid:%s:uuid:%s' % (userid, uuid) + encrypted = scrypt.encrypt(data, passwd, maxtime=0.05) + return base64.encodestring(encrypted).replace('\n', '') + + +def _decode_uuid_line(line, passwd): + decoded = base64.decodestring(line) + try: + maybe_decrypted = scrypt.decrypt(decoded, passwd, maxtime=0.1) + except scrypt.error: + return None + match = re.findall("userid\:(.+)\:uuid\:(.+)", maybe_decrypted) + if match: + return match[0] diff --git a/src/leap/bitmask/core/web/__init__.py b/src/leap/bitmask/core/web/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/leap/bitmask/core/web/index.html b/src/leap/bitmask/core/web/index.html new file mode 100644 index 00000000..9490eca8 --- /dev/null +++ b/src/leap/bitmask/core/web/index.html @@ -0,0 +1,70 @@ + + + + Bitmask WebSockets Endpoint + + + +

    Bitmask Control Panel

    + +
    +

    Command:

    +
    + +
    
    +   
    +
    diff --git a/src/leap/bitmask/core/web/root.py b/src/leap/bitmask/core/web/root.py
    new file mode 100644
    index 00000000..e69de29b
    diff --git a/src/leap/bitmask/core/websocket.py b/src/leap/bitmask/core/websocket.py
    new file mode 100644
    index 00000000..5569c6c7
    --- /dev/null
    +++ b/src/leap/bitmask/core/websocket.py
    @@ -0,0 +1,98 @@
    +# -*- coding: utf-8 -*-
    +# websocket.py
    +# Copyright (C) 2015, 2016 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 .
    +
    +"""
    +WebSockets Dispatcher Service.
    +"""
    +
    +import os
    +import pkg_resources
    +
    +from twisted.internet import reactor
    +from twisted.application import service
    +
    +from twisted.web.server import Site
    +from twisted.web.static import File
    +
    +from autobahn.twisted.resource import WebSocketResource
    +from autobahn.twisted.websocket import WebSocketServerFactory
    +from autobahn.twisted.websocket import WebSocketServerProtocol
    +
    +from leap.bitmask.core.dispatcher import CommandDispatcher
    +
    +
    +class WebSocketsDispatcherService(service.Service):
    +
    +    """
    +    A Dispatcher for BitmaskCore exposing a WebSockets Endpoint.
    +    """
    +
    +    def __init__(self, core, port=8080, debug=False):
    +        self._core = core
    +        self.port = port
    +        self.debug = debug
    +
    +    def startService(self):
    +
    +        factory = WebSocketServerFactory(u"ws://127.0.0.1:%d" % self.port,
    +                                         debug=self.debug)
    +        factory.protocol = DispatcherProtocol
    +        factory.protocol.dispatcher = CommandDispatcher(self._core)
    +
    +        # FIXME: Site.start/stopFactory should start/stop factories wrapped as
    +        # Resources
    +        factory.startFactory()
    +
    +        resource = WebSocketResource(factory)
    +
    +        # we server static files under "/" ..
    +        webdir = os.path.abspath(
    +            pkg_resources.resource_filename("leap.bitmask.core", "web"))
    +        root = File(webdir)
    +
    +        # and our WebSocket server under "/ws"
    +        root.putChild(u"bitmask", resource)
    +
    +        # both under one Twisted Web Site
    +        site = Site(root)
    +
    +        self.site = site
    +        self.factory = factory
    +
    +        self.listener = reactor.listenTCP(self.port, site)
    +
    +    def stopService(self):
    +        self.factory.stopFactory()
    +        self.site.stopFactory()
    +        self.listener.stopListening()
    +
    +
    +class DispatcherProtocol(WebSocketServerProtocol):
    +
    +    def onMessage(self, msg, binary):
    +        parts = msg.split()
    +        r = self.dispatcher.dispatch(parts)
    +        r.addCallback(self.defer_reply, binary)
    +
    +    def reply(self, response, binary):
    +        self.sendMessage(response, binary)
    +
    +    def defer_reply(self, response, binary):
    +        reactor.callLater(0, self.reply, response, binary)
    +
    +    def _get_service(self, name):
    +        return self.core.getServiceNamed(name)
    -- 
    cgit v1.2.3
    
    
    From 59009db169e9e75ec6271601d38cd0fba2bd8806 Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Tue, 8 Mar 2016 00:58:45 -0400
    Subject: [feat] multi-binary specs for cli
    
    ---
     pkg/pyinst/bitmask_cli      |  1 +
     pkg/pyinst/bitmask_cli.spec | 32 +++++++++++++++++++++++++++
     pkg/pyinst/multi.spec       | 54 +++++++++++++++++++++++++++++++++++++++++++++
     3 files changed, 87 insertions(+)
     create mode 120000 pkg/pyinst/bitmask_cli
     create mode 100644 pkg/pyinst/bitmask_cli.spec
     create mode 100644 pkg/pyinst/multi.spec
    
    diff --git a/pkg/pyinst/bitmask_cli b/pkg/pyinst/bitmask_cli
    new file mode 120000
    index 00000000..7842f03b
    --- /dev/null
    +++ b/pkg/pyinst/bitmask_cli
    @@ -0,0 +1 @@
    +../../src/leap/bitmask/cli/bitmask_cli.py
    \ No newline at end of file
    diff --git a/pkg/pyinst/bitmask_cli.spec b/pkg/pyinst/bitmask_cli.spec
    new file mode 100644
    index 00000000..5157e179
    --- /dev/null
    +++ b/pkg/pyinst/bitmask_cli.spec
    @@ -0,0 +1,32 @@
    +# -*- mode: python -*-
    +
    +block_cipher = None
    +
    +
    +a = Analysis(['bitmask_cli'],
    +             pathex=['/home/kali/leap/bitmask_client/pkg/pyinst'],
    +             binaries=None,
    +             datas=None,
    +             hiddenimports=[],
    +             hookspath=[],
    +             runtime_hooks=[],
    +             excludes=[],
    +             win_no_prefer_redirects=False,
    +             win_private_assemblies=False,
    +             cipher=block_cipher)
    +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
    +exe = EXE(pyz,
    +          a.scripts,
    +          exclude_binaries=True,
    +          name='bitmask_cli',
    +          debug=False,
    +          strip=False,
    +          upx=True,
    +          console=True )
    +coll = COLLECT(exe,
    +               a.binaries,
    +               a.zipfiles,
    +               a.datas,
    +               strip=False,
    +               upx=True,
    +               name='bitmask_cli')
    diff --git a/pkg/pyinst/multi.spec b/pkg/pyinst/multi.spec
    new file mode 100644
    index 00000000..2bb9d248
    --- /dev/null
    +++ b/pkg/pyinst/multi.spec
    @@ -0,0 +1,54 @@
    +
    +# -*- mode: python -*-
    +
    +block_cipher = None
    +
    +
    +gui_a = Analysis(['bitmask.py'],
    +             hiddenimports=[
    +	     	'zope.interface', 'zope.proxy',
    +		'PySide.QtCore', 'PySide.QtGui'],
    +             hookspath=None,
    +             runtime_hooks=None,
    +             excludes=None,
    +             cipher=block_cipher)
    +cli_a = Analysis(['bitmask_cli'],
    +             pathex=['/home/kali/leap/bitmask_client/pkg/pyinst'],
    +             binaries=None,
    +             datas=None,
    +             hiddenimports=[
    +	     	'zope.interface', 'zope.proxy'],
    +             hookspath=[],
    +             runtime_hooks=[],
    +             excludes=[],
    +             win_no_prefer_redirects=False,
    +             win_private_assemblies=False,
    +             cipher=block_cipher)
    +
    +MERGE( (gui_a, 'bitmask', 'bitmask'),
    +       (cli_a, 'bitmask_cli', 'bitmask'))
    +
    +gui_pyz = PYZ(gui_a.pure, gui_a.zipped_data, cipher=block_cipher)
    +gui_exe = EXE(gui_pyz,
    +          gui_a.scripts,
    +          exclude_binaries=True,
    +          name='bitmask', debug=False, strip=False,
    +          upx=True, console=False )
    +
    +cli_pyz = PYZ(cli_a.pure, cli_a.zipped_data, cipher=block_cipher)
    +cli_exe = EXE(cli_pyz,
    +          cli_a.scripts,
    +          exclude_binaries=True,
    +          name='bitmask_cli', debug=False, strip=False,
    +          upx=True, console=True)
    +
    +gui_coll = COLLECT(gui_exe,
    +               gui_a.binaries,
    +               gui_a.zipfiles,
    +               gui_a.datas,
    +               strip=False, upx=True, name='bitmask')
    +cli_coll = COLLECT(cli_exe,
    +               cli_a.binaries,
    +               cli_a.zipfiles,
    +               cli_a.datas,
    +               strip=False, upx=True, name='bitmask_cli')
    -- 
    cgit v1.2.3
    
    
    From 23d5143d7b66e30f84556be85eeced5f914598a9 Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Tue, 8 Mar 2016 11:36:21 -0400
    Subject: [refactor] use __version__ directly
    
    ---
     src/leap/bitmask/core/service.py | 14 +++++++-------
     1 file changed, 7 insertions(+), 7 deletions(-)
    
    diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py
    index 4c18ab5d..87349ef6 100644
    --- a/src/leap/bitmask/core/service.py
    +++ b/src/leap/bitmask/core/service.py
    @@ -22,16 +22,14 @@ import resource
     from twisted.internet import reactor
     from twisted.python import log
     
    -from leap.bonafide.service import BonafideService
    -
    +from leap.bitmask import __version__
     from leap.bitmask.core import configurable
     from leap.bitmask.core import mail_services
     from leap.bitmask.core import _zmq
     from leap.bitmask.core import websocket
    -from leap.bitmask.core._version import get_versions
    -
    +from leap.bonafide.service import BonafideService
     from leap.common.events import server as event_server
    -from leap.vpn import EIPService
    +#from leap.vpn import EIPService
     
     
     class BitmaskBackend(configurable.ConfigurableService):
    @@ -96,7 +94,9 @@ class BitmaskBackend(configurable.ConfigurableService):
             self._maybe_start_service('mail', service, self.basedir)
     
         def init_eip(self):
    -        self._maybe_start_service('eip', EIPService)
    +        # FIXME -- land EIP into leap.vpn
    +        pass
    +        #self._maybe_start_service('eip', EIPService)
     
         def init_zmq(self):
             zs = _zmq.ZMQServerService(self)
    @@ -139,7 +139,7 @@ class BitmaskBackend(configurable.ConfigurableService):
             return " ".join(status_messages)
     
         def do_version(self):
    -        version = get_versions()['version']
    +        version = __version__
             return 'BitmaskCore: %s' % version
     
         def do_shutdown(self):
    -- 
    cgit v1.2.3
    
    
    From b3f5538984502d97c77a9666f39c8d5b8f4a5c31 Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Tue, 8 Mar 2016 11:36:37 -0400
    Subject: [feature] add bitmaskd entrypoint
    
    ---
     setup.py                          | 3 ++-
     src/leap/bitmask/core/launcher.py | 4 ++++
     2 files changed, 6 insertions(+), 1 deletion(-)
    
    diff --git a/setup.py b/setup.py
    index 3e720971..24ed98aa 100755
    --- a/setup.py
    +++ b/setup.py
    @@ -479,6 +479,7 @@ extra_options = {}
     
     gui_launcher = 'bitmask=leap.bitmask.app:start_app'
     bitmask_cli = 'bitmask_cli=leap.bitmask.cli.bitmask_cli:main'
    +bitmaskd = 'bitmaskd=leap.bitmask.core.launcher:run_bitmaskd'
     
     
     setup(
    @@ -516,7 +517,7 @@ setup(
         zip_safe=False,
         platforms="all",
         entry_points={
    -        'console_scripts': [gui_launcher, bitmask_cli]
    +        'console_scripts': [gui_launcher, bitmask_cli, bitmaskd]
         },
         **extra_options
     )
    diff --git a/src/leap/bitmask/core/launcher.py b/src/leap/bitmask/core/launcher.py
    index 7d658017..e363046e 100644
    --- a/src/leap/bitmask/core/launcher.py
    +++ b/src/leap/bitmask/core/launcher.py
    @@ -35,3 +35,7 @@ def run_bitmaskd():
         ]
         print '[+] launching bitmaskd...'
         run()
    +
    +
    +if __name__ == "__main__":
    +    run_bitmaskd()
    -- 
    cgit v1.2.3
    
    
    From b546d64221f5c10d7ec076fd278b7e60c3fb6f31 Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Wed, 9 Mar 2016 16:27:12 -0400
    Subject: [feat] add bitmaskd to multipackage spec
    
    ---
     pkg/pyinst/bitmaskd      |  1 +
     pkg/pyinst/bitmaskd.spec | 33 +++++++++++++++++++++++++++++++++
     pkg/pyinst/multi.spec    | 25 +++++++++++++++++++++++--
     3 files changed, 57 insertions(+), 2 deletions(-)
     create mode 120000 pkg/pyinst/bitmaskd
     create mode 100644 pkg/pyinst/bitmaskd.spec
    
    diff --git a/pkg/pyinst/bitmaskd b/pkg/pyinst/bitmaskd
    new file mode 120000
    index 00000000..c5b08597
    --- /dev/null
    +++ b/pkg/pyinst/bitmaskd
    @@ -0,0 +1 @@
    +../../src/leap/bitmask/core/launcher.py
    \ No newline at end of file
    diff --git a/pkg/pyinst/bitmaskd.spec b/pkg/pyinst/bitmaskd.spec
    new file mode 100644
    index 00000000..4e2e2d6a
    --- /dev/null
    +++ b/pkg/pyinst/bitmaskd.spec
    @@ -0,0 +1,33 @@
    +# -*- mode: python -*-
    +
    +block_cipher = None
    +
    +
    +a = Analysis(['bitmaskd'],
    +             pathex=['/home/kali/leap/bitmask_client/pkg/pyinst'],
    +             binaries=None,
    +             datas=None,
    +             hiddenimports=[],
    +             hookspath=[],
    +             runtime_hooks=[],
    +             excludes=[],
    +             win_no_prefer_redirects=False,
    +             win_private_assemblies=False,
    +             cipher=block_cipher)
    +pyz = PYZ(a.pure, a.zipped_data,
    +             cipher=block_cipher)
    +exe = EXE(pyz,
    +          a.scripts,
    +          exclude_binaries=True,
    +          name='bitmaskd',
    +          debug=False,
    +          strip=False,
    +          upx=True,
    +          console=True )
    +coll = COLLECT(exe,
    +               a.binaries,
    +               a.zipfiles,
    +               a.datas,
    +               strip=False,
    +               upx=True,
    +               name='bitmaskd')
    diff --git a/pkg/pyinst/multi.spec b/pkg/pyinst/multi.spec
    index 2bb9d248..aa5c83c0 100644
    --- a/pkg/pyinst/multi.spec
    +++ b/pkg/pyinst/multi.spec
    @@ -13,7 +13,6 @@ gui_a = Analysis(['bitmask.py'],
                  excludes=None,
                  cipher=block_cipher)
     cli_a = Analysis(['bitmask_cli'],
    -             pathex=['/home/kali/leap/bitmask_client/pkg/pyinst'],
                  binaries=None,
                  datas=None,
                  hiddenimports=[
    @@ -24,9 +23,21 @@ cli_a = Analysis(['bitmask_cli'],
                  win_no_prefer_redirects=False,
                  win_private_assemblies=False,
                  cipher=block_cipher)
    +daemon_a = Analysis(['bitmaskd'],
    +             binaries=None,
    +             datas=None,
    +             hiddenimports=[
    +	     	'leap.bitmask.core.service'],
    +             hookspath=[],
    +             runtime_hooks=[],
    +             excludes=[],
    +             win_no_prefer_redirects=False,
    +             win_private_assemblies=False,
    +             cipher=block_cipher)
     
     MERGE( (gui_a, 'bitmask', 'bitmask'),
    -       (cli_a, 'bitmask_cli', 'bitmask'))
    +       (cli_a, 'bitmask_cli', 'bitmask'),
    +       (daemon_a, 'bitmaskd', 'bitmaskd'))
     
     gui_pyz = PYZ(gui_a.pure, gui_a.zipped_data, cipher=block_cipher)
     gui_exe = EXE(gui_pyz,
    @@ -41,6 +52,11 @@ cli_exe = EXE(cli_pyz,
               exclude_binaries=True,
               name='bitmask_cli', debug=False, strip=False,
               upx=True, console=True)
    +daemon_pyz = PYZ(daemon_a.pure, daemon_a.zipped_data, cipher=block_cipher)
    +daemon_exe = EXE(daemon_pyz,
    +          daemon_a.scripts,
    +          exclude_binaries=True,
    +          name='bitmaskd', debug=False, strip=False, upx=True, console=True )
     
     gui_coll = COLLECT(gui_exe,
                    gui_a.binaries,
    @@ -52,3 +68,8 @@ cli_coll = COLLECT(cli_exe,
                    cli_a.zipfiles,
                    cli_a.datas,
                    strip=False, upx=True, name='bitmask_cli')
    +daemon_coll = COLLECT(daemon_exe,
    +               daemon_a.binaries,
    +               daemon_a.zipfiles,
    +               daemon_a.datas,
    +               strip=False, upx=True, name='bitmaskd')
    -- 
    cgit v1.2.3
    
    
    From 449bb9f2336d2f50a63d8558d1198b64b1ec4814 Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Wed, 9 Mar 2016 16:27:36 -0400
    Subject: [pkg] adapt launcher to work inside frozen binary
    
    ---
     src/leap/bitmask/core/launcher.py | 5 +++--
     1 file changed, 3 insertions(+), 2 deletions(-)
    
    diff --git a/src/leap/bitmask/core/launcher.py b/src/leap/bitmask/core/launcher.py
    index e363046e..b2319077 100644
    --- a/src/leap/bitmask/core/launcher.py
    +++ b/src/leap/bitmask/core/launcher.py
    @@ -18,9 +18,10 @@
     Run bitmask daemon.
     """
     from twisted.scripts.twistd import run
    -from os.path import join, dirname
    +from os.path import join
     from sys import argv
     
    +from leap.bitmask.util import here
     from leap.bitmask import core
     
     
    @@ -28,7 +29,7 @@ def run_bitmaskd():
         # TODO --- configure where to put the logs... (get --logfile, --logdir
         # from the bitmask_cli
         argv[1:] = [
    -        '-y', join(dirname(core.__file__), "bitmaskd.tac"),
    +        '-y', join(here(core), "bitmaskd.tac"),
             '--pidfile', '/tmp/bitmaskd.pid',
             '--logfile', '/tmp/bitmaskd.log',
             '--umask=0022',
    -- 
    cgit v1.2.3
    
    
    From 40235da29812dcbf767eaabe0bcf93fe8c414a07 Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Wed, 9 Mar 2016 16:28:18 -0400
    Subject: [doc] notes about unneeded deps in the bundle
    
    ---
     pkg/pyinst/trim-notes.txt | 12 ++++++++++++
     1 file changed, 12 insertions(+)
     create mode 100644 pkg/pyinst/trim-notes.txt
    
    diff --git a/pkg/pyinst/trim-notes.txt b/pkg/pyinst/trim-notes.txt
    new file mode 100644
    index 00000000..2d825760
    --- /dev/null
    +++ b/pkg/pyinst/trim-notes.txt
    @@ -0,0 +1,12 @@
    +88M
    +-------------
    +libaudio.so.2
    +libQt3Support.so.4
    +libQtNetwork.so.4
    +libtiff.so.5
    +libQtSvg.so.4
    +libQtXml.so.4
    +libQtSql.so.4
    +libQtOpenGL.so.4
    +libnvidia-glcore.so.352.79
    +PySide.QtNetwork.x86_64-linux-gnu.so
    -- 
    cgit v1.2.3
    
    
    From c8de65eab85a8fd0a416dbcc3282f5fc105a5716 Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Wed, 9 Mar 2016 16:34:43 -0400
    Subject: [feature] add logging
    
    ---
     src/leap/bitmask/core/mail_services.py | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py
    index 9858d498..ba428258 100644
    --- a/src/leap/bitmask/core/mail_services.py
    +++ b/src/leap/bitmask/core/mail_services.py
    @@ -330,7 +330,7 @@ class KeymanagerService(service.Service, HookableService):
                     log.msg('Passing a new SRP Token to Keymanager: %s' % userid)
                     container.set_remote_auth_token(userid, token)
                 else:
    -                log.msg('storing the keymanager token...')
    +                log.msg('storing the keymanager token... %s ' % token)
                     self.tokens[userid] = token
     
         # commands
    -- 
    cgit v1.2.3
    
    
    From 9130d203bf15629c51dcc90bce83a8cd730900c4 Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Wed, 9 Mar 2016 22:34:10 -0400
    Subject: [refactor] use latest implementation of service_hooks
    
    ---
     src/leap/bitmask/core/mail_services.py | 8 ++------
     1 file changed, 2 insertions(+), 6 deletions(-)
    
    diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py
    index ba428258..a0be1211 100644
    --- a/src/leap/bitmask/core/mail_services.py
    +++ b/src/leap/bitmask/core/mail_services.py
    @@ -135,9 +135,7 @@ def is_service_ready(service, provider):
         return has_service and has_config and is_enabled
     
     
    -class SoledadService(service.Service, HookableService):
    -
    -    subscribed_to_hooks = ('on_bonafide_auth', 'on_passphrase_entry')
    +class SoledadService(HookableService):
     
         def __init__(self, basedir):
             service.Service.__init__(self)
    @@ -286,9 +284,7 @@ class KeymanagerContainer(Container):
                 provider=provider)
     
     
    -class KeymanagerService(service.Service, HookableService):
    -
    -    subscribed_to_hooks = ('on_new_soledad_instance', 'on_bonafide_auth')
    +class KeymanagerService(HookableService):
     
         def __init__(self, basedir='~/.config/leap'):
             service.Service.__init__(self)
    -- 
    cgit v1.2.3
    
    
    From c2a30ff3ae4d47266a1173fd2b1293e6812705aa Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Mon, 18 Apr 2016 16:58:48 -0400
    Subject: [pkg] bump leap dependencies for 0.9.2 release
    
    ---
     pkg/requirements-leap.pip | 8 ++++----
     1 file changed, 4 insertions(+), 4 deletions(-)
    
    diff --git a/pkg/requirements-leap.pip b/pkg/requirements-leap.pip
    index 8e353c33..abaeb8fc 100644
    --- a/pkg/requirements-leap.pip
    +++ b/pkg/requirements-leap.pip
    @@ -1,4 +1,4 @@
    -leap.soledad.client>=0.6.0
    -leap.keymanager>=0.4.0
    -leap.mail>=0.4.0
    -leap.common>=0.4.0
    +leap.soledad.client>=0.8.0
    +leap.keymanager>=0.5.0
    +leap.mail>=0.4.1
    +leap.common>=0.5.1
    -- 
    cgit v1.2.3
    
    
    From bfd8886e806468e42f0d8d7e9569ea83a62e522f Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Mon, 18 Apr 2016 17:02:31 -0400
    Subject: [docs] document bitmask.core in the changelog
    
    ---
     changes/next-changelog.rst | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst
    index 3240d2bb..4f09b291 100644
    --- a/changes/next-changelog.rst
    +++ b/changes/next-changelog.rst
    @@ -16,6 +16,7 @@ Features
     - `#6041 `_: Write service tokens to a file to allow email clients to read them from there.
     - Use cred-based authentication on SMTP.
     - Experimental support for the Pixelated WebMail.
    +- Ability to launch detached bitmask.core daemon, and a simplistic bitmask_cli. Not used by the main client yet.
     
     - `#1234 `_: Description of the new feature corresponding with issue #1234.
     - New feature without related issue number.
    -- 
    cgit v1.2.3
    
    
    From c6db8a1f9ba7b6fc9dd117c0a699f4dfd339a375 Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Mon, 18 Apr 2016 18:27:29 -0400
    Subject: some fixes after review
    
    ---
     src/leap/bitmask/core/configurable.py  | 174 ++-------------------------------
     src/leap/bitmask/core/dispatcher.py    |   1 +
     src/leap/bitmask/core/mail_services.py |  64 +++++++-----
     3 files changed, 52 insertions(+), 187 deletions(-)
    
    diff --git a/src/leap/bitmask/core/configurable.py b/src/leap/bitmask/core/configurable.py
    index 3b97916d..8e33de95 100644
    --- a/src/leap/bitmask/core/configurable.py
    +++ b/src/leap/bitmask/core/configurable.py
    @@ -18,15 +18,15 @@
     Configurable Backend for Bitmask Service.
     """
     import ConfigParser
    -import locale
     import os
    -import re
    -import sys
     
     from twisted.application import service
    -from twisted.python import log
     
     from leap.common import files
    +from leap.common.config import get_path_prefix
    +
    +
    +DEFAULT_BASEDIR = os.path.join(get_path_prefix(), 'leap')
     
     
     class MissingConfigEntry(Exception):
    @@ -40,7 +40,7 @@ class ConfigurableService(service.MultiService):
         config_file = u"bitmaskd.cfg"
         service_names = ('mail', 'eip', 'zmq', 'web')
     
    -    def __init__(self, basedir='~/.config/leap'):
    +    def __init__(self, basedir=DEFAULT_BASEDIR):
             service.MultiService.__init__(self)
     
             path = os.path.abspath(os.path.expanduser(basedir))
    @@ -61,10 +61,9 @@ class ConfigurableService(service.MultiService):
     
             except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
                 if default is None:
    -                fn = os.path.join(self.basedir, self.config_file)
    +                fn = self._get_config_path()
                     raise MissingConfigEntry("%s is missing the [%s]%s entry"
    -                                         % (_quote_output(fn),
    -                                            section, option))
    +                                         % fn, section, option)
                 return default
     
         def set_config(self, section, option, value):
    @@ -82,12 +81,8 @@ class ConfigurableService(service.MultiService):
             if not os.path.isfile(bitmaskd_cfg):
                 self._create_default_config(bitmaskd_cfg)
     
    -        try:
    -            with open(bitmaskd_cfg, "rb") as f:
    -                self.config.readfp(f)
    -        except EnvironmentError:
    -            if os.path.exists(bitmaskd_cfg):
    -                raise
    +        with open(bitmaskd_cfg, "rb") as f:
    +            self.config.readfp(f)
     
         def save_config(self):
             bitmaskd_cfg = self._get_config_path()
    @@ -109,154 +104,3 @@ eip = True
     zmq = True
     web = False
     """
    -
    -
    -def canonical_encoding(encoding):
    -    if encoding is None:
    -        log.msg("Warning: falling back to UTF-8 encoding.", level=log.WEIRD)
    -        encoding = 'utf-8'
    -    encoding = encoding.lower()
    -    if encoding == "cp65001":
    -        encoding = 'utf-8'
    -    elif (encoding == "us-ascii" or encoding == "646" or encoding ==
    -          "ansi_x3.4-1968"):
    -        encoding = 'ascii'
    -
    -    return encoding
    -
    -
    -def check_encoding(encoding):
    -    # sometimes Python returns an encoding name that it doesn't support for
    -    # conversion fail early if this happens
    -    try:
    -        u"test".encode(encoding)
    -    except (LookupError, AttributeError):
    -        raise AssertionError(
    -            "The character encoding '%s' is not supported for conversion." % (
    -                encoding,))
    -
    -filesystem_encoding = None
    -io_encoding = None
    -is_unicode_platform = False
    -
    -
    -def _reload():
    -    global filesystem_encoding, io_encoding, is_unicode_platform
    -
    -    filesystem_encoding = canonical_encoding(sys.getfilesystemencoding())
    -    check_encoding(filesystem_encoding)
    -
    -    if sys.platform == 'win32':
    -        # On Windows we install UTF-8 stream wrappers for sys.stdout and
    -        # sys.stderr, and reencode the arguments as UTF-8 (see
    -        # scripts/runner.py).
    -        io_encoding = 'utf-8'
    -    else:
    -        ioenc = None
    -        if hasattr(sys.stdout, 'encoding'):
    -            ioenc = sys.stdout.encoding
    -        if ioenc is None:
    -            try:
    -                ioenc = locale.getpreferredencoding()
    -            except Exception:
    -                pass  # work around 
    -        io_encoding = canonical_encoding(ioenc)
    -
    -    check_encoding(io_encoding)
    -
    -    is_unicode_platform = sys.platform in ["win32", "darwin"]
    -
    -_reload()
    -
    -
    -def _quote_output(s, quotemarks=True, quote_newlines=None, encoding=None):
    -    """
    -    Encode either a Unicode string or a UTF-8-encoded bytestring for
    -    representation on stdout or stderr, tolerating errors. If 'quotemarks' is
    -    True, the string is always quoted; otherwise, it is quoted only if
    -    necessary to avoid ambiguity or control bytes in the output. (Newlines are
    -    counted as control bytes iff quote_newlines is True.)
    -
    -    Quoting may use either single or double quotes. Within single quotes, all
    -    characters stand for themselves, and ' will not appear. Within double
    -    quotes, Python-compatible backslash escaping is used.
    -
    -    If not explicitly given, quote_newlines is True when quotemarks is True.
    -    """
    -    assert isinstance(s, (str, unicode))
    -    if quote_newlines is None:
    -        quote_newlines = quotemarks
    -
    -    if isinstance(s, str):
    -        try:
    -            s = s.decode('utf-8')
    -        except UnicodeDecodeError:
    -            return 'b"%s"' % (
    -                ESCAPABLE_8BIT.sub(
    -                    lambda m: _str_escape(m, quote_newlines), s),)
    -
    -    must_double_quote = (quote_newlines and MUST_DOUBLE_QUOTE_NL or
    -                         MUST_DOUBLE_QUOTE)
    -    if must_double_quote.search(s) is None:
    -        try:
    -            out = s.encode(encoding or io_encoding)
    -            if quotemarks or out.startswith('"'):
    -                return "'%s'" % (out,)
    -            else:
    -                return out
    -        except (UnicodeDecodeError, UnicodeEncodeError):
    -            pass
    -
    -    escaped = ESCAPABLE_UNICODE.sub(
    -        lambda m: _unicode_escape(m, quote_newlines), s)
    -    return '"%s"' % (
    -        escaped.encode(encoding or io_encoding, 'backslashreplace'),)
    -
    -
    -def _unicode_escape(m, quote_newlines):
    -    u = m.group(0)
    -    if u == u'"' or u == u'$' or u == u'`' or u == u'\\':
    -        return u'\\' + u
    -    elif u == u'\n' and not quote_newlines:
    -        return u
    -    if len(u) == 2:
    -        codepoint = (
    -            ord(u[0]) - 0xD800) * 0x400 + ord(u[1]) - 0xDC00 + 0x10000
    -    else:
    -        codepoint = ord(u)
    -    if codepoint > 0xFFFF:
    -        return u'\\U%08x' % (codepoint,)
    -    elif codepoint > 0xFF:
    -        return u'\\u%04x' % (codepoint,)
    -    else:
    -        return u'\\x%02x' % (codepoint,)
    -
    -
    -def _str_escape(m, quote_newlines):
    -    c = m.group(0)
    -    if c == '"' or c == '$' or c == '`' or c == '\\':
    -        return '\\' + c
    -    elif c == '\n' and not quote_newlines:
    -        return c
    -    else:
    -        return '\\x%02x' % (ord(c),)
    -
    -MUST_DOUBLE_QUOTE_NL = re.compile(
    -    ur'[^\x20-\x26\x28-\x7E\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFC]',
    -    re.DOTALL)
    -MUST_DOUBLE_QUOTE = re.compile(
    -    ur'[^\n\x20-\x26\x28-\x7E\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFC]',
    -    re.DOTALL)
    -
    -ESCAPABLE_8BIT = re.compile(
    -    r'[^ !#\x25-\x5B\x5D-\x5F\x61-\x7E]',
    -    re.DOTALL)
    -
    -# if we must double-quote, then we have to escape ", $ and `, but need not
    -# escape '
    -
    -ESCAPABLE_UNICODE = re.compile(
    -    ur'([\uD800-\uDBFF][\uDC00-\uDFFF])|'  # valid surrogate pairs
    -    ur'[^ !#\x25-\x5B\x5D-\x5F\x61-\x7E\u00A0-\uD7FF'
    -    ur'\uE000-\uFDCF\uFDF0-\uFFFC]',
    -    re.DOTALL)
    diff --git a/src/leap/bitmask/core/dispatcher.py b/src/leap/bitmask/core/dispatcher.py
    index 4d7e1813..648cbe9b 100644
    --- a/src/leap/bitmask/core/dispatcher.py
    +++ b/src/leap/bitmask/core/dispatcher.py
    @@ -84,6 +84,7 @@ class CommandDispatcher(object):
             return d
     
         def do_EIP(self, *parts):
    +
             subcmd = parts[1]
             eip_label = 'eip'
     
    diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py
    index a0be1211..6472bdc2 100644
    --- a/src/leap/bitmask/core/mail_services.py
    +++ b/src/leap/bitmask/core/mail_services.py
    @@ -1,3 +1,19 @@
    +# -*- coding: utf-8 -*-
    +# mail_services.py
    +# Copyright (C) 2016 LEAP Encryption Acess 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 .
     """
     Mail services.
     
    @@ -6,7 +22,6 @@ This should be moved to the different packages when it stabilizes.
     """
     import json
     import os
    -from glob import glob
     from collections import defaultdict
     from collections import namedtuple
     
    @@ -26,31 +41,30 @@ from leap.mail.incoming.service import IncomingMail, INCOMING_CHECK_PERIOD
     from leap.mail import smtp
     
     from leap.bitmask.core.uuid_map import UserMap
    +from leap.bitmask.core.configurable import DEFAULT_BASEDIR
     
     
     class Container(object):
     
    -    def __init__(self):
    +    def __init__(self, service=None):
             self._instances = defaultdict(None)
    +        if service is not None:
    +            self.service = service
     
         def get_instance(self, key):
             return self._instances.get(key, None)
     
    +    def add_instance(self, key, data):
    +        self._instances[key] = data
    +
     
     class ImproperlyConfigured(Exception):
         pass
     
     
    -def get_all_soledad_uuids():
    -    return [os.path.split(p)[-1].split('.db')[0] for p in
    -            glob(os.path.expanduser('~/.config/leap/soledad/*.db'))]
    -    # FIXME do not hardcode basedir
    -
    -
     class SoledadContainer(Container):
     
    -    def __init__(self, basedir='~/.config/leap'):
    -        # FIXME do not hardcode basedir
    +    def __init__(self, basedir=DEFAULT_BASEDIR):
             self._basedir = os.path.expanduser(basedir)
             self._usermap = UserMap()
             super(SoledadContainer, self).__init__()
    @@ -75,19 +89,17 @@ class SoledadContainer(Container):
                 uuid, passphrase, soledad_path, soledad_url,
                 cert_path, token)
     
    -        self._instances[userid] = soledad
    +        self.add_instances(userid, soledad)
     
             data = {'user': userid, 'uuid': uuid, 'token': token,
                     'soledad': soledad}
             self.service.trigger_hook('on_new_soledad_instance', **data)
     
    -    def _create_soledad_instance(self, uuid, passphrase, basedir, server_url,
    -                                 cert_file, token):
    +    def _create_soledad_instance(self, uuid, passphrase, soledad_path,
    +                                 server_url, cert_file, token):
             # setup soledad info
    -        secrets_path = os.path.join(
    -            basedir, '%s.secret' % uuid)
    -        local_db_path = os.path.join(
    -            basedir, '%s.db' % uuid)
    +        secrets_path = os.path.join(soledad_path, '%s.secret' % uuid)
    +        local_db_path = os.path.join(soledad_path, '%s.db' % uuid)
     
             if token is None:
                 syncable = False
    @@ -143,8 +155,7 @@ class SoledadService(HookableService):
     
         def startService(self):
             log.msg('Starting Soledad Service')
    -        self._container = SoledadContainer()
    -        self._container.service = self
    +        self._container = SoledadContainer(service=self)
             super(SoledadService, self).startService()
     
         # hooks
    @@ -209,7 +220,7 @@ class KeymanagerContainer(Container):
     
         def _on_keymanager_ready_cb(self, keymanager, userid, soledad):
             # TODO use onready-deferreds instead
    -        self._instances[userid] = keymanager
    +        self.add_instance(userid, keymanager)
     
             log.msg("Adding Keymanager instance for: %s" % userid)
             data = {'userid': userid, 'soledad': soledad, 'keymanager': keymanager}
    @@ -264,6 +275,11 @@ class KeymanagerContainer(Container):
                 token = self.service.tokens.get(userid)
     
             km_args = (userid, nickserver_uri, soledad)
    +
    +        # TODO use the method in
    +        # services.soledadbootstrapper._get_gpg_bin_path.
    +        # That should probably live in keymanager package.
    +
             km_kwargs = {
                 "token": token, "uid": uuid,
                 "api_uri": api_uri, "api_version": "1",
    @@ -373,10 +389,12 @@ class StandardMailService(service.MultiService, HookableService):
     
         def initializeChildrenServices(self):
             self.addService(IMAPService(self._soledad_sessions))
    -        self.addService(IncomingMailService(self))
             self.addService(SMTPService(
                 self._soledad_sessions, self._keymanager_sessions,
                 self._sendmail_opts))
    +        # TODO adapt the service to receive soledad/keymanager sessions object.
    +        # See also the TODO before IncomingMailService.startInstance
    +        self.addService(IncomingMailService(self))
     
         def startService(self):
             log.msg('Starting Mail Service...')
    @@ -438,6 +456,7 @@ class StandardMailService(service.MultiService, HookableService):
             if not active_user:
                 return defer.succeed('NO ACTIVE USER')
             token = self._imap_tokens.get(active_user)
    +        # TODO return just the tuple, no format.
             return defer.succeed("IMAP TOKEN (%s): %s" % (active_user, token))
     
         def get_smtp_token(self):
    @@ -445,6 +464,7 @@ class StandardMailService(service.MultiService, HookableService):
             if not active_user:
                 return defer.succeed('NO ACTIVE USER')
             token = self._smtp_tokens.get(active_user)
    +        # TODO return just the tuple, no format.
             return defer.succeed("SMTP TOKEN (%s): %s" % (active_user, token))
     
         def do_get_smtp_cert_path(self, userid):
    @@ -560,7 +580,7 @@ class IncomingMailService(service.Service):
                 if start_sync:
                     incoming_instance.startService()
     
    -        acc = Account(soledad)
    +        acc = Account(soledad, userid)
             d = acc.callWhenReady(
                 lambda _: acc.get_collection_by_mailbox(INBOX_NAME))
             d.addCallback(setUpIncomingMail)
    -- 
    cgit v1.2.3
    
    
    From 229f803235ae9b9a71313d11071c7a0fbea0a681 Mon Sep 17 00:00:00 2001
    From: elijah 
    Date: Mon, 18 Apr 2016 23:44:12 -0700
    Subject: [feature] add email panel to preferences
    
    ---
     changes/feature-email-preferences-panel           |   1 +
     src/leap/bitmask/backend/components.py            |  20 +-
     src/leap/bitmask/gui/account.py                   |  10 +-
     src/leap/bitmask/gui/app.py                       |  34 ++
     src/leap/bitmask/gui/mainwindow.py                |  32 +-
     src/leap/bitmask/gui/passwordwindow.py            |   2 +-
     src/leap/bitmask/gui/preferences_account_page.py  |  31 +-
     src/leap/bitmask/gui/preferences_email_page.py    | 184 ++++++-
     src/leap/bitmask/gui/preferences_page.py          |  50 ++
     src/leap/bitmask/gui/preferences_vpn_page.py      |  31 +-
     src/leap/bitmask/gui/preferenceswindow.py         | 131 +++--
     src/leap/bitmask/gui/ui/preferences.ui            |  22 +-
     src/leap/bitmask/gui/ui/preferences_email_page.ui | 563 +++++++++++++++++++++-
     13 files changed, 1007 insertions(+), 104 deletions(-)
     create mode 100644 changes/feature-email-preferences-panel
     create mode 100644 src/leap/bitmask/gui/preferences_page.py
    
    diff --git a/changes/feature-email-preferences-panel b/changes/feature-email-preferences-panel
    new file mode 100644
    index 00000000..e9bbfb6a
    --- /dev/null
    +++ b/changes/feature-email-preferences-panel
    @@ -0,0 +1 @@
    +- Add email panel to preferences window.
    \ No newline at end of file
    diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py
    index acb562c7..3192e1c4 100644
    --- a/src/leap/bitmask/backend/components.py
    +++ b/src/leap/bitmask/backend/components.py
    @@ -576,8 +576,10 @@ class EIP(object):
                         self._signaler.eip_uninitialized_provider)
                 return
     
    -        eip_config = eipconfig.EIPConfig()
             provider_config = ProviderConfig.get_provider_config(domain)
    +        if EIP_SERVICE not in provider_config.get_services():
    +            return
    +        eip_config = eipconfig.EIPConfig()
     
             api_version = provider_config.get_api_version()
             eip_config.set_api_version(api_version)
    @@ -1003,13 +1005,11 @@ class Keymanager(object):
     
         def get_key_details(self, username):
             """
    -        List all the keys stored in the local DB.
    +        Get information on our primary key pair
             """
             def signal_details(public_key):
    -            # XXX: We should avoid the key-id
    -            details = (public_key.fingerprint[-16:], public_key.fingerprint)
                 self._signaler.signal(self._signaler.keymanager_key_details,
    -                                  details)
    +                                  public_key.get_dict())
     
             d = self._keymanager_proxy.get_key(username,
                                                openpgp.OpenPGPKey)
    @@ -1215,15 +1215,13 @@ class Authenticate(object):
     
         def get_logged_in_status(self):
             """
    -        Signal if the user is currently logged in or not.
    +        Signal if the user is currently logged in or not. If logged in,
    +        authenticated username is passed as argument to the signal.
             """
             if self._signaler is None:
                 return
     
    -        signal = None
             if self._is_logged_in():
    -            signal = self._signaler.srp_status_logged_in
    +            self._signaler.signal(self._signaler.srp_status_logged_in)
             else:
    -            signal = self._signaler.srp_status_not_logged_in
    -
    -        self._signaler.signal(signal)
    +            self._signaler.signal(self._signaler.srp_status_not_logged_in)
    diff --git a/src/leap/bitmask/gui/account.py b/src/leap/bitmask/gui/account.py
    index 81f96389..5e43c8fe 100644
    --- a/src/leap/bitmask/gui/account.py
    +++ b/src/leap/bitmask/gui/account.py
    @@ -20,7 +20,7 @@ A frontend GUI object to hold the current username and domain.
     from leap.bitmask.util import make_address
     from leap.bitmask.config.leapsettings import LeapSettings
     from leap.bitmask.services import EIP_SERVICE, MX_SERVICE
    -
    +from leap.bitmask._components import HAS_EIP, HAS_MAIL
     
     class Account():
     
    @@ -42,8 +42,8 @@ class Account():
             """
             return self._settings.get_enabled_services(self.domain)
     
    -    def is_email_enabled(self):
    -        return MX_SERVICE in self.services()
    +    def has_email(self):
    +        return HAS_MAIL and MX_SERVICE in self.services()
     
    -    def is_eip_enabled(self):
    -        return EIP_SERVICE in self.services()
    +    def has_eip(self):
    +        return HAS_EIP and EIP_SERVICE in self.services()
    diff --git a/src/leap/bitmask/gui/app.py b/src/leap/bitmask/gui/app.py
    index 97fd0549..e3a4d7fe 100644
    --- a/src/leap/bitmask/gui/app.py
    +++ b/src/leap/bitmask/gui/app.py
    @@ -20,6 +20,7 @@ and the signaler get signals from the backend.
     """
     from PySide import QtCore, QtGui
     
    +from leap.bitmask.gui.account import Account
     from leap.bitmask.config.leapsettings import LeapSettings
     from leap.bitmask.backend.backend_proxy import BackendProxy
     from leap.bitmask.backend.leapsignaler import LeapSignaler
    @@ -44,12 +45,37 @@ class App(QtGui.QWidget):
             self.signaler.start()
     
             self.soledad_started = False
    +        self.service_tokens = {}
    +        self.login_state = None
    +        self.providers_widget = None
     
             # periodically check if the backend is alive
             self._backend_checker = QtCore.QTimer(self)
             self._backend_checker.timeout.connect(self._check_backend_status)
             self._backend_checker.start(2000)
     
    +        # store the service tokens for later use, once they are known.
    +        self.signaler.soledad_got_service_token.connect(
    +            self._set_service_tokens)
    +
    +    def current_account(self):
    +        """
    +        Alas, the only definitive account information is buried in the memory of
    +        QT widgets.
    +
    +        :returns: an object representing the current user account.
    +        :rtype: Account
    +        """
    +        if self.login_state is None or self.providers_widget is None:
    +            return None
    +
    +        if self.login_state.full_logged_username is not None:
    +            username, domain = self.login_state.full_logged_username.split('@')
    +            return Account(username, domain)
    +        else:
    +            domain = self.providers_widget.get_selected_provider()
    +            return Account(None, domain)
    +
         def _check_backend_status(self):
             """
             TRIGGERS:
    @@ -64,3 +90,11 @@ class App(QtGui.QWidget):
                     self.tr("There is a problem contacting the backend, please "
                             "restart Bitmask."))
                 self._backend_checker.stop()
    +
    +    def _set_service_tokens(self, data):
    +        """
    +        Triggered by signal soledad_got_service_token.
    +        Saves the service tokens.
    +        """
    +        service, token = data
    +        self.service_tokens[service] = token
    diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
    index cde44f7b..ca14e631 100644
    --- a/src/leap/bitmask/gui/mainwindow.py
    +++ b/src/leap/bitmask/gui/mainwindow.py
    @@ -46,7 +46,6 @@ from leap.bitmask.gui.signaltracker import SignalTracker
     from leap.bitmask.gui.systray import SysTray
     from leap.bitmask.gui.wizard import Wizard
     from leap.bitmask.gui.providers import Providers
    -from leap.bitmask.gui.account import Account
     from leap.bitmask.gui.app import App
     
     from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX
    @@ -154,6 +153,14 @@ class MainWindow(QtGui.QMainWindow, SignalTracker):
             # Provider List
             self._providers = Providers(self.ui.cmbProviders)
     
    +        ##
    +        ## tmphack: important state information about the application is stored
    +        ## in widgets. Rather than rewrite the UI, for now we simulate this
    +        ## info being stored in an application object:
    +        ##
    +        self.app.login_state      = self._login_widget._state
    +        self.app.providers_widget = self._providers
    +
             # Qt Signal Connections #####################################
             # TODO separate logic from ui signals.
     
    @@ -416,10 +423,6 @@ class MainWindow(QtGui.QMainWindow, SignalTracker):
             sig.soledad_invalid_auth_token.connect(
                 self._mail_status.set_soledad_invalid_auth_token)
     
    -        self._service_tokens = {}
    -        sig.soledad_got_service_token.connect(
    -            self._set_service_tokens)
    -
             # TODO: connect this with something
             # sig.soledad_cancelled_bootstrap.connect()
     
    @@ -569,15 +572,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker):
     
             Display the preferences window.
             """
    -        logged_user = self._login_widget.get_logged_user()
    -        if logged_user is not None:
    -            user, domain = logged_user.split('@')
    -        else:
    -            user = None
    -            domain = self._providers.get_selected_provider()
    -
    -        account = Account(user, domain)
    -        pref_win = PreferencesWindow(self, account, self.app)
    +        pref_win = PreferencesWindow(self, self.app)
             pref_win.show()
     
         def _show_pixelated_browser(self):
    @@ -1054,13 +1049,6 @@ class MainWindow(QtGui.QMainWindow, SignalTracker):
             msg = msg.format(ver=VERSION, ver_hash=VERSION_HASH[:10], greet=greet)
             QtGui.QMessageBox.about(self, title, msg)
     
    -    def _set_service_tokens(self, data):
    -        """
    -        Set the received service token.
    -        """
    -        service, token = data
    -        self._service_tokens[service] = token
    -
         def _help(self):
             """
             TRIGGERS:
    @@ -1095,7 +1083,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker):
             # FIXME on i3, this doens't allow to mouse-select.
             # Switch to a dialog in which we can set the QLabel
             mail_auth_token = (
    -            self._service_tokens.get('mail_auth', None) or
    +            self.app.service_tokens.get('mail_auth', None) or
                 "??? (log in to unlock)")
             mail_password = self.tr("IMAP/SMTP Password:") + " %s" % (
                 mail_auth_token,)
    diff --git a/src/leap/bitmask/gui/passwordwindow.py b/src/leap/bitmask/gui/passwordwindow.py
    index dedfcb10..fe70b250 100644
    --- a/src/leap/bitmask/gui/passwordwindow.py
    +++ b/src/leap/bitmask/gui/passwordwindow.py
    @@ -83,7 +83,7 @@ class PasswordWindow(QtGui.QDialog, Flashable):
             Returns true if the current account needs to change the soledad
             password as well as the SRP password.
             """
    -        return self.account.is_email_enabled()
    +        return self.account.has_email()
     
         #
         # MANAGE WIDGETS
    diff --git a/src/leap/bitmask/gui/preferences_account_page.py b/src/leap/bitmask/gui/preferences_account_page.py
    index da9da14d..c175c42b 100644
    --- a/src/leap/bitmask/gui/preferences_account_page.py
    +++ b/src/leap/bitmask/gui/preferences_account_page.py
    @@ -22,6 +22,7 @@ from PySide import QtCore, QtGui
     
     from leap.bitmask.logs.utils import get_logger
     from leap.bitmask.gui import ui_preferences_account_page as ui_pref
    +from leap.bitmask.gui.preferences_page import PreferencesPage
     from leap.bitmask.gui.passwordwindow import PasswordWindow
     from leap.bitmask.services import get_service_display_name
     from leap.bitmask._components import HAS_EIP
    @@ -29,7 +30,7 @@ from leap.bitmask._components import HAS_EIP
     logger = get_logger()
     
     
    -class PreferencesAccountPage(QtGui.QWidget):
    +class PreferencesAccountPage(PreferencesPage):
     
         def __init__(self, parent, account, app):
             """
    @@ -42,20 +43,15 @@ class PreferencesAccountPage(QtGui.QWidget):
             :param app: the current App object
             :type app: App
             """
    -        QtGui.QWidget.__init__(self, parent)
    +        PreferencesPage.__init__(self, parent, account, app)
             self.ui = ui_pref.Ui_PreferencesAccountPage()
             self.ui.setupUi(self)
     
    -        self.account = account
    -        self.app = app
    -
             self._selected_services = set()
             self.ui.change_password_label.setVisible(False)
             self.ui.provider_services_label.setVisible(False)
     
    -        self.ui.change_password_button.clicked.connect(
    -            self._show_change_password)
    -        app.signaler.prov_get_supported_services.connect(self._load_services)
    +        self.setup_connections()
             app.backend.provider_get_supported_services(domain=account.domain)
     
             if account.username is None:
    @@ -64,6 +60,25 @@ class PreferencesAccountPage(QtGui.QWidget):
                 self.ui.change_password_label.setVisible(True)
                 self.ui.change_password_button.setEnabled(False)
     
    +    def setup_connections(self):
    +        """
    +        connect signals
    +        """
    +        self.ui.change_password_button.clicked.connect(
    +            self._show_change_password)
    +        self.app.signaler.prov_get_supported_services.connect(
    +            self._load_services)
    +
    +
    +    def teardown_connections(self):
    +        """
    +        disconnect signals
    +        """
    +        self.ui.change_password_button.clicked.disconnect(
    +            self._show_change_password)
    +        self.app.signaler.prov_get_supported_services.disconnect(
    +            self._load_services)
    +
         def _service_selection_changed(self, service, state):
             """
             TRIGGERS:
    diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py
    index 3087f343..50f244fb 100644
    --- a/src/leap/bitmask/gui/preferences_email_page.py
    +++ b/src/leap/bitmask/gui/preferences_email_page.py
    @@ -16,20 +16,192 @@
     """
     Widget for "email" preferences
     """
    -from PySide import QtGui
    +from PySide import QtCore, QtGui
    +
    +from datetime import datetime
     
     from leap.bitmask.logs.utils import get_logger
     from leap.bitmask.gui.ui_preferences_email_page import Ui_PreferencesEmailPage
    +from leap.bitmask.gui.preferences_page import PreferencesPage
    +from leap.mail.imap.service.imap import IMAP_PORT
     
     logger = get_logger()
     
    -
    -class PreferencesEmailPage(QtGui.QWidget):
    +class PreferencesEmailPage(PreferencesPage):
     
         def __init__(self, parent, account, app):
    -        QtGui.QWidget.__init__(self, parent)
    +        """
    +        :param parent: parent object of the PreferencesWindow.
    +        :parent type: QWidget
    +
    +        :param account: user account (user + provider or just provider)
    +        :type account: Account
    +
    +        :param app: the current App object
    +        :type app: App
    +        """
    +        PreferencesPage.__init__(self, parent, account, app)
             self.ui = Ui_PreferencesEmailPage()
             self.ui.setupUi(self)
     
    -        self.account = account
    -        self.app = app
    +        # the only way to set the tab titles is to re-add them:
    +        self.ui.email_tabs.addTab(self.ui.config_tab,
    +          self.tr("Mail Client"))
    +        self.ui.email_tabs.addTab(self.ui.my_key_tab,
    +          self.tr("My Key"))
    +        self.ui.email_tabs.addTab(self.ui.other_keys_tab,
    +          self.tr("Other Keys"))
    +
    +        # set mail client configuration help text
    +        lang = QtCore.QLocale.system().name().replace('_', '-')
    +        thunderbird_extension_url = \
    +            "https://addons.mozilla.org/{0}/" \
    +            "thunderbird/addon/bitmask/".format(lang)
    +        self.ui.thunderbird_label.setText(self.tr(
    +            "For Thunderbird, you can use the Bitmask extension. "
    +            "Search for \"Bitmask\" in the add-on manager or "
    +            "download it from addons.mozilla.org.".format(
    +            thunderbird_extension_url)))
    +        self.ui.mail_client_label.setText(self.tr(
    +            "Alternatively, you can manually configure your mail client to "
    +            "use Bitmask Email with these options:"))
    +
    +        self.ui.keys_table.horizontalHeader().setResizeMode(
    +            0, QtGui.QHeaderView.Stretch)
    +
    +        self.setup_connections()
    +
    +
    +    def setup_connections(self):
    +        """
    +        connect signals
    +        """
    +        self.app.signaler.keymanager_key_details.connect(self._key_details)
    +        self.app.signaler.keymanager_export_ok.connect(
    +            self._keymanager_export_ok)
    +        self.app.signaler.keymanager_export_error.connect(
    +            self._keymanager_export_error)
    +        self.ui.import_button.clicked.connect(self._import_keys)
    +        self.ui.export_button.clicked.connect(self._export_keys)
    +
    +    def teardown_connections(self):
    +        """
    +        disconnect signals
    +        """
    +        self.app.signaler.keymanager_key_details.disconnect(self._key_details)
    +        self.app.signaler.keymanager_export_ok.disconnect(
    +            self._keymanager_export_ok)
    +        self.app.signaler.keymanager_export_error.disconnect(
    +            self._keymanager_export_error)
    +
    +    def showEvent(self, event):
    +        """
    +        called whenever this widget is shown
    +        """
    +        self.ui.keys_table.clearContents()
    +
    +        if self.account.username is None:
    +            self.ui.email_tabs.setVisible(False)
    +            self.ui.message_label.setVisible(True)
    +            self.ui.message_label.setText(
    +                self.tr('You must be logged in to edit email settings.'))
    +        else:
    +            self.ui.import_button.setVisible(False) # hide this until working
    +            self.ui.message_label.setVisible(False)
    +            self.ui.email_tabs.setVisible(True)
    +            smtp_port = 2013
    +            self.ui.imap_port_edit.setText(str(IMAP_PORT))
    +            self.ui.imap_host_edit.setText("127.0.0.1")
    +            self.ui.smtp_port_edit.setText(str(smtp_port))
    +            self.ui.smtp_host_edit.setText("127.0.0.1")
    +            self.ui.username_edit.setText(self.account.address)
    +            self.ui.password_edit.setText(
    +                self.app.service_tokens.get('mail_auth', ''))
    +
    +            self.app.backend.keymanager_list_keys()
    +            self.app.backend.keymanager_get_key_details(
    +                username=self.account.address)
    +
    +    def _key_details(self, details):
    +        """
    +        Trigger by signal: keymanager_key_details
    +        Set the current user's key details into the gui.
    +        """
    +        self.ui.fingerprint_edit.setPlainText(
    +          self._format_fingerprint(details["fingerprint"]))
    +        self.ui.expiration_edit.setText(details["expiry_date"])
    +        self.ui.uid_edit.setText(" ".join(details["uids"]))
    +        self.ui.public_key_edit.setPlainText(details["key_data"])
    +
    +    def _format_fingerprint(self, fingerprint):
    +        """
    +        formats an openpgp fingerprint in a manner similar to what gpg
    +        produces, wrapped to two lines.
    +        """
    +        fp = fingerprint.upper()
    +        fp_list = [fp[i:i+4] for i in range(0, len(fp), 4)]
    +        fp_wrapped =  " ".join(fp_list[0:5]) + "\n" + " ".join(fp_list[5:10])
    +        return fp_wrapped
    +
    +    def _export_keys(self):
    +        """
    +        Exports the user's key pair.
    +        """
    +        file_name, filtr = QtGui.QFileDialog.getSaveFileName(
    +            self, self.tr("Save private key file"),
    +            filter="*.pem",
    +            options=QtGui.QFileDialog.DontUseNativeDialog)
    +
    +        if file_name:
    +            if not file_name.endswith('.pem'):
    +                file_name += '.pem'
    +            self.app.backend.keymanager_export_keys(
    +              username=self.account.address,
    +              filename=file_name)
    +        else:
    +            logger.debug('Export canceled by the user.')
    +
    +    def _keymanager_export_ok(self):
    +        """
    +        TRIGGERS:
    +            Signaler.keymanager_export_ok
    +
    +        Notify the user that the key export went OK.
    +        """
    +        QtGui.QMessageBox.information(
    +            self, self.tr("Export Successful"),
    +            self.tr("The key pair was exported successfully.\n"
    +                    "Please, store your private key in a safe place."))
    +
    +    def _keymanager_export_error(self):
    +        """
    +        TRIGGERS:
    +            Signaler.keymanager_export_error
    +
    +        Notify the user that the key export didn't go well.
    +        """
    +        QtGui.QMessageBox.critical(
    +            self, self.tr("Input/Output error"),
    +            self.tr("There was an error accessing the file.\n"
    +                    "Export canceled."))
    +
    +    def _import_keys(self):
    +      """
    +      not yet supported
    +      """
    +
    +    def _keymanager_keys_list(self, keys):
    +        """
    +        TRIGGERS:
    +            Signaler.keymanager_keys_list
    +
    +        Load the keys given as parameter in the table.
    +
    +        :param keys: the list of keys to load.
    +        :type keys: list
    +        """
    +        for key in keys:
    +            row = self.ui.keys_table.rowCount()
    +            self.ui.keys_table.insertRow(row)
    +            self.ui.keys_table.setItem(row, 0, QtGui.QTableWidgetItem(key.address))
    +            self.ui.keys_table.setItem(row, 1, QtGui.QTableWidgetItem(key.fingerprint))
    diff --git a/src/leap/bitmask/gui/preferences_page.py b/src/leap/bitmask/gui/preferences_page.py
    new file mode 100644
    index 00000000..c75b4991
    --- /dev/null
    +++ b/src/leap/bitmask/gui/preferences_page.py
    @@ -0,0 +1,50 @@
    +# -*- coding: utf-8 -*-
    +# Copyright (C) 2014 LEAP
    +#
    +# This program is free software: you can redistribute it and/or modify
    +# it under the terms of the GNU General Public License as published by
    +# the Free Software Foundation, either version 3 of the License, or
    +# (at your option) any later version.
    +#
    +# This program is distributed in the hope that it will be useful,
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    +# GNU General Public License for more details.
    +#
    +# You should have received a copy of the GNU General Public License
    +# along with this program.  If not, see .
    +"""
    +base class for preference pages
    +"""
    +
    +from PySide import QtCore, QtGui
    +
    +class PreferencesPage(QtGui.QWidget):
    +
    +    def __init__(self, parent, account=None, app=None):
    +        """
    +        :param parent: parent object of the EIPPreferencesWindow.
    +        :type parent: QWidget
    +
    +        :param account: the currently active account
    +        :type account: Account
    +
    +        :param app: shared App instance
    +        :type app: App
    +        """
    +        QtGui.QWidget.__init__(self, parent)
    +        self.app = app
    +        self.account = account
    +
    +    def setup_connections(self):
    +        """
    +        connect signals
    +        must be overridden by subclass
    +        """
    +
    +    def teardown_connections(self):
    +        """
    +        disconnect signals
    +        must be overridden by subclass
    +        """
    +
    diff --git a/src/leap/bitmask/gui/preferences_vpn_page.py b/src/leap/bitmask/gui/preferences_vpn_page.py
    index 5b5c9604..fc15340f 100644
    --- a/src/leap/bitmask/gui/preferences_vpn_page.py
    +++ b/src/leap/bitmask/gui/preferences_vpn_page.py
    @@ -22,9 +22,9 @@ from leap.bitmask.gui.ui_preferences_vpn_page import Ui_PreferencesVpnPage
     
     from leap.bitmask.config.leapsettings import LeapSettings
     from leap.bitmask.gui.flashable import Flashable
    +from leap.bitmask.gui.preferences_page import PreferencesPage
     
    -
    -class PreferencesVpnPage(QtGui.QWidget, Flashable):
    +class PreferencesVpnPage(PreferencesPage, Flashable):
     
         """
         Page in the preferences window that shows VPN settings
    @@ -41,19 +41,24 @@ class PreferencesVpnPage(QtGui.QWidget, Flashable):
             :param app: shared App instance
             :type app: App
             """
    -        QtGui.QWidget.__init__(self, parent)
    +        PreferencesPage.__init__(self, parent, account, app)
             self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic")
     
    -        self.account = account
    -        self.app = app
    -
             # Load UI
             self.ui = Ui_PreferencesVpnPage()
             self.ui.setupUi(self)
             self.ui.flash_label.setVisible(False)
             self.hide_flash()
     
    -        # Connections
    +        self.setup_connections()
    +
    +        # Trigger update
    +        self.app.backend.eip_get_gateways_list(domain=self.account.domain)
    +
    +    def setup_connections(self):
    +        """
    +        connect signals
    +        """
             self.ui.gateways_list.clicked.connect(self._save_selected_gateway)
             sig = self.app.signaler
             sig.eip_get_gateways_list.connect(self._update_gateways_list)
    @@ -61,8 +66,16 @@ class PreferencesVpnPage(QtGui.QWidget, Flashable):
             sig.eip_uninitialized_provider.connect(
                 self._gateways_list_uninitialized)
     
    -        # Trigger update
    -        self.app.backend.eip_get_gateways_list(domain=self.account.domain)
    +    def teardown_connections(self):
    +        """
    +        disconnect signals
    +        """
    +        self.ui.gateways_list.clicked.disconnect(self._save_selected_gateway)
    +        sig = self.app.signaler
    +        sig.eip_get_gateways_list.disconnect(self._update_gateways_list)
    +        sig.eip_get_gateways_list_error.disconnect(self._gateways_list_error)
    +        sig.eip_uninitialized_provider.disconnect(
    +            self._gateways_list_uninitialized)
     
         def _save_selected_gateway(self, index):
             """
    diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py
    index 44c4641c..1facba69 100644
    --- a/src/leap/bitmask/gui/preferenceswindow.py
    +++ b/src/leap/bitmask/gui/preferenceswindow.py
    @@ -20,18 +20,15 @@ Preferences window
     """
     from PySide import QtCore, QtGui
     
    -from leap.bitmask.services import EIP_SERVICE
    -from leap.bitmask._components import HAS_EIP
    -
     from leap.bitmask.logs.utils import get_logger
     from leap.bitmask.gui.ui_preferences import Ui_Preferences
    +from leap.bitmask.gui.preferences_page import PreferencesPage
     from leap.bitmask.gui.preferences_account_page import PreferencesAccountPage
     from leap.bitmask.gui.preferences_vpn_page import PreferencesVpnPage
     from leap.bitmask.gui.preferences_email_page import PreferencesEmailPage
     
     logger = get_logger()
     
    -
     class PreferencesWindow(QtGui.QDialog):
     
         """
    @@ -40,39 +37,46 @@ class PreferencesWindow(QtGui.QDialog):
     
         _current_window = None  # currently visible preferences window
     
    -    def __init__(self, parent, account, app):
    +    def __init__(self, parent, app):
             """
             :param parent: parent object of the PreferencesWindow.
             :parent type: QWidget
     
    -        :param account: the user or provider
    -        :type account: Account
    -
             :param app: the current App object
             :type app: App
             """
             QtGui.QDialog.__init__(self, parent)
     
    -        self.account = account
             self.app = app
     
             self.ui = Ui_Preferences()
             self.ui.setupUi(self)
     
    -        self.ui.close_button.clicked.connect(self.close)
    -        self.ui.account_label.setText(account.address)
    -
    -        self.app.service_selection_changed.connect(self._update_icons)
    +        self._account_page = None
    +        self._vpn_page = None
    +        self._email_page = None
     
             self._add_icons()
    -        self._add_pages()
    -        self._update_icons(self.account, self.account.services())
    +        self._set_account(app.current_account())
    +        self._setup_connections()
     
             # only allow a single preferences window at a time.
             if PreferencesWindow._current_window is not None:
                 PreferencesWindow._current_window.close()
             PreferencesWindow._current_window = self
     
    +    def _set_account(self, account):
    +        """
    +        Initially sets, or resets, the currently viewed account.
    +        The account might not represent an authenticated user, but
    +        just a domain.
    +        """
    +        self.ui.account_label.setText(account.address)
    +        self._add_pages(account)
    +        self._update_icons(account)
    +        self.ui.pages_widget.setCurrentIndex(0)
    +        self.ui.nav_widget.setCurrentRow(0)
    +
         def _add_icons(self):
             """
             Adds all the icons for the different configuration categories.
    @@ -114,22 +118,71 @@ class PreferencesWindow(QtGui.QDialog):
             email_item.setSizeHint(QtCore.QSize(98, 56))
             self._email_item = email_item
     
    -        self.ui.nav_widget.currentItemChanged.connect(self._change_page)
    -        self.ui.nav_widget.setCurrentRow(0)
    -
    -    def _add_pages(self):
    +    def _add_pages(self, account):
             """
             Adds the pages for the different configuration categories.
             """
    -        self._account_page = PreferencesAccountPage(
    -            self, self.account, self.app)
    -        self._vpn_page = PreferencesVpnPage(self, self.account, self.app)
    -        self._email_page = PreferencesEmailPage(self, self.account, self.app)
    -
    +        self._remove_pages() # in case different account was loaded.
    +
    +        # load placeholder widgets if the page should not be loaded.
    +        # the order of the pages is important, and must match the order
    +        # of the nav_widget icons.
    +        self._account_page = PreferencesAccountPage(self, account, self.app)
    +        if account.has_eip():
    +            self._vpn_page = PreferencesVpnPage(self, account, self.app)
    +        else:
    +            self._vpn_page = PreferencesPage(self)
    +        if account.has_email():
    +            self._email_page = PreferencesEmailPage(self, account, self.app)
    +        else:
    +            self._email_page = PreferencesPage(self)
             self.ui.pages_widget.addWidget(self._account_page)
             self.ui.pages_widget.addWidget(self._vpn_page)
             self.ui.pages_widget.addWidget(self._email_page)
     
    +    def _remove_pages(self):
    +        # deleteLater does not seem to cascade to items in stackLayout
    +        # (even with QtCore.Qt.WA_DeleteOnClose attribute).
    +        # so, here we call deleteLater() explicitly.
    +        if self._account_page is not None:
    +            self.ui.pages_widget.removeWidget(self._account_page)
    +            self._account_page.teardown_connections()
    +            self._account_page.deleteLater()
    +        if self._vpn_page is not None:
    +            self.ui.pages_widget.removeWidget(self._vpn_page)
    +            self._vpn_page.teardown_connections()
    +            self._vpn_page.deleteLater()
    +        if self._email_page is not None:
    +            self.ui.pages_widget.removeWidget(self._email_page)
    +            self._email_page.teardown_connections()
    +            self._email_page.deleteLater()
    +
    +    def _setup_connections(self):
    +        """
    +        setup signal connections
    +        """
    +        self.ui.nav_widget.currentItemChanged.connect(self._change_page)
    +        self.ui.close_button.clicked.connect(self.close)
    +        self.app.service_selection_changed.connect(self._update_icons)
    +        sig = self.app.signaler
    +        sig.srp_auth_ok.connect(self._login_status_changed)
    +        sig.srp_logout_ok.connect(self._login_status_changed)
    +        sig.srp_status_logged_in.connect(self._update_account)
    +        sig.srp_status_not_logged_in.connect(self._update_account)
    +
    +    def _teardown_connections(self):
    +        """
    +        clean up signal connections
    +        """
    +        self.ui.nav_widget.currentItemChanged.disconnect(self._change_page)
    +        self.ui.close_button.clicked.disconnect(self.close)
    +        self.app.service_selection_changed.disconnect(self._update_icons)
    +        sig = self.app.signaler
    +        sig.srp_auth_ok.disconnect(self._login_status_changed)
    +        sig.srp_logout_ok.disconnect(self._login_status_changed)
    +        sig.srp_status_logged_in.disconnect(self._update_account)
    +        sig.srp_status_not_logged_in.disconnect(self._update_account)
    +
         #
         # Slots
         #
    @@ -144,13 +197,8 @@ class PreferencesWindow(QtGui.QDialog):
             Close this dialog and destroy it.
             """
             PreferencesWindow._current_window = None
    -
    -        # deleteLater does not seem to cascade to items in stackLayout
    -        # (even with QtCore.Qt.WA_DeleteOnClose attribute).
    -        # so, here we call deleteLater() explicitly:
    -        self._account_page.deleteLater()
    -        self._vpn_page.deleteLater()
    -        self._email_page.deleteLater()
    +        self._teardown_connections();
    +        self._remove_pages();
             self.deleteLater()
     
         def _change_page(self, current, previous):
    @@ -170,17 +218,24 @@ class PreferencesWindow(QtGui.QDialog):
                 current = previous
             self.ui.pages_widget.setCurrentIndex(self.ui.nav_widget.row(current))
     
    -    def _update_icons(self, account, services):
    +    def _update_icons(self, account):
             """
             TRIGGERS:
                 self.app.service_selection_changed
     
             Change which icons are visible.
             """
    -        if account != self.account:
    -            return
    +        self._vpn_item.setHidden(not account.has_eip())
    +        self._email_item.setHidden(not account.has_email())
    +
    +    def _login_status_changed(self):
    +        """
    +        Triggered by signal srp_auth_ok, srp_logout_ok
    +        """
    +        self.app.backend.user_get_logged_in_status()
     
    -        if HAS_EIP:
    -            self._vpn_item.setHidden(EIP_SERVICE not in services)
    -        # self._email_item.setHidden(not MX_SERVICE in services)
    -        # ^^ disable email for now, there is nothing there yet.
    +    def _update_account(self):
    +        """
    +        Triggered by get srp_status_logged_in, srp_status_not_logged_in
    +        """
    +        self._set_account(self.app.current_account())
    diff --git a/src/leap/bitmask/gui/ui/preferences.ui b/src/leap/bitmask/gui/ui/preferences.ui
    index 5e30ea57..51cad0a1 100644
    --- a/src/leap/bitmask/gui/ui/preferences.ui
    +++ b/src/leap/bitmask/gui/ui/preferences.ui
    @@ -6,8 +6,8 @@
        
         0
         0
    -    520
    -    439
    +    630
    +    500
        
       
    @@ -60,6 +60,24 @@ 16777215 + + background: palette(base); border: 1px solid palette(dark); border-radius: 2px; + + + QFrame::StyledPanel + + + QFrame::Plain + + + 1 + + + 0 + + + Qt::ScrollBarAlwaysOff + 32 diff --git a/src/leap/bitmask/gui/ui/preferences_email_page.ui b/src/leap/bitmask/gui/ui/preferences_email_page.ui index 7cc5bb3c..5f83426b 100644 --- a/src/leap/bitmask/gui/ui/preferences_email_page.ui +++ b/src/leap/bitmask/gui/ui/preferences_email_page.ui @@ -6,13 +6,572 @@ 0 0 - 400 - 300 + 526 + 605 Form + + + 0 + + + 0 + + + + + 2 + + + + + + + + + + Tab 1 + + + + + + Thunderbird Configuration + + + + + + thunderbird information + + + true + + + + + + + + + + + 0 + 0 + + + + Mail Client Configuration + + + + + + mail client information + + + true + + + + + + + + + + + + 0 + 0 + + + + Host + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + Port + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + + + + true + + + + + + + + 0 + 0 + + + + TLS: off + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + IMAP + + + + + + + Username + + + + + + + SMTP + + + + + + + + + + 0 + 0 + + + + Host + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + + + + true + + + + + + + + 0 + 0 + + + + Port + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + TLS: off + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + true + + + + + + + + + true + + + + + + + Password + + + + + + + true + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Tab 2 + + + + + + + + + Public Key + + + + + + + + 0 + 0 + + + + + Courier + + + + QPlainTextEdit::NoWrap + + + true + + + + + + + + 0 + 42 + + + + + 16777215 + 48 + + + + + Courier + 50 + false + + + + false + + + + + + QPlainTextEdit::NoWrap + + + true + + + + + + + + + + Expiration + + + + + + + Fingerprint + + + + + + + true + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true + + + + + + + Address + + + + + + + true + + + + + + + + + Export Private Key + + + + + + + Import Private Key + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + uid_edit + fp_label + uid_label + expiration_edit + expiration_label + fingerprint_edit + public_key_edit + pub_label + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Page + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + Qt::ElideRight + + + true + + + + Email + + + + + Key ID + + + + + + + + + + + + this message should be hidden + + + +
    -- cgit v1.2.3 From 5b90ad3552025436edb40665203ab98eceaa065b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 19 Apr 2016 12:07:09 -0400 Subject: pep8/flake8 --- src/leap/bitmask/core/service.py | 4 +-- src/leap/bitmask/gui/account.py | 1 + src/leap/bitmask/gui/app.py | 4 +-- src/leap/bitmask/gui/mainwindow.py | 8 +++--- src/leap/bitmask/gui/preferences_account_page.py | 1 - src/leap/bitmask/gui/preferences_email_page.py | 36 ++++++++++++------------ src/leap/bitmask/gui/preferences_page.py | 6 ++-- src/leap/bitmask/gui/preferences_vpn_page.py | 2 +- src/leap/bitmask/gui/preferenceswindow.py | 7 +++-- src/leap/bitmask/gui/statemachines.py | 5 +++- src/leap/bitmask/services/eip/vpnprocess.py | 1 - 11 files changed, 39 insertions(+), 36 deletions(-) diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py index 87349ef6..13c8864a 100644 --- a/src/leap/bitmask/core/service.py +++ b/src/leap/bitmask/core/service.py @@ -29,7 +29,7 @@ from leap.bitmask.core import _zmq from leap.bitmask.core import websocket from leap.bonafide.service import BonafideService from leap.common.events import server as event_server -#from leap.vpn import EIPService +# from leap.vpn import EIPService class BitmaskBackend(configurable.ConfigurableService): @@ -96,7 +96,7 @@ class BitmaskBackend(configurable.ConfigurableService): def init_eip(self): # FIXME -- land EIP into leap.vpn pass - #self._maybe_start_service('eip', EIPService) + # self._maybe_start_service('eip', EIPService) def init_zmq(self): zs = _zmq.ZMQServerService(self) diff --git a/src/leap/bitmask/gui/account.py b/src/leap/bitmask/gui/account.py index 5e43c8fe..b8b9509a 100644 --- a/src/leap/bitmask/gui/account.py +++ b/src/leap/bitmask/gui/account.py @@ -22,6 +22,7 @@ from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.services import EIP_SERVICE, MX_SERVICE from leap.bitmask._components import HAS_EIP, HAS_MAIL + class Account(): def __init__(self, username, domain): diff --git a/src/leap/bitmask/gui/app.py b/src/leap/bitmask/gui/app.py index e3a4d7fe..1011454e 100644 --- a/src/leap/bitmask/gui/app.py +++ b/src/leap/bitmask/gui/app.py @@ -60,8 +60,8 @@ class App(QtGui.QWidget): def current_account(self): """ - Alas, the only definitive account information is buried in the memory of - QT widgets. + Alas, the only definitive account information is buried in the memory + of QT widgets. :returns: an object representing the current user account. :rtype: Account diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index ca14e631..daf49eb6 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -154,11 +154,11 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._providers = Providers(self.ui.cmbProviders) ## - ## tmphack: important state information about the application is stored - ## in widgets. Rather than rewrite the UI, for now we simulate this - ## info being stored in an application object: + # tmphack: important state information about the application is stored + # in widgets. Rather than rewrite the UI, for now we simulate this + # info being stored in an application object: ## - self.app.login_state = self._login_widget._state + self.app.login_state = self._login_widget._state self.app.providers_widget = self._providers # Qt Signal Connections ##################################### diff --git a/src/leap/bitmask/gui/preferences_account_page.py b/src/leap/bitmask/gui/preferences_account_page.py index c175c42b..141523c8 100644 --- a/src/leap/bitmask/gui/preferences_account_page.py +++ b/src/leap/bitmask/gui/preferences_account_page.py @@ -69,7 +69,6 @@ class PreferencesAccountPage(PreferencesPage): self.app.signaler.prov_get_supported_services.connect( self._load_services) - def teardown_connections(self): """ disconnect signals diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index 50f244fb..8211aeb8 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -18,8 +18,6 @@ Widget for "email" preferences """ from PySide import QtCore, QtGui -from datetime import datetime - from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui.ui_preferences_email_page import Ui_PreferencesEmailPage from leap.bitmask.gui.preferences_page import PreferencesPage @@ -27,6 +25,7 @@ from leap.mail.imap.service.imap import IMAP_PORT logger = get_logger() + class PreferencesEmailPage(PreferencesPage): def __init__(self, parent, account, app): @@ -46,11 +45,11 @@ class PreferencesEmailPage(PreferencesPage): # the only way to set the tab titles is to re-add them: self.ui.email_tabs.addTab(self.ui.config_tab, - self.tr("Mail Client")) + self.tr("Mail Client")) self.ui.email_tabs.addTab(self.ui.my_key_tab, - self.tr("My Key")) + self.tr("My Key")) self.ui.email_tabs.addTab(self.ui.other_keys_tab, - self.tr("Other Keys")) + self.tr("Other Keys")) # set mail client configuration help text lang = QtCore.QLocale.system().name().replace('_', '-') @@ -61,7 +60,7 @@ class PreferencesEmailPage(PreferencesPage): "For Thunderbird, you can use the Bitmask extension. " "Search for \"Bitmask\" in the add-on manager or " "download it from addons.mozilla.org.".format( - thunderbird_extension_url))) + thunderbird_extension_url))) self.ui.mail_client_label.setText(self.tr( "Alternatively, you can manually configure your mail client to " "use Bitmask Email with these options:")) @@ -71,7 +70,6 @@ class PreferencesEmailPage(PreferencesPage): self.setup_connections() - def setup_connections(self): """ connect signals @@ -106,7 +104,7 @@ class PreferencesEmailPage(PreferencesPage): self.ui.message_label.setText( self.tr('You must be logged in to edit email settings.')) else: - self.ui.import_button.setVisible(False) # hide this until working + self.ui.import_button.setVisible(False) # hide this until working self.ui.message_label.setVisible(False) self.ui.email_tabs.setVisible(True) smtp_port = 2013 @@ -128,7 +126,7 @@ class PreferencesEmailPage(PreferencesPage): Set the current user's key details into the gui. """ self.ui.fingerprint_edit.setPlainText( - self._format_fingerprint(details["fingerprint"])) + self._format_fingerprint(details["fingerprint"])) self.ui.expiration_edit.setText(details["expiry_date"]) self.ui.uid_edit.setText(" ".join(details["uids"])) self.ui.public_key_edit.setPlainText(details["key_data"]) @@ -139,8 +137,8 @@ class PreferencesEmailPage(PreferencesPage): produces, wrapped to two lines. """ fp = fingerprint.upper() - fp_list = [fp[i:i+4] for i in range(0, len(fp), 4)] - fp_wrapped = " ".join(fp_list[0:5]) + "\n" + " ".join(fp_list[5:10]) + fp_list = [fp[i:i + 4] for i in range(0, len(fp), 4)] + fp_wrapped = " ".join(fp_list[0:5]) + "\n" + " ".join(fp_list[5:10]) return fp_wrapped def _export_keys(self): @@ -156,8 +154,8 @@ class PreferencesEmailPage(PreferencesPage): if not file_name.endswith('.pem'): file_name += '.pem' self.app.backend.keymanager_export_keys( - username=self.account.address, - filename=file_name) + username=self.account.address, + filename=file_name) else: logger.debug('Export canceled by the user.') @@ -186,9 +184,9 @@ class PreferencesEmailPage(PreferencesPage): "Export canceled.")) def _import_keys(self): - """ - not yet supported - """ + """ + not yet supported + """ def _keymanager_keys_list(self, keys): """ @@ -203,5 +201,7 @@ class PreferencesEmailPage(PreferencesPage): for key in keys: row = self.ui.keys_table.rowCount() self.ui.keys_table.insertRow(row) - self.ui.keys_table.setItem(row, 0, QtGui.QTableWidgetItem(key.address)) - self.ui.keys_table.setItem(row, 1, QtGui.QTableWidgetItem(key.fingerprint)) + self.ui.keys_table.setItem( + row, 0, QtGui.QTableWidgetItem(key.address)) + self.ui.keys_table.setItem( + row, 1, QtGui.QTableWidgetItem(key.fingerprint)) diff --git a/src/leap/bitmask/gui/preferences_page.py b/src/leap/bitmask/gui/preferences_page.py index c75b4991..a5d811f9 100644 --- a/src/leap/bitmask/gui/preferences_page.py +++ b/src/leap/bitmask/gui/preferences_page.py @@ -14,10 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ -base class for preference pages +Base class for preference pages """ -from PySide import QtCore, QtGui +from PySide import QtGui + class PreferencesPage(QtGui.QWidget): @@ -47,4 +48,3 @@ class PreferencesPage(QtGui.QWidget): disconnect signals must be overridden by subclass """ - diff --git a/src/leap/bitmask/gui/preferences_vpn_page.py b/src/leap/bitmask/gui/preferences_vpn_page.py index fc15340f..87b86c4e 100644 --- a/src/leap/bitmask/gui/preferences_vpn_page.py +++ b/src/leap/bitmask/gui/preferences_vpn_page.py @@ -20,10 +20,10 @@ Widget for "vpn" preferences from PySide import QtCore, QtGui from leap.bitmask.gui.ui_preferences_vpn_page import Ui_PreferencesVpnPage -from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.gui.flashable import Flashable from leap.bitmask.gui.preferences_page import PreferencesPage + class PreferencesVpnPage(PreferencesPage, Flashable): """ diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 1facba69..82dc8d77 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -29,6 +29,7 @@ from leap.bitmask.gui.preferences_email_page import PreferencesEmailPage logger = get_logger() + class PreferencesWindow(QtGui.QDialog): """ @@ -122,7 +123,7 @@ class PreferencesWindow(QtGui.QDialog): """ Adds the pages for the different configuration categories. """ - self._remove_pages() # in case different account was loaded. + self._remove_pages() # in case different account was loaded. # load placeholder widgets if the page should not be loaded. # the order of the pages is important, and must match the order @@ -197,8 +198,8 @@ class PreferencesWindow(QtGui.QDialog): Close this dialog and destroy it. """ PreferencesWindow._current_window = None - self._teardown_connections(); - self._remove_pages(); + self._teardown_connections() + self._remove_pages() self.deleteLater() def _change_page(self, current, previous): diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py index ab48b756..92c5431d 100644 --- a/src/leap/bitmask/gui/statemachines.py +++ b/src/leap/bitmask/gui/statemachines.py @@ -40,6 +40,7 @@ class SignallingState(QState): """ A state that emits a custom signal on entry. """ + def __init__(self, signal, parent=None, name=None): """ Initializer. @@ -134,6 +135,7 @@ class States(object): class CompositeEvent(QtCore.QEvent): + def __init__(self): super(CompositeEvent, self).__init__( QtCore.QEvent.Type(self.ID)) @@ -174,6 +176,7 @@ class Events(QtCore.QObject): A Wrapper object for containing the events that will be posted to a composite state machine. """ + def __init__(self, parent=None): """ Initializes the QObject with the given parent. @@ -289,6 +292,7 @@ class ConnectionMachineBuilder(object): """ Builder class for state machines made from LEAPConnections. """ + def __init__(self, connection): """ :param connection: an instance of a concrete LEAPConnection @@ -352,7 +356,6 @@ class ConnectionMachineBuilder(object): :rtype: QStateMachine """ # TODO split this method in smaller utility functions. - parent = kwargs.get('parent', None) # 1. create machine machine = CompositeMachine() diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index de8d92f3..580bd572 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -23,7 +23,6 @@ import shutil import socket import subprocess import sys -import time from itertools import chain, repeat -- cgit v1.2.3 From ca2e16bb9fbef938bc17b232e801d484d9602ec4 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 19 Apr 2016 12:09:43 -0400 Subject: [docs] add email prefs to changelog --- changes/feature-7552_mail-help-message-on-ui | 1 - changes/feature-email-preferences-panel | 1 - changes/next-changelog.rst | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 changes/feature-7552_mail-help-message-on-ui delete mode 100644 changes/feature-email-preferences-panel diff --git a/changes/feature-7552_mail-help-message-on-ui b/changes/feature-7552_mail-help-message-on-ui deleted file mode 100644 index 44ab8fa7..00000000 --- a/changes/feature-7552_mail-help-message-on-ui +++ /dev/null @@ -1 +0,0 @@ -- Add UI message to help new mail users to get started. Closes feature #7552. diff --git a/changes/feature-email-preferences-panel b/changes/feature-email-preferences-panel deleted file mode 100644 index e9bbfb6a..00000000 --- a/changes/feature-email-preferences-panel +++ /dev/null @@ -1 +0,0 @@ -- Add email panel to preferences window. \ No newline at end of file diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst index 4f09b291..ca74746f 100644 --- a/changes/next-changelog.rst +++ b/changes/next-changelog.rst @@ -16,6 +16,7 @@ Features - `#6041 `_: Write service tokens to a file to allow email clients to read them from there. - Use cred-based authentication on SMTP. - Experimental support for the Pixelated WebMail. +- Add email panel to preferences window. - Ability to launch detached bitmask.core daemon, and a simplistic bitmask_cli. Not used by the main client yet. - `#1234 `_: Description of the new feature corresponding with issue #1234. -- cgit v1.2.3 From 80078e7e85cb40485e74bf7cf9a93d9db5592400 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 19 Apr 2016 12:17:59 -0400 Subject: [pkg] bump version_compat with keymanager version --- changes/VERSION_COMPAT | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changes/VERSION_COMPAT b/changes/VERSION_COMPAT index 660f9640..cd698ae7 100644 --- a/changes/VERSION_COMPAT +++ b/changes/VERSION_COMPAT @@ -11,3 +11,5 @@ # # BEGIN DEPENDENCY LIST ------------------------- # leap.foo.bar>=x.y.z + +leap.keymanager >= 0.5.1 -- cgit v1.2.3 From 9a0568c18ed38558a481d5f6e4be12c68f9870a6 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 19 Apr 2016 13:14:54 -0400 Subject: [feature] enable webmail from the mail preferences window this box is only visible when the HAS_PIXELATED flag is True, that means we have pixelated-user-agent and pixelated-www in the environment. - Releases: 0.9.2 --- src/leap/bitmask/config/leapsettings.py | 2 +- src/leap/bitmask/gui/preferences_email_page.py | 19 ++++++++++++++ src/leap/bitmask/gui/ui/preferences_email_page.ui | 30 +++++++++++++++++++++-- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index 01900484..075be8a7 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -360,4 +360,4 @@ class LeapSettings(object): def set_pixelmail_enabled(self, enabled): leap_assert_type(enabled, bool) - self._settings.setvalue(self.PIXELMAIL_KEY, enabled) + self._settings.setValue(self.PIXELMAIL_KEY, enabled) diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index 8211aeb8..b8633f07 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -18,11 +18,14 @@ Widget for "email" preferences """ from PySide import QtCore, QtGui +from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui.ui_preferences_email_page import Ui_PreferencesEmailPage from leap.bitmask.gui.preferences_page import PreferencesPage +from leap.bitmask.pix import HAS_PIXELATED from leap.mail.imap.service.imap import IMAP_PORT + logger = get_logger() @@ -40,6 +43,7 @@ class PreferencesEmailPage(PreferencesPage): :type app: App """ PreferencesPage.__init__(self, parent, account, app) + self.settings = LeapSettings() self.ui = Ui_PreferencesEmailPage() self.ui.setupUi(self) @@ -65,6 +69,16 @@ class PreferencesEmailPage(PreferencesPage): "Alternatively, you can manually configure your mail client to " "use Bitmask Email with these options:")) + self.ui.webmail_label.setText(self.tr( + "This distribution of Bitmask ships an experimental integration " + "of the Pixelated " + "Webmail. It is not stable yet, but you can enable it to help " + "beta-testing it. (Needs restart!)")) + webmail_enabled = self.settings.get_pixelmail_enabled() + self.ui.webmail_checkbox.setChecked(webmail_enabled) + if not HAS_PIXELATED: + self.ui.webmail_box.setVisible(False) + self.ui.keys_table.horizontalHeader().setResizeMode( 0, QtGui.QHeaderView.Stretch) @@ -81,6 +95,7 @@ class PreferencesEmailPage(PreferencesPage): self._keymanager_export_error) self.ui.import_button.clicked.connect(self._import_keys) self.ui.export_button.clicked.connect(self._export_keys) + self.ui.webmail_checkbox.stateChanged.connect(self._toggle_webmail) def teardown_connections(self): """ @@ -205,3 +220,7 @@ class PreferencesEmailPage(PreferencesPage): row, 0, QtGui.QTableWidgetItem(key.address)) self.ui.keys_table.setItem( row, 1, QtGui.QTableWidgetItem(key.fingerprint)) + + def _toggle_webmail(self, state): + value = True if state == QtCore.Qt.Checked else False + self.settings.set_pixelmail_enabled(value) diff --git a/src/leap/bitmask/gui/ui/preferences_email_page.ui b/src/leap/bitmask/gui/ui/preferences_email_page.ui index 5f83426b..22d690d9 100644 --- a/src/leap/bitmask/gui/ui/preferences_email_page.ui +++ b/src/leap/bitmask/gui/ui/preferences_email_page.ui @@ -23,7 +23,7 @@ - 2 + 0 @@ -35,7 +35,7 @@ Tab 1 - + @@ -332,6 +332,32 @@ + + + + Webmail Configuration + + + + + + Enable Bitmask Webmail (beta) + + + + + + + webmail info + + + true + + + + + + -- cgit v1.2.3 From a68bbe5e69a3481ede78f518b1637761e8f8cd01 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 20 Apr 2016 12:44:15 -0300 Subject: [bug] let the failure propagate The failure was processed in start_incoming_mail_service what will make it return a None when an IncomingMail object was expected. If we propagate the failure it can be treated properly by the IMAPController. - Related: #8051 --- src/leap/bitmask/services/mail/imap.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py index 2f000b2a..7875a4af 100644 --- a/src/leap/bitmask/services/mail/imap.py +++ b/src/leap/bitmask/services/mail/imap.py @@ -94,5 +94,4 @@ def start_incoming_mail_service(keymanager, soledad, userid): acc = Account(soledad, userid) d = acc.callWhenReady(lambda _: acc.get_collection_by_mailbox(INBOX_NAME)) d.addCallback(setUpIncomingMail) - d.addErrback(log.err) return d -- cgit v1.2.3 From b6bf547e3a8b96b5fcdba739ed4be74bf040a7ed Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 25 Apr 2016 17:25:36 -0400 Subject: [refactor] move the pixelated panel upwards --- src/leap/bitmask/gui/preferences_email_page.py | 7 +-- src/leap/bitmask/gui/qt_browser.py | 2 +- src/leap/bitmask/gui/ui/preferences_email_page.ui | 54 +++++++++++------------ 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index b8633f07..f6d6f036 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -71,9 +71,10 @@ class PreferencesEmailPage(PreferencesPage): self.ui.webmail_label.setText(self.tr( "This distribution of Bitmask ships an experimental integration " - "of the Pixelated " - "Webmail. It is not stable yet, but you can enable it to help " - "beta-testing it. (Needs restart!)")) + "of Pixelated " + "Mail. Note: at the current state, anyone with access to " + "your device can read your mail without authentication, " + "by opening a browser pointing to http://localhost:9090 ")) webmail_enabled = self.settings.get_pixelmail_enabled() self.ui.webmail_checkbox.setChecked(webmail_enabled) if not HAS_PIXELATED: diff --git a/src/leap/bitmask/gui/qt_browser.py b/src/leap/bitmask/gui/qt_browser.py index b75bfb64..c62e7770 100644 --- a/src/leap/bitmask/gui/qt_browser.py +++ b/src/leap/bitmask/gui/qt_browser.py @@ -33,7 +33,7 @@ class PixelatedWindow(QtGui.QDialog): layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.view) self.setLayout(layout) - self.setWindowTitle('Bitmask/Pixelated WebMail') + self.setWindowTitle('Bitmask Mail') def load_app(self): self.view.load(QtCore.QUrl(PIXELATED_URI)) diff --git a/src/leap/bitmask/gui/ui/preferences_email_page.ui b/src/leap/bitmask/gui/ui/preferences_email_page.ui index 22d690d9..87e7121d 100644 --- a/src/leap/bitmask/gui/ui/preferences_email_page.ui +++ b/src/leap/bitmask/gui/ui/preferences_email_page.ui @@ -36,6 +36,32 @@ Tab 1 + + + + Bitmask Mail Configuration + + + + + + Enable Bitmask Mail (needs restart) + + + + + + + webmail info + + + true + + + + + + @@ -64,7 +90,7 @@ - Mail Client Configuration + Other Mail Clients Configuration @@ -332,32 +358,6 @@ - - - - Webmail Configuration - - - - - - Enable Bitmask Webmail (beta) - - - - - - - webmail info - - - true - - - - - - -- cgit v1.2.3 From dd31801ba7361271001a81a53b0ba688375be419 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 25 Apr 2016 17:26:09 -0400 Subject: [docs] bury the history in docs folder --- HISTORY.rst | 870 ------------------------------------------------------- docs/HISTORY.rst | 870 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 870 insertions(+), 870 deletions(-) delete mode 100644 HISTORY.rst create mode 100644 docs/HISTORY.rst diff --git a/HISTORY.rst b/HISTORY.rst deleted file mode 100644 index ba0df9ef..00000000 --- a/HISTORY.rst +++ /dev/null @@ -1,870 +0,0 @@ -.. :history:: - -History -------- - -==== -2015 -==== - -0.9.1 November 03 - "the day of the calaca" -+++++++++++++++++++++++++++++++++++++++++++ - -Features -~~~~~~~~ -- `#7542 `_: Pin mail.bitmask.net provider. Closes feature #7542. - -Bugfixes -~~~~~~~~ -- `#7563 `_: try to look for /usr/bin/gpg1 -- `#7562 `_: use zmq embedded minitornado, instead of system lib. - -0.9.0 October 28 -++++++++++++++++ - -Features -~~~~~~~~ - -- `#4284 `_: Download specific smtp certificate from provider, instead of using the vpn one. -- `#5526 `_: Make "check" button selected by default. -- `#6359 `_: Adapt bitmask to the new events api on leap.common. -- `#6360 `_: Use txzmq in backend. -- `#6368 `_: Add support to the new async-api of keymanager. -- `#6683 `_: Add ability to generate sumo tarball. -- `#6713 `_: Add support for xfce-polkit agent. -- `#6876 `_: Update api port for pinned riseup. -- `#7139 `_: Use logbook zmq handler to centralize logging. -- `#7140 `_: Implement a thread-safe zmq handler for logbook. -- `#7141 `_: Add log handler to display colored logs on the terminal. -- `#7142 `_: Add log handler to store logs on bitmask.log. -- `#7143 `_: Adapt existing log filter/silencer to the new logbook handler. -- `#7144 `_: Replace logging handler with logbook handler bitmask-wide. -- `#7162 `_: Log LSB-release info if available. -- `#7180 `_: Add log rotation for bitmask.log. -- `#7184 `_: Forward twisted logs to logging and handle logging logs with logbook. -- `#7250 `_: Enable '--danger' for stable versions. -- `#7291 `_: Move the updater code from the launcher to the client. -- `#7342 `_: Added apply_updates.py script for the pyinstaller bundle. -- `#7353 `_: Add notifications of soledad sync progress to UI. -- `#7356 `_: Allow to disable EIP component on build. -- `#7414 `_: Remove taskthread dependency, replace with custom (and small) code. -- `#7419 `_: Load credentials from environment variables and trigger login. -- `#7471 `_: Disable email firewall if we are running inside a docker container. -- Add support to the new async-api of soledad - -Bugfixes -~~~~~~~~ - -- `#6418 `_: Cannot change preseeded providers if checks for one fail. -- `#6424 `_: Do not disable autostart if the quit is triggered by a system logout. -- `#6536 `_, `#6568 `_, `#6691 `_: Refactor soledad sync to do it the twisted way. -- `#6541 `_: Client must honor the ports specified in eip-service.json. -- `#6594 `_: Handle disabled registration on provider. -- `#6654 `_: Regression fix, login attempt is made against previously selected provider. -- `#6682 `_: Handle user cancel keyring open operation, this prevents a bitmask freeze. -- `#6894 `_: Change 'ip' command location to support Fedora/RHEL distros. -- `#7093 `_: Fix controller attribute error. -- `#7126 `_: Don't run the event server on the backend for the standalone bundle since the launcher takes care of that. -- `#7149 `_: Start the events server when reactor is running. -- `#7185 `_: Log contains exported PGP Private Key. -- `#7222 `_: Run the zmq log subscriber in the background to avoid hitting the zmq's buffer limits. -- `#7273 `_: Logbook subscriber stop fails if not started. -- `#7273 `_: ZMQError: address already in use - logbook subscriber already started. -- `#7281 `_: Support a provider not providing location for the eip gateways. -- `#7319 `_: Raise the maxfiles limit in OSX -- `#7343 `_: Clean up and fix the tests. -- `#7415 `_: Fix wrong argument number on window raise event. -- `#7448 `_: Fix hangs during logout. -- `#7451 `_: Assign the timeout 'call later' before starting the sync to prevent race conditions. -- `#7453 `_: After a complete sync show the user the amount of unread emails. -- `#7470 `_: Fix bug with password change. -- `#7474 `_: Track soledad ready state on a shared place for easy access. Enable password change window. -- `#7503 `_: Handle soledad init fail after several retries. -- `#7512 `_: Pass on standalone flag to common. -- `#7512 `_: Store logs in the right place. -- `#7512 `_: Store zmq certs in the right path. -- Authenticate properly logout calls to API. -- Fix soledad bootstrap sync retries. -- Fix the bootstrap script for developers so it works on Fedora/RHEL systems where there is /usr/lib64 for python libs. -- Remove bubble argument from the logbook NullHandler - -0.8.1 February 25 -+++++++++++++++++ - -Features -~~~~~~~~ -- `#6646 `_: Gracefully fall back to ZMQ ipc sockets with restricted access if CurveZMQ is not available. -- `#6717 `_: Split changes log into changelog and history. - -Bugfixes -~~~~~~~~ -- `#6654 `_: Regression fix, login attempt is made against previously selected provider. -- `#6058 `_: Support 'nobody' (used on Arch) as well as 'nogroup' as group names. - -0.8.0 January 04 -- "Charlie and the code refactory" -++++++++++++++++++++++++++++++++++++++++++++++++++++ - -Features -~~~~~~~~ -- #5873: Allow frontend and backend to be run separately. -- Refactor login widgets/logic. -- Improved changelog :). - -Bugfixes -~~~~~~~~ -- #6058: Support 'nobody' (used on Arch) as well as 'nogroup' as group names. -- #6123: Forward the right environment data to subprocess call. -- #6150: Do not allow Bitmask to start if there is no polkit agent running. -- #6631: Fix failing tests. -- #6638: Fix set initialization to support python 2.6. -- #6652: Fix regression: polkit agent is not automatically launched. -- #6654: Login attempt is made against previously selected provider. -- Create zmq certificates if they don't exist. -- Disable '--offline' flag temporarily. -- Make pkg/tuf/release.py handle removals in the repo. -- Reduce the wait for running threads timeout on quit. - -==== -2014 -==== - -0.7.0 December 12 -- the "One window to rule them all, and in the darkness bind them." release: -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -- Select current provider on EIP preferences. Closes #5815. -- Handle logout correctly when we stop_services to launch the - wizard. Related to #5815. -- Properly remove /tmp/bitmask.lock. Closes #5866. -- Hide EIP Start button and display correct warning on missing helpers - files. Closes #5945. -- Save default provider if changed on the combo box. Closes #5995. -- Update the EIP status on provider change. Closes #5996. -- Update and get ready to start a provider on change. Closes #5997. -- Use python2 to run bitmask-root to work fine on systems with python3 - as default. Closes #6048. -- Use python2.7 in bitmask-root shebang since is the common name for - python 2 in Ubuntu, Debian, Arch. Related to #6048. -- Remove dict comprenension in util, for 2.6 compat. -- Login shall not wait for eip to finish if eip is not able to - start. Closes #5994 -- Properly send the token for querying the EIP certificate. Fixes - #6060. -- Code cleanup and logging improvements. -- Add email firewall blocking other users to access bitmask imap & - smtp. Closes #6040 -- Remove the Advanced Key Management since we don't support stable - mail yet. Closes #6087. -- Single combined preferences window. Closes #4704, #4119, #5885. -- Fix soledad imports (#5989). -- Make pkg/tuf/release.py handle removals in the repo -- Remove instructions/references of mail from the client. Closes #6140. -- Add support for the internal LXDE polkit agent. Closes #6043. -- Allow the server to set a custom --fragment openvpn option (#5933) -- Add Calyx.net as pinned provider. Closes #6518. - -0.6.1 August 15 -- the "knock knock knocking on beta's door" release: -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -- Add checks to ensure that the backend is alive or notify the - user. Related to #5873. -- Stop the backend if the frontend process does not exist any more and - backend is not a daemon. Related to #5873. -- Add autostart on Linux. Closes #4989. -- Pressing ESC on 'install helper files' defaults to No. Closes #5819. -- Replace twisted thread with QThread and get pastebin send - working. Closes #5949. -- Wait until EIP is up to autologin. Closes #5939 -- Fix the handling of vpn launcher errors in the UI. Closes: #5955 -- Fix logger window blocking the bitmask quit(). -- Set the standalone value for BaseConfig according to the global - flags. -- Improve Hide and Show Window behavior on Ubuntu. Fixes #5511. -- Use smaller height on the window so it fits better on smaller - resolutions. Closes #5722. -- Disable daemon mode when we run the backend so we can spawn child - processes on it. -- Restrict access to the zmq certificates folder. -- Stop the services if the selected provider is changed. Related to - #4704. Closes #5912, #5554. -- Minor adjustments to the layout of UI elements. Fixes #5514, #5515, - #5510. -- Moved provider selection dropdown menu to be at the top of the main - windowUse same user/password restrictions as in the webapp. Closes - #5894. - -0.6.0 July 18 -- the "nothing to see here" release: -+++++++++++++++++++++++++++++++++++++++++++++++++++ - -- Initial sync message is confusing. Closes #5875. -- Use preferred provider on first run. Closes #5813. -- Add TUF init repository and release tools. Closes #5864. -- Add support for fingerprint-gui's polkit agent. Closes #5880. -- Reroute DNS packets instead of blocking them, eliminating need to - muck around with resolv.conf. Closes #4633, #5655, #5738, #4823 -- Use inline decrypting for initial soledad syncrhonization, to wait - for secrets. -- Add the ability to create an osx bundle with py2app. Closes #5845. -- Split frontend/backend in different files. Closes #5719. -- Implement ZMQ based messaging system. Closes #5733. -- Launch the backend in a different process than the app. Closes - #5734. - -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: -+++++++++++++++++++++++++++++++++++++++++++++++ - -- Unblock local multicast IPs from linux firewall, to allow SSDP and - Bonjour/mDNS to work. -- Add support for gnome-shell polkit agent. Closes #4144, #4218. -- Update username regex to support the same as webapp. Closes #5965. -- Wrong error message for username too short. Fixes #5697. -- Cleanup and refactor username/password validators. -- Fix EIP autostart failing. Closes #5721. -- Block ipv6 traffic for the moment. Closes #5693 -- Fix bug with ipv6 blocking that caused block to not get removed from - firewall when Bitmask quit. -- Bring firewall down when switching EIP off. Closes #5687 -- Add OPENVPN_BIN_PATH for OSX so that EIP starts properly. -- Allow usernames to end in a digit. -- Improve signal handling in the mainwindow and wizard. -- Enable UI when OpenVPN bin is not found, plus check before starting - EIP. Fixes #5619. -- Properly set the userid for SMTP. -- Update EIP UI if it fails to download the config. -- Make use of cmdline in psutil backwards-compatible. Closes #5689 -- Add versioning support to bitmask-root. -- Show flag of country for eip exit node, if available. Related #1232 -- Fix nameserver restoring. Closes #5692 -- Warn user if resolvconf cannot be found. -- Refactor Keymanager to backend. Closes #5711. -- Cleanup backend from hacks. Closes #5698. -- Improve wait and quit process. -- Move soledad password change to backend. -- Move Mail logic to backend. -- Separate imap/smtp logic from conductor. -- Refactor SoledadBootstrapper to backend. Closes #5481. - -0.5.1 May 16 -- the "lil less leaky" release: -+++++++++++++++++++++++++++++++++++++++++++++ - -- Use non blocking dialog so the Pastebin result does not block the - app. Closes #5404. -- Handle provider setup problems and show an error to the user. Closes - #5424. -- Disable providers combo box during check and enable combo or line - edit depending on radio button. Closes #5495. -- Hide the bandwidth widget and update status icon if the openvpn - process is killed. Closes #5497. -- Change password doesn't work. Closes #5540. -- Hide services that the current logged in provider does not - have. Closes #5550. -- If we don't have a provider supporting that service we hide the - actions along with the widgets. Related to #5550. -- Client mistakenly says that traffic is routed in the clear. Closes - #5551. -- Avoid user getting errors if he does a 'ctrl-c' on the wizard during - the first run. Closes #5559. -- Download/upload rates were displayed backwards in the widget - rate. Closes #5563. -- Fix unable to login issue. Closes #5581. -- Hardcode paths for openvpn if STANDALONE=True. Related: #5592 -- Increase waiting time to wait for polkit agent to be up. Closes: - #5595 -- Use openvpn hard restart. Closes: #5669 -- Enable Turn ON button for EIP whenever possible (json and cert are - in place). Fixes #5665, #5666. -- Fix Logout button bottom margin. Fixes #4987. -- Properly finish the Qt app before stopping the reactor. -- Let OpenVPN run its course when a ping-restart happens. Fixes #5564. -- Refactor smtp logic into its bootstrapper. -- Add flag to allow the user to start the app hidden in the - tray. Closes #4990. -- Refactor: move SRPAuth to the backend. Closes #5347. -- Refactor: move EIP to backend. Closes #5349. -- Use PySide @Slot decorator instead of 'SLOT' docstring. Closes - #5506. -- Advanced key management: show a note to the user if the provider - does not support Encrypted Email. Closes #5513. -- Gracefully handle SIGTERM, with addSystemEventTrigger twisted - reactor's method. Closes #5672. -- Hide the main window on quit as first thing and show a tooltip to - inform that we are closing. -- Increase expiration life of a pastebin log from 1 week to 1 month. -- Use iptables firewall. Closes: #5588 -- Refactor Soledad initialization retries to SoledadBootstrapper. -- Refactor EIPBootstrapper to the backend. Closes #5348. -- Add flag to skip provider checks in wizard (only for testing). -- Add support for Mate's polkit agent. - -0.5.0 Apr 4 -- the "Long time no see" release: -++++++++++++++++++++++++++++++++++++++++++++++ -- Fix logging out typo, closes #4815. -- Improve logout action, related to #5131. -- In case of soledad bootstrap error (e.g.: network failure), re run - all the setup process. -- Correct resolvconf usage. Avoids permanent break of - resolv.conf. Closes #4633. -- Disable and stop EIP when you set EIP as disabled in the preferences - dialog. Closes #4670. -- Advanced Key Management: add view for stored public keys. Closes - #4734. -- Reset registration error and input widgets if the user goes back to - provider selection in wizard. Closes #4742. -- Disconnect signals before closing the wizard. Closes #4817. -- Fix logout error message, display it similarly to other errors in - the app. Closes #4942. -- Client should say 1 unread email, not emails. Closes #4952. -- Update menu name in Wizard. Closes #4984. -- Config help menu: do not use an empty password. Closes #4985. -- Handle wizard close correctly. Closes #4986. -- Fix "Something went wrong with the logout" misleading error in every - logout. Closes #4995 and #5071. -- Use version checks in the wizard when the user choose to use an - existing provider. Closes #5048. -- Move error messages from srpauth to the GUI and refactor - signals. Closes #5219. -- Fix psutil version to avoid conflicts with gnupg required - version. Closes #5309. -- Update bitmask url in PKG-INFO. Closes #5395. -- Disable 'next' button if the checks passed but the provider is - changed. Closes #5396. -- Do not start soledad and mail if the mail service is - disabled. Closes #5411. -- Don't escape logs for pastebin. Closes #5433. -- Handle closed Soledad database on quit, speedup exit. Closes #5130. -- Catch shutdown errors. Closes: #5313 -- Properly reset imap session on logout. Closes: #4925 -- Sync Soledad before bootstrapping mail only if the key for the user - is not found locally. Otherwise, defer to thread and - continue. Closes #5083. -- Set as selected default for the eip preferences window the item - selented in the bitmask main window. Closes #5153. -- Cancel login does not work or needs to be pressed twice. Closes - #4869, #4973. -- Fail gracefully against keyring import errors. -- Update requirements and code for the new psutil version. -- Use Bitmask icon instead of LEAP's for the super user dialog in - OSX. Fixes #4273. -- Workaround a bug in Ubuntu where the menu is not displayed in the - global menu bar. Fixes #5420. -- Wizard: select by default the use of an existing provider if we have - configured at least one. Closes #4488. -- Add in-app indication of how to connect to local imap and - smtp. Closes #4530. -- Warn the user on incompatible api error. -- Warn the user if is using an old app version. Closes #4636. -- Minor UI changes: re-arrange main window so that the login widget is - at the top and preferences are available under the menu. -- Disable Advanced Key Manager import feature since it's experimental - and may cause data loss. Closes #4877. -- Offline mode for debugging. Closes: #4943 -- Add pastebin button to upload logs from the logs window to ease bug - report. Closes #5163. -- Add support for self signed certs. Closes #5391. -- Add hotkey for the Help menu. Closes #5401. -- Add --repair-mailboxes command line option. It will be needed to - migrate existing account after a data schema changes, like it will - be happening for 0.5.0. Closes #4792. -- Make first Soledad sync wait for EIP to come up after logging in. - Fixes #4885. -- Ensure IMAP flushes data to disk before quitting. Closes #5095. -- Update key manager auth to interact with webapp v2. Fixes #5120. -- Handle invalid auth tokens when syncing Soledad, and show an error - on the GUI. Fixes #5191. -- After connecting EIP check for DNS resolution and warn the user on - error. Closes #5301. -- Display domain for provider the user has just logged in. Fixes - #4631. -- Add ability to import a maildir into a local mailbox. -- Add ability to write mail logs to a separate file. -- Show hash info in About bitmask (for debian versions). -- Add the appname in the reported version string. -- Move/refactor SRPRegister to the backend. -- Add ability to nice application via environment variable. -- Refactor ProviderBootstrapper out of the UI modules to a Backend - module, obscuring all the details. -- Remove qt4reactor as a dependency. - -==== -2013 -==== - -0.3.8 Dec 6 -- the "Three week child" release: -+++++++++++++++++++++++++++++++++++++++++++++++ -- Make the preferences window selects the current selected provider in - the login widget even if the user is not logged in. Closes #4490. -- Support non-ascii characters in a provider name. Closes #4952. -- Disable Turn On EIP in tray if the service is disabled. Closes #4630. -- Do not show the generic message "EIP has stopped" since it's - redundant. Fixes #4632. -- Avoid attempt to install policykit file in debian package. Closes: - #4404 -- Properly close Soledad at quit time. Fixes #4504. -- Fix soledad bootstrap subtasks order. Closes #4537. -- Add --nobind as a VPN parameter to prevent binding on local - addresses. Fixes #4543. -- Disable Turn On EIP until we have an usable provider. Closes #4523. -- Load provider if the wizard was rejected and the setup was - completed. -- Disable Turn On EIP if the "Encrypted Internet" service is disabled. - Closes #4555. -- If EIP service is disabled display 'Disabled' instead of 'You need - to login to use Encrypted Internet'. -- Disable eip-config until we have configured the provider. Closes - #4422. - -0.3.7 Nov 15 -- the "The Big Lebowsky" release: -+++++++++++++++++++++++++++++++++++++++++++++++ -- Use custom SysTray in order to display per-service tooltip easily. - Closes #3998. -- Escape logs with html contents so they get displayed in plaintext - on the log viewer. Closes #4146. -- Wizard now behaves correctly in provider selection after click - 'cancel' or 'back'. Closes #4148. -- Handle Timeout errors during register process. Closes #4358. -- Send user's key to nickserver whenever keymanager is - initialized. Closes #4364. -- Password change dialog is now properly enabled. Closes #4449. -- Remember provider checks in wizard, do not re-run them if the user - goes back and forth through the wizard. Closes #3814 and #3815. -- Improve compatibility with OSX Mavericks. Fixes #4379. -- Initialize mail service with the userid after login, to allow - multiple accounts. Closes: #4394 -- Give SMTP the current logged in userid. Related to #3952. -- Do not wait for initial soledad sync to complete to launch mail - services. Closes: #4452 -- Add hint to user about the duration of the key generation. Closes - #3958. -- Add advanced key management feature. Closes #4448. -- Properly log EIP status changes. - -0.3.6 Nov 1 -- the "bạn có thể đọc này?" release: -+++++++++++++++++++++++++++++++++++++++++++++++++ - -- Fix problem changing a non-ascii password. Closes #4003. -- Enable password change in the client only if it has started the - correct services. Closes #4093. -- Select the current logged in provider in the preferences - window. Closes #4117. -- Fix problem with non-ascii paths. Closes #4189. -- Capture soledad boostrap errors after latest soledad changes. -- Refactor keyring handling and make it properly save user and - password. Fixes #4190. -- Properly stop the imap daemon at logout. Fixes #4199. -- Align left the speed and transferred displays for EIP. Fixes #4204. -- Remove autostart eip option from settings panel, rely on last used - setting. Closes #4132. -- Add support for requests 1.1.0 (raring). Closes: #4308 -- Refactor mail connections to use state machine. Closes: #4059 -- Add a command to setup.py to freeze the versions reported under - debian branches. Closes: #4315 -- Use coloredlogs handler if present (for development, not a - requirement). -- Hide the GUI for services that are not supported on the set of - configured providers. Closes #4170. - -0.3.5 Oct 18 -- the "I can stand on one foot" release: -++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -- In case of Soledad failure, display to the user that there was a - problem. Closes #4025. -- Widget squashing problem in wizard checking a new provider. Closes - #4058. -- Remember last domain used to login. Closes #4116. -- Display first run wizard, regardless of pinned providers. Closes - #4143. -- Show EIP status 'ON' in the systray tooltip when is - connected. Related to #3998. -- Catch u1db errors during soledad initialization. -- Disable --danger flag on release versions. Closes #4124. -- Display mail status in the tray icon as an enabled item. Fixes - #4036. -- Only show N unread Emails when N > 0. Fixes #4098. -- Hide login error message when the user interacts with the widgets - to fix the potential problem. Fixes #4022. -- Add call to `make` to the bootstrap script. -- Improve GUI based on QA rounds. Fixes #4041 and #4042. -- Increase the amount of retries for the authentication request - session. Fixes #4037. -- Rename EIP to Encrypted Internet in its preference panel. Fixes - #4057. -- Disable stdout redirection on Windows for the time being since it - breaks the bundle. -- Default UP_SCRIPT and DOWN_SCRIPT to None and only add that - parameter to the vpn command if not None. -- Look for gpg on windows with the .exe extension. -- Change the Util menu to be named File in OSX. Fixes #4039. -- Show more context information in the logs. Closes #3923. -- Automate internationalization process, create project file - dynamically on make. Closes #3925. -- Add support for running lxde polkit agent. Closes #4028. -- Added Vietnamese and English (United Kingdom) translations. -- Implements openvpn observer. Closes: #3901 -- Reconnect EIP if network down. Closes #3790 -- Reconnect if tls-restart. Closes: #3262 - -0.3.4 Oct 4 -- the "look at my new makeup" release: -+++++++++++++++++++++++++++++++++++++++++++++++++++ - -- Fixes a bug where you cannot login to a different provider once - you logged in to another one. Fixes #3695. -- Resets the session for every login attempt. Related to #3695. -- Avoid error message if --version flag is used. Closes #3914. -- Fix a bug in which failing to authenticate properly left - connection in an unconsistent state. Closes: #3926 -- Avoids errors due to the EIP switch button and action being - enabled when we do not have a configured provider. Closes: #3927 -- Add more verbose error handling during key generation and syncing. - Helps diagnose: #3985; Addresses in part: #3965 -- Choose one gnupg binary path that is also not a symlink. Closes - #3999. -- Refactor vpn launchers, reuse code, improve implementations, - update documentation. Closes #2858. -- Add preferences option to enable/disable the automatic start of - EIP and selection of the EIP provider to auto start. Closes #3631. -- Force cleanlooks style for kde only if the app is running from - bundle. Closes #3981. -- Add a dropdown for known providers in the wizard. Closes #3995. -- Separate pinned providers from user configures ones. Closes #3996. -- Improve error handling during soledad bootstrap. Closes: #3965. - Affects: #3619, #3867, #3966 -- Implement new UI design. Closes #3973. -- Make the initial provider cert verifications against our modified - CA-bundle (includes ca-cert certificates, for now). Closes: #3850 -- Use token header for authenticated requests. Closes #3910. -- Do not distinguish between different possible authentication - errors. Fixes #3859. -- Do not start Soledad if Mail is not enabled. Fixes #3989. -- Allow window minization on OSX. Fixes #3932. -- Properly stop the smtp daemon. Fixes #3873. - -0.3.3 Sep 20 -- "the calm after the tempest" release: -+++++++++++++++++++++++++++++++++++++++++++++++++++++ - -- Remove execution bits in text files in bundle. Closes #3617. -- Use generic bad username/password message instead of specific ones when - the user uses incorrect data during login. Closes #3656. -- Fix LoggerWindow saving more than one line return per line in the logs - file. Closes #3714. -- Fix keyring imports so we do not get import errors. Closes: #3759 -- Catch logout problem, display a user message and allow log back in after a - successful logout if there was a logout error before. Closes #3774. -- Fix path prefix helper for the bundle and add regresion tests. Closes #3778. -- Prevent dialogs closing the app when it has been minimized to the tray. Closes #3791. -- Do not try to install resolv-update globally. Closes: #3803 -- Inconsistent hide/show main window from tray action. Closes #3821. -- Allow SMTP to start even when provider does not offer EIP. Closes: #3847 -- Fix username case problem at register/login. Closes #3857. -- Catch IndexError on `first` utility. -- Update git repo name in docs. Closes: #3417 -- Move STANDALONE flag to a module and unify get_path_prefix queries. - Closes #3636. -- Display the Encrypted Internet and Encrypted Email status in the systray - tooltip. Closes #3758. -- Tasktray menu changes, closes #3792. -- Remove the provider domain item (e.g. bitmask.net). -- Rename the EIP status menu items to be more descriptive. -- Change the EIP status menu items from disabled menu items - to submenus with children. -- Move the EIP action menu items under the EIP status submenu tree. -- Adds ``--version`` flag. Closes: #3816 -- Refactors EIPConnection to use LEAPConnection state machine. Closes: #3900 -- Include resource files and ui in the distrubution tarball. Closes: #3825 - -0.3.2 Sep 6 -- the "no crashes or anything" release: -++++++++++++++++++++++++++++++++++++++++++++++++++++ - -- Fix up script in non-bundle linuces. Closes: #3450 -- Logout stops imap and smtp services. Closes: #3553 -- Properly daemonize polkit-gnome-authentication-agent. Closes: #3554 -- Set appropiate error on login cancel. Closes #3582. -- Fix gateway selection problem. Closes 3595. -- Fix typo in wizard: stablish -> establish. Closes #3615. -- Display Encrypted Mail instead of mx in wizard. Closes #3657. -- Fix save logs to file dialog freezing. Closes #3675. -- Complain if setup.py is run with python3. Closes: #3711 -- Enable preferences option in systray. Closes #3717. -- Make soledad emit failed signal for all kinds of socket error. -- Allow to selectively silence logs from different leap components. Closes: #3504 -- Add option to select gateway manually in the preferences panel. Closes #3505. -- Add preferences option to select the enabled services of a provider. Closes #3534. -- Refactor basic password checks. Closes #3552. -- Use dirspec instead of plain xdg. Closes #3574. -- Remove last page from wizard. Closes #3616. -- Display encrypted mail status in the tray. Closes #3659. - -0.3.1 Aug 23: -+++++++++++++ - -- Replace wizard images with the rainbow mask. Closes #3425. -- Update leap.common minimum version needed. -- Set the standalone flag before it's being used. Fixes #3426. -- Stop the twisted reactor adding the stop call to the call chain - instead of stopping it directly. Fixes #3406. -- Allow soledad initialization to retry if it times out. Closes: - #3413 -- Activate window when setting it visible. Also display Hide/Show - message in the tray icon taking into account the window - activation. Fixes #3433. -- Do not start IMAP daemon if mail was not selected among the - services. Fixes #3435. -- Reword RECONNECTING state of openvpn. Fixes #3429. -- Improve OpenVPN detection by searching for a specific leap-only - string in the command line. This makes it possible to run other - VPN instances while also using EIP. Fixes #3268 and #3364. -- OSX: Check for the tun.kext existence in /Library/Extensions - instead of /System/Library/Extensions. Fixes #3271. -- Use DELETE /1/logout to properly logout. Fixes #3510. -- Make the poll interval bigger to improve openvpn's internal - behavior. If it gets queried too many times per second, it's - behavior won't be good. Fixes #3430. -- Transforms usernames to lower case before they are used in the - registration and authentication. Closes #3541. -- Add filter option to the logger window. Closes #3407. -- Add a preference panel that lets you change your password. Closes - #3500 #2798 #3533. -- Move all client code into its own namespace - (leap.bitmask). Closes: #2959 -- Make mail fetch interval in imap service configurable via - environment variable. Closes: #3409 -- Update to new soledad package scheme (common, client and - server). Closes #3487. -- Fetch incoming mail when mail client logs in. Closes: #3525 -- Add first draft of the UI for Encrypted Mail. Closes #3499. - -0.3.0 Aug 9: -++++++++++++ - -- Add missing scripts does not stop if a command fails, also warns - the user if there was an error. Closes #3294. -- Replace 'Sign Out' with 'Log Out' and 'User' with - 'Username'. Closes #3319. -- Verify cacert existence before using it. Closes bug #3362. -- Properly handle login failures. Closes bug #3401. -- Bugfix, avoid getting negative rates. Closes #3274. -- Raise window when setting it as visible. Fixes #3374 -- Fail gracefully when the events port 8090 is in use by something - else. Fixes #3276. -- Validate the username in the login form against the same regexp as - the wizard registration form. Fixes #3214. -- Update text from the tray menu based on the visibility of the - window. Fixes #3400. -- Add check for outdated polkit file. Closes #3209. -- Add support for multiple schemas so we can support multiples api - versions. Closes #3310. -- Rebrand the client to be named Bitmask. Feature #3313. -- Add cancel button to login. Closes #3318. -- Add multiple schema support for SMTP. Closes #3403. -- Add multiple schema support for Soledad. Closes #3404. -- Update Transifex project name and translators' - documentation. Closes #3418. -- Add check for tuntap kext before launching openvpn. Closes: #2906 -- Accept flag for changing openvpn verbosity in logs. Closes: #3305 -- Add imap service to the client. Closes: #2579 -- Add pyside-uic support inside the virtualenv. This way it won't - fail to 'make' if the virtualenv is activated. Closes #3411. -- Reintegrate SMTP relay module. Closes #3375 -- Reintegrate Soledad into the client. Closes #3307. -- Support bundled gpg. Related to #3397. -- Set the default port for SMTP to be 2013. -- Display a more generic error message in the main window, and leave - the detailed one for the log. Closes #3373. - -0.2.4 Jul 26: -+++++++++++++ - -- Use the provider CA cert for every request once we have it - bootstrapped (TOFU). Closes #3227. -- Make calls to leap.common.events asynchronous. Closes #2937. -- Always logout when closing the app if the user previously signed - in. Fixes #3245. -- Make sure the domain field in provider.json is escaped to avoid - potential problems. Fixes #3244. -- Fix incorrect handling of locks in Windows so that stalled locks - do not avoid raising the first instance of the app. Closes: #2910 -- Use traffic rates instead of totals. Closes #2913 -- Allow to alternate between rates and total throughput for the - virtual interface. Closes: #3232 -- Reset rates/totals when terminating connection. Closes #3249 -- Fix a bug in the displayed magnitude for the up/down traffic rates - and totals. -- Force Cleanlooks style if we are running in a KDE environment, so - that it doesn't load potentially incompatible Qt libs. Fixes - #3194. -- Wrap long login status messages to 40 characters. Fixes #3124 -- Workaround a segmentation fault when emitting a signal with its - last parameter being None. Fixes #3083. -- Added IS_RELEASE_VERSION flag that allows us to use code only in - develop versions. Closes #3224. -- Try to terminate already running openvpn instances. Closes #2916 -- Linux: Dynamically generate policy file for polkit. Closes #3208 -- Workaround some OpenVPN problems with priviledge dropping and - routing. Fixes #3178 #3135 #3207 #3203 - -0.2.3 Jul 12: -+++++++++++++ - -- Adapt code to Soledad 0.2.1 api. -- Fix Main Window briefly display before the wizard on first - start. Closes Bug #2954. -- Bugfix: Remember should not be automatically set to - checked. Closes #2955. -- Bugfix: reload config if switching to a different provider. Closes - #3067. -- Bugfix: logger window's toggle button reflects window - state. Closes #3152. -- Set timeout for requests to 10 seconds globally, configurable from - leap.util.constants. Fixes #2878. -- Bugfix: display error message on registration problem. Closes - #3039. -- Make wizard use the main event loop, ensuring clean termination. -- Use cocoasudo for installing missing updown scripts. -- Bugfix: Systray Turn ON action fails because is not correctly - enabled/disabled. Closes #3125. -- Bugfix: wrong systray icon on startup. Closes #3147. -- Bugfix: parse line return in the logger window. Closes #3151. -- Do not log user data on registration. Fixes #3168. -- Add --log-append eip.log to windows EIP launcher options to save - the logs in case of any problems. Fixes #2054. -- OSX: Make the install_path relative to the launcher path instead - -f absolute. -- OSX: Fix icon display in cocoasudo. -- OSX: Raise window when showing if running on OSX. -- Bugfix: EIP status button moved to status panel. -- Check if there is no gateway to use and display correct - message. Close #2921. -- Reorder tray icons according new design. Closes #2919. -- Redirect stdout/stderr and twisted log to the logger. Closes - #3134. -- Improve LoggerWindow colors for easier debugging. -- Move the key manager to its own repository/package. - -0.2.2 Jun 28: -+++++++++++++ - -- Add support for the kde polkit daemon -- Handle 'Incorrect Password' exception (keyring) -- Select the configured domain in the providers combo box. Closes - #2693. -- Remember provider along with the username and password. Closes - #2755. -- Close the app on rejected wizard. Closes bug #2905. -- Only use the Keyring when it's using a known good backend. Closes - #2960 -- Update implementation and semantics of the supported and available - services by a provider. Closes bug #3032. -- Only show the question mark for a check being done if the previous - -ne passed. Fixes #2569. -- Fix main client window not restoring after minimized into - systray. Closes #2574 -- Set EIP different status icons depending on OS. Closes #2643. -- Reimplement openvpn invocation to use twisted ProcessProtocol -- Add runtime requirements checker, verifies that the requirements - are installed and in its correct versions. Closes #2563 -- Add centraliced logging facility, log history in a window. Closes - #2566 -- Improve wizard, hide registration widgets (labels, inputs, button) - and only display a message. Closes #2694 -- Clarify labels through the app (use of EIP) -- Check if the provider api version is supported. Closes feature - #2774. -- Autoselect VPN gateway based on timezone. Closes #2790. -- Disable vpn disconnect on logout. Closes #2795. -- Improve gateway selector based on timezone. It allows to use - multiple gateways in openvpn for redundancy. Closes #2894. -- Use cocoasudo in place of osascript for osx privilege escalation - during openvpn launch. -- Clicking in the tray icon will always show the context menu - instead of activating the window under certain - circumstances. Closes #2788 -- Autostart EIP whenever possible. Closes #2815 -- Update test suite, run_scripts and requirements to run smoothly - with buildbot. -- Add a copy of the processed requirements to util/ -- Display the default provider configured in the systray menu. Close - #2813 -- Make the login steps be a chain of defers in order to be able to - have more cancel points for the whole procedure. Closes #2571 -- Linux: check for up/down scripts and policy files and ask user for - permission to install them in a root-writeable location. Used from - within bundle or for broken installations. -- Integrate SMTP-Relay into the client. -- Integrate Soledad and KeyManager. -- Move the KeyManager from leap.common to leap-client. -- Only use one systray icon, repesenting the status for EIP. Closes - #2762 -- Properly set the binary manifest to the windows openvpn - binary. Closes #203 -- OSX: Add dialog with suggestion to install up/down scripts if - these not found. Closes: #1264, #2759, #2249 -- Workaround for PySide breaking with multiple inheritance. Closes - #2827 -- Refactor login to its own widget and remove Utils menu. Closes - #2789 -- Refactor the status bits out of the MainWindow to its own - StatusPanelWidget. Closes #2792 -- Save the default provider to be used for autostart EIP as - DefaultProvider in leap.conf. Closes #2793 -- Cleanly terminate openvpn process, sending SIGTERM and SIGKILL - after a while. Closes #2753 -- Use twisted's deferToThread and Deferreds to handle parallel tasks -- Use a qt4 reactor for twisted, for launching leap twisted - services. - -0.2.1 May 15: -+++++++++++++ - -- Rewrite most of the client based on the insight gained so far. -- Deselecting the remember checkbox makes the app not populate - user/password values on the login widget. Closes #2059 -- Rewording of setup steps in wizard, to make them more meaningful - to the non-technical user. Closes #2061 -- Fix typo in wizard. -- Fix multiple drawing of services if going back. -- Make registration errors show in red. -- Add a warning if EIP service needs admin password. Addresses part - -f #2062 -- Make traffic indicators display fixed precision. Closes #2114 -- Do not hide the application if the user right clicked the system - tray icon. -- Sanitize network-fetched content that is used to build openvpn - command. -- Avoids multiple instances of leap-client. Each new one just raises - the existing instance and quits. -- Use dark eip icons os osx. Closes #2130 -- Moves BaseConfig to leap.common.config. Closes #2164 -- Add handling for ASSIGN_IP state from OpenVPN in the mainwindow. -- Emit events notifying of the session_id and uid after - authentication so other services can make use of it. Closes #1957 -- Working packaging workflow with rewritten client, using - pyinstaller and platypus. -- Remove network checks temporarily until we find a good way of - doing it, and a good way to deal with them. -- Saves the token to allow token authenticated queries. -- Turn "leap" into namespace package, move common files to - leap_common package that can be shared by other LEAP projects. -- Support standalone configurations for distribution in thumbdrives - and the like. -- Add support for requests < 1.0.0 -- Tests infrastructure, and tests for crypto/srpauth and crypto/srpregister. -- Documentation updated for 0.2.1 release. -- Docstrings style changed to fit sphinx autodoc format. -- Add a simple UI to notify of pending updates. -- Add Windows support. -- Try to install TAP driver on Windows if no tap device is preset. - diff --git a/docs/HISTORY.rst b/docs/HISTORY.rst new file mode 100644 index 00000000..ba0df9ef --- /dev/null +++ b/docs/HISTORY.rst @@ -0,0 +1,870 @@ +.. :history:: + +History +------- + +==== +2015 +==== + +0.9.1 November 03 - "the day of the calaca" ++++++++++++++++++++++++++++++++++++++++++++ + +Features +~~~~~~~~ +- `#7542 `_: Pin mail.bitmask.net provider. Closes feature #7542. + +Bugfixes +~~~~~~~~ +- `#7563 `_: try to look for /usr/bin/gpg1 +- `#7562 `_: use zmq embedded minitornado, instead of system lib. + +0.9.0 October 28 +++++++++++++++++ + +Features +~~~~~~~~ + +- `#4284 `_: Download specific smtp certificate from provider, instead of using the vpn one. +- `#5526 `_: Make "check" button selected by default. +- `#6359 `_: Adapt bitmask to the new events api on leap.common. +- `#6360 `_: Use txzmq in backend. +- `#6368 `_: Add support to the new async-api of keymanager. +- `#6683 `_: Add ability to generate sumo tarball. +- `#6713 `_: Add support for xfce-polkit agent. +- `#6876 `_: Update api port for pinned riseup. +- `#7139 `_: Use logbook zmq handler to centralize logging. +- `#7140 `_: Implement a thread-safe zmq handler for logbook. +- `#7141 `_: Add log handler to display colored logs on the terminal. +- `#7142 `_: Add log handler to store logs on bitmask.log. +- `#7143 `_: Adapt existing log filter/silencer to the new logbook handler. +- `#7144 `_: Replace logging handler with logbook handler bitmask-wide. +- `#7162 `_: Log LSB-release info if available. +- `#7180 `_: Add log rotation for bitmask.log. +- `#7184 `_: Forward twisted logs to logging and handle logging logs with logbook. +- `#7250 `_: Enable '--danger' for stable versions. +- `#7291 `_: Move the updater code from the launcher to the client. +- `#7342 `_: Added apply_updates.py script for the pyinstaller bundle. +- `#7353 `_: Add notifications of soledad sync progress to UI. +- `#7356 `_: Allow to disable EIP component on build. +- `#7414 `_: Remove taskthread dependency, replace with custom (and small) code. +- `#7419 `_: Load credentials from environment variables and trigger login. +- `#7471 `_: Disable email firewall if we are running inside a docker container. +- Add support to the new async-api of soledad + +Bugfixes +~~~~~~~~ + +- `#6418 `_: Cannot change preseeded providers if checks for one fail. +- `#6424 `_: Do not disable autostart if the quit is triggered by a system logout. +- `#6536 `_, `#6568 `_, `#6691 `_: Refactor soledad sync to do it the twisted way. +- `#6541 `_: Client must honor the ports specified in eip-service.json. +- `#6594 `_: Handle disabled registration on provider. +- `#6654 `_: Regression fix, login attempt is made against previously selected provider. +- `#6682 `_: Handle user cancel keyring open operation, this prevents a bitmask freeze. +- `#6894 `_: Change 'ip' command location to support Fedora/RHEL distros. +- `#7093 `_: Fix controller attribute error. +- `#7126 `_: Don't run the event server on the backend for the standalone bundle since the launcher takes care of that. +- `#7149 `_: Start the events server when reactor is running. +- `#7185 `_: Log contains exported PGP Private Key. +- `#7222 `_: Run the zmq log subscriber in the background to avoid hitting the zmq's buffer limits. +- `#7273 `_: Logbook subscriber stop fails if not started. +- `#7273 `_: ZMQError: address already in use - logbook subscriber already started. +- `#7281 `_: Support a provider not providing location for the eip gateways. +- `#7319 `_: Raise the maxfiles limit in OSX +- `#7343 `_: Clean up and fix the tests. +- `#7415 `_: Fix wrong argument number on window raise event. +- `#7448 `_: Fix hangs during logout. +- `#7451 `_: Assign the timeout 'call later' before starting the sync to prevent race conditions. +- `#7453 `_: After a complete sync show the user the amount of unread emails. +- `#7470 `_: Fix bug with password change. +- `#7474 `_: Track soledad ready state on a shared place for easy access. Enable password change window. +- `#7503 `_: Handle soledad init fail after several retries. +- `#7512 `_: Pass on standalone flag to common. +- `#7512 `_: Store logs in the right place. +- `#7512 `_: Store zmq certs in the right path. +- Authenticate properly logout calls to API. +- Fix soledad bootstrap sync retries. +- Fix the bootstrap script for developers so it works on Fedora/RHEL systems where there is /usr/lib64 for python libs. +- Remove bubble argument from the logbook NullHandler + +0.8.1 February 25 ++++++++++++++++++ + +Features +~~~~~~~~ +- `#6646 `_: Gracefully fall back to ZMQ ipc sockets with restricted access if CurveZMQ is not available. +- `#6717 `_: Split changes log into changelog and history. + +Bugfixes +~~~~~~~~ +- `#6654 `_: Regression fix, login attempt is made against previously selected provider. +- `#6058 `_: Support 'nobody' (used on Arch) as well as 'nogroup' as group names. + +0.8.0 January 04 -- "Charlie and the code refactory" +++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Features +~~~~~~~~ +- #5873: Allow frontend and backend to be run separately. +- Refactor login widgets/logic. +- Improved changelog :). + +Bugfixes +~~~~~~~~ +- #6058: Support 'nobody' (used on Arch) as well as 'nogroup' as group names. +- #6123: Forward the right environment data to subprocess call. +- #6150: Do not allow Bitmask to start if there is no polkit agent running. +- #6631: Fix failing tests. +- #6638: Fix set initialization to support python 2.6. +- #6652: Fix regression: polkit agent is not automatically launched. +- #6654: Login attempt is made against previously selected provider. +- Create zmq certificates if they don't exist. +- Disable '--offline' flag temporarily. +- Make pkg/tuf/release.py handle removals in the repo. +- Reduce the wait for running threads timeout on quit. + +==== +2014 +==== + +0.7.0 December 12 -- the "One window to rule them all, and in the darkness bind them." release: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +- Select current provider on EIP preferences. Closes #5815. +- Handle logout correctly when we stop_services to launch the + wizard. Related to #5815. +- Properly remove /tmp/bitmask.lock. Closes #5866. +- Hide EIP Start button and display correct warning on missing helpers + files. Closes #5945. +- Save default provider if changed on the combo box. Closes #5995. +- Update the EIP status on provider change. Closes #5996. +- Update and get ready to start a provider on change. Closes #5997. +- Use python2 to run bitmask-root to work fine on systems with python3 + as default. Closes #6048. +- Use python2.7 in bitmask-root shebang since is the common name for + python 2 in Ubuntu, Debian, Arch. Related to #6048. +- Remove dict comprenension in util, for 2.6 compat. +- Login shall not wait for eip to finish if eip is not able to + start. Closes #5994 +- Properly send the token for querying the EIP certificate. Fixes + #6060. +- Code cleanup and logging improvements. +- Add email firewall blocking other users to access bitmask imap & + smtp. Closes #6040 +- Remove the Advanced Key Management since we don't support stable + mail yet. Closes #6087. +- Single combined preferences window. Closes #4704, #4119, #5885. +- Fix soledad imports (#5989). +- Make pkg/tuf/release.py handle removals in the repo +- Remove instructions/references of mail from the client. Closes #6140. +- Add support for the internal LXDE polkit agent. Closes #6043. +- Allow the server to set a custom --fragment openvpn option (#5933) +- Add Calyx.net as pinned provider. Closes #6518. + +0.6.1 August 15 -- the "knock knock knocking on beta's door" release: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +- Add checks to ensure that the backend is alive or notify the + user. Related to #5873. +- Stop the backend if the frontend process does not exist any more and + backend is not a daemon. Related to #5873. +- Add autostart on Linux. Closes #4989. +- Pressing ESC on 'install helper files' defaults to No. Closes #5819. +- Replace twisted thread with QThread and get pastebin send + working. Closes #5949. +- Wait until EIP is up to autologin. Closes #5939 +- Fix the handling of vpn launcher errors in the UI. Closes: #5955 +- Fix logger window blocking the bitmask quit(). +- Set the standalone value for BaseConfig according to the global + flags. +- Improve Hide and Show Window behavior on Ubuntu. Fixes #5511. +- Use smaller height on the window so it fits better on smaller + resolutions. Closes #5722. +- Disable daemon mode when we run the backend so we can spawn child + processes on it. +- Restrict access to the zmq certificates folder. +- Stop the services if the selected provider is changed. Related to + #4704. Closes #5912, #5554. +- Minor adjustments to the layout of UI elements. Fixes #5514, #5515, + #5510. +- Moved provider selection dropdown menu to be at the top of the main + windowUse same user/password restrictions as in the webapp. Closes + #5894. + +0.6.0 July 18 -- the "nothing to see here" release: ++++++++++++++++++++++++++++++++++++++++++++++++++++ + +- Initial sync message is confusing. Closes #5875. +- Use preferred provider on first run. Closes #5813. +- Add TUF init repository and release tools. Closes #5864. +- Add support for fingerprint-gui's polkit agent. Closes #5880. +- Reroute DNS packets instead of blocking them, eliminating need to + muck around with resolv.conf. Closes #4633, #5655, #5738, #4823 +- Use inline decrypting for initial soledad syncrhonization, to wait + for secrets. +- Add the ability to create an osx bundle with py2app. Closes #5845. +- Split frontend/backend in different files. Closes #5719. +- Implement ZMQ based messaging system. Closes #5733. +- Launch the backend in a different process than the app. Closes + #5734. + +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: ++++++++++++++++++++++++++++++++++++++++++++++++ + +- Unblock local multicast IPs from linux firewall, to allow SSDP and + Bonjour/mDNS to work. +- Add support for gnome-shell polkit agent. Closes #4144, #4218. +- Update username regex to support the same as webapp. Closes #5965. +- Wrong error message for username too short. Fixes #5697. +- Cleanup and refactor username/password validators. +- Fix EIP autostart failing. Closes #5721. +- Block ipv6 traffic for the moment. Closes #5693 +- Fix bug with ipv6 blocking that caused block to not get removed from + firewall when Bitmask quit. +- Bring firewall down when switching EIP off. Closes #5687 +- Add OPENVPN_BIN_PATH for OSX so that EIP starts properly. +- Allow usernames to end in a digit. +- Improve signal handling in the mainwindow and wizard. +- Enable UI when OpenVPN bin is not found, plus check before starting + EIP. Fixes #5619. +- Properly set the userid for SMTP. +- Update EIP UI if it fails to download the config. +- Make use of cmdline in psutil backwards-compatible. Closes #5689 +- Add versioning support to bitmask-root. +- Show flag of country for eip exit node, if available. Related #1232 +- Fix nameserver restoring. Closes #5692 +- Warn user if resolvconf cannot be found. +- Refactor Keymanager to backend. Closes #5711. +- Cleanup backend from hacks. Closes #5698. +- Improve wait and quit process. +- Move soledad password change to backend. +- Move Mail logic to backend. +- Separate imap/smtp logic from conductor. +- Refactor SoledadBootstrapper to backend. Closes #5481. + +0.5.1 May 16 -- the "lil less leaky" release: ++++++++++++++++++++++++++++++++++++++++++++++ + +- Use non blocking dialog so the Pastebin result does not block the + app. Closes #5404. +- Handle provider setup problems and show an error to the user. Closes + #5424. +- Disable providers combo box during check and enable combo or line + edit depending on radio button. Closes #5495. +- Hide the bandwidth widget and update status icon if the openvpn + process is killed. Closes #5497. +- Change password doesn't work. Closes #5540. +- Hide services that the current logged in provider does not + have. Closes #5550. +- If we don't have a provider supporting that service we hide the + actions along with the widgets. Related to #5550. +- Client mistakenly says that traffic is routed in the clear. Closes + #5551. +- Avoid user getting errors if he does a 'ctrl-c' on the wizard during + the first run. Closes #5559. +- Download/upload rates were displayed backwards in the widget + rate. Closes #5563. +- Fix unable to login issue. Closes #5581. +- Hardcode paths for openvpn if STANDALONE=True. Related: #5592 +- Increase waiting time to wait for polkit agent to be up. Closes: + #5595 +- Use openvpn hard restart. Closes: #5669 +- Enable Turn ON button for EIP whenever possible (json and cert are + in place). Fixes #5665, #5666. +- Fix Logout button bottom margin. Fixes #4987. +- Properly finish the Qt app before stopping the reactor. +- Let OpenVPN run its course when a ping-restart happens. Fixes #5564. +- Refactor smtp logic into its bootstrapper. +- Add flag to allow the user to start the app hidden in the + tray. Closes #4990. +- Refactor: move SRPAuth to the backend. Closes #5347. +- Refactor: move EIP to backend. Closes #5349. +- Use PySide @Slot decorator instead of 'SLOT' docstring. Closes + #5506. +- Advanced key management: show a note to the user if the provider + does not support Encrypted Email. Closes #5513. +- Gracefully handle SIGTERM, with addSystemEventTrigger twisted + reactor's method. Closes #5672. +- Hide the main window on quit as first thing and show a tooltip to + inform that we are closing. +- Increase expiration life of a pastebin log from 1 week to 1 month. +- Use iptables firewall. Closes: #5588 +- Refactor Soledad initialization retries to SoledadBootstrapper. +- Refactor EIPBootstrapper to the backend. Closes #5348. +- Add flag to skip provider checks in wizard (only for testing). +- Add support for Mate's polkit agent. + +0.5.0 Apr 4 -- the "Long time no see" release: +++++++++++++++++++++++++++++++++++++++++++++++ +- Fix logging out typo, closes #4815. +- Improve logout action, related to #5131. +- In case of soledad bootstrap error (e.g.: network failure), re run + all the setup process. +- Correct resolvconf usage. Avoids permanent break of + resolv.conf. Closes #4633. +- Disable and stop EIP when you set EIP as disabled in the preferences + dialog. Closes #4670. +- Advanced Key Management: add view for stored public keys. Closes + #4734. +- Reset registration error and input widgets if the user goes back to + provider selection in wizard. Closes #4742. +- Disconnect signals before closing the wizard. Closes #4817. +- Fix logout error message, display it similarly to other errors in + the app. Closes #4942. +- Client should say 1 unread email, not emails. Closes #4952. +- Update menu name in Wizard. Closes #4984. +- Config help menu: do not use an empty password. Closes #4985. +- Handle wizard close correctly. Closes #4986. +- Fix "Something went wrong with the logout" misleading error in every + logout. Closes #4995 and #5071. +- Use version checks in the wizard when the user choose to use an + existing provider. Closes #5048. +- Move error messages from srpauth to the GUI and refactor + signals. Closes #5219. +- Fix psutil version to avoid conflicts with gnupg required + version. Closes #5309. +- Update bitmask url in PKG-INFO. Closes #5395. +- Disable 'next' button if the checks passed but the provider is + changed. Closes #5396. +- Do not start soledad and mail if the mail service is + disabled. Closes #5411. +- Don't escape logs for pastebin. Closes #5433. +- Handle closed Soledad database on quit, speedup exit. Closes #5130. +- Catch shutdown errors. Closes: #5313 +- Properly reset imap session on logout. Closes: #4925 +- Sync Soledad before bootstrapping mail only if the key for the user + is not found locally. Otherwise, defer to thread and + continue. Closes #5083. +- Set as selected default for the eip preferences window the item + selented in the bitmask main window. Closes #5153. +- Cancel login does not work or needs to be pressed twice. Closes + #4869, #4973. +- Fail gracefully against keyring import errors. +- Update requirements and code for the new psutil version. +- Use Bitmask icon instead of LEAP's for the super user dialog in + OSX. Fixes #4273. +- Workaround a bug in Ubuntu where the menu is not displayed in the + global menu bar. Fixes #5420. +- Wizard: select by default the use of an existing provider if we have + configured at least one. Closes #4488. +- Add in-app indication of how to connect to local imap and + smtp. Closes #4530. +- Warn the user on incompatible api error. +- Warn the user if is using an old app version. Closes #4636. +- Minor UI changes: re-arrange main window so that the login widget is + at the top and preferences are available under the menu. +- Disable Advanced Key Manager import feature since it's experimental + and may cause data loss. Closes #4877. +- Offline mode for debugging. Closes: #4943 +- Add pastebin button to upload logs from the logs window to ease bug + report. Closes #5163. +- Add support for self signed certs. Closes #5391. +- Add hotkey for the Help menu. Closes #5401. +- Add --repair-mailboxes command line option. It will be needed to + migrate existing account after a data schema changes, like it will + be happening for 0.5.0. Closes #4792. +- Make first Soledad sync wait for EIP to come up after logging in. + Fixes #4885. +- Ensure IMAP flushes data to disk before quitting. Closes #5095. +- Update key manager auth to interact with webapp v2. Fixes #5120. +- Handle invalid auth tokens when syncing Soledad, and show an error + on the GUI. Fixes #5191. +- After connecting EIP check for DNS resolution and warn the user on + error. Closes #5301. +- Display domain for provider the user has just logged in. Fixes + #4631. +- Add ability to import a maildir into a local mailbox. +- Add ability to write mail logs to a separate file. +- Show hash info in About bitmask (for debian versions). +- Add the appname in the reported version string. +- Move/refactor SRPRegister to the backend. +- Add ability to nice application via environment variable. +- Refactor ProviderBootstrapper out of the UI modules to a Backend + module, obscuring all the details. +- Remove qt4reactor as a dependency. + +==== +2013 +==== + +0.3.8 Dec 6 -- the "Three week child" release: ++++++++++++++++++++++++++++++++++++++++++++++++ +- Make the preferences window selects the current selected provider in + the login widget even if the user is not logged in. Closes #4490. +- Support non-ascii characters in a provider name. Closes #4952. +- Disable Turn On EIP in tray if the service is disabled. Closes #4630. +- Do not show the generic message "EIP has stopped" since it's + redundant. Fixes #4632. +- Avoid attempt to install policykit file in debian package. Closes: + #4404 +- Properly close Soledad at quit time. Fixes #4504. +- Fix soledad bootstrap subtasks order. Closes #4537. +- Add --nobind as a VPN parameter to prevent binding on local + addresses. Fixes #4543. +- Disable Turn On EIP until we have an usable provider. Closes #4523. +- Load provider if the wizard was rejected and the setup was + completed. +- Disable Turn On EIP if the "Encrypted Internet" service is disabled. + Closes #4555. +- If EIP service is disabled display 'Disabled' instead of 'You need + to login to use Encrypted Internet'. +- Disable eip-config until we have configured the provider. Closes + #4422. + +0.3.7 Nov 15 -- the "The Big Lebowsky" release: ++++++++++++++++++++++++++++++++++++++++++++++++ +- Use custom SysTray in order to display per-service tooltip easily. + Closes #3998. +- Escape logs with html contents so they get displayed in plaintext + on the log viewer. Closes #4146. +- Wizard now behaves correctly in provider selection after click + 'cancel' or 'back'. Closes #4148. +- Handle Timeout errors during register process. Closes #4358. +- Send user's key to nickserver whenever keymanager is + initialized. Closes #4364. +- Password change dialog is now properly enabled. Closes #4449. +- Remember provider checks in wizard, do not re-run them if the user + goes back and forth through the wizard. Closes #3814 and #3815. +- Improve compatibility with OSX Mavericks. Fixes #4379. +- Initialize mail service with the userid after login, to allow + multiple accounts. Closes: #4394 +- Give SMTP the current logged in userid. Related to #3952. +- Do not wait for initial soledad sync to complete to launch mail + services. Closes: #4452 +- Add hint to user about the duration of the key generation. Closes + #3958. +- Add advanced key management feature. Closes #4448. +- Properly log EIP status changes. + +0.3.6 Nov 1 -- the "bạn có thể đọc này?" release: ++++++++++++++++++++++++++++++++++++++++++++++++++ + +- Fix problem changing a non-ascii password. Closes #4003. +- Enable password change in the client only if it has started the + correct services. Closes #4093. +- Select the current logged in provider in the preferences + window. Closes #4117. +- Fix problem with non-ascii paths. Closes #4189. +- Capture soledad boostrap errors after latest soledad changes. +- Refactor keyring handling and make it properly save user and + password. Fixes #4190. +- Properly stop the imap daemon at logout. Fixes #4199. +- Align left the speed and transferred displays for EIP. Fixes #4204. +- Remove autostart eip option from settings panel, rely on last used + setting. Closes #4132. +- Add support for requests 1.1.0 (raring). Closes: #4308 +- Refactor mail connections to use state machine. Closes: #4059 +- Add a command to setup.py to freeze the versions reported under + debian branches. Closes: #4315 +- Use coloredlogs handler if present (for development, not a + requirement). +- Hide the GUI for services that are not supported on the set of + configured providers. Closes #4170. + +0.3.5 Oct 18 -- the "I can stand on one foot" release: +++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +- In case of Soledad failure, display to the user that there was a + problem. Closes #4025. +- Widget squashing problem in wizard checking a new provider. Closes + #4058. +- Remember last domain used to login. Closes #4116. +- Display first run wizard, regardless of pinned providers. Closes + #4143. +- Show EIP status 'ON' in the systray tooltip when is + connected. Related to #3998. +- Catch u1db errors during soledad initialization. +- Disable --danger flag on release versions. Closes #4124. +- Display mail status in the tray icon as an enabled item. Fixes + #4036. +- Only show N unread Emails when N > 0. Fixes #4098. +- Hide login error message when the user interacts with the widgets + to fix the potential problem. Fixes #4022. +- Add call to `make` to the bootstrap script. +- Improve GUI based on QA rounds. Fixes #4041 and #4042. +- Increase the amount of retries for the authentication request + session. Fixes #4037. +- Rename EIP to Encrypted Internet in its preference panel. Fixes + #4057. +- Disable stdout redirection on Windows for the time being since it + breaks the bundle. +- Default UP_SCRIPT and DOWN_SCRIPT to None and only add that + parameter to the vpn command if not None. +- Look for gpg on windows with the .exe extension. +- Change the Util menu to be named File in OSX. Fixes #4039. +- Show more context information in the logs. Closes #3923. +- Automate internationalization process, create project file + dynamically on make. Closes #3925. +- Add support for running lxde polkit agent. Closes #4028. +- Added Vietnamese and English (United Kingdom) translations. +- Implements openvpn observer. Closes: #3901 +- Reconnect EIP if network down. Closes #3790 +- Reconnect if tls-restart. Closes: #3262 + +0.3.4 Oct 4 -- the "look at my new makeup" release: ++++++++++++++++++++++++++++++++++++++++++++++++++++ + +- Fixes a bug where you cannot login to a different provider once + you logged in to another one. Fixes #3695. +- Resets the session for every login attempt. Related to #3695. +- Avoid error message if --version flag is used. Closes #3914. +- Fix a bug in which failing to authenticate properly left + connection in an unconsistent state. Closes: #3926 +- Avoids errors due to the EIP switch button and action being + enabled when we do not have a configured provider. Closes: #3927 +- Add more verbose error handling during key generation and syncing. + Helps diagnose: #3985; Addresses in part: #3965 +- Choose one gnupg binary path that is also not a symlink. Closes + #3999. +- Refactor vpn launchers, reuse code, improve implementations, + update documentation. Closes #2858. +- Add preferences option to enable/disable the automatic start of + EIP and selection of the EIP provider to auto start. Closes #3631. +- Force cleanlooks style for kde only if the app is running from + bundle. Closes #3981. +- Add a dropdown for known providers in the wizard. Closes #3995. +- Separate pinned providers from user configures ones. Closes #3996. +- Improve error handling during soledad bootstrap. Closes: #3965. + Affects: #3619, #3867, #3966 +- Implement new UI design. Closes #3973. +- Make the initial provider cert verifications against our modified + CA-bundle (includes ca-cert certificates, for now). Closes: #3850 +- Use token header for authenticated requests. Closes #3910. +- Do not distinguish between different possible authentication + errors. Fixes #3859. +- Do not start Soledad if Mail is not enabled. Fixes #3989. +- Allow window minization on OSX. Fixes #3932. +- Properly stop the smtp daemon. Fixes #3873. + +0.3.3 Sep 20 -- "the calm after the tempest" release: ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +- Remove execution bits in text files in bundle. Closes #3617. +- Use generic bad username/password message instead of specific ones when + the user uses incorrect data during login. Closes #3656. +- Fix LoggerWindow saving more than one line return per line in the logs + file. Closes #3714. +- Fix keyring imports so we do not get import errors. Closes: #3759 +- Catch logout problem, display a user message and allow log back in after a + successful logout if there was a logout error before. Closes #3774. +- Fix path prefix helper for the bundle and add regresion tests. Closes #3778. +- Prevent dialogs closing the app when it has been minimized to the tray. Closes #3791. +- Do not try to install resolv-update globally. Closes: #3803 +- Inconsistent hide/show main window from tray action. Closes #3821. +- Allow SMTP to start even when provider does not offer EIP. Closes: #3847 +- Fix username case problem at register/login. Closes #3857. +- Catch IndexError on `first` utility. +- Update git repo name in docs. Closes: #3417 +- Move STANDALONE flag to a module and unify get_path_prefix queries. + Closes #3636. +- Display the Encrypted Internet and Encrypted Email status in the systray + tooltip. Closes #3758. +- Tasktray menu changes, closes #3792. +- Remove the provider domain item (e.g. bitmask.net). +- Rename the EIP status menu items to be more descriptive. +- Change the EIP status menu items from disabled menu items + to submenus with children. +- Move the EIP action menu items under the EIP status submenu tree. +- Adds ``--version`` flag. Closes: #3816 +- Refactors EIPConnection to use LEAPConnection state machine. Closes: #3900 +- Include resource files and ui in the distrubution tarball. Closes: #3825 + +0.3.2 Sep 6 -- the "no crashes or anything" release: +++++++++++++++++++++++++++++++++++++++++++++++++++++ + +- Fix up script in non-bundle linuces. Closes: #3450 +- Logout stops imap and smtp services. Closes: #3553 +- Properly daemonize polkit-gnome-authentication-agent. Closes: #3554 +- Set appropiate error on login cancel. Closes #3582. +- Fix gateway selection problem. Closes 3595. +- Fix typo in wizard: stablish -> establish. Closes #3615. +- Display Encrypted Mail instead of mx in wizard. Closes #3657. +- Fix save logs to file dialog freezing. Closes #3675. +- Complain if setup.py is run with python3. Closes: #3711 +- Enable preferences option in systray. Closes #3717. +- Make soledad emit failed signal for all kinds of socket error. +- Allow to selectively silence logs from different leap components. Closes: #3504 +- Add option to select gateway manually in the preferences panel. Closes #3505. +- Add preferences option to select the enabled services of a provider. Closes #3534. +- Refactor basic password checks. Closes #3552. +- Use dirspec instead of plain xdg. Closes #3574. +- Remove last page from wizard. Closes #3616. +- Display encrypted mail status in the tray. Closes #3659. + +0.3.1 Aug 23: ++++++++++++++ + +- Replace wizard images with the rainbow mask. Closes #3425. +- Update leap.common minimum version needed. +- Set the standalone flag before it's being used. Fixes #3426. +- Stop the twisted reactor adding the stop call to the call chain + instead of stopping it directly. Fixes #3406. +- Allow soledad initialization to retry if it times out. Closes: + #3413 +- Activate window when setting it visible. Also display Hide/Show + message in the tray icon taking into account the window + activation. Fixes #3433. +- Do not start IMAP daemon if mail was not selected among the + services. Fixes #3435. +- Reword RECONNECTING state of openvpn. Fixes #3429. +- Improve OpenVPN detection by searching for a specific leap-only + string in the command line. This makes it possible to run other + VPN instances while also using EIP. Fixes #3268 and #3364. +- OSX: Check for the tun.kext existence in /Library/Extensions + instead of /System/Library/Extensions. Fixes #3271. +- Use DELETE /1/logout to properly logout. Fixes #3510. +- Make the poll interval bigger to improve openvpn's internal + behavior. If it gets queried too many times per second, it's + behavior won't be good. Fixes #3430. +- Transforms usernames to lower case before they are used in the + registration and authentication. Closes #3541. +- Add filter option to the logger window. Closes #3407. +- Add a preference panel that lets you change your password. Closes + #3500 #2798 #3533. +- Move all client code into its own namespace + (leap.bitmask). Closes: #2959 +- Make mail fetch interval in imap service configurable via + environment variable. Closes: #3409 +- Update to new soledad package scheme (common, client and + server). Closes #3487. +- Fetch incoming mail when mail client logs in. Closes: #3525 +- Add first draft of the UI for Encrypted Mail. Closes #3499. + +0.3.0 Aug 9: +++++++++++++ + +- Add missing scripts does not stop if a command fails, also warns + the user if there was an error. Closes #3294. +- Replace 'Sign Out' with 'Log Out' and 'User' with + 'Username'. Closes #3319. +- Verify cacert existence before using it. Closes bug #3362. +- Properly handle login failures. Closes bug #3401. +- Bugfix, avoid getting negative rates. Closes #3274. +- Raise window when setting it as visible. Fixes #3374 +- Fail gracefully when the events port 8090 is in use by something + else. Fixes #3276. +- Validate the username in the login form against the same regexp as + the wizard registration form. Fixes #3214. +- Update text from the tray menu based on the visibility of the + window. Fixes #3400. +- Add check for outdated polkit file. Closes #3209. +- Add support for multiple schemas so we can support multiples api + versions. Closes #3310. +- Rebrand the client to be named Bitmask. Feature #3313. +- Add cancel button to login. Closes #3318. +- Add multiple schema support for SMTP. Closes #3403. +- Add multiple schema support for Soledad. Closes #3404. +- Update Transifex project name and translators' + documentation. Closes #3418. +- Add check for tuntap kext before launching openvpn. Closes: #2906 +- Accept flag for changing openvpn verbosity in logs. Closes: #3305 +- Add imap service to the client. Closes: #2579 +- Add pyside-uic support inside the virtualenv. This way it won't + fail to 'make' if the virtualenv is activated. Closes #3411. +- Reintegrate SMTP relay module. Closes #3375 +- Reintegrate Soledad into the client. Closes #3307. +- Support bundled gpg. Related to #3397. +- Set the default port for SMTP to be 2013. +- Display a more generic error message in the main window, and leave + the detailed one for the log. Closes #3373. + +0.2.4 Jul 26: ++++++++++++++ + +- Use the provider CA cert for every request once we have it + bootstrapped (TOFU). Closes #3227. +- Make calls to leap.common.events asynchronous. Closes #2937. +- Always logout when closing the app if the user previously signed + in. Fixes #3245. +- Make sure the domain field in provider.json is escaped to avoid + potential problems. Fixes #3244. +- Fix incorrect handling of locks in Windows so that stalled locks + do not avoid raising the first instance of the app. Closes: #2910 +- Use traffic rates instead of totals. Closes #2913 +- Allow to alternate between rates and total throughput for the + virtual interface. Closes: #3232 +- Reset rates/totals when terminating connection. Closes #3249 +- Fix a bug in the displayed magnitude for the up/down traffic rates + and totals. +- Force Cleanlooks style if we are running in a KDE environment, so + that it doesn't load potentially incompatible Qt libs. Fixes + #3194. +- Wrap long login status messages to 40 characters. Fixes #3124 +- Workaround a segmentation fault when emitting a signal with its + last parameter being None. Fixes #3083. +- Added IS_RELEASE_VERSION flag that allows us to use code only in + develop versions. Closes #3224. +- Try to terminate already running openvpn instances. Closes #2916 +- Linux: Dynamically generate policy file for polkit. Closes #3208 +- Workaround some OpenVPN problems with priviledge dropping and + routing. Fixes #3178 #3135 #3207 #3203 + +0.2.3 Jul 12: ++++++++++++++ + +- Adapt code to Soledad 0.2.1 api. +- Fix Main Window briefly display before the wizard on first + start. Closes Bug #2954. +- Bugfix: Remember should not be automatically set to + checked. Closes #2955. +- Bugfix: reload config if switching to a different provider. Closes + #3067. +- Bugfix: logger window's toggle button reflects window + state. Closes #3152. +- Set timeout for requests to 10 seconds globally, configurable from + leap.util.constants. Fixes #2878. +- Bugfix: display error message on registration problem. Closes + #3039. +- Make wizard use the main event loop, ensuring clean termination. +- Use cocoasudo for installing missing updown scripts. +- Bugfix: Systray Turn ON action fails because is not correctly + enabled/disabled. Closes #3125. +- Bugfix: wrong systray icon on startup. Closes #3147. +- Bugfix: parse line return in the logger window. Closes #3151. +- Do not log user data on registration. Fixes #3168. +- Add --log-append eip.log to windows EIP launcher options to save + the logs in case of any problems. Fixes #2054. +- OSX: Make the install_path relative to the launcher path instead + -f absolute. +- OSX: Fix icon display in cocoasudo. +- OSX: Raise window when showing if running on OSX. +- Bugfix: EIP status button moved to status panel. +- Check if there is no gateway to use and display correct + message. Close #2921. +- Reorder tray icons according new design. Closes #2919. +- Redirect stdout/stderr and twisted log to the logger. Closes + #3134. +- Improve LoggerWindow colors for easier debugging. +- Move the key manager to its own repository/package. + +0.2.2 Jun 28: ++++++++++++++ + +- Add support for the kde polkit daemon +- Handle 'Incorrect Password' exception (keyring) +- Select the configured domain in the providers combo box. Closes + #2693. +- Remember provider along with the username and password. Closes + #2755. +- Close the app on rejected wizard. Closes bug #2905. +- Only use the Keyring when it's using a known good backend. Closes + #2960 +- Update implementation and semantics of the supported and available + services by a provider. Closes bug #3032. +- Only show the question mark for a check being done if the previous + -ne passed. Fixes #2569. +- Fix main client window not restoring after minimized into + systray. Closes #2574 +- Set EIP different status icons depending on OS. Closes #2643. +- Reimplement openvpn invocation to use twisted ProcessProtocol +- Add runtime requirements checker, verifies that the requirements + are installed and in its correct versions. Closes #2563 +- Add centraliced logging facility, log history in a window. Closes + #2566 +- Improve wizard, hide registration widgets (labels, inputs, button) + and only display a message. Closes #2694 +- Clarify labels through the app (use of EIP) +- Check if the provider api version is supported. Closes feature + #2774. +- Autoselect VPN gateway based on timezone. Closes #2790. +- Disable vpn disconnect on logout. Closes #2795. +- Improve gateway selector based on timezone. It allows to use + multiple gateways in openvpn for redundancy. Closes #2894. +- Use cocoasudo in place of osascript for osx privilege escalation + during openvpn launch. +- Clicking in the tray icon will always show the context menu + instead of activating the window under certain + circumstances. Closes #2788 +- Autostart EIP whenever possible. Closes #2815 +- Update test suite, run_scripts and requirements to run smoothly + with buildbot. +- Add a copy of the processed requirements to util/ +- Display the default provider configured in the systray menu. Close + #2813 +- Make the login steps be a chain of defers in order to be able to + have more cancel points for the whole procedure. Closes #2571 +- Linux: check for up/down scripts and policy files and ask user for + permission to install them in a root-writeable location. Used from + within bundle or for broken installations. +- Integrate SMTP-Relay into the client. +- Integrate Soledad and KeyManager. +- Move the KeyManager from leap.common to leap-client. +- Only use one systray icon, repesenting the status for EIP. Closes + #2762 +- Properly set the binary manifest to the windows openvpn + binary. Closes #203 +- OSX: Add dialog with suggestion to install up/down scripts if + these not found. Closes: #1264, #2759, #2249 +- Workaround for PySide breaking with multiple inheritance. Closes + #2827 +- Refactor login to its own widget and remove Utils menu. Closes + #2789 +- Refactor the status bits out of the MainWindow to its own + StatusPanelWidget. Closes #2792 +- Save the default provider to be used for autostart EIP as + DefaultProvider in leap.conf. Closes #2793 +- Cleanly terminate openvpn process, sending SIGTERM and SIGKILL + after a while. Closes #2753 +- Use twisted's deferToThread and Deferreds to handle parallel tasks +- Use a qt4 reactor for twisted, for launching leap twisted + services. + +0.2.1 May 15: ++++++++++++++ + +- Rewrite most of the client based on the insight gained so far. +- Deselecting the remember checkbox makes the app not populate + user/password values on the login widget. Closes #2059 +- Rewording of setup steps in wizard, to make them more meaningful + to the non-technical user. Closes #2061 +- Fix typo in wizard. +- Fix multiple drawing of services if going back. +- Make registration errors show in red. +- Add a warning if EIP service needs admin password. Addresses part + -f #2062 +- Make traffic indicators display fixed precision. Closes #2114 +- Do not hide the application if the user right clicked the system + tray icon. +- Sanitize network-fetched content that is used to build openvpn + command. +- Avoids multiple instances of leap-client. Each new one just raises + the existing instance and quits. +- Use dark eip icons os osx. Closes #2130 +- Moves BaseConfig to leap.common.config. Closes #2164 +- Add handling for ASSIGN_IP state from OpenVPN in the mainwindow. +- Emit events notifying of the session_id and uid after + authentication so other services can make use of it. Closes #1957 +- Working packaging workflow with rewritten client, using + pyinstaller and platypus. +- Remove network checks temporarily until we find a good way of + doing it, and a good way to deal with them. +- Saves the token to allow token authenticated queries. +- Turn "leap" into namespace package, move common files to + leap_common package that can be shared by other LEAP projects. +- Support standalone configurations for distribution in thumbdrives + and the like. +- Add support for requests < 1.0.0 +- Tests infrastructure, and tests for crypto/srpauth and crypto/srpregister. +- Documentation updated for 0.2.1 release. +- Docstrings style changed to fit sphinx autodoc format. +- Add a simple UI to notify of pending updates. +- Add Windows support. +- Try to install TAP driver on Windows if no tap device is preset. + -- cgit v1.2.3 From 4c9074d27f25db6069721eb3cac35bafe53b7c48 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 25 Apr 2016 17:26:30 -0400 Subject: [pkg] remove the run_tests script --- run_tests.sh | 172 ----------------------------------------------------------- 1 file changed, 172 deletions(-) delete mode 100755 run_tests.sh diff --git a/run_tests.sh b/run_tests.sh deleted file mode 100755 index 0d7e7463..00000000 --- a/run_tests.sh +++ /dev/null @@ -1,172 +0,0 @@ -#!/bin/bash - -set -e - -function usage { - echo "Usage: $0 [OPTION]...[@virtualenv-name]" - echo "Run leap-client test suite" - echo "" - echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" - echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" - echo " -s, --no-site-packages Isolate the virtualenv from the global Python environment" - echo " -x, --stop Stop running tests after the first error or failure." - echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." - echo " -p, --pep8 Just run pep8" - echo " -P, --no-pep8 Don't run pep8" - echo " -c, --coverage Generate coverage report" - echo " -h, --help Print this usage message" - echo " -A, --all Run all tests, without excluding any" - echo " -i, --progressive Run with nose-progressive plugin" - echo " --hide-elapsed Don't print the elapsed time for each test along with slow test list" - echo "" - echo "Note: with no options specified, the script will try to run the tests in a virtual environment," - echo " If no virtualenv is found, the script will ask if you would like to create one. If you " - echo " prefer to run tests NOT in a virtual environment, simply pass the -N option." - echo " If you pass @virtualenv-name, the given virtualenv will be used as long as " - echo " virtualenvwrapper.sh can be found in the PATH." - exit -} - -function process_option { - case "$1" in - -h|--help) usage;; - -V|--virtual-env) always_venv=1; never_venv=0;; - -N|--no-virtual-env) always_venv=0; never_venv=1;; - -s|--no-site-packages) no_site_packages=1;; - -f|--force) force=1;; - -p|--pep8) just_pep8=1;; - -P|--no-pep8) no_pep8=1;; - -c|--coverage) coverage=1;; - -A|--all) alltests=1;; - -i|--progressive) progressive=1;; - @*) venvwrapper=1; source_venv=`echo $1 | cut -c 2-`;; - -*) noseopts="$noseopts $1";; - *) noseargs="$noseargs $1" - esac -} - -venv=.venv -with_venv=pkg/tools/with_venv.sh -with_venvwrapper=pkg/tools/with_venvwrapper.sh -always_venv=0 -never_venv=0 -force=0 -no_site_packages=0 -installvenvopts= -noseargs= -noseopts= -venvwrapper=0 -source_venv= -wrapper="" -just_pep8=0 -no_pep8=0 -coverage=0 -alltests=0 -progressive=0 - -for arg in "$@"; do - process_option $arg -done - -# If enabled, tell nose to collect coverage data -if [ $coverage -eq 1 ]; then - noseopts="$noseopts --with-coverage --cover-package=leap --cover-html --cover-html-dir=docs/covhtml/ --cover-erase" -fi - -if [ $no_site_packages -eq 1 ]; then - installvenvopts="--no-site-packages" -fi - -# If alltests flag is not set, let's exclude some dirs that are troublesome. -if [ $alltests -eq 0 ]; then - echo "[+] Running ALL tests..." - #noseopts="$noseopts --exclude-dir=leap/soledad" -fi - -# If progressive flag enabled, run with this nice plugin :) -if [ $progressive -eq 1 ]; then - noseopts="$noseopts --with-progressive" -fi - -function run_tests { - echo "running tests..." - - if [ $venvwrapper -eq 1 ]; then - VIRTUAL_ENV=$WORKON_HOME/$source_venv - wrapper="$with_venvwrapper $source_venv" - - fi - - #NOSETESTS="nosetests leap --exclude=soledad* $noseopts $noseargs" - NOSETESTS="$VIRTUAL_ENV/bin/nosetests . $noseopts $noseargs" - #--with-coverage --cover-package=leap" - - # Just run the test suites in current environment - echo "NOSETESTS=$NOSETESTS" - ${wrapper} $NOSETESTS - # If we get some short import error right away, print the error log directly - RESULT=$? - return $RESULT -} - -function run_pep8 { - echo "Running pep8 ..." - srcfiles="src/leap" - # Just run PEP8 in current environment - ${wrapper} flake8 ${pep8_opts} ${srcfiles} -} - -# XXX we cannot run tests that need X server -# in the current debhelper build process, -# so I exclude the topmost tests - - -if [ $never_venv -eq 0 ] -then - # Remove the virtual environment if --force used - if [ $force -eq 1 ]; then - echo "Cleaning virtualenv..." - rm -rf ${venv} - fi - if [ -e ${venv} ]; then - wrapper="${with_venv}" - else - if [ $always_venv -eq 1 ]; then - # Automatically install the virtualenv - python pkg/install_venv.py $installvenvopts - wrapper="${with_venv}" - else - echo -e "No virtual environment found...create one? (Y/n) \c" - read use_ve - if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then - # Install the virtualenv and run the test suite in it - python pkg/install_venv.py $installvenvopts - wrapper=${with_venv} - fi - fi - fi -fi - -# Delete old coverage data from previous runs -if [ $coverage -eq 1 ]; then - ${wrapper} coverage erase -fi - -if [ $just_pep8 -eq 1 ]; then - run_pep8 - exit -fi - -run_tests - -if [ -z "$noseargs" ]; then - if [ $no_pep8 -eq 0 ]; then - run_pep8 - fi -fi - -if [ $coverage -eq 1 ]; then - echo "Generating coverage report in docs/covhtml/" - echo "now point your browser at docs/covhtml/index.html" - exit -fi -- cgit v1.2.3 From 2f7773d2985fa5aa3c0cadb0935cd6b557a35a4c Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 25 Apr 2016 17:27:04 -0400 Subject: [tests] remove tox config file --- tox.ini | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 tox.ini diff --git a/tox.ini b/tox.ini deleted file mode 100644 index e041515d..00000000 --- a/tox.ini +++ /dev/null @@ -1,12 +0,0 @@ -[tox] -envlist = py26,py27 - -[testenv] -deps = -r{toxinidir}/pkg/requirements.pip - -r{toxinidir}/pkg/requirements-testing.pip -sitepackages = True -commands = xvfb-run nosetests leap --first-package-wins --exclude=soledad* - -[testenv:pep8] -deps = pep8==1.1 -commands = pep8 --repeat --show-source src/leap setup.py --ignore=E202,W602 --exclude=*_rc.py --repeat -- cgit v1.2.3 From f47416804ad2f88ba27aa032e0d2fc1c9fd314c8 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 25 Apr 2016 17:30:35 -0400 Subject: [docs] add logout segfault to known issues --- changes/next-changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst index ca74746f..8a5035ff 100644 --- a/changes/next-changelog.rst +++ b/changes/next-changelog.rst @@ -41,4 +41,5 @@ Misc Known Issues ~~~~~~~~~~~~ +- `#8057 `_: Logging out twice produces a segfault in Qt - `#1236 `_: Description of the known issue corresponding with issue #1236. -- cgit v1.2.3 From 5a1dd49debdcd9a1ce0568217e9411d1e45a3cad Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 25 Apr 2016 20:24:04 -0300 Subject: [bug] represent keys correctly in preference window --- src/leap/bitmask/backend/components.py | 10 +++++----- src/leap/bitmask/gui/preferences_email_page.py | 6 ++++-- src/leap/bitmask/gui/ui/preferences_email_page.ui | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 3192e1c4..ba64fd65 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -994,14 +994,14 @@ class Keymanager(object): d.addCallback(export) d.addErrback(log_error) + @defer.inlineCallbacks def list_keys(self): """ List all the keys stored in the local DB. """ - d = self._keymanager_proxy.get_all_keys() - d.addCallback( - lambda keys: - self._signaler.signal(self._signaler.keymanager_keys_list, keys)) + keys = yield self._keymanager_proxy.get_all_keys() + keydicts = [dict(key) for key in keys] + self._signaler.signal(self._signaler.keymanager_keys_list, keydicts) def get_key_details(self, username): """ @@ -1009,7 +1009,7 @@ class Keymanager(object): """ def signal_details(public_key): self._signaler.signal(self._signaler.keymanager_key_details, - public_key.get_dict()) + dict(public_key)) d = self._keymanager_proxy.get_key(username, openpgp.OpenPGPKey) diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index f6d6f036..7186450d 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -90,6 +90,8 @@ class PreferencesEmailPage(PreferencesPage): connect signals """ self.app.signaler.keymanager_key_details.connect(self._key_details) + self.app.signaler.keymanager_keys_list.connect( + self._keymanager_keys_list) self.app.signaler.keymanager_export_ok.connect( self._keymanager_export_ok) self.app.signaler.keymanager_export_error.connect( @@ -218,9 +220,9 @@ class PreferencesEmailPage(PreferencesPage): row = self.ui.keys_table.rowCount() self.ui.keys_table.insertRow(row) self.ui.keys_table.setItem( - row, 0, QtGui.QTableWidgetItem(key.address)) + row, 0, QtGui.QTableWidgetItem(" ".join(key["uids"]))) self.ui.keys_table.setItem( - row, 1, QtGui.QTableWidgetItem(key.fingerprint)) + row, 1, QtGui.QTableWidgetItem(key["fingerprint"])) def _toggle_webmail(self, state): value = True if state == QtCore.Qt.Checked else False diff --git a/src/leap/bitmask/gui/ui/preferences_email_page.ui b/src/leap/bitmask/gui/ui/preferences_email_page.ui index 87e7121d..08feb7b0 100644 --- a/src/leap/bitmask/gui/ui/preferences_email_page.ui +++ b/src/leap/bitmask/gui/ui/preferences_email_page.ui @@ -581,7 +581,7 @@ - Key ID + Fingerprint -- cgit v1.2.3 From cc4b55926d20b81a3c381dfb41cae42ab080f557 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 26 Apr 2016 20:03:44 -0400 Subject: [pkg] pick cacert from the repo, not the venv --- pkg/next-version | 2 +- pkg/pyinst/pyinst-build.mk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/next-version b/pkg/next-version index 3d4f0a85..29a0f215 100644 --- a/pkg/next-version +++ b/pkg/next-version @@ -1 +1 @@ -0.9.2.rc1 +0.9.2.rc2 diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index 92a1cbb3..664f13ec 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -22,7 +22,7 @@ reset-ver: pyinst-hacks-linux: # XXX this should be taken care of by pyinstaller data collector - cp $(VIRTUAL_ENV)/lib/python2.7/site-packages/leap/common/cacert.pem $(DIST) + cp ../leap_common/src/leap/common/cacert.pem $(DIST) mkdir -p $(DIST)pysqlcipher mkdir -p $(DIST)pixelated mkdir -p $(DIST)twisted/web -- cgit v1.2.3 From 68096fa81eebb07165c27648ab804d9e1c695f8e Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 26 Apr 2016 23:07:42 -0400 Subject: [style] fix typo: Webmail -> Mail --- src/leap/bitmask/gui/ui/mainwindow.ui | 2 +- src/leap/bitmask/gui/ui/preferences_email_page.ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index 976c0c0a..0dd0b891 100644 --- a/src/leap/bitmask/gui/ui/mainwindow.ui +++ b/src/leap/bitmask/gui/ui/mainwindow.ui @@ -373,7 +373,7 @@ - Bitmask Webmail + Bitmask Mail diff --git a/src/leap/bitmask/gui/ui/preferences_email_page.ui b/src/leap/bitmask/gui/ui/preferences_email_page.ui index 87e7121d..1101d7ba 100644 --- a/src/leap/bitmask/gui/ui/preferences_email_page.ui +++ b/src/leap/bitmask/gui/ui/preferences_email_page.ui @@ -39,7 +39,7 @@ - Bitmask Mail Configuration + Bitmask Mail -- cgit v1.2.3 From da45ed15190ea0f9c7c38b6312239f27664545a4 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 28 Apr 2016 15:46:13 -0400 Subject: [bug] do not force autobahn dependency yet websockets interface is not mature enough yet, make this dependency optional, for the case the user actively enables it. - Releases: 0.9.2 --- src/leap/bitmask/core/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py index 13c8864a..ceda6353 100644 --- a/src/leap/bitmask/core/service.py +++ b/src/leap/bitmask/core/service.py @@ -26,7 +26,6 @@ from leap.bitmask import __version__ from leap.bitmask.core import configurable from leap.bitmask.core import mail_services from leap.bitmask.core import _zmq -from leap.bitmask.core import websocket from leap.bonafide.service import BonafideService from leap.common.events import server as event_server # from leap.vpn import EIPService @@ -103,6 +102,7 @@ class BitmaskBackend(configurable.ConfigurableService): zs.setServiceParent(self) def init_web(self): + from leap.bitmask.core import websocket ws = websocket.WebSocketsDispatcherService(self) ws.setServiceParent(self) -- cgit v1.2.3 From 928f547a1d1235306056be1d81e8400d4d77ecce Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sat, 30 Apr 2016 12:15:16 -0400 Subject: [bug] fix hardcoded paths --- src/leap/bitmask/core/mail_services.py | 4 ++-- src/leap/bitmask/core/service.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py index 6472bdc2..3495aa39 100644 --- a/src/leap/bitmask/core/mail_services.py +++ b/src/leap/bitmask/core/mail_services.py @@ -302,7 +302,7 @@ class KeymanagerContainer(Container): class KeymanagerService(HookableService): - def __init__(self, basedir='~/.config/leap'): + def __init__(self, basedir=DEFAULT_BASEDIR): service.Service.__init__(self) self._basedir = basedir @@ -507,7 +507,7 @@ class SMTPService(service.Service): name = 'smtp' def __init__(self, soledad_sessions, keymanager_sessions, sendmail_opts, - basedir='~/.config/leap'): + basedir=DEFAULT_BASEDIR): self._basedir = os.path.expanduser(basedir) port, factory = smtp.run_service( diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py index ceda6353..ddd86155 100644 --- a/src/leap/bitmask/core/service.py +++ b/src/leap/bitmask/core/service.py @@ -33,7 +33,7 @@ from leap.common.events import server as event_server class BitmaskBackend(configurable.ConfigurableService): - def __init__(self, basedir='~/.config/leap'): + def __init__(self, basedir=configurable.DEFAULT_BASEDIR): configurable.ConfigurableService.__init__(self, basedir) -- cgit v1.2.3 From 08da5b11103cdd132c3ac4110ba42fcc8510a78b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sat, 30 Apr 2016 12:15:58 -0400 Subject: [refactor] pass backend to core service --- src/leap/bitmask/core/flags.py | 1 + src/leap/bitmask/core/launcher.py | 7 ++++++- src/leap/bitmask/core/service.py | 13 ++++++++----- 3 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 src/leap/bitmask/core/flags.py diff --git a/src/leap/bitmask/core/flags.py b/src/leap/bitmask/core/flags.py new file mode 100644 index 00000000..9a40c70c --- /dev/null +++ b/src/leap/bitmask/core/flags.py @@ -0,0 +1 @@ +BACKEND = 'default' diff --git a/src/leap/bitmask/core/launcher.py b/src/leap/bitmask/core/launcher.py index b2319077..b8916a1e 100644 --- a/src/leap/bitmask/core/launcher.py +++ b/src/leap/bitmask/core/launcher.py @@ -17,17 +17,22 @@ """ Run bitmask daemon. """ -from twisted.scripts.twistd import run from os.path import join from sys import argv +from twisted.scripts.twistd import run + from leap.bitmask.util import here from leap.bitmask import core +from leap.bitmask.core import flags def run_bitmaskd(): # TODO --- configure where to put the logs... (get --logfile, --logdir # from the bitmask_cli + for (index, arg) in enumerate(argv): + if arg == '--backend': + flags.BACKEND = argv[index + 1] argv[1:] = [ '-y', join(here(core), "bitmaskd.tac"), '--pidfile', '/tmp/bitmaskd.pid', diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py index ddd86155..fca51048 100644 --- a/src/leap/bitmask/core/service.py +++ b/src/leap/bitmask/core/service.py @@ -17,6 +17,7 @@ """ Bitmask-core Service. """ +import json import resource from twisted.internet import reactor @@ -26,6 +27,7 @@ from leap.bitmask import __version__ from leap.bitmask.core import configurable from leap.bitmask.core import mail_services from leap.bitmask.core import _zmq +from leap.bitmask.core import flags from leap.bonafide.service import BonafideService from leap.common.events import server as event_server # from leap.vpn import EIPService @@ -126,17 +128,18 @@ class BitmaskBackend(configurable.ConfigurableService): # we may want to make this tuple a class member services = ('soledad', 'keymanager', 'mail', 'eip') - status_messages = [] + status = {} for name in services: - status = 'stopped' + _status = 'stopped' try: if self.getServiceNamed(name).running: - status = "running" + _status = 'running' except KeyError: pass - status_messages.append("[{}: {}]".format(name, status)) + status[name] = _status + status['backend'] = flags.BACKEND - return " ".join(status_messages) + return json.dumps(status) def do_version(self): version = __version__ -- cgit v1.2.3 From 1173e77cb8d635936c9730ba4ad8b88b24ad1be2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sun, 1 May 2016 11:09:07 -0400 Subject: [feature] pluggable backends and api registry the idea behind this mechanism (partially implemented for that) is to be able to check the backend output against some type annotations. We want to be able to detect if a given backend (real services or authoritative mocks) have diverged from what's specified in the API annotations. --- src/leap/bitmask/cli/bitmask_cli.py | 2 +- src/leap/bitmask/core/api.py | 54 +++++++ src/leap/bitmask/core/api_contract.py | 40 +++++ src/leap/bitmask/core/dispatcher.py | 286 +++++++++++++++++++++------------ src/leap/bitmask/core/dummy.py | 80 +++++++++ src/leap/bitmask/core/mail_services.py | 10 +- src/leap/bitmask/core/service.py | 86 ++++++---- 7 files changed, 420 insertions(+), 138 deletions(-) create mode 100644 src/leap/bitmask/core/api.py create mode 100644 src/leap/bitmask/core/api_contract.py create mode 100644 src/leap/bitmask/core/dummy.py diff --git a/src/leap/bitmask/cli/bitmask_cli.py b/src/leap/bitmask/cli/bitmask_cli.py index c5bb1b15..c2b1ba71 100755 --- a/src/leap/bitmask/cli/bitmask_cli.py +++ b/src/leap/bitmask/cli/bitmask_cli.py @@ -325,7 +325,7 @@ def send_command(cli): s = get_zmq_connection() - d = s.sendMsg(*data, timeout=20) + d = s.sendMsg(*data, timeout=60) d.addCallback(cb) d.addCallback(lambda x: reactor.stop()) d.addErrback(timeout_handler) diff --git a/src/leap/bitmask/core/api.py b/src/leap/bitmask/core/api.py new file mode 100644 index 00000000..9f3725dc --- /dev/null +++ b/src/leap/bitmask/core/api.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# api.py +# Copyright (C) 2016 LEAP Encryption Acess 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 . +""" +Registry for the public API for the Bitmask Backend. +""" +from collections import OrderedDict + +registry = OrderedDict() + + +class APICommand(type): + """ + A metaclass to keep a global registry of all the methods that compose the + public API for the Bitmask Backend. + """ + def __init__(cls, name, bases, attrs): + for key, val in attrs.iteritems(): + properties = getattr(val, 'register', None) + label = getattr(cls, 'label', None) + if label: + name = label + if properties is not None: + registry['%s.%s' % (name, key)] = properties + + +def register_method(*args): + """ + This method gathers info about all the methods that are supposed to + compose the public API to communicate with the backend. + + It sets up a register property for any method that uses it. + A type annotation is supposed to be in this property. + The APICommand metaclass collects these properties of the methods and + stores them in the global api_registry object, where they can be + introspected at runtime. + """ + def decorator(f): + f.register = tuple(args) + return f + return decorator diff --git a/src/leap/bitmask/core/api_contract.py b/src/leap/bitmask/core/api_contract.py new file mode 100644 index 00000000..86b600c1 --- /dev/null +++ b/src/leap/bitmask/core/api_contract.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# api_contract.py +# Copyright (C) 2016 LEAP Encryption Acess 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 . +""" +Display a human-readable representation of the methods that compound the public +api for Bitmask Core. + +The values are meant to be type annotations. +""" + +if __name__ == "__main__": + from leap.bitmask.core.service import BitmaskBackend + from leap.bitmask.core import api + backend = BitmaskBackend() + + print '========= Bitmask Core API ==================' + print + + for key in api.registry: + human_key = key.replace('do_', '').lower() + value = api.registry[key] + + print("{}:\t\t{}".format( + human_key, + ' '.join([x for x in value]))) + print + print '=============================================' diff --git a/src/leap/bitmask/core/dispatcher.py b/src/leap/bitmask/core/dispatcher.py index 648cbe9b..e7c961fd 100644 --- a/src/leap/bitmask/core/dispatcher.py +++ b/src/leap/bitmask/core/dispatcher.py @@ -22,154 +22,214 @@ import json from twisted.internet import defer from twisted.python import failure, log +from .api import APICommand, register_method -# TODO implement sub-classes to dispatch subcommands (user, mail). +class SubCommand(object): -class CommandDispatcher(object): + __metaclass__ = APICommand - def __init__(self, core): + def dispatch(self, service, *parts, **kw): + subcmd = parts[1] - self.core = core + _method = getattr(self, 'do_' + subcmd.upper(), None) + if not _method: + raise RuntimeError('No such subcommand') + return _method(service, *parts, **kw) - def _get_service(self, name): - try: - return self.core.getServiceNamed(name) - except KeyError: - return None +class UserCmd(SubCommand): - def dispatch(self, msg): - cmd = msg[0] + label = 'user' - _method = getattr(self, 'do_' + cmd.upper(), None) + @register_method("{'srp_token': unicode, 'uuid': unicode}") + def do_AUTHENTICATE(self, bonafide, *parts): + user, password = parts[2], parts[3] + d = defer.maybeDeferred(bonafide.do_authenticate, user, password) + return d - if not _method: - return defer.fail(failure.Failure(RuntimeError('No such command'))) + @register_method("{'signup': 'ok', 'user': str}") + def do_SIGNUP(self, bonafide, *parts): + user, password = parts[2], parts[3] + d = defer.maybeDeferred(bonafide.do_signup, user, password) + return d - return defer.maybeDeferred(_method, *msg) + @register_method("{'logout': 'ok'}") + def do_LOGOUT(self, bonafide, *parts): + user, password = parts[2], parts[3] + d = defer.maybeDeferred(bonafide.do_logout, user, password) + return d - def do_STATS(self, *parts): - return _format_result(self.core.do_stats()) + @register_method('str') + def do_ACTIVE(self, bonafide, *parts): + d = defer.maybeDeferred(bonafide.do_get_active_user) + return d - def do_VERSION(self, *parts): - return _format_result(self.core.do_version()) - def do_STATUS(self, *parts): - return _format_result(self.core.do_status()) +class EIPCmd(SubCommand): - def do_SHUTDOWN(self, *parts): - return _format_result(self.core.do_shutdown()) + label = 'eip' - def do_USER(self, *parts): + @register_method('dict') + def do_ENABLE(self, service, *parts): + d = service.do_enable_service(self.label) + return d - subcmd = parts[1] - user, password = parts[2], parts[3] + @register_method('dict') + def do_DISABLE(self, service, *parts): + d = service.do_disable_service(self.label) + return d - bf = self._get_service('bonafide') + @register_method('dict') + def do_STATUS(self, eip, *parts): + d = eip.do_status() + return d - if subcmd == 'authenticate': - d = bf.do_authenticate(user, password) + @register_method('dict') + def do_START(self, eip, *parts): + # TODO --- attempt to get active provider + # TODO or catch the exception and send error + provider = parts[2] + d = eip.do_start(provider) + return d - elif subcmd == 'signup': - d = bf.do_signup(user, password) + @register_method('dict') + def do_STOP(self, eip, *parts): + d = eip.do_stop() + return d - elif subcmd == 'logout': - d = bf.do_logout(user, password) - elif subcmd == 'active': - d = bf.do_get_active_user() +class MailCmd(SubCommand): - d.addCallbacks(_format_result, _format_error) + label = 'mail' + + @register_method('dict') + def do_ENABLE(self, service, *parts): + d = service.do_enable_service(self.label) return d - def do_EIP(self, *parts): + @register_method('dict') + def do_DISABLE(self, service, *parts): + d = service.do_disable_service(self.label) + return d - subcmd = parts[1] - eip_label = 'eip' + @register_method('dict') + def do_STATUS(self, mail, *parts): + d = mail.do_status() + return d - if subcmd == 'enable': - return _format_result( - self.core.do_enable_service(eip_label)) + @register_method('dict') + def do_GET_IMAP_TOKEN(self, mail, *parts): + d = mail.get_imap_token() + return d - eip = self._get_service(eip_label) - if not eip: - return _format_result('eip: disabled') + @register_method('dict') + def do_GET_SMTP_TOKEN(self, mail, *parts): + d = mail.get_smtp_token() + return d - if subcmd == 'status': - return _format_result(eip.do_status()) + @register_method('dict') + def do_GET_SMTP_CERTIFICATE(self, mail, *parts, **kw): + # TODO move to mail service + # TODO should ask for confirmation? like --force or something, + # if we already have a valid one. or better just refuse if cert + # exists. + # TODO how should we pass the userid?? + # - Keep an 'active' user in bonafide (last authenticated) + # (doing it now) + # - Get active user from Mail Service (maybe preferred?) + # - Have a command/method to set 'active' user. + + @defer.inlineCallbacks + def save_cert(cert_data): + userid, cert_str = cert_data + cert_path = yield mail.do_get_smtp_cert_path(userid) + with open(cert_path, 'w') as outf: + outf.write(cert_str) + defer.returnValue('certificate saved to %s' % cert_path) + + bonafide = kw['bonafide'] + d = bonafide.do_get_smtp_cert() + d.addCallback(save_cert) + return d - elif subcmd == 'disable': - return _format_result( - self.core.do_disable_service(eip_label)) - elif subcmd == 'start': - # TODO --- attempt to get active provider - # TODO or catch the exception and send error - provider = parts[2] - d = eip.do_start(provider) - d.addCallbacks(_format_result, _format_error) - return d +class CommandDispatcher(object): - elif subcmd == 'stop': - d = eip.do_stop() - d.addCallbacks(_format_result, _format_error) - return d + __metaclass__ = APICommand - def do_MAIL(self, *parts): + label = 'core' + def __init__(self, core): + + self.core = core + self.subcommand_user = UserCmd() + self.subcommand_eip = EIPCmd() + self.subcommand_mail = MailCmd() + + # XXX -------------------------------------------- + # TODO move general services to another subclass + + @register_method("{'mem_usage': str}") + def do_STATS(self, *parts): + return _format_result(self.core.do_stats()) + + @register_method("{version_core': '0.0.0'}") + def do_VERSION(self, *parts): + return _format_result(self.core.do_version()) + + @register_method("{'mail': 'running'}") + def do_STATUS(self, *parts): + return _format_result(self.core.do_status()) + + @register_method("{'shutdown': 'ok'}") + def do_SHUTDOWN(self, *parts): + return _format_result(self.core.do_shutdown()) + + # ----------------------------------------------- + + def do_USER(self, *parts): + bonafide = self._get_service('bonafide') + d = self.subcommand_user.dispatch(bonafide, *parts) + d.addCallbacks(_format_result, _format_error) + return d + + def do_EIP(self, *parts): + eip = self._get_service(self.subcommand_eip.label) + if not eip: + return _format_result('eip: disabled') subcmd = parts[1] - mail_label = 'mail' - if subcmd == 'enable': - return _format_result( - self.core.do_enable_service(mail_label)) + dispatch = self._subcommand_eip.dispatch + if subcmd in ('enable', 'disable'): + d = dispatch(self.core, *parts) + else: + d = dispatch(eip, *parts) - m = self._get_service(mail_label) - bf = self._get_service('bonafide') + d.addCallbacks(_format_result, _format_error) + return d - if not m: - return _format_result('mail: disabled') + def do_MAIL(self, *parts): + subcmd = parts[1] + dispatch = self.subcommand_mail.dispatch - if subcmd == 'status': - return _format_result(m.do_status()) + if subcmd == 'enable': + d = dispatch(self.core, *parts) - elif subcmd == 'disable': - return _format_result(self.core.do_disable_service(mail_label)) + mail = self._get_service(self.subcommand_mail.label) + bonafide = self._get_service('bonafide') + kw = {'bonafide': bonafide} - elif subcmd == 'get_imap_token': - d = m.get_imap_token() - d.addCallbacks(_format_result, _format_error) - return d + if not mail: + return _format_result('mail: disabled') - elif subcmd == 'get_smtp_token': - d = m.get_smtp_token() - d.addCallbacks(_format_result, _format_error) - return d + if subcmd == 'disable': + d = dispatch(self.core) + else: + d = dispatch(mail, *parts, **kw) - elif subcmd == 'get_smtp_certificate': - # TODO move to mail service - # TODO should ask for confirmation? like --force or something, - # if we already have a valid one. or better just refuse if cert - # exists. - # TODO how should we pass the userid?? - # - Keep an 'active' user in bonafide (last authenticated) - # (doing it now) - # - Get active user from Mail Service (maybe preferred?) - # - Have a command/method to set 'active' user. - - @defer.inlineCallbacks - def save_cert(cert_data): - userid, cert_str = cert_data - cert_path = yield m.do_get_smtp_cert_path(userid) - with open(cert_path, 'w') as outf: - outf.write(cert_str) - defer.returnValue('certificate saved to %s' % cert_path) - - d = bf.do_get_smtp_cert() - d.addCallback(save_cert) - d.addCallbacks(_format_result, _format_error) - return d + d.addCallbacks(_format_result, _format_error) + return d def do_KEYS(self, *parts): subcmd = parts[1] @@ -187,6 +247,22 @@ class CommandDispatcher(object): d.addCallbacks(_format_result, _format_error) return d + def dispatch(self, msg): + cmd = msg[0] + + _method = getattr(self, 'do_' + cmd.upper(), None) + + if not _method: + return defer.fail(failure.Failure(RuntimeError('No such command'))) + + return defer.maybeDeferred(_method, *msg) + + def _get_service(self, name): + try: + return self.core.getServiceNamed(name) + except KeyError: + return None + def _format_result(result): return json.dumps({'error': None, 'result': result}) diff --git a/src/leap/bitmask/core/dummy.py b/src/leap/bitmask/core/dummy.py new file mode 100644 index 00000000..99dfafa5 --- /dev/null +++ b/src/leap/bitmask/core/dummy.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# dummy.py +# Copyright (C) 2016 LEAP Encryption Acess 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 . +""" +An authoritative dummy backend for tests. +""" +import json + +from leap.common.service_hooks import HookableService + + +class BackendCommands(object): + + """ + General commands for the BitmaskBackend Core Service. + """ + + def __init__(self, core): + self.core = core + + def do_status(self): + return json.dumps( + {'soledad': 'running', + 'keymanager': 'running', + 'mail': 'running', + 'eip': 'stopped', + 'backend': 'dummy'}) + + def do_version(self): + return {'version_core': '0.0.1'} + + def do_stats(self): + return {'mem_usage': '01 KB'} + + def do_shutdown(self): + return {'shutdown': 'ok'} + + +class mail_services(object): + + class SoledadService(HookableService): + pass + + class KeymanagerService(HookableService): + pass + + class StandardMailService(HookableService): + pass + + +class BonafideService(HookableService): + + def __init__(self, basedir): + pass + + def do_authenticate(self, user, password): + return {u'srp_token': u'deadbeef123456789012345678901234567890123', + u'uuid': u'01234567890abcde01234567890abcde'} + + def do_signup(self, user, password): + return {'signup': 'ok', 'user': 'dummyuser@provider.example.org'} + + def do_logout(self, user, password): + return {'logout': 'ok'} + + def do_get_active_user(self): + return 'dummyuser@provider.example.org' diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py index 3495aa39..fb9ee698 100644 --- a/src/leap/bitmask/core/mail_services.py +++ b/src/leap/bitmask/core/mail_services.py @@ -64,10 +64,10 @@ class ImproperlyConfigured(Exception): class SoledadContainer(Container): - def __init__(self, basedir=DEFAULT_BASEDIR): + def __init__(self, service=None, basedir=DEFAULT_BASEDIR): self._basedir = os.path.expanduser(basedir) self._usermap = UserMap() - super(SoledadContainer, self).__init__() + super(SoledadContainer, self).__init__(service=service) def add_instance(self, userid, passphrase, uuid=None, token=None): @@ -89,7 +89,7 @@ class SoledadContainer(Container): uuid, passphrase, soledad_path, soledad_url, cert_path, token) - self.add_instances(userid, soledad) + super(SoledadContainer, self).add_instance(userid, soledad) data = {'user': userid, 'uuid': uuid, 'token': token, 'soledad': soledad} @@ -202,9 +202,9 @@ class SoledadService(HookableService): class KeymanagerContainer(Container): - def __init__(self, basedir): + def __init__(self, service=None, basedir=DEFAULT_BASEDIR): self._basedir = os.path.expanduser(basedir) - super(KeymanagerContainer, self).__init__() + super(KeymanagerContainer, self).__init__(service=service) def add_instance(self, userid, token, uuid, soledad): diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py index fca51048..99132c2d 100644 --- a/src/leap/bitmask/core/service.py +++ b/src/leap/bitmask/core/service.py @@ -25,19 +25,30 @@ from twisted.python import log from leap.bitmask import __version__ from leap.bitmask.core import configurable -from leap.bitmask.core import mail_services from leap.bitmask.core import _zmq from leap.bitmask.core import flags -from leap.bonafide.service import BonafideService from leap.common.events import server as event_server # from leap.vpn import EIPService +backend = flags.BACKEND + +if backend == 'default': + from leap.bitmask.core import mail_services + from leap.bonafide.service import BonafideService +elif backend == 'dummy': + from leap.bitmask.core.dummy import mail_services + from leap.bitmask.core.dummy import BonafideService +else: + raise RuntimeError('Backend not supported') + + class BitmaskBackend(configurable.ConfigurableService): def __init__(self, basedir=configurable.DEFAULT_BASEDIR): configurable.ConfigurableService.__init__(self, basedir) + self.core_commands = BackendCommands(self) def enabled(service): return self.get_config('services', service, False, boolean=True) @@ -117,38 +128,19 @@ class BitmaskBackend(configurable.ConfigurableService): service.setServiceParent(self) return service - # General commands for the BitmaskBackend Core Service - def do_stats(self): - log.msg('BitmaskCore Service STATS') - mem = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss - return 'BitmaskCore: [Mem usage: %s KB]' % (mem / 1024) + return self.core_commands.do_stats() def do_status(self): - # we may want to make this tuple a class member - services = ('soledad', 'keymanager', 'mail', 'eip') - - status = {} - for name in services: - _status = 'stopped' - try: - if self.getServiceNamed(name).running: - _status = 'running' - except KeyError: - pass - status[name] = _status - status['backend'] = flags.BACKEND - - return json.dumps(status) + return self.core_commands.do_status() def do_version(self): - version = __version__ - return 'BitmaskCore: %s' % version + return self.core_commands.do_version() def do_shutdown(self): - self.stopService() - reactor.callLater(1, reactor.stop) - return 'shutting down...' + return self.core_commands.do_shutdown() + + # Service Toggling def do_enable_service(self, service): assert service in self.service_names @@ -175,3 +167,43 @@ class BitmaskBackend(configurable.ConfigurableService): # TODO -- should stop also? self.set_config('services', service, 'False') return 'ok' + + +class BackendCommands(object): + + """ + General commands for the BitmaskBackend Core Service. + """ + + def __init__(self, core): + self.core = core + + def do_status(self): + # we may want to make this tuple a class member + services = ('soledad', 'keymanager', 'mail', 'eip') + + status = {} + for name in services: + _status = 'stopped' + try: + if self.core.getServiceNamed(name).running: + _status = 'running' + except KeyError: + pass + status[name] = _status + status['backend'] = flags.BACKEND + + return json.dumps(status) + + def do_version(self): + return {'version_core': __version__} + + def do_stats(self): + log.msg('BitmaskCore Service STATS') + mem = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss + return {'mem_usage': '%s KB' % (mem / 1024)} + + def do_shutdown(self): + self.core.stopService() + reactor.callLater(1, reactor.stop) + return {'shutdown': 'ok'} -- cgit v1.2.3 From cfdd93937a7af65be73d5cc482686d1fe559d0a9 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 3 May 2016 10:18:06 -0400 Subject: [refactor] rename and move api_contract --- docs/core_api_contract | 41 +++++++++++++++++++++++++++++++++++ src/leap/bitmask/core/api_contract.py | 40 ---------------------------------- 2 files changed, 41 insertions(+), 40 deletions(-) create mode 100755 docs/core_api_contract delete mode 100644 src/leap/bitmask/core/api_contract.py diff --git a/docs/core_api_contract b/docs/core_api_contract new file mode 100755 index 00000000..b70fb8fa --- /dev/null +++ b/docs/core_api_contract @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# api_contract.py +# Copyright (C) 2016 LEAP Encryption Acess 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 . +""" +Display a human-readable representation of the methods that compound the public +api for Bitmask Core. + +The values are meant to be type annotations. +""" + +if __name__ == "__main__": + from leap.bitmask.core.service import BitmaskBackend + from leap.bitmask.core import api + backend = BitmaskBackend() + + print '========= Bitmask Core API ==================' + print + + for key in api.registry: + human_key = key.replace('do_', '').lower() + value = api.registry[key] + + print("{}:\t\t{}".format( + human_key, + ' '.join([x for x in value]))) + print + print '=============================================' diff --git a/src/leap/bitmask/core/api_contract.py b/src/leap/bitmask/core/api_contract.py deleted file mode 100644 index 86b600c1..00000000 --- a/src/leap/bitmask/core/api_contract.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# api_contract.py -# Copyright (C) 2016 LEAP Encryption Acess 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 . -""" -Display a human-readable representation of the methods that compound the public -api for Bitmask Core. - -The values are meant to be type annotations. -""" - -if __name__ == "__main__": - from leap.bitmask.core.service import BitmaskBackend - from leap.bitmask.core import api - backend = BitmaskBackend() - - print '========= Bitmask Core API ==================' - print - - for key in api.registry: - human_key = key.replace('do_', '').lower() - value = api.registry[key] - - print("{}:\t\t{}".format( - human_key, - ' '.join([x for x in value]))) - print - print '=============================================' -- cgit v1.2.3 From 2a802378a6ffd1c1c677e4fa035ee3b8f932dfbe Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 3 May 2016 10:40:30 -0400 Subject: [pkg] remove pixelated readme, can be enabled in prefswin --- pkg/PixelatedWebmail.README | 38 -------------------------------------- pkg/pyinst/pyinst-build.mk | 1 - 2 files changed, 39 deletions(-) delete mode 100644 pkg/PixelatedWebmail.README diff --git a/pkg/PixelatedWebmail.README b/pkg/PixelatedWebmail.README deleted file mode 100644 index 06e52964..00000000 --- a/pkg/PixelatedWebmail.README +++ /dev/null @@ -1,38 +0,0 @@ -How to enable Pixelated Webmail -------------------------------- - -WARNING! This is an experimental feature. -It can expose your mail to *any* user with access to your machine, since there -is no authentication in place at the moment. It could even eat your data. You -have been warned. - -Ok, how do I enable this wonderful feature? -------------------------------------------- - -First, run the bundle for a first time, and ensure that you can register a new -account with a mail-enabled provider (for instance, mail.bitmask.net). - -Then, you have to edit a config file living inside the bundle folders. You have -to add "Pixmail=true" under the [General] section, like this: - -lib/config/leap/leap.conf: - -[General] -SkipFirstRun=true -Provider=mail.bitmask.net -Pixmail=true - -[mail.bitmask.net] -Services=mx - -Then, run bitmask again: - -./bitmask --debug - -And a new "Bitmask Webmail" option should have appeared under the "Bitmask" -menu. - -If you want to disable the Webmail functionality, just set the Pixmail property -to 'false'. - -Enjoy your local and encrypted pixelated webmail! diff --git a/pkg/pyinst/pyinst-build.mk b/pkg/pyinst/pyinst-build.mk index 664f13ec..32239f8e 100644 --- a/pkg/pyinst/pyinst-build.mk +++ b/pkg/pyinst/pyinst-build.mk @@ -55,7 +55,6 @@ pyinst-cleanup: pyinst-distribution-data: cp release-notes.rst $(DIST_VERSION) - cp pkg/PixelatedWebmail.README $(DIST_VERSION) cp LICENSE $(DIST_VERSION) pyinst-helpers-linux: -- cgit v1.2.3 From 54560623ac5c325bebbe627582b3895c3354368a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 3 May 2016 12:18:25 -0400 Subject: [feature] enable to download attachments from within webkit - Resolves: #8069 - Releases: 0.9.2 --- src/leap/bitmask/gui/qt_browser.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/leap/bitmask/gui/qt_browser.py b/src/leap/bitmask/gui/qt_browser.py index c62e7770..2f7e6086 100644 --- a/src/leap/bitmask/gui/qt_browser.py +++ b/src/leap/bitmask/gui/qt_browser.py @@ -17,8 +17,10 @@ """ QtWebKit-based browser to display Pixelated User Agent """ +import os +import urlparse -from PySide import QtCore, QtWebKit, QtGui +from PySide import QtCore, QtWebKit, QtGui, QtNetwork PIXELATED_URI = 'http://localhost:9090' @@ -37,3 +39,29 @@ class PixelatedWindow(QtGui.QDialog): def load_app(self): self.view.load(QtCore.QUrl(PIXELATED_URI)) + self.view.page().setForwardUnsupportedContent(True) + self.view.page().unsupportedContent.connect(self.download) + + self.manager = QtNetwork.QNetworkAccessManager() + self.manager.finished.connect(self.finished) + + def download(self, reply): + self.request = QtNetwork.QNetworkRequest(reply.url()) + self.reply = self.manager.get(self.request) + + def finished(self): + url = self.reply.url().toString() + + filename = urlparse.parse_qs(url).get('filename', None) + if filename: + filename = filename[0] + name = filename or url + + path = os.path.expanduser(os.path.join( + '~', unicode(name).split('/')[-1])) + destination = QtGui.QFileDialog.getSaveFileName(self, "Save", path) + if destination: + filename = destination[0] + with open(filename, 'wb') as f: + f.write(str(self.reply.readAll())) + f.close() -- cgit v1.2.3 From e7439f48b4279dcda0dc966903840a2affee3353 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 11 May 2016 14:45:09 -0700 Subject: [bug] add scrollbars to mail preference panel --- src/leap/bitmask/gui/preferences_email_page.py | 20 +- src/leap/bitmask/gui/ui/preferences_email_page.ui | 648 ++++++++++++---------- 2 files changed, 354 insertions(+), 314 deletions(-) diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index 7186450d..0f44dfee 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -65,20 +65,16 @@ class PreferencesEmailPage(PreferencesPage): "Search for \"Bitmask\" in the add-on manager or " "download it from addons.mozilla.org.".format( thunderbird_extension_url))) + self.ui.mail_client_label.setText(self.tr( "Alternatively, you can manually configure your mail client to " - "use Bitmask Email with these options:")) + "use Bitmask with these options:")) self.ui.webmail_label.setText(self.tr( - "This distribution of Bitmask ships an experimental integration " - "of Pixelated " - "Mail. Note: at the current state, anyone with access to " - "your device can read your mail without authentication, " - "by opening a browser pointing to http://localhost:9090 ")) - webmail_enabled = self.settings.get_pixelmail_enabled() - self.ui.webmail_checkbox.setChecked(webmail_enabled) - if not HAS_PIXELATED: - self.ui.webmail_box.setVisible(False) + "Bitmask Mail is an integrated mail client based " + "on Pixelated " + "User Agent. If enabled, any user on your device " + "can read your mail by opening http://localhost:9090")) self.ui.keys_table.horizontalHeader().setResizeMode( 0, QtGui.QHeaderView.Stretch) @@ -122,6 +118,10 @@ class PreferencesEmailPage(PreferencesPage): self.ui.message_label.setText( self.tr('You must be logged in to edit email settings.')) else: + webmail_enabled = self.settings.get_pixelmail_enabled() + self.ui.webmail_checkbox.setChecked(webmail_enabled) + if not HAS_PIXELATED: + self.ui.webmail_box.setVisible(False) self.ui.import_button.setVisible(False) # hide this until working self.ui.message_label.setVisible(False) self.ui.email_tabs.setVisible(True) diff --git a/src/leap/bitmask/gui/ui/preferences_email_page.ui b/src/leap/bitmask/gui/ui/preferences_email_page.ui index e4a72951..610a43c7 100644 --- a/src/leap/bitmask/gui/ui/preferences_email_page.ui +++ b/src/leap/bitmask/gui/ui/preferences_email_page.ui @@ -32,345 +32,385 @@ + + background: palette(base); + Tab 1 - - - Bitmask Mail + + + - - - - - Enable Bitmask Mail (needs restart) - - - - - - - webmail info - - - true - - - - - - - - - - Thunderbird Configuration + + QFrame::NoFrame - - - - - thunderbird information - - - true - - - - - - - - - - - 0 - 0 - - - - Other Mail Clients Configuration + + true - - - - - mail client information - - - true - - - - - - - - - - - - 0 - 0 - - - - Host - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - + + + + 0 + 0 + 504 + 537 + + + + false + + + background: palette(base); + + + + 6 + + + 0 + + + + + Bitmask Mail + + + + - Port - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Enable Bitmask Mail (needs restart) - - - - - 0 - 0 - - - - - 100 - 16777215 - - + + - + webmail info - + true - - - - - 0 - 0 - - + + + + + + + Thunderbird Configuration + + + + - TLS: off - - - - - - - - 0 - 0 - - - - - 50 - 16777215 - + thunderbird information - + true - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - IMAP - - - - - - - Username - - - - - - - SMTP - - - - - - - - - - 0 - 0 - - + + + + + + + 0 + 0 + + + + Other Mail Clients Configuration + + + + - Host + mail client information - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - 100 - 16777215 - - - - - - + true - - - - - 0 - 0 - - - - Port - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - TLS: off - - - - - - - - 0 - 0 - - - - - 50 - 16777215 - - - - true - - + + + + + + + + + 0 + 0 + + + + Host + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + Port + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + + + + true + + + + + + + + 0 + 0 + + + + TLS: off + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + IMAP + + + + + + + Username + + + + + + + SMTP + + + + + + + + + + 0 + 0 + + + + Host + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + + + + true + + + + + + + + 0 + 0 + + + + Port + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + TLS: off + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + true + + + + + + + + + true + + + + + + + Password + + + + + + + true + + + + - - - - - true - - - - - - - Password - - - - - - - true - - - - - - + + + + + + Qt::Vertical + + + + 20 + 4 + + + + + + - - - - Qt::Vertical - - - - 20 - 40 - - - - -- cgit v1.2.3 From c7f9bbcc8fe075750e4041c5a7f00c8767dd38b5 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 11 May 2016 15:03:23 -0700 Subject: [feat] Make help menu item open bitmask.net/help directly --- src/leap/bitmask/gui/mainwindow.py | 50 +++----------------------------------- 1 file changed, 3 insertions(+), 47 deletions(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index daf49eb6..25ee4f3d 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1054,53 +1054,9 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): TRIGGERS: self.ui.action_help.triggered - Display the Bitmask help dialog. - """ - # TODO: don't hardcode! - smtp_port = 2013 - - help_url = "

    {0}

    ".format( - self.tr("bitmask.net/help")) - - lang = QtCore.QLocale.system().name().replace('_', '-') - thunderbird_extension_url = \ - "https://addons.mozilla.org/{0}/" \ - "thunderbird/addon/bitmask/".format(lang) - - email_quick_reference = self.tr("Email quick reference") - thunderbird_text = self.tr( - "For Thunderbird, you can use the " - "Bitmask extension. Search for \"Bitmask\" in the add-on " - "manager or download it from " - "addons.mozilla.org.".format(thunderbird_extension_url)) - manual_text = self.tr( - "Alternatively, you can manually configure " - "your mail client to use Bitmask Email with these options:") - manual_imap = self.tr("IMAP: localhost, port {0}".format(IMAP_PORT)) - manual_smtp = self.tr("SMTP: localhost, port {0}".format(smtp_port)) - manual_username = self.tr("Username: your full email address") - - # FIXME on i3, this doens't allow to mouse-select. - # Switch to a dialog in which we can set the QLabel - mail_auth_token = ( - self.app.service_tokens.get('mail_auth', None) or - "??? (log in to unlock)") - mail_password = self.tr("IMAP/SMTP Password:") + " %s" % ( - mail_auth_token,) - - msg = help_url + self.tr( - "

    {0}

    " - "

    {1}

    " - "

    {2}" - "

      " - "
    •  {3}
    • " - "
    •  {4}
    • " - "
    •  {5}
    • " - "
    •  {6}
    • " - "

    ").format(email_quick_reference, thunderbird_text, - manual_text, manual_imap, manual_smtp, - manual_username, mail_password) - QtGui.QMessageBox.about(self, self.tr("Bitmask Help"), msg) + Open bitmask.net/help + """ + QtGui.QDesktopServices.openUrl("https://bitmask.net/help") def _needs_update(self): """ -- cgit v1.2.3 From cd95c8d2e285538e282dbf2b5e848b4154c3606c Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 11 May 2016 15:05:57 -0700 Subject: [bug] remove "about bitmask" easter egg --- src/leap/bitmask/gui/mainwindow.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 25ee4f3d..e7b849e5 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1030,12 +1030,9 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): Display the About Bitmask dialog """ today = datetime.now().date() - greet = ("Happy New 1984!... or not ;)

    " - if today.month == 1 and today.day < 15 else "") title = self.tr("About Bitmask - %s") % (VERSION,) msg = self.tr( "Version: {ver} ({ver_hash})
    " - "
    {greet}" "Bitmask is the Desktop client application for the LEAP " "platform, supporting Encrypted Internet Proxy " "and " @@ -1046,7 +1043,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): "available.
    " "
    " "
    More about LEAP") - msg = msg.format(ver=VERSION, ver_hash=VERSION_HASH[:10], greet=greet) + msg = msg.format(ver=VERSION, ver_hash=VERSION_HASH[:10]) QtGui.QMessageBox.about(self, title, msg) def _help(self): -- cgit v1.2.3 From 61970d6395afcaa5439d866fbfb1ab2ad471141a Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 11 May 2016 17:09:54 -0700 Subject: [feat] move pix UA launcher from menu to mail status widget. --- src/leap/bitmask/gui/mail_status.py | 52 +++++-- src/leap/bitmask/gui/mainwindow.py | 12 -- src/leap/bitmask/gui/preferenceswindow.py | 15 ++ src/leap/bitmask/gui/ui/mail_status.ui | 239 +++++++++++++++++------------- src/leap/bitmask/gui/ui/mainwindow.ui | 10 +- src/leap/bitmask/gui/ui/preferences.ui | 2 +- 6 files changed, 197 insertions(+), 133 deletions(-) diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 419a85c0..1045343b 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -26,7 +26,9 @@ from leap.common.check import leap_assert, leap_assert_type from leap.common.events import register from leap.common.events import catalog +from leap.bitmask.gui.preferenceswindow import PreferencesWindow from ui_mail_status import Ui_MailStatusWidget +from .qt_browser import PixelatedWindow logger = get_logger() @@ -52,13 +54,21 @@ class MailStatusWidget(QtGui.QWidget): self._systray = None self._disabled = True self._started = False + self._mainwindow = parent self._unread_mails = 0 self.ui = Ui_MailStatusWidget() self.ui.setupUi(self) - self.ui.lblMailReadyHelp.setVisible(False) + self.ui.email_ready.setVisible(False) + self.ui.configure_button.clicked.connect( + self._show_configure) + self.ui.open_mail_button.clicked.connect( + self._show_pix_ua) + if not self._mainwindow._settings.get_pixelmail_enabled(): + self.ui.open_mail_button.setVisible(False) + self.ui.or_label.setVisible(False) # set systray tooltip status self._mx_status = "" @@ -144,7 +154,23 @@ class MailStatusWidget(QtGui.QWidget): self.CONNECTED_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[1]) self.ERROR_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[2]) - # Systray and actions + # + # Button actions + # + + def _show_configure(self): + pref_win = PreferencesWindow(self._mainwindow, self._mainwindow.app) + pref_win.set_page("email") + pref_win.show() + + def _show_pix_ua(self): + win = PixelatedWindow(self._mainwindow) + win.show() + win.load_app() + + # + # Systray + # def set_systray(self, systray): """ @@ -166,6 +192,10 @@ class MailStatusWidget(QtGui.QWidget): mx_status = u"{0}: {1}".format(self._service_name, self._mx_status) self._systray.set_service_tooltip(MX_SERVICE, mx_status) + # + # Status + # + def set_action_mail_status(self, action_mail_status): """ Sets the action_mail_status to use. @@ -229,6 +259,9 @@ class MailStatusWidget(QtGui.QWidget): elif ready < 0: tray_status = self.tr("Mail is disabled") + if ready < 1: + self._hide_mail_ready() + self.ui.lblMailStatusIcon.setPixmap(icon) self._action_mail_status.setText(tray_status) self._update_systray_tooltip() @@ -424,9 +457,10 @@ class MailStatusWidget(QtGui.QWidget): self._show_unread_mails() elif event == catalog.IMAP_SERVICE_STARTED: self._imap_started = True - elif event == catalog.IMAP_CLIENT_LOGIN: - # If a MUA has logged in then we don't need to show this. - self._hide_mail_ready_help() + # this is disabled for now, because this event was being + # triggered at weird times. + #elif event == catalog.IMAP_CLIENT_LOGIN: + # self._hide_mail_ready() if ext_status is not None: self._set_mail_status(ext_status, ready=1) @@ -495,15 +529,13 @@ class MailStatusWidget(QtGui.QWidget): Display the correct UI for the connected state. """ self._set_mail_status(self.tr("ON"), 2) + self.ui.email_ready.setVisible(True) - # this help message will hide when the MUA connects - self.ui.lblMailReadyHelp.setVisible(True) - - def _hide_mail_ready_help(self): + def _hide_mail_ready(self): """ Hide the mail help message on the UI. """ - self.ui.lblMailReadyHelp.setVisible(False) + self.ui.email_ready.setVisible(False) def mail_state_disabled(self): """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index e7b849e5..6637f170 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -59,8 +59,6 @@ from leap.bitmask.util.keyring_helpers import has_keyring from leap.common.events import register from leap.common.events import catalog -from .qt_browser import PixelatedWindow - from leap.mail.imap.service.imap import IMAP_PORT from ui_mainwindow import Ui_MainWindow @@ -227,11 +225,6 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._backend_connect() self.ui.action_preferences.triggered.connect(self._show_preferences) - self.ui.action_pixelated_mail.triggered.connect( - self._show_pixelated_browser) - - pixelated_enabled = self._settings.get_pixelmail_enabled() - self.ui.action_pixelated_mail.setVisible(pixelated_enabled) self.ui.action_about_leap.triggered.connect(self._about) self.ui.action_quit.triggered.connect(self.quit) @@ -575,11 +568,6 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): pref_win = PreferencesWindow(self, self.app) pref_win.show() - def _show_pixelated_browser(self): - win = PixelatedWindow(self) - win.show() - win.load_app() - def _update_eip_enabled_status(self, account=None, services=None): """ TRIGGER: diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 82dc8d77..30091312 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -38,6 +38,12 @@ class PreferencesWindow(QtGui.QDialog): _current_window = None # currently visible preferences window + _panels = { + "account": 0, + "vpn": 1, + "email": 2 + } + def __init__(self, parent, app): """ :param parent: parent object of the PreferencesWindow. @@ -240,3 +246,12 @@ class PreferencesWindow(QtGui.QDialog): Triggered by get srp_status_logged_in, srp_status_not_logged_in """ self._set_account(self.app.current_account()) + + def set_page(self, page): + """ + Jump to a particular page + """ + index = PreferencesWindow._panels[page] + self.ui.nav_widget.setCurrentRow(index) + self.ui.pages_widget.setCurrentIndex(index) + diff --git a/src/leap/bitmask/gui/ui/mail_status.ui b/src/leap/bitmask/gui/ui/mail_status.ui index 89e1843f..f8ebb5a8 100644 --- a/src/leap/bitmask/gui/ui/mail_status.ui +++ b/src/leap/bitmask/gui/ui/mail_status.ui @@ -6,12 +6,12 @@ 0 0 - 417 - 185 + 427 + 157
    - + 0 0 @@ -20,26 +20,131 @@ Form - - 0 - + + + + color: rgb(80, 80, 80); + + + You must login to use encrypted email. + + + + + + + + 22 + 22 + + + + + 22 + 22 + + + + + + + :/images/black/22/off.png + + + true + + + + + + + + 0 + + + 6 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Configure Client + + + + + + + + 0 + 0 + + + + or + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + Open Bitmask Mail + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 10 + 10 + + + + + + + - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - + + + + + + :/images/black/32/email.png + + + + + + + 0 @@ -51,89 +156,18 @@ - - - + + + + Qt::Horizontal + + - 24 - 24 + 1 + 1 - - - - - :/images/black/22/off.png - - - true - - - - - - - false - - - background-color: #e0efd8; -padding: 10px; -margin-top:5px; - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - Bitmask is ready to encrypt your email. Go to <a href="https://bitmask.net/en/help/email">https://bitmask.net/en/help/email</a> for email application setup instructions. - - - Qt::AutoText - - - false - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - 0 - - - -1 - - - true - - - - - - - - - - :/images/black/32/email.png - - - - - - - color: rgb(80, 80, 80); - - - You must login to use encrypted email. - - +
    @@ -141,6 +175,7 @@ margin-top:5px; + diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index 0dd0b891..5d8e0f35 100644 --- a/src/leap/bitmask/gui/ui/mainwindow.ui +++ b/src/leap/bitmask/gui/ui/mainwindow.ui @@ -75,7 +75,7 @@ 0 0 524 - 549 + 541
    @@ -306,7 +306,7 @@ 0 0 524 - 21 + 25 @@ -315,7 +315,6 @@ - @@ -371,11 +370,6 @@ Create a new account... - - - Bitmask Mail - - false diff --git a/src/leap/bitmask/gui/ui/preferences.ui b/src/leap/bitmask/gui/ui/preferences.ui index 51cad0a1..8e884a63 100644 --- a/src/leap/bitmask/gui/ui/preferences.ui +++ b/src/leap/bitmask/gui/ui/preferences.ui @@ -7,7 +7,7 @@ 0 0 630 - 500 + 560 -- cgit v1.2.3 From 2b7aafab06b1af0cad2c7f5c785bd6eea3063440 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 11 May 2016 17:10:43 -0700 Subject: [bug] add leap.auth to requirements-pixelated.pip --- pkg/requirements-pixelated.pip | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/requirements-pixelated.pip b/pkg/requirements-pixelated.pip index d1f8004d..b3e57a1b 100644 --- a/pkg/requirements-pixelated.pip +++ b/pkg/requirements-pixelated.pip @@ -2,3 +2,4 @@ pixelated-user-agent pixelated-www whoosh +leap.auth \ No newline at end of file -- cgit v1.2.3 From 6d4c24af969d72331f0177ec302b9d48381683b8 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 11 May 2016 20:49:31 -0400 Subject: [style] pep8 --- src/leap/bitmask/gui/mail_status.py | 2 +- src/leap/bitmask/gui/preferences_email_page.py | 2 +- src/leap/bitmask/gui/preferenceswindow.py | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 1045343b..cb0314b5 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -459,7 +459,7 @@ class MailStatusWidget(QtGui.QWidget): self._imap_started = True # this is disabled for now, because this event was being # triggered at weird times. - #elif event == catalog.IMAP_CLIENT_LOGIN: + # elif event == catalog.IMAP_CLIENT_LOGIN: # self._hide_mail_ready() if ext_status is not None: diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index 0f44dfee..93c77df1 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -121,7 +121,7 @@ class PreferencesEmailPage(PreferencesPage): webmail_enabled = self.settings.get_pixelmail_enabled() self.ui.webmail_checkbox.setChecked(webmail_enabled) if not HAS_PIXELATED: - self.ui.webmail_box.setVisible(False) + self.ui.webmail_box.setVisible(False) self.ui.import_button.setVisible(False) # hide this until working self.ui.message_label.setVisible(False) self.ui.email_tabs.setVisible(True) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 30091312..50a972e1 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -39,9 +39,9 @@ class PreferencesWindow(QtGui.QDialog): _current_window = None # currently visible preferences window _panels = { - "account": 0, - "vpn": 1, - "email": 2 + "account": 0, + "vpn": 1, + "email": 2 } def __init__(self, parent, app): @@ -254,4 +254,3 @@ class PreferencesWindow(QtGui.QDialog): index = PreferencesWindow._panels[page] self.ui.nav_widget.setCurrentRow(index) self.ui.pages_widget.setCurrentIndex(index) - -- cgit v1.2.3 From 13b5afd1bdea38e908bb774becfc0f49a532d5bd Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 11 May 2016 13:38:07 -0400 Subject: [feat] resize qt-browser to a sensible default mainly to avoid the "Send" button to become out of view in the Compose pane (it needed scrolling to get it on view). this resize shouldn't be needed when pixelated solves the size-responsiveness issue. --- src/leap/bitmask/gui/qt_browser.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/leap/bitmask/gui/qt_browser.py b/src/leap/bitmask/gui/qt_browser.py index 2f7e6086..2d9e20e6 100644 --- a/src/leap/bitmask/gui/qt_browser.py +++ b/src/leap/bitmask/gui/qt_browser.py @@ -37,6 +37,11 @@ class PixelatedWindow(QtGui.QDialog): self.setLayout(layout) self.setWindowTitle('Bitmask Mail') + # For the moment, we need to resize to a sensible default to avoid the + # "send" button to be out of view in the compose pane. This should be + # removed as soon as pixelated becomes size-responsive. + self.resize(800, 700) + def load_app(self): self.view.load(QtCore.QUrl(PIXELATED_URI)) self.view.page().setForwardUnsupportedContent(True) -- cgit v1.2.3 From cba8beb034eade7951ca08b69a152fc76c4362a5 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 11 May 2016 20:51:16 -0400 Subject: [pkg] bump bundle to rc4 --- pkg/next-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/next-version b/pkg/next-version index 29a0f215..c29ffb37 100644 --- a/pkg/next-version +++ b/pkg/next-version @@ -1 +1 @@ -0.9.2.rc2 +0.9.2.rc4 -- cgit v1.2.3 From e5cf9ff70cc27cbbf17b9e3c4df98d336d0ed0af Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 13 May 2016 11:55:48 -0400 Subject: [pkg] fold in changelog --- CHANGELOG.rst | 32 ++++++++++++++++++++++++++++++++ changes/next-changelog.rst | 18 ------------------ 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1c8422fa..3974b120 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,38 @@ Changelog --------- +0.9.2 May 13 - "panis et circenses" ++++++++++++++++++++++++++++++++++++++++++++ + +Features +~~~~~~~~ +- `#7552 `_: Improve UI message and add some margin above the msg box. +- `#7656 `_: Adapt to multi-user aware events. +- `#4469 `_: Display randomly generated service token on the Help Window. +- `#6041 `_: Write service tokens to a file to allow email clients to read them from there. +- Use cred-based authentication on SMTP. +- Add email panel to preferences window. +- Ability to launch detached bitmask.core daemon, and a simplistic bitmask_cli. Not used by the main client yet. +- Experimental support for the Pixelated User Agent, branded as "Bitmask Mail". + +Bugfixes +~~~~~~~~ +- `#7568 `_: Fix typo on signal name. +- `#7583 `_: Fix set_soledad_auth_token event callback signature. +- `#7585 `_: Open email help link on browser. +- `#7598 `_: Fix errback on InvalidAuthToken. +- `#7869 `_: Redownload smtp certificate if needed. +- Do not translate 'https' text on QLabel. + +Misc +~~~~ +- PyInstaller based new style of bundles. + +Known Issues +~~~~~~~~~~~~ +- `#8057 `_: Logging out twice produces a segfault in Qt +- `#1236 `_: Description of the known issue corresponding with issue #1236. + 0.9.1 November 03 - "the day of the calaca" +++++++++++++++++++++++++++++++++++++++++++ diff --git a/changes/next-changelog.rst b/changes/next-changelog.rst index 8a5035ff..59f68d8d 100644 --- a/changes/next-changelog.rst +++ b/changes/next-changelog.rst @@ -10,27 +10,11 @@ I've added a new category `Misc` so we can track doc/style/packaging stuff. Features ~~~~~~~~ -- `#7552 `_: Improve UI message and add some margin above the msg box. -- `#7656 `_: Adapt to multi-user aware events. -- `#4469 `_: Display randomly generated service token on the Help Window. -- `#6041 `_: Write service tokens to a file to allow email clients to read them from there. -- Use cred-based authentication on SMTP. -- Experimental support for the Pixelated WebMail. -- Add email panel to preferences window. -- Ability to launch detached bitmask.core daemon, and a simplistic bitmask_cli. Not used by the main client yet. - - `#1234 `_: Description of the new feature corresponding with issue #1234. - New feature without related issue number. Bugfixes ~~~~~~~~ -- `#7568 `_: Fix typo on signal name. -- `#7583 `_: Fix set_soledad_auth_token event callback signature. -- `#7585 `_: Open email help link on browser. -- `#7598 `_: Fix errback on InvalidAuthToken. -- `#7869 `_: Redownload smtp certificate if needed. -- Do not translate 'https' text on QLabel. - - `#1235 `_: Description for the fixed stuff corresponding with issue #1235. - Bugfix without related issue number. @@ -41,5 +25,3 @@ Misc Known Issues ~~~~~~~~~~~~ -- `#8057 `_: Logging out twice produces a segfault in Qt -- `#1236 `_: Description of the known issue corresponding with issue #1236. -- cgit v1.2.3 From facefed990195bd89bf1064e75b171edd084f632 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 18 May 2016 06:23:06 -0400 Subject: [pkg] bump version to 0.9.2 final --- pkg/next-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/next-version b/pkg/next-version index c29ffb37..2003b639 100644 --- a/pkg/next-version +++ b/pkg/next-version @@ -1 +1 @@ -0.9.2.rc4 +0.9.2 -- cgit v1.2.3