[openerp] Initial import (#817271)
leamas
leamas at fedoraproject.org
Mon Jul 2 20:18:54 UTC 2012
commit 119c6e863865df06930563e23f21427540208e1f
Author: Alec Leamas <alec at tests.notat.diaspora.com>
Date: Mon Jul 2 22:18:37 2012 +0200
Initial import (#817271)
.gitignore | 1 +
LICENSING | 102 +
README.fedora | 72 +
openerp-fsf-fix.patch | 847 ++++++
openerp-gen-cert | 53 +
...rp-server-relicense-dict_tools-to-LGPL2.1.patch | 25 +
openerp-unbundle-pyftpdlib.patch | 3075 ++++++++++++++++++++
openerp.service | 16 +
openerp.spec | 227 ++
sources | 1 +
10 files changed, 4419 insertions(+), 0 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index e69de29..cd3e38f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1 @@
+/openerp-6.1-20120505-233516.tar.gz
diff --git a/LICENSING b/LICENSING
new file mode 100644
index 0000000..8c3be98
--- /dev/null
+++ b/LICENSING
@@ -0,0 +1,102 @@
+Unless stated below, openerp is published under the Affero General
+Public License version 3 (AGPLv3)
+
+BSD licensed files
+------------------
+
+./bin/report/pyPdf/utils.py: BSD (3 clause)
+./bin/report/pyPdf/filters.py: BSD (3 clause)
+./bin/report/pyPdf/pdf.py: BSD (3 clause)
+./bin/report/pyPdf/generic.py: BSD (3 clause)
+./bin/tools/threadinglocal.py: BSD (3 clause)
+./bin/addons/l10n_cr/__init__.py: BSD (2 clause)
+./bin/addons/l10n_cr/__openerp__.py: BSD (2 clause)
+../bin/addons/auction/barcode/common.py: BSD (4 clause)
+./bin/addons/auction/barcode/code128.py: BSD (4 clause)
+./bin/addons/auction/barcode/usps.py: BSD (4 clause)
+./bin/addons/auction/barcode/code93.py: BSD (4 clause)
+./bin/addons/auction/barcode/__init__.py: BSD (4 clause)
+./bin/addons/auction/barcode/code39.py: BSD (4 clause)
+./bin/addons/auction/barcode/fourstate.py: BSD (4 clause)
+./bin/addons/crm/scripts/php/xmlrpc.inc: BSD (3 clause)
+./bin/addons/wiki/web/widgets/rss/feedparser.py: BSD (2 clause)
+
+
+GPL version 2 or later, GPL version 3.
+--------------------------------------
+
+All these files are published under the terms of GPL version 3.
+
+./bin/netsvc.py: GPL (v3 or later)
+./bin/service/http_server.py: GPL (v2 or later)
+./bin/service/websrv_lib.py: GPL (v2 or later)
+./bin/addons/report_webkit_sample/report/__init__.py: GPL (v2 or later)
+./bin/addons/report_webkit_sample/__init__.py: GPL (v2 or later)
+./bin/addons/report_webkit_sample/__openerp__.py: GPL (v2 or later)
+./bin/addons/l10n_es/__init__.py: GPL (v3 or later)
+./bin/addons/l10n_es/__openerp__.py: GPL (v3 or later)
+./bin/addons/email_template/email_template.py: GPL (v3 or later)
+./bin/addons/email_template/email_template_account.py: GPL (v3 or later)
+./bin/addons/email_template/__init__.py: GPL (v3 or later)
+./bin/addons/email_template/__openerp__.py: GPL (v3 or later)
+./bin/addons/email_template/wizard/email_template_send_wizard.py: GPL (v3 or later)
+./bin/addons/email_template/wizard/__init__.py: GPL (v3 or later)
+./bin/addons/email_template/email_template_mailbox.py: GPL (v3 or later)
+./bin/addons/l10n_in/__init__.py: GPL (v2 or later)
+./bin/addons/thunderbird/__openerp__.py: GPL (v3 or later)
+./bin/addons/document_webdav/webdav_server.py: GPL (v3 or later)
+./bin/addons/document_webdav/test_davclient.py: GPL (v2 or later)
+./bin/addons/document_webdav/__openerp__.py: GPL (v2 or later)
+./bin/addons/document/document_storage.py: GPL (v3 or later)
+./bin/addons/l10n_nl/__init__.py: GPL (v2 or later)
+./bin/addons/l10n_nl/__openerp__.py: GPL (v2 or later)
+./bin/addons/l10n_gt/__init__.py: GPL (v3 or later)
+./bin/addons/l10n_gt/__openerp__.py: GPL (v3 or later)
+./bin/addons/l10n_mx/__init__.py: GPL (v3 or later)
+./bin/addons/l10n_mx/__openerp__.py: GPL (v3 or later)
+./bin/addons/l10n_ve/__init__.py: GPL (v3 or later)
+./bin/addons/l10n_ve/__openerp__.py: GPL (v3 or later)
+./bin/addons/account_sequence/account_sequence.py: GPL (v3 or later)
+./bin/addons/base_crypt/crypt.py: GPL (v2 or later)
+./bin/addons/base_vat/base_vat.py: GPL (v3 or later)
+./bin/addons/l10n_ca/__init__.py: GPL (v3 or later)
+./bin/addons/l10n_gr/__init__.py: GPL (v3 or later)
+./bin/addons/l10n_gr/__openerp__.py: GPL (v3 or later)
+./bin/addons/l10n_br/__init__.py: GPL (v3 or later)
+./bin/addons/l10n_br/__openerp__.py: GPL (v3 or later)
+./bin/addons/l10n_cn/__openerp__.py: GPL (v3 or later)
+./bin/addons/l10n_fr/report/compute_resultant_report.py: GPL (v2 or later)
+./bin/addons/l10n_fr/report/base_report.py: GPL (v2 or later)
+./bin/addons/l10n_fr/report/__init__.py: GPL (v2 or later)
+./bin/addons/l10n_fr/report/bilan_report.py: GPL (v2 or later)
+./bin/addons/l10n_fr/__init__.py: GPL (v2 or later)
+./bin/addons/l10n_fr/__openerp__.py: GPL (v2 or later)
+./bin/addons/l10n_fr/wizard/fr_report_bilan.py: GPL (v2 or later)
+./bin/addons/l10n_fr/wizard/__init__.py: GPL (v2 or later)
+./bin/addons/l10n_fr/wizard/fr_report_compute_resultant.py: GPL (v2 or later)
+./bin/addons/anonymization/anonymization.py: GPL (v3 or later)
+./bin/addons/anonymization/__init__.py: GPL (v3 or later)
+./bin/addons/anonymization/__openerp__.py: GPL (v3 or later)
+./bin/addons/l10n_ro/__init__.py: GPL (v3 or later)
+./bin/addons/l10n_ro/res_partner.py: GPL (v3 or later)
+./bin/addons/l10n_ro/__openerp__.py: GPL (v3 or later)
+./bin/addons/l10n_uk/__init__.py: GPL (v2 or later)
+./bin/addons/l10n_ma/l10n_ma.py: GPL (v3 or later)
+./bin/addons/l10n_ch/report/__init__.py: GPL (v2 or later)
+./bin/addons/report_webkit/ir_report.py: GPL (v2 or later)
+./bin/addons/report_webkit/webkit_report.py: GPL (v2 or later)
+./bin/addons/report_webkit/header.py: GPL (v2 or later)
+./bin/addons/report_webkit/company.py: GPL (v2 or later)
+./bin/addons/report_webkit/__init__.py: GPL (v2 or later)
+./bin/addons/report_webkit/__openerp__.py: GPL (v2 or later)
+./bin/addons/report_webkit/wizard/report_webkit_actions.py: GPL (v2 or later)
+./bin/addons/report_webkit/wizard/__init__.py: GPL (v2 or later)
+./bin/addons/report_webkit/report_helper.py: GPL (v2 or later)
+./bin/addons/account/report/account_general_ledger.py: GPL (v2 or later)
+./bin/addons/account/account_cash_statement.py: GPL (v3 or later)
+./bin/addons/l10n_pl/__init__.py: GPL (v3 or later)
+./bin/addons/l10n_pl/__openerp__.py: GPL (v3 or later)
+
+Files under LGPLv2+:
+--------------------
+./bin/addons/document/dict_tools.py: LGPL
diff --git a/README.fedora b/README.fedora
new file mode 100644
index 0000000..9c7ccf2
--- /dev/null
+++ b/README.fedora
@@ -0,0 +1,72 @@
+# Fedora getting-started for Fedora >= F16
+
+There are several steps to get the server running:
+
+- Install postgresql and start the server.
+- Generate openerp server key and certificate.
+- Create the postgresql openerp user and it's database
+- Start the server.
+
+## Install postgresql.
+
+ $ sudo yum -y install postgresql-server
+ $ sudo postgresql-setup initdb
+ $ sudo su - postgres
+ bash-2.4$ sed -i '/^host/s/[^ ]*$/password/' \
+ /var/lib/pgsql/data/pg_hba.conf
+ bash-2.4$ exit
+ $ sudo systemctl enable postgresql.service
+ $ sudo systemctl start postgresql.service
+
+The modifications of pg_hba.conf let the local postgresql user run also
+as postgres db user without authenticationĀ§, while all TCP-IP connections
+need a password.
+
+See: /usr/share/doc/postgresql-*/README.rpm-dist
+
+## Install openerp and generate server key and certificate.
+
+To install and generate a simple certificate and a key, both stored
+in /etc/openerp::
+
+ $ sudo yum install openerp
+ $ sudo openerp-gen-cert
+
+## Create database user. and it's database
+
+To create the openerp user, you need to set the password by
+editing /etc/openerp/openerp-server.conf. You should set
+db_name, db_user and db_password. To create database,
+assuming db_name == db_user == 'openerp'::
+
+ $ sudo su - postgres
+ bash-4.2$ createuser --createdb --no-createrole --pwprompt openerp
+ Enter password for new role: XXXXXXXXXX
+ Enter it again: XXXXXXXXXX
+ Shall the new role be a superuser? (y/n) y
+ CREATE ROLE
+ bash-4.2$ createdb -W -U openerp openerp
+ Password: XXXXXXXX
+ bash-4.2$ exit
+
+All three passwords are the same as db_password in
+/etc/openerp/openerp-server.conf.
+
+## Start openerp server
+
+ $ sudo systemctl start openerp.service
+
+and verify that /var/log/messages looks OK. You can make server start at
+boot using 'systemctl enable openerp.service'
+
+## Continue installation
+
+From now on you need to create an openerp database. For this, you need the
+gtk client:
+
+ $ sudo yum install openerp-client
+ $ openerp-client
+
+and proceed according to
+http://doc.openerp.com/v6.0/book/1/1_1_Inst_Config/1_1_Inst_Config_db_create.html
+
diff --git a/openerp-fsf-fix.patch b/openerp-fsf-fix.patch
new file mode 100644
index 0000000..5f21fdd
--- /dev/null
+++ b/openerp-fsf-fix.patch
@@ -0,0 +1,847 @@
+diff --git a/openerp/addons/account/report/account_general_ledger.py b/openerp/addons/account/report/account_general_ledger.py
+index 294c7fa..620f5be 100644
+--- a/openerp/addons/account/report/account_general_ledger.py
++++ b/openerp/addons/account/report/account_general_ledger.py
+@@ -23,7 +23,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/base_crypt/crypt.py b/openerp/addons/base_crypt/crypt.py
+index c2fd0a2..575d5cd 100644
+--- a/openerp/addons/base_crypt/crypt.py
++++ b/openerp/addons/base_crypt/crypt.py
+@@ -32,8 +32,8 @@
+ # with this program; if not, write to the:
+ #
+ # Free Software Foundation, Inc.
+-# 59 Temple Place - Suite 330
+-# Boston, MA 02111-1307
++# 51 Franklin Street, Fifth Floor
++# Boston, MA 02110-1301
+ # USA.
+
+ from random import seed, sample
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/About.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/About.py
+index 401cf88..f7b8264 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/About.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/About.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/AddAttachment.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/AddAttachment.py
+index c65ba32..4d6be2b 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/AddAttachment.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/AddAttachment.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Change.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Change.py
+index cdc5c75..9707d5b 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Change.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Change.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ConvertBracesToField.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ConvertBracesToField.py
+index bb80c3e..8a9227c 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ConvertBracesToField.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ConvertBracesToField.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ConvertFieldsToBraces.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ConvertFieldsToBraces.py
+index 892061c..fb5516f 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ConvertFieldsToBraces.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ConvertFieldsToBraces.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ExportToRML.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ExportToRML.py
+index dd00f17..70f9c2b 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ExportToRML.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ExportToRML.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Expression.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Expression.py
+index ecdf134..839d2d8 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Expression.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Expression.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Fields.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Fields.py
+index 92044f7..3e8113d 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Fields.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Fields.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/LoginTest.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/LoginTest.py
+index 85059d7..4b7896f 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/LoginTest.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/LoginTest.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ModifyExistingReport.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ModifyExistingReport.py
+index 2ccd835..568c2f9 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ModifyExistingReport.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ModifyExistingReport.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/NewReport.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/NewReport.py
+index 6022e44..b95e967 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/NewReport.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/NewReport.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Repeatln.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Repeatln.py
+index 0eab6d1..3747737 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Repeatln.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Repeatln.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/SendToServer.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/SendToServer.py
+index 3a6d799..7425162 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/SendToServer.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/SendToServer.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ServerParameter.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ServerParameter.py
+index 9b69e4a..b22a27c 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ServerParameter.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ServerParameter.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Translation.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Translation.py
+index 73146f1..864ae63 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Translation.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Translation.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/__init__.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/__init__.py
+index 0ccd1c9..8b773dc 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/__init__.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/__init__.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/compile_all.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/compile_all.py
+index 4740c61..082b2cc 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/compile_all.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/compile_all.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/actions.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/actions.py
+index 8511bb8..39127b8 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/actions.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/actions.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/error.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/error.py
+index c0a3e5b..9a31e44 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/error.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/error.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/functions.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/functions.py
+index 29f1cd8..eac97c0 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/functions.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/functions.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/gui.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/gui.py
+index 975aa38..faffde6 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/gui.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/gui.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/modify.py b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/modify.py
+index fc3d667..0f60b6b 100644
+--- a/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/modify.py
++++ b/openerp/addons/base_report_designer/plugin/openerp_report_designer/bin/script/modify.py
+@@ -18,7 +18,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ #
+ # See: http://www.gnu.org/licenses/lgpl.html
+ #
+diff --git a/openerp/addons/document_webdav/__openerp__.py b/openerp/addons/document_webdav/__openerp__.py
+index 82541b7..09245e7 100644
+--- a/openerp/addons/document_webdav/__openerp__.py
++++ b/openerp/addons/document_webdav/__openerp__.py
+@@ -24,7 +24,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/document_webdav/test_davclient.py b/openerp/addons/document_webdav/test_davclient.py
+index 3c20cfe..44be73a 100755
+--- a/openerp/addons/document_webdav/test_davclient.py
++++ b/openerp/addons/document_webdav/test_davclient.py
+@@ -24,7 +24,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ ###############################################################################
+
+ """ A trivial HTTP/WebDAV client, used for testing the server
+diff --git a/openerp/addons/document_webdav/webdav_server.py b/openerp/addons/document_webdav/webdav_server.py
+index ae75d0b..dd39d55 100644
+--- a/openerp/addons/document_webdav/webdav_server.py
++++ b/openerp/addons/document_webdav/webdav_server.py
+@@ -30,7 +30,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ ###############################################################################
+
+
+diff --git a/openerp/addons/l10n_ch/report/__init__.py b/openerp/addons/l10n_ch/report/__init__.py
+index e8703f1..eecc67f 100644
+--- a/openerp/addons/l10n_ch/report/__init__.py
++++ b/openerp/addons/l10n_ch/report/__init__.py
+@@ -25,7 +25,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/l10n_fr/__init__.py b/openerp/addons/l10n_fr/__init__.py
+index 8d5728e..d2ec220 100644
+--- a/openerp/addons/l10n_fr/__init__.py
++++ b/openerp/addons/l10n_fr/__init__.py
+@@ -22,7 +22,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/l10n_fr/__openerp__.py b/openerp/addons/l10n_fr/__openerp__.py
+index 839907c..c375d73 100644
+--- a/openerp/addons/l10n_fr/__openerp__.py
++++ b/openerp/addons/l10n_fr/__openerp__.py
+@@ -22,7 +22,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+ {
+diff --git a/openerp/addons/l10n_fr/report/__init__.py b/openerp/addons/l10n_fr/report/__init__.py
+index 9907a2f..5056318 100644
+--- a/openerp/addons/l10n_fr/report/__init__.py
++++ b/openerp/addons/l10n_fr/report/__init__.py
+@@ -22,7 +22,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/l10n_fr/report/base_report.py b/openerp/addons/l10n_fr/report/base_report.py
+index 2c44b06..8cb0009 100644
+--- a/openerp/addons/l10n_fr/report/base_report.py
++++ b/openerp/addons/l10n_fr/report/base_report.py
+@@ -22,7 +22,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/l10n_fr/report/bilan_report.py b/openerp/addons/l10n_fr/report/bilan_report.py
+index fafacd8..b47b0c3 100644
+--- a/openerp/addons/l10n_fr/report/bilan_report.py
++++ b/openerp/addons/l10n_fr/report/bilan_report.py
+@@ -22,7 +22,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/l10n_fr/report/compute_resultant_report.py b/openerp/addons/l10n_fr/report/compute_resultant_report.py
+index e91f9fc..5ea6dd4 100644
+--- a/openerp/addons/l10n_fr/report/compute_resultant_report.py
++++ b/openerp/addons/l10n_fr/report/compute_resultant_report.py
+@@ -22,7 +22,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/l10n_fr/wizard/__init__.py b/openerp/addons/l10n_fr/wizard/__init__.py
+index 6e29471..32d4c5e 100644
+--- a/openerp/addons/l10n_fr/wizard/__init__.py
++++ b/openerp/addons/l10n_fr/wizard/__init__.py
+@@ -22,7 +22,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/l10n_fr/wizard/fr_report_bilan.py b/openerp/addons/l10n_fr/wizard/fr_report_bilan.py
+index cec2e51..1676ba6 100644
+--- a/openerp/addons/l10n_fr/wizard/fr_report_bilan.py
++++ b/openerp/addons/l10n_fr/wizard/fr_report_bilan.py
+@@ -22,7 +22,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/l10n_fr/wizard/fr_report_compute_resultant.py b/openerp/addons/l10n_fr/wizard/fr_report_compute_resultant.py
+index d90466e..bd0b29a 100644
+--- a/openerp/addons/l10n_fr/wizard/fr_report_compute_resultant.py
++++ b/openerp/addons/l10n_fr/wizard/fr_report_compute_resultant.py
+@@ -22,7 +22,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/l10n_in/__init__.py b/openerp/addons/l10n_in/__init__.py
+index 19de03b..c45b21d 100644
+--- a/openerp/addons/l10n_in/__init__.py
++++ b/openerp/addons/l10n_in/__init__.py
+@@ -23,7 +23,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/l10n_nl/__init__.py b/openerp/addons/l10n_nl/__init__.py
+index 57a3bb1..4235db8 100644
+--- a/openerp/addons/l10n_nl/__init__.py
++++ b/openerp/addons/l10n_nl/__init__.py
+@@ -22,7 +22,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/l10n_nl/__openerp__.py b/openerp/addons/l10n_nl/__openerp__.py
+index 562fd0a..561d23b 100644
+--- a/openerp/addons/l10n_nl/__openerp__.py
++++ b/openerp/addons/l10n_nl/__openerp__.py
+@@ -22,7 +22,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+ #
+diff --git a/openerp/addons/l10n_pe/__init__.py b/openerp/addons/l10n_pe/__init__.py
+index 92da5db..e8fb5de 100644
+--- a/openerp/addons/l10n_pe/__init__.py
++++ b/openerp/addons/l10n_pe/__init__.py
+@@ -23,7 +23,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/report_webkit/__init__.py b/openerp/addons/report_webkit/__init__.py
+index dd44517..8c7533a 100644
+--- a/openerp/addons/report_webkit/__init__.py
++++ b/openerp/addons/report_webkit/__init__.py
+@@ -25,7 +25,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/report_webkit/__openerp__.py b/openerp/addons/report_webkit/__openerp__.py
+index f0bf0d1..ee782d0 100644
+--- a/openerp/addons/report_webkit/__openerp__.py
++++ b/openerp/addons/report_webkit/__openerp__.py
+@@ -25,7 +25,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/report_webkit/company.py b/openerp/addons/report_webkit/company.py
+index eaf3c02..2859454 100644
+--- a/openerp/addons/report_webkit/company.py
++++ b/openerp/addons/report_webkit/company.py
+@@ -25,7 +25,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/report_webkit/convert.py b/openerp/addons/report_webkit/convert.py
+index b4d4232..645437b 100644
+--- a/openerp/addons/report_webkit/convert.py
++++ b/openerp/addons/report_webkit/convert.py
+@@ -25,7 +25,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/report_webkit/header.py b/openerp/addons/report_webkit/header.py
+index b052c61..5b7cc8f 100644
+--- a/openerp/addons/report_webkit/header.py
++++ b/openerp/addons/report_webkit/header.py
+@@ -25,7 +25,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/report_webkit/ir_report.py b/openerp/addons/report_webkit/ir_report.py
+index d2709ea..0ed89f2 100644
+--- a/openerp/addons/report_webkit/ir_report.py
++++ b/openerp/addons/report_webkit/ir_report.py
+@@ -25,7 +25,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/report_webkit/report_helper.py b/openerp/addons/report_webkit/report_helper.py
+index 12651f3..4408f00 100644
+--- a/openerp/addons/report_webkit/report_helper.py
++++ b/openerp/addons/report_webkit/report_helper.py
+@@ -25,7 +25,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/report_webkit/webkit_report.py b/openerp/addons/report_webkit/webkit_report.py
+index 4d0bd62..4cfb858 100644
+--- a/openerp/addons/report_webkit/webkit_report.py
++++ b/openerp/addons/report_webkit/webkit_report.py
+@@ -26,7 +26,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/report_webkit/wizard/__init__.py b/openerp/addons/report_webkit/wizard/__init__.py
+index e8ae8b7..649ad01 100644
+--- a/openerp/addons/report_webkit/wizard/__init__.py
++++ b/openerp/addons/report_webkit/wizard/__init__.py
+@@ -25,7 +25,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/report_webkit/wizard/report_webkit_actions.py b/openerp/addons/report_webkit/wizard/report_webkit_actions.py
+index bb2c531..e32db38 100644
+--- a/openerp/addons/report_webkit/wizard/report_webkit_actions.py
++++ b/openerp/addons/report_webkit/wizard/report_webkit_actions.py
+@@ -25,7 +25,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/report_webkit_sample/__init__.py b/openerp/addons/report_webkit_sample/__init__.py
+index c1d68d5..783e271 100644
+--- a/openerp/addons/report_webkit_sample/__init__.py
++++ b/openerp/addons/report_webkit_sample/__init__.py
+@@ -25,7 +25,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+ import wizard
+diff --git a/openerp/addons/report_webkit_sample/__openerp__.py b/openerp/addons/report_webkit_sample/__openerp__.py
+index 01eed24..e0ab052 100644
+--- a/openerp/addons/report_webkit_sample/__openerp__.py
++++ b/openerp/addons/report_webkit_sample/__openerp__.py
+@@ -25,7 +25,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/report_webkit_sample/report/__init__.py b/openerp/addons/report_webkit_sample/report/__init__.py
+index e8703f1..eecc67f 100644
+--- a/openerp/addons/report_webkit_sample/report/__init__.py
++++ b/openerp/addons/report_webkit_sample/report/__init__.py
+@@ -25,7 +25,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ #
+ ##############################################################################
+
+diff --git a/openerp/addons/resource/faces/observer.py b/openerp/addons/resource/faces/observer.py
+index 8943452..de5e990 100644
+--- a/openerp/addons/resource/faces/observer.py
++++ b/openerp/addons/resource/faces/observer.py
+@@ -22,7 +22,7 @@
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the
+ # Free Software Foundation, Inc.,
+-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ ############################################################################
+
+ #@-node:<< Copyright >>
+diff --git a/openerp/addons/resource/faces/pcalendar.py b/openerp/addons/resource/faces/pcalendar.py
+index c82c49d..213aac7 100644
+--- a/openerp/addons/resource/faces/pcalendar.py
++++ b/openerp/addons/resource/faces/pcalendar.py
+@@ -22,7 +22,7 @@
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the
+ # Free Software Foundation, Inc.,
+-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ ############################################################################
+
+ #@-node:<< Copyright >>
+diff --git a/openerp/addons/resource/faces/plocale.py b/openerp/addons/resource/faces/plocale.py
+index 61bfa91..5b2d308 100644
+--- a/openerp/addons/resource/faces/plocale.py
++++ b/openerp/addons/resource/faces/plocale.py
+@@ -17,7 +17,7 @@
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the
+ # Free Software Foundation, Inc.,
+-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ ############################################################################
+
+ import gettext
+diff --git a/openerp/addons/resource/faces/resource.py b/openerp/addons/resource/faces/resource.py
+index 3451806..e8246fc 100644
+--- a/openerp/addons/resource/faces/resource.py
++++ b/openerp/addons/resource/faces/resource.py
+@@ -22,7 +22,7 @@
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the
+ # Free Software Foundation, Inc.,
+-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ ############################################################################
+
+ #@-node:<< Copyright >>
+diff --git a/openerp/addons/resource/faces/task.py b/openerp/addons/resource/faces/task.py
+index f524011..b2ad264 100644
+--- a/openerp/addons/resource/faces/task.py
++++ b/openerp/addons/resource/faces/task.py
+@@ -22,7 +22,7 @@
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the
+ # Free Software Foundation, Inc.,
+-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ ############################################################################
+
+ #@-node:<< Copyright >>
+diff --git a/openerp/addons/resource/faces/timescale.py b/openerp/addons/resource/faces/timescale.py
+index 3e7c3c1..50f8e09 100644
+--- a/openerp/addons/resource/faces/timescale.py
++++ b/openerp/addons/resource/faces/timescale.py
+@@ -17,7 +17,7 @@
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the
+ # Free Software Foundation, Inc.,
+-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ ############################################################################
+
+ import faces.pcalendar as pcal
+diff --git a/openerp/addons/resource/faces/utils.py b/openerp/addons/resource/faces/utils.py
+index 5498576..92fc88e 100644
+--- a/openerp/addons/resource/faces/utils.py
++++ b/openerp/addons/resource/faces/utils.py
+@@ -17,7 +17,7 @@
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the
+ # Free Software Foundation, Inc.,
+-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ ############################################################################
+
+ import observer
+diff --git a/openerp/report/render/rml2html/rml2html.py b/openerp/report/render/rml2html/rml2html.py
+index 5044927..e9bd07e 100644
+--- a/openerp/report/render/rml2html/rml2html.py
++++ b/openerp/report/render/rml2html/rml2html.py
+@@ -33,7 +33,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+ import sys
+ import cStringIO
+diff --git a/openerp/report/render/rml2pdf/utils.py b/openerp/report/render/rml2pdf/utils.py
+index 273d581..0e24d35 100644
+--- a/openerp/report/render/rml2pdf/utils.py
++++ b/openerp/report/render/rml2pdf/utils.py
+@@ -34,7 +34,7 @@
+ #
+ # You should have received a copy of the GNU Lesser General Public
+ # License along with this library; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+ import copy
+ import locale
+diff --git a/openerp/service/http_server.py b/openerp/service/http_server.py
+index 07e31ea..bcb41a4 100644
+--- a/openerp/service/http_server.py
++++ b/openerp/service/http_server.py
+@@ -23,7 +23,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ ###############################################################################
+
+ #.apidoc title: HTTP and XML-RPC Server
+diff --git a/openerp/service/websrv_lib.py b/openerp/service/websrv_lib.py
+index 4da6536..e693111 100644
+--- a/openerp/service/websrv_lib.py
++++ b/openerp/service/websrv_lib.py
+@@ -21,7 +21,7 @@
+ #
+ # You should have received a copy of the GNU General Public License
+ # along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ ###############################################################################
+
+ #.apidoc title: HTTP Layer library (websrv_lib)
diff --git a/openerp-gen-cert b/openerp-gen-cert
new file mode 100755
index 0000000..57fe784
--- /dev/null
+++ b/openerp-gen-cert
@@ -0,0 +1,53 @@
+#!/bin/bash
+KEYFILE=/etc/openerp/server.pem
+CERT_FILE=/etc/openerp/server.cert
+
+gen_cert()
+{
+ if [ ! -f $KEYFILE ] ; then
+ if [ ! -r /dev/urandom ]; then
+ echo "Your system doesn't seem to support urandom(4)" >&2
+ echo "Cannot create server key. Sorry" >&2
+ echo "Use \"openssl genrsa\" to create $KEYFILE manually >&2"
+ exit 2
+ fi
+ umask o-rwx g-w
+ if /usr/bin/openssl genrsa -rand /dev/urandom 1024 > $KEYFILE
+ then
+ echo "Created new key $KEYFILE"
+ chown root:openerp $KEY_FILE
+ else
+ echo "Error creating server key" >&2
+ exit 1
+ fi
+ else
+ echo "Using existing key $KEYFILE"
+ fi
+
+ umask o+r
+ /usr/bin/openssl req -new -key $KEYFILE -x509 -days 365 \
+ -set_serial $RANDOM -extensions v3_req -out $CERT_FILE
+
+ if (( $? == 0 )); then
+ echo "Created a self-signed SSL certificate for OpenERP."
+ echo "You may want to revise it or get a real one."
+ chown root:openerp $CERT_FILE
+ else
+ echo "Error creating certificate"
+ rm -f $CERT_FILE
+ fi
+}
+
+
+if [ -r $CERT_FILE ] ; then
+ echo "Certificate $CERT_FILE already in place"
+ echo "Remove before creating a new one. Goodbye"
+ exit 0
+fi
+
+if [ "$UID" != 0 ]; then
+ echo "You must be root to do this" >&2
+ exit 1
+fi
+
+gen_cert
diff --git a/openerp-server-relicense-dict_tools-to-LGPL2.1.patch b/openerp-server-relicense-dict_tools-to-LGPL2.1.patch
new file mode 100644
index 0000000..a077a08
--- /dev/null
+++ b/openerp-server-relicense-dict_tools-to-LGPL2.1.patch
@@ -0,0 +1,25 @@
+diff --git a/openerp/addons/document/dict_tools.py b/bin/addons/document/dict_tools.py
+index 10d2fdc..49fdcf8 100644
+--- a/openerp/addons/document/dict_tools.py
++++ b/openerp/addons/document/dict_tools.py
+@@ -4,7 +4,8 @@
+ #
+ # This program is Free Software; you can redistribute it and/or
+ # modify it under the terms of the GNU Lesser General Public License
+-# as published by the Free Software Foundation; version 2 of the License.
++# as published by the Free Software Foundation; version 2.1 of the
++# License or 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
+@@ -12,8 +13,8 @@
+ # GNU Lesser General Public License for more details.
+ #
+ # You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++# along with this program; if not, write to the Free Software Foundation
++# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ ###############################################################################
+
+
diff --git a/openerp-unbundle-pyftpdlib.patch b/openerp-unbundle-pyftpdlib.patch
new file mode 100644
index 0000000..7388340
--- /dev/null
+++ b/openerp-unbundle-pyftpdlib.patch
@@ -0,0 +1,3075 @@
+diff --git a/openerp/addons/document_ftp/ftpserver/ftpserver.py b/openerp/addons/document_ftp/ftpserver/ftpserver.py
+deleted file mode 100755
+index bd72742..0000000
+--- a/openerp/addons/document_ftp/ftpserver/ftpserver.py
++++ /dev/null
+@@ -1,3047 +0,0 @@
+-#!/usr/bin/env python
+-# -*- encoding: utf-8 -*-
+-# ftpserver.py
+-#
+-# pyftpdlib is released under the MIT license, reproduced below:
+-# ======================================================================
+-# Copyright (C) 2007 Giampaolo Rodola' <g.rodola at gmail.com>
+-# Hacked by Fabien Pinckaers (C) 2008 <fp at tinyerp.com>
+-#
+-# All Rights Reserved
+-#
+-# Permission to use, copy, modify, and distribute this software and
+-# its documentation for any purpose and without fee is hereby
+-# granted, provided that the above copyright notice appear in all
+-# copies and that both that copyright notice and this permission
+-# notice appear in supporting documentation, and that the name of
+-# Giampaolo Rodola' not be used in advertising or publicity pertaining to
+-# distribution of the software without specific, written prior
+-# permission.
+-#
+-# Giampaolo Rodola' DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+-# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+-# NO EVENT Giampaolo Rodola' BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+-# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+-# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+-# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+-# ======================================================================
+-
+-
+-"""pyftpdlib: RFC-959 asynchronous FTP server.
+-
+-pyftpdlib implements a fully functioning asynchronous FTP server as
+-defined in RFC-959. A hierarchy of classes outlined below implement
+-the backend functionality for the FTPd:
+-
+- [FTPServer] - the base class for the backend.
+-
+- [FTPHandler] - a class representing the server-protocol-interpreter
+- (server-PI, see RFC-959). Each time a new connection occurs
+- FTPServer will create a new FTPHandler instance to handle the
+- current PI session.
+-
+- [ActiveDTP], [PassiveDTP] - base classes for active/passive-DTP
+- backends.
+-
+- [DTPHandler] - this class handles processing of data transfer
+- operations (server-DTP, see RFC-959).
+-
+- [DummyAuthorizer] - an "authorizer" is a class handling FTPd
+- authentications and permissions. It is used inside FTPHandler class
+- to verify user passwords, to get user's home directory and to get
+- permissions when a filesystem read/write occurs. "DummyAuthorizer"
+- is the base authorizer class providing a platform independent
+- interface for managing virtual users.
+-
+- [AbstractedFS] - class used to interact with the file system,
+- providing a high level, cross-platform interface compatible
+- with both Windows and UNIX style filesystems.
+-
+- [AuthorizerError] - base class for authorizers exceptions.
+-
+-
+-pyftpdlib also provides 3 different logging streams through 3 functions
+-which can be overridden to allow for custom logging.
+-
+- [log] - the main logger that logs the most important messages for
+- the end user regarding the FTPd.
+-
+- [logline] - this function is used to log commands and responses
+- passing through the control FTP channel.
+-
+- [logerror] - log traceback outputs occurring in case of errors.
+-
+-
+-Usage example:
+-
+->>> from pyftpdlib import ftpserver
+->>> authorizer = ftpserver.DummyAuthorizer()
+->>> authorizer.add_user('user', 'password', '/home/user', perm='elradfmw')
+->>> authorizer.add_anonymous('/home/nobody')
+->>> ftp_handler = ftpserver.FTPHandler
+->>> ftp_handler.authorizer = authorizer
+->>> address = ("127.0.0.1", 21)
+->>> ftpd = ftpserver.FTPServer(address, ftp_handler)
+->>> ftpd.serve_forever()
+-Serving FTP on 127.0.0.1:21
+-[]127.0.0.1:2503 connected.
+-127.0.0.1:2503 ==> 220 Ready.
+-127.0.0.1:2503 <== USER anonymous
+-127.0.0.1:2503 ==> 331 Username ok, send password.
+-127.0.0.1:2503 <== PASS ******
+-127.0.0.1:2503 ==> 230 Login successful.
+-[anonymous]@127.0.0.1:2503 User anonymous logged in.
+-127.0.0.1:2503 <== TYPE A
+-127.0.0.1:2503 ==> 200 Type set to: ASCII.
+-127.0.0.1:2503 <== PASV
+-127.0.0.1:2503 ==> 227 Entering passive mode (127,0,0,1,9,201).
+-127.0.0.1:2503 <== LIST
+-127.0.0.1:2503 ==> 150 File status okay. About to open data connection.
+-[anonymous]@127.0.0.1:2503 OK LIST "/". Transfer starting.
+-127.0.0.1:2503 ==> 226 Transfer complete.
+-[anonymous]@127.0.0.1:2503 Transfer complete. 706 bytes transmitted.
+-127.0.0.1:2503 <== QUIT
+-127.0.0.1:2503 ==> 221 Goodbye.
+-[anonymous]@127.0.0.1:2503 Disconnected.
+-"""
+-
+-
+-import asyncore
+-import asynchat
+-import socket
+-import os
+-import sys
+-import traceback
+-import errno
+-import time
+-import glob
+-import fnmatch
+-import tempfile
+-import warnings
+-import random
+-import stat
+-from collections import deque
+-from tarfile import filemode
+-
+-LOG_ACTIVE = True
+-
+-__all__ = ['proto_cmds', 'Error', 'log', 'logline', 'logerror', 'DummyAuthorizer',
+- 'FTPHandler', 'FTPServer', 'PassiveDTP', 'ActiveDTP', 'DTPHandler',
+- 'FileProducer', 'IteratorProducer', 'BufferedIteratorProducer',
+- 'AbstractedFS',]
+-
+-
+-__pname__ = 'Python FTP server library (pyftpdlib)'
+-__ver__ = '0.4.0'
+-__date__ = '2008-05-16'
+-__author__ = "Giampaolo Rodola' <g.rodola at gmail.com>"
+-__web__ = 'http://code.google.com/p/pyftpdlib/'
+-
+-
+-proto_cmds = {
+- 'ABOR': 'Syntax: ABOR (abort transfer).',
+- 'ALLO': 'Syntax: ALLO <SP> bytes (obsolete; allocate storage).',
+- 'APPE': 'Syntax: APPE <SP> file-name (append data to an existent file).',
+- 'CDUP': 'Syntax: CDUP (go to parent directory).',
+- 'CWD' : 'Syntax: CWD <SP> dir-name (change current working directory).',
+- 'DELE': 'Syntax: DELE <SP> file-name (delete file).',
+- 'EPRT': 'Syntax: EPRT <SP> |proto|ip|port| (set server in extended active mode).',
+- 'EPSV': 'Syntax: EPSV [<SP> proto/"ALL"] (set server in extended passive mode).',
+- 'FEAT': 'Syntax: FEAT (list all new features supported).',
+- 'HELP': 'Syntax: HELP [<SP> cmd] (show help).',
+- 'LIST': 'Syntax: LIST [<SP> path-name] (list files).',
+- 'MDTM': 'Syntax: MDTM <SP> file-name (get last modification time).',
+- 'MLSD': 'Syntax: MLSD [<SP> dir-name] (list files in a machine-processable form)',
+- 'MLST': 'Syntax: MLST [<SP> path-name] (show a path in a machine-processable form)',
+- 'MODE': 'Syntax: MODE <SP> mode (obsolete; set data transfer mode).',
+- 'MKD' : 'Syntax: MDK <SP> dir-name (create directory).',
+- 'NLST': 'Syntax: NLST [<SP> path-name] (list files in a compact form).',
+- 'NOOP': 'Syntax: NOOP (just do nothing).',
+- 'OPTS': 'Syntax: OPTS <SP> ftp-command [<SP> option] (specify options for FTP commands)',
+- 'PASS': 'Syntax: PASS <SP> user-name (set user password).',
+- 'PASV': 'Syntax: PASV (set server in passive mode).',
+- 'PORT': 'Syntax: PORT <sp> h1,h2,h3,h4,p1,p2 (set server in active mode).',
+- 'PWD' : 'Syntax: PWD (get current working directory).',
+- 'QUIT': 'Syntax: QUIT (quit current session).',
+- 'REIN': 'Syntax: REIN (reinitialize / flush account).',
+- 'REST': 'Syntax: REST <SP> marker (restart file position).',
+- 'RETR': 'Syntax: RETR <SP> file-name (retrieve a file).',
+- 'RMD' : 'Syntax: RMD <SP> dir-name (remove directory).',
+- 'RNFR': 'Syntax: RNFR <SP> file-name (file renaming (source name)).',
+- 'RNTO': 'Syntax: RNTO <SP> file-name (file renaming (destination name)).',
+- 'SIZE': 'Syntax: HELP <SP> file-name (get file size).',
+- 'STAT': 'Syntax: STAT [<SP> path name] (status information [list files]).',
+- 'STOR': 'Syntax: STOR <SP> file-name (store a file).',
+- 'STOU': 'Syntax: STOU [<SP> file-name] (store a file with a unique name).',
+- 'STRU': 'Syntax: STRU <SP> type (obsolete; set file structure).',
+- 'SYST': 'Syntax: SYST (get operating system type).',
+- 'TYPE': 'Syntax: TYPE <SP> [A | I] (set transfer type).',
+- 'USER': 'Syntax: USER <SP> user-name (set username).',
+- 'XCUP': 'Syntax: XCUP (obsolete; go to parent directory).',
+- 'XCWD': 'Syntax: XCWD <SP> dir-name (obsolete; change current directory).',
+- 'XMKD': 'Syntax: XMDK <SP> dir-name (obsolete; create directory).',
+- 'XPWD': 'Syntax: XPWD (obsolete; get current dir).',
+- 'XRMD': 'Syntax: XRMD <SP> dir-name (obsolete; remove directory).',
+- }
+-
+-
+-def _strerror(err):
+- """A wrap around os.strerror() which may be not available on all
+- platforms (e.g. pythonCE).
+-
+- - (instance) err: an EnvironmentError or derived class instance.
+- """
+- if hasattr(os, 'strerror'):
+- return os.strerror(err.errno)
+- else:
+- return err.strerror
+-
+-def _to_unicode(s):
+- try:
+- return s.decode('utf-8')
+- except UnicodeError:
+- pass
+- try:
+- return s.decode('latin')
+- except UnicodeError:
+- pass
+- try:
+- return s.encode('ascii')
+- except UnicodeError:
+- return s
+-
+-def _to_decode(s):
+- try:
+- return s.encode('utf-8')
+- except UnicodeError:
+- pass
+- try:
+- return s.encode('latin')
+- except UnicodeError:
+- pass
+- try:
+- return s.decode('ascii')
+- except UnicodeError:
+- return s
+-
+-# --- library defined exceptions
+-
+-class Error(Exception):
+- """Base class for module exceptions."""
+-
+-class AuthorizerError(Error):
+- """Base class for authorizer exceptions."""
+-
+-
+-# --- loggers
+-
+-def log(msg):
+- """Log messages intended for the end user."""
+- if LOG_ACTIVE:
+- print msg
+-
+-def logline(msg):
+- """Log commands and responses passing through the command channel."""
+- if LOG_ACTIVE:
+- print msg
+-
+-def logerror(msg):
+- """Log traceback outputs occurring in case of errors."""
+- sys.stderr.write(str(msg) + '\n')
+- sys.stderr.flush()
+-
+-
+-# --- authorizers
+-
+-class DummyAuthorizer:
+- """Basic "dummy" authorizer class, suitable for subclassing to
+- create your own custom authorizers.
+-
+- An "authorizer" is a class handling authentications and permissions
+- of the FTP server. It is used inside FTPHandler class for verifying
+- user's password, getting users home directory, checking user
+- permissions when a file read/write event occurs and changing user
+- before accessing the filesystem.
+-
+- DummyAuthorizer is the base authorizer, providing a platform
+- independent interface for managing "virtual" FTP users. System
+- dependent authorizers can by written by subclassing this base
+- class and overriding appropriate methods as necessary.
+- """
+-
+- read_perms = "elr"
+- write_perms = "adfmw"
+-
+- def __init__(self):
+- self.user_table = {}
+-
+- def add_user(self, username, password, homedir, perm='elr',
+- msg_login="Login successful.", msg_quit="Goodbye."):
+- """Add a user to the virtual users table.
+-
+- AuthorizerError exceptions raised on error conditions such as
+- invalid permissions, missing home directory or duplicate usernames.
+-
+- Optional perm argument is a string referencing the user's
+- permissions explained below:
+-
+- Read permissions:
+- - "e" = change directory (CWD command)
+- - "l" = list files (LIST, NLST, MLSD commands)
+- - "r" = retrieve file from the server (RETR command)
+-
+- Write permissions:
+- - "a" = append data to an existing file (APPE command)
+- - "d" = delete file or directory (DELE, RMD commands)
+- - "f" = rename file or directory (RNFR, RNTO commands)
+- - "m" = create directory (MKD command)
+- - "w" = store a file to the server (STOR, STOU commands)
+-
+- Optional msg_login and msg_quit arguments can be specified to
+- provide customized response strings when user log-in and quit.
+- """
+- if self.has_user(username):
+- raise AuthorizerError('User "%s" already exists' %username)
+- homedir = os.path.realpath(homedir)
+- if not os.path.isdir(homedir):
+- raise AuthorizerError('No such directory: "%s"' %homedir)
+- for p in perm:
+- if p not in 'elradfmw':
+- raise AuthorizerError('No such permission "%s"' %p)
+- for p in perm:
+- if (p in self.write_perms) and (username == 'anonymous'):
+- warnings.warn("write permissions assigned to anonymous user.",
+- RuntimeWarning)
+- break
+- dic = {'pwd': str(password),
+- 'home': homedir,
+- 'perm': perm,
+- 'msg_login': str(msg_login),
+- 'msg_quit': str(msg_quit)
+- }
+- self.user_table[username] = dic
+-
+- def add_anonymous(self, homedir, **kwargs):
+- """Add an anonymous user to the virtual users table.
+-
+- AuthorizerError exception raised on error conditions such as
+- invalid permissions, missing home directory, or duplicate
+- anonymous users.
+-
+- The keyword arguments in kwargs are the same expected by
+- add_user method: "perm", "msg_login" and "msg_quit".
+-
+- The optional "perm" keyword argument is a string defaulting to
+- "elr" referencing "read-only" anonymous user's permissions.
+-
+- Using write permission values ("adfmw") results in a
+- RuntimeWarning.
+- """
+- DummyAuthorizer.add_user(self, 'anonymous', '', homedir, **kwargs)
+-
+- def remove_user(self, username):
+- """Remove a user from the virtual users table."""
+- del self.user_table[username]
+-
+- def validate_authentication(self, username, password):
+- """Return True if the supplied username and password match the
+- stored credentials."""
+- return self.user_table[username]['pwd'] == password
+-
+- def impersonate_user(self, username, password):
+- """Impersonate another user (noop).
+-
+- It is always called before accessing the filesystem.
+- By default it does nothing. The subclass overriding this
+- method is expected to provide a mechanism to change the
+- current user.
+- """
+-
+- def terminate_impersonation(self):
+- """Terminate impersonation (noop).
+-
+- It is always called after having accessed the filesystem.
+- By default it does nothing. The subclass overriding this
+- method is expected to provide a mechanism to switch back
+- to the original user.
+- """
+-
+- def has_user(self, username):
+- """Whether the username exists in the virtual users table."""
+- return username in self.user_table
+-
+- def has_perm(self, username, perm, path=None):
+- """Whether the user has permission over path (an absolute
+- pathname of a file or a directory).
+-
+- Expected perm argument is one of the following letters:
+- "elradfmw".
+- """
+- return perm in self.user_table[username]['perm']
+-
+- def get_perms(self, username):
+- """Return current user permissions."""
+- return self.user_table[username]['perm']
+-
+- def get_home_dir(self, username):
+- """Return the user's home directory."""
+- return self.user_table[username]['home']
+-
+- def get_msg_login(self, username):
+- """Return the user's login message."""
+- return self.user_table[username]['msg_login']
+-
+- def get_msg_quit(self, username):
+- """Return the user's quitting message."""
+- return self.user_table[username]['msg_quit']
+-
+-
+-# --- DTP classes
+-
+-class PassiveDTP(asyncore.dispatcher):
+- """This class is an asyncore.disptacher subclass. It creates a
+- socket listening on a local port, dispatching the resultant
+- connection to DTPHandler.
+- """
+-
+- def __init__(self, cmd_channel, extmode=False):
+- """Initialize the passive data server.
+-
+- - (instance) cmd_channel: the command channel class instance.
+- - (bool) extmode: wheter use extended passive mode response type.
+- """
+- asyncore.dispatcher.__init__(self)
+- self.cmd_channel = cmd_channel
+-
+- ip = self.cmd_channel.getsockname()[0]
+- self.create_socket(self.cmd_channel.af, socket.SOCK_STREAM)
+-
+- if not self.cmd_channel.passive_ports:
+- # By using 0 as port number value we let kernel choose a free
+- # unprivileged random port.
+- self.bind((ip, 0))
+- else:
+- ports = list(self.cmd_channel.passive_ports)
+- while ports:
+- port = ports.pop(random.randint(0, len(ports) -1))
+- try:
+- self.bind((ip, port))
+- except socket.error, why:
+- if why[0] == errno.EADDRINUSE: # port already in use
+- if ports:
+- continue
+- # If cannot use one of the ports in the configured
+- # range we'll use a kernel-assigned port, and log
+- # a message reporting the issue.
+- # By using 0 as port number value we let kernel
+- # choose a free unprivileged random port.
+- else:
+- self.bind((ip, 0))
+- self.cmd_channel.log(
+- "Can't find a valid passive port in the "
+- "configured range. A random kernel-assigned "
+- "port will be used."
+- )
+- else:
+- raise
+- else:
+- break
+- self.listen(5)
+- port = self.socket.getsockname()[1]
+- if not extmode:
+- if self.cmd_channel.masquerade_address:
+- ip = self.cmd_channel.masquerade_address
+- # The format of 227 response in not standardized.
+- # This is the most expected:
+- self.cmd_channel.respond('227 Entering passive mode (%s,%d,%d).' %(
+- ip.replace('.', ','), port / 256, port % 256))
+- else:
+- self.cmd_channel.respond('229 Entering extended passive mode '
+- '(|||%d|).' %port)
+-
+- # --- connection / overridden
+-
+- def handle_accept(self):
+- """Called when remote client initiates a connection."""
+- sock, addr = self.accept()
+-
+- # Check the origin of data connection. If not expressively
+- # configured we drop the incoming data connection if remote
+- # IP address does not match the client's IP address.
+- if (self.cmd_channel.remote_ip != addr[0]):
+- if not self.cmd_channel.permit_foreign_addresses:
+- try:
+- sock.close()
+- except socket.error:
+- pass
+- msg = 'Rejected data connection from foreign address %s:%s.' \
+- %(addr[0], addr[1])
+- self.cmd_channel.respond("425 %s" %msg)
+- self.cmd_channel.log(msg)
+- # do not close listening socket: it couldn't be client's blame
+- return
+- else:
+- # site-to-site FTP allowed
+- msg = 'Established data connection with foreign address %s:%s.'\
+- %(addr[0], addr[1])
+- self.cmd_channel.log(msg)
+- # Immediately close the current channel (we accept only one
+- # connection at time) and avoid running out of max connections
+- # limit.
+- self.close()
+- # delegate such connection to DTP handler
+- handler = self.cmd_channel.dtp_handler(sock, self.cmd_channel)
+- self.cmd_channel.data_channel = handler
+- self.cmd_channel.on_dtp_connection()
+-
+- def writable(self):
+- return 0
+-
+- def handle_error(self):
+- """Called to handle any uncaught exceptions."""
+- try:
+- raise
+- except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+- raise
+- logerror(traceback.format_exc())
+- self.close()
+-
+- def handle_close(self):
+- """Called on closing the data connection."""
+- self.close()
+-
+-
+-class ActiveDTP(asyncore.dispatcher):
+- """This class is an asyncore.disptacher subclass. It creates a
+- socket resulting from the connection to a remote user-port,
+- dispatching it to DTPHandler.
+- """
+-
+- def __init__(self, ip, port, cmd_channel):
+- """Initialize the active data channel attemping to connect
+- to remote data socket.
+-
+- - (str) ip: the remote IP address.
+- - (int) port: the remote port.
+- - (instance) cmd_channel: the command channel class instance.
+- """
+- asyncore.dispatcher.__init__(self)
+- self.cmd_channel = cmd_channel
+- self.create_socket(self.cmd_channel.af, socket.SOCK_STREAM)
+- try:
+- self.connect((ip, port))
+- except socket.gaierror:
+- self.cmd_channel.respond("425 Can't connect to specified address.")
+- self.close()
+-
+- # --- connection / overridden
+-
+- def handle_write(self):
+- """NOOP, must be overridden to prevent unhandled write event."""
+-
+- def handle_connect(self):
+- """Called when connection is established."""
+- self.cmd_channel.respond('200 Active data connection established.')
+- # delegate such connection to DTP handler
+- handler = self.cmd_channel.dtp_handler(self.socket, self.cmd_channel)
+- self.cmd_channel.data_channel = handler
+- self.cmd_channel.on_dtp_connection()
+-
+- def handle_expt(self):
+- self.cmd_channel.respond("425 Can't connect to specified address.")
+- self.close()
+-
+- def handle_error(self):
+- """Called to handle any uncaught exceptions."""
+- try:
+- raise
+- except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+- raise
+- except socket.error:
+- pass
+- except:
+- logerror(traceback.format_exc())
+- self.cmd_channel.respond("425 Can't connect to specified address.")
+- self.close()
+-
+-class DTPHandler(asyncore.dispatcher):
+- """Class handling server-data-transfer-process (server-DTP, see
+- RFC-959) managing data-transfer operations involving sending
+- and receiving data.
+-
+- Instance attributes defined in this class, initialized when
+- channel is opened:
+-
+- - (instance) cmd_channel: the command channel class instance.
+- - (file) file_obj: the file transferred (if any).
+- - (bool) receive: True if channel is used for receiving data.
+- - (bool) transfer_finished: True if transfer completed successfully.
+- - (int) tot_bytes_sent: the total bytes sent.
+- - (int) tot_bytes_received: the total bytes received.
+-
+- DTPHandler implementation note:
+-
+- When a producer is consumed and close_when_done() has been called
+- previously, refill_buffer() erroneously calls close() instead of
+- handle_close() - (see: http://bugs.python.org/issue1740572)
+-
+- To avoid this problem DTPHandler is implemented as a subclass of
+- asyncore.dispatcher instead of asynchat.async_chat.
+- This implementation follows the same approach that asynchat module
+- should use in Python 2.6.
+-
+- The most important change in the implementation is related to
+- producer_fifo, which is a pure deque object instead of a
+- producer_fifo instance.
+-
+- Since we don't want to break backward compatibily with older python
+- versions (deque has been introduced in Python 2.4), if deque is not
+- available we use a list instead.
+- """
+-
+- ac_in_buffer_size = 8192
+- ac_out_buffer_size = 8192
+-
+- def __init__(self, sock_obj, cmd_channel):
+- """Initialize the command channel.
+-
+- - (instance) sock_obj: the socket object instance of the newly
+- established connection.
+- - (instance) cmd_channel: the command channel class instance.
+- """
+- asyncore.dispatcher.__init__(self, sock_obj)
+- # we toss the use of the asynchat's "simple producer" and
+- # replace it with a pure deque, which the original fifo
+- # was a wrapping of
+- self.producer_fifo = deque()
+-
+- self.cmd_channel = cmd_channel
+- self.file_obj = None
+- self.receive = False
+- self.transfer_finished = False
+- self.tot_bytes_sent = 0
+- self.tot_bytes_received = 0
+- self.data_wrapper = lambda x: x
+-
+- # --- utility methods
+-
+- def enable_receiving(self, type):
+- """Enable receiving of data over the channel. Depending on the
+- TYPE currently in use it creates an appropriate wrapper for the
+- incoming data.
+-
+- - (str) type: current transfer type, 'a' (ASCII) or 'i' (binary).
+- """
+- if type == 'a':
+- self.data_wrapper = lambda x: x.replace('\r\n', os.linesep)
+- elif type == 'i':
+- self.data_wrapper = lambda x: x
+- else:
+- raise TypeError, "Unsupported type"
+- self.receive = True
+-
+- def get_transmitted_bytes(self):
+- "Return the number of transmitted bytes."
+- return self.tot_bytes_sent + self.tot_bytes_received
+-
+- def transfer_in_progress(self):
+- "Return True if a transfer is in progress, else False."
+- return self.get_transmitted_bytes() != 0
+-
+- # --- connection
+-
+- def handle_read(self):
+- """Called when there is data waiting to be read."""
+- try:
+- chunk = self.recv(self.ac_in_buffer_size)
+- except socket.error:
+- self.handle_error()
+- else:
+- self.tot_bytes_received += len(chunk)
+- if not chunk:
+- self.transfer_finished = True
+- #self.close() # <-- asyncore.recv() already do that...
+- return
+- # while we're writing on the file an exception could occur
+- # in case that filesystem gets full; if this happens we
+- # let handle_error() method handle this exception, providing
+- # a detailed error message.
+- self.file_obj.write(self.data_wrapper(chunk))
+-
+- def handle_write(self):
+- """Called when data is ready to be written, initiates send."""
+- self.initiate_send()
+-
+- def push(self, data):
+- """Push data onto the deque and initiate send."""
+- sabs = self.ac_out_buffer_size
+- if len(data) > sabs:
+- for i in xrange(0, len(data), sabs):
+- self.producer_fifo.append(data[i:i+sabs])
+- else:
+- self.producer_fifo.append(data)
+- self.initiate_send()
+-
+- def push_with_producer(self, producer):
+- """Push data using a producer and initiate send."""
+- self.producer_fifo.append(producer)
+- self.initiate_send()
+-
+- def readable(self):
+- """Predicate for inclusion in the readable for select()."""
+- return self.receive
+-
+- def writable(self):
+- """Predicate for inclusion in the writable for select()."""
+- return self.producer_fifo or (not self.connected)
+-
+- def close_when_done(self):
+- """Automatically close this channel once the outgoing queue is empty."""
+- self.producer_fifo.append(None)
+-
+- def initiate_send(self):
+- """Attempt to send data in fifo order."""
+- while self.producer_fifo and self.connected:
+- first = self.producer_fifo[0]
+- # handle empty string/buffer or None entry
+- if not first:
+- del self.producer_fifo[0]
+- if first is None:
+- self.transfer_finished = True
+- self.handle_close()
+- return
+-
+- # handle classic producer behavior
+- obs = self.ac_out_buffer_size
+- try:
+- data = buffer(first, 0, obs)
+- except TypeError:
+- data = first.more()
+- if data:
+- self.producer_fifo.appendleft(data)
+- else:
+- del self.producer_fifo[0]
+- continue
+-
+- # send the data
+- try:
+- num_sent = self.send(data)
+- except socket.error:
+- self.handle_error()
+- return
+-
+- if num_sent:
+- self.tot_bytes_sent += num_sent
+- if num_sent < len(data) or obs < len(first):
+- self.producer_fifo[0] = first[num_sent:]
+- else:
+- del self.producer_fifo[0]
+- # we tried to send some actual data
+- return
+-
+- def handle_expt(self):
+- """Called on "exceptional" data events."""
+- self.cmd_channel.respond("426 Connection error; transfer aborted.")
+- self.close()
+-
+- def handle_error(self):
+- """Called when an exception is raised and not otherwise handled."""
+- try:
+- raise
+- except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+- raise
+- except socket.error, err:
+- # fix around asyncore bug (http://bugs.python.org/issue1736101)
+- if err[0] in (errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN, \
+- errno.ECONNABORTED):
+- self.handle_close()
+- return
+- else:
+- error = str(err[1])
+- # an error could occur in case we fail reading / writing
+- # from / to file (e.g. file system gets full)
+- except EnvironmentError, err:
+- error = _strerror(err)
+- except:
+- # some other exception occurred; we don't want to provide
+- # confidential error messages
+- logerror(traceback.format_exc())
+- error = "Internal error"
+- self.cmd_channel.respond("426 %s; transfer aborted." %error)
+- self.close()
+-
+- def handle_close(self):
+- """Called when the socket is closed."""
+- # If we used channel for receiving we assume that transfer is
+- # finished when client close connection , if we used channel
+- # for sending we have to check that all data has been sent
+- # (responding with 226) or not (responding with 426).
+- if self.receive:
+- self.transfer_finished = True
+- action = 'received'
+- else:
+- action = 'sent'
+- if self.transfer_finished:
+- self.cmd_channel.respond("226 Transfer complete.")
+- if self.file_obj:
+- fname = self.file_obj.name
+- self.cmd_channel.log('"%s" %s.' %(fname, action))
+- else:
+- tot_bytes = self.get_transmitted_bytes()
+- msg = "Transfer aborted; %d bytes transmitted." %tot_bytes
+- self.cmd_channel.respond("426 " + msg)
+- self.cmd_channel.log(msg)
+- self.close()
+-
+- def close(self):
+- """Close the data channel, first attempting to close any remaining
+- file handles."""
+- if self.file_obj and not self.file_obj.closed:
+- self.file_obj.close()
+- asyncore.dispatcher.close(self)
+- self.cmd_channel.on_dtp_close()
+-
+-
+-# --- producers
+-
+-class FileProducer:
+- """Producer wrapper for file[-like] objects."""
+-
+- buffer_size = 65536
+-
+- def __init__(self, file, type):
+- """Initialize the producer with a data_wrapper appropriate to TYPE.
+-
+- - (file) file: the file[-like] object.
+- - (str) type: the current TYPE, 'a' (ASCII) or 'i' (binary).
+- """
+- self.done = False
+- self.file = file
+- if type == 'a':
+- self.data_wrapper = lambda x: x.replace(os.linesep, '\r\n')
+- elif type == 'i':
+- self.data_wrapper = lambda x: x
+- else:
+- raise TypeError, "Unsupported type"
+-
+- def more(self):
+- """Attempt a chunk of data of size self.buffer_size."""
+- if self.done:
+- return ''
+- data = self.data_wrapper(self.file.read(self.buffer_size))
+- if not data:
+- self.done = True
+- if not self.file.closed:
+- self.file.close()
+- return data
+-
+-
+-class IteratorProducer:
+- """Producer for iterator objects."""
+-
+- def __init__(self, iterator):
+- self.iterator = iterator
+-
+- def more(self):
+- """Attempt a chunk of data from iterator by calling its next()
+- method.
+- """
+- try:
+- return self.iterator.next()
+- except StopIteration:
+- return ''
+-
+-
+-class BufferedIteratorProducer:
+- """Producer for iterator objects with buffer capabilities."""
+- # how many times iterator.next() will be called before
+- # returning some data
+- loops = 20
+-
+- def __init__(self, iterator):
+- self.iterator = iterator
+-
+- def more(self):
+- """Attempt a chunk of data from iterator by calling
+- its next() method different times.
+- """
+- buffer = []
+- for x in xrange(self.loops):
+- try:
+- buffer.append(self.iterator.next())
+- except StopIteration:
+- break
+- return ''.join(buffer)
+-
+-
+-# --- filesystem
+-
+-class AbstractedFS:
+- """A class used to interact with the file system, providing a high
+- level, cross-platform interface compatible with both Windows and
+- UNIX style filesystems.
+-
+- It provides some utility methods and some wraps around operations
+- involved in file creation and file system operations like moving
+- files or removing directories.
+-
+- Instance attributes:
+- - (str) root: the user home directory.
+- - (str) cwd: the current working directory.
+- - (str) rnfr: source file to be renamed.
+- """
+-
+- def __init__(self):
+- self.root = None
+- self.cwd = '/'
+- self.rnfr = None
+-
+- # --- Pathname / conversion utilities
+-
+- def ftpnorm(self, ftppath):
+- """Normalize a "virtual" ftp pathname (tipically the raw string
+- coming from client) depending on the current working directory.
+-
+- Example (having "/foo" as current working directory):
+- 'x' -> '/foo/x'
+-
+- Note: directory separators are system independent ("/").
+- Pathname returned is always absolutized.
+- """
+- if os.path.isabs(ftppath):
+- p = os.path.normpath(ftppath)
+- else:
+- p = os.path.normpath(os.path.join(self.cwd, ftppath))
+- # normalize string in a standard web-path notation having '/'
+- # as separator.
+- p = p.replace("\\", "/")
+- # os.path.normpath supports UNC paths (e.g. "//a/b/c") but we
+- # don't need them. In case we get an UNC path we collapse
+- # redundant separators appearing at the beginning of the string
+- while p[:2] == '//':
+- p = p[1:]
+- # Anti path traversal: don't trust user input, in the event
+- # that self.cwd is not absolute, return "/" as a safety measure.
+- # This is for extra protection, maybe not really necessary.
+- if not os.path.isabs(p):
+- p = "/"
+- return p
+-
+- def ftp2fs(self, ftppath):
+- """Translate a "virtual" ftp pathname (tipically the raw string
+- coming from client) into equivalent absolute "real" filesystem
+- pathname.
+-
+- Example (having "/home/user" as root directory):
+- 'x' -> '/home/user/x'
+-
+- Note: directory separators are system dependent.
+- """
+- # as far as I know, it should always be path traversal safe...
+- if os.path.normpath(self.root) == os.sep:
+- return os.path.normpath(self.ftpnorm(ftppath))
+- else:
+- p = self.ftpnorm(ftppath)[1:]
+- return os.path.normpath(os.path.join(self.root, p))
+-
+- def fs2ftp(self, fspath):
+- """Translate a "real" filesystem pathname into equivalent
+- absolute "virtual" ftp pathname depending on the user's
+- root directory.
+-
+- Example (having "/home/user" as root directory):
+- '/home/user/x' -> '/x'
+-
+- As for ftpnorm, directory separators are system independent
+- ("/") and pathname returned is always absolutized.
+-
+- On invalid pathnames escaping from user's root directory
+- (e.g. "/home" when root is "/home/user") always return "/".
+- """
+- if os.path.isabs(fspath):
+- p = os.path.normpath(fspath)
+- else:
+- p = os.path.normpath(os.path.join(self.root, fspath))
+- if not self.validpath(p):
+- return '/'
+- p = p.replace(os.sep, "/")
+- p = p[len(self.root):]
+- if not p.startswith('/'):
+- p = '/' + p
+- return p
+-
+- # alias for backward compatibility with 0.2.0
+- normalize = ftpnorm
+- translate = ftp2fs
+-
+- def validpath(self, path):
+- """Check whether the path belongs to user's home directory.
+- Expected argument is a "real" filesystem pathname.
+-
+- If path is a symbolic link it is resolved to check its real
+- destination.
+-
+- Pathnames escaping from user's root directory are considered
+- not valid.
+- """
+- root = self.realpath(self.root)
+- path = self.realpath(path)
+- if not self.root.endswith(os.sep):
+- root = self.root + os.sep
+- if not path.endswith(os.sep):
+- path = path + os.sep
+- if path[0:len(root)] == root:
+- return True
+- return False
+-
+- # --- Wrapper methods around open() and tempfile.mkstemp
+-
+- def open(self, filename, mode):
+- """Open a file returning its handler."""
+- return open(filename, mode)
+-
+- def mkstemp(self, suffix='', prefix='', dir=None, mode='wb'):
+- """A wrap around tempfile.mkstemp creating a file with a unique
+- name. Unlike mkstemp it returns an object with a file-like
+- interface.
+- """
+- class FileWrapper:
+- def __init__(self, fd, name):
+- self.file = fd
+- self.name = name
+- def __getattr__(self, attr):
+- return getattr(self.file, attr)
+-
+- text = not 'b' in mode
+- # max number of tries to find out a unique file name
+- tempfile.TMP_MAX = 50
+- fd, name = tempfile.mkstemp(suffix, prefix, dir, text=text)
+- file = os.fdopen(fd, mode)
+- return FileWrapper(file, name)
+-
+- # --- Wrapper methods around os.*
+-
+- def chdir(self, path):
+- """Change the current directory."""
+- # temporarily join the specified directory to see if we have
+- # permissions to do so
+- basedir = os.getcwd()
+- try:
+- os.chdir(path)
+- except os.error:
+- raise
+- else:
+- os.chdir(basedir)
+- self.cwd = self.fs2ftp(path)
+-
+- def mkdir(self, path, basename):
+- """Create the specified directory."""
+- os.mkdir(os.path.join(path, basename))
+-
+- def listdir(self, path):
+- """List the content of a directory."""
+- return os.listdir(path)
+-
+- def rmdir(self, path):
+- """Remove the specified directory."""
+- os.rmdir(path)
+-
+- def remove(self, path):
+- """Remove the specified file."""
+- os.remove(path)
+-
+- def rename(self, src, dst):
+- """Rename the specified src file to the dst filename."""
+- os.rename(src, dst)
+-
+- def stat(self, path):
+- """Perform a stat() system call on the given path."""
+- return os.stat(path)
+-
+- def lstat(self, path):
+- """Like stat but does not follow symbolic links."""
+- return os.lstat(path)
+-
+- if not hasattr(os, 'lstat'):
+- lstat = stat
+-
+- # --- Wrapper methods around os.path.*
+-
+- def isfile(self, path):
+- """Return True if path is a file."""
+- return os.path.isfile(path)
+-
+- def islink(self, path):
+- """Return True if path is a symbolic link."""
+- return os.path.islink(path)
+-
+- def isdir(self, path):
+- """Return True if path is a directory."""
+- return os.path.isdir(path)
+-
+- def getsize(self, path):
+- """Return the size of the specified file in bytes."""
+- return os.path.getsize(path)
+-
+- def getmtime(self, path):
+- """Return the last modified time as a number of seconds since
+- the epoch."""
+- return os.path.getmtime(path)
+-
+- def realpath(self, path):
+- """Return the canonical version of path eliminating any
+- symbolic links encountered in the path (if they are
+- supported by the operating system).
+- """
+- return os.path.realpath(path)
+-
+- def lexists(self, path):
+- """Return True if path refers to an existing path, including
+- a broken or circular symbolic link.
+- """
+- if hasattr(os.path, 'lexists'):
+- return os.path.lexists(path)
+- # grant backward compatibility with python 2.3
+- elif hasattr(os, 'lstat'):
+- try:
+- os.lstat(path)
+- except os.error:
+- return False
+- return True
+- # fallback
+- else:
+- return os.path.exists(path)
+-
+- exists = lexists # alias for backward compatibility with 0.2.0
+-
+- def glob1(self, dirname, pattern):
+- """Return a list of files matching a dirname pattern
+- non-recursively.
+-
+- Unlike glob.glob1 raises exception if os.listdir() fails.
+- """
+- names = self.listdir(dirname)
+- if pattern[0] != '.':
+- names = filter(lambda x: x[0] != '.', names)
+- return fnmatch.filter(names, pattern)
+-
+- # --- Listing utilities
+-
+- # note: the following operations are no more blocking
+-
+- def get_list_dir(self, datacr):
+- """"Return an iterator object that yields a directory listing
+- in a form suitable for LIST command.
+- """
+- raise DeprecationWarning()
+-
+- def get_stat_dir(self, rawline):
+- """Return an iterator object that yields a list of files
+- matching a dirname pattern non-recursively in a form
+- suitable for STAT command.
+-
+- - (str) rawline: the raw string passed by client as command
+- argument.
+- """
+- ftppath = self.ftpnorm(rawline)
+- if not glob.has_magic(ftppath):
+- return self.get_list_dir(self.ftp2fs(rawline))
+- else:
+- basedir, basename = os.path.split(ftppath)
+- if glob.has_magic(basedir):
+- return iter(['Directory recursion not supported.\r\n'])
+- else:
+- basedir = self.ftp2fs(basedir)
+- listing = self.glob1(basedir, basename)
+- if listing:
+- listing.sort()
+- return self.format_list(basedir, listing)
+-
+- def format_list(self, basedir, listing, ignore_err=True):
+- """Return an iterator object that yields the entries of given
+- directory emulating the "/bin/ls -lA" UNIX command output.
+-
+- - (str) basedir: the absolute dirname.
+- - (list) listing: the names of the entries in basedir
+- - (bool) ignore_err: when False raise exception if os.lstat()
+- call fails.
+-
+- On platforms which do not support the pwd and grp modules (such
+- as Windows), ownership is printed as "owner" and "group" as a
+- default, and number of hard links is always "1". On UNIX
+- systems, the actual owner, group, and number of links are
+- printed.
+-
+- This is how output appears to client:
+-
+- -rw-rw-rw- 1 owner group 7045120 Sep 02 3:47 music.mp3
+- drwxrwxrwx 1 owner group 0 Aug 31 18:50 e-books
+- -rw-rw-rw- 1 owner group 380 Sep 02 3:40 module.py
+- """
+- for basename in listing:
+- file = os.path.join(basedir, basename)
+- try:
+- st = self.lstat(file)
+- except os.error:
+- if ignore_err:
+- continue
+- raise
+- perms = filemode(st.st_mode) # permissions
+- nlinks = st.st_nlink # number of links to inode
+- if not nlinks: # non-posix system, let's use a bogus value
+- nlinks = 1
+- size = st.st_size # file size
+- uname = st.st_uid or "owner"
+- gname = st.st_gid or "group"
+-
+- # stat.st_mtime could fail (-1) if last mtime is too old
+- # in which case we return the local time as last mtime
+- try:
+- mtime = time.strftime("%b %d %H:%M", time.localtime(st.st_mtime))
+- except ValueError:
+- mtime = time.strftime("%b %d %H:%M")
+- # if the file is a symlink, resolve it, e.g. "symlink -> realfile"
+- if stat.S_ISLNK(st.st_mode):
+- basename = basename + " -> " + os.readlink(file)
+-
+- # formatting is matched with proftpd ls output
+- yield "%s %3s %-8s %-8s %8s %s %s\r\n" %(perms, nlinks, uname, gname,
+- size, mtime, basename)
+-
+- def format_mlsx(self, basedir, listing, perms, facts, ignore_err=True):
+- """Return an iterator object that yields the entries of a given
+- directory or of a single file in a form suitable with MLSD and
+- MLST commands.
+-
+- Every entry includes a list of "facts" referring the listed
+- element. See RFC-3659, chapter 7, to see what every single
+- fact stands for.
+-
+- - (str) basedir: the absolute dirname.
+- - (list) listing: the names of the entries in basedir
+- - (str) perms: the string referencing the user permissions.
+- - (str) facts: the list of "facts" to be returned.
+- - (bool) ignore_err: when False raise exception if os.stat()
+- call fails.
+-
+- Note that "facts" returned may change depending on the platform
+- and on what user specified by using the OPTS command.
+-
+- This is how output could appear to the client issuing
+- a MLSD request:
+-
+- type=file;size=156;perm=r;modify=20071029155301;unique=801cd2; music.mp3
+- type=dir;size=0;perm=el;modify=20071127230206;unique=801e33; ebooks
+- type=file;size=211;perm=r;modify=20071103093626;unique=801e32; module.py
+- """
+- permdir = ''.join([x for x in perms if x not in 'arw'])
+- permfile = ''.join([x for x in perms if x not in 'celmp'])
+- if ('w' in perms) or ('a' in perms) or ('f' in perms):
+- permdir += 'c'
+- if 'd' in perms:
+- permdir += 'p'
+- type = size = perm = modify = create = unique = mode = uid = gid = ""
+- for basename in listing:
+- file = os.path.join(basedir, basename)
+- try:
+- st = self.stat(file)
+- except OSError:
+- if ignore_err:
+- continue
+- raise
+- # type + perm
+- if stat.S_ISDIR(st.st_mode):
+- if 'type' in facts:
+- if basename == '.':
+- type = 'type=cdir;'
+- elif basename == '..':
+- type = 'type=pdir;'
+- else:
+- type = 'type=dir;'
+- if 'perm' in facts:
+- perm = 'perm=%s;' %permdir
+- else:
+- if 'type' in facts:
+- type = 'type=file;'
+- if 'perm' in facts:
+- perm = 'perm=%s;' %permfile
+- if 'size' in facts:
+- size = 'size=%s;' %st.st_size # file size
+- # last modification time
+- if 'modify' in facts:
+- try:
+- modify = 'modify=%s;' %time.strftime("%Y%m%d%H%M%S",
+- time.localtime(st.st_mtime))
+- except ValueError:
+- # stat.st_mtime could fail (-1) if last mtime is too old
+- modify = ""
+- if 'create' in facts:
+- # on Windows we can provide also the creation time
+- try:
+- create = 'create=%s;' %time.strftime("%Y%m%d%H%M%S",
+- time.localtime(st.st_ctime))
+- except ValueError:
+- create = ""
+- # UNIX only
+- if 'unix.mode' in facts:
+- mode = 'unix.mode=%s;' %oct(st.st_mode & 0777)
+- if 'unix.uid' in facts:
+- uid = 'unix.uid=%s;' %st.st_uid
+- if 'unix.gid' in facts:
+- gid = 'unix.gid=%s;' %st.st_gid
+- # We provide unique fact (see RFC-3659, chapter 7.5.2) on
+- # posix platforms only; we get it by mixing st_dev and
+- # st_ino values which should be enough for granting an
+- # uniqueness for the file listed.
+- # The same approach is used by pure-ftpd.
+- # Implementors who want to provide unique fact on other
+- # platforms should use some platform-specific method (e.g.
+- # on Windows NTFS filesystems MTF records could be used).
+- if 'unique' in facts:
+- unique = "unique=%x%x;" %(st.st_dev, st.st_ino)
+-
+- yield "%s%s%s%s%s%s%s%s%s %s\r\n" %(type, size, perm, modify, create,
+- mode, uid, gid, unique, basename)
+-
+-
+-# --- FTP
+-
+-class FTPExceptionSent(Exception):
+- """An FTP exception that FTPHandler has processed
+- """
+- pass
+-
+-class FTPHandler(asynchat.async_chat):
+- """Implements the FTP server Protocol Interpreter (see RFC-959),
+- handling commands received from the client on the control channel.
+-
+- All relevant session information is stored in class attributes
+- reproduced below and can be modified before instantiating this
+- class.
+-
+- - (str) banner: the string sent when client connects.
+-
+- - (int) max_login_attempts:
+- the maximum number of wrong authentications before disconnecting
+- the client (default 3).
+-
+- - (bool)permit_foreign_addresses:
+- FTP site-to-site transfer feature: also referenced as "FXP" it
+- permits for transferring a file between two remote FTP servers
+- without the transfer going through the client's host (not
+- recommended for security reasons as described in RFC-2577).
+- Having this attribute set to False means that all data
+- connections from/to remote IP addresses which do not match the
+- client's IP address will be dropped (defualt False).
+-
+- - (bool) permit_privileged_ports:
+- set to True if you want to permit active data connections (PORT)
+- over privileged ports (not recommended, defaulting to False).
+-
+- - (str) masquerade_address:
+- the "masqueraded" IP address to provide along PASV reply when
+- pyftpdlib is running behind a NAT or other types of gateways.
+- When configured pyftpdlib will hide its local address and
+- instead use the public address of your NAT (default None).
+-
+- - (list) passive_ports:
+- what ports ftpd will use for its passive data transfers.
+- Value expected is a list of integers (e.g. range(60000, 65535)).
+- When configured pyftpdlib will no longer use kernel-assigned
+- random ports (default None).
+-
+-
+- All relevant instance attributes initialized when client connects
+- are reproduced below. You may be interested in them in case you
+- want to subclass the original FTPHandler.
+-
+- - (bool) authenticated: True if client authenticated himself.
+- - (str) username: the name of the connected user (if any).
+- - (int) attempted_logins: number of currently attempted logins.
+- - (str) current_type: the current transfer type (default "a")
+- - (int) af: the address family (IPv4/IPv6)
+- - (instance) server: the FTPServer class instance.
+- - (instance) data_server: the data server instance (if any).
+- - (instance) data_channel: the data channel instance (if any).
+- """
+- # these are overridable defaults
+-
+- # default classes
+- authorizer = DummyAuthorizer()
+- active_dtp = ActiveDTP
+- passive_dtp = PassiveDTP
+- dtp_handler = DTPHandler
+- abstracted_fs = AbstractedFS
+-
+- # session attributes (explained in the docstring)
+- banner = "pyftpdlib %s ready." %__ver__
+- max_login_attempts = 3
+- permit_foreign_addresses = False
+- permit_privileged_ports = False
+- masquerade_address = None
+- passive_ports = None
+-
+- def __init__(self, conn, server):
+- """Initialize the command channel.
+-
+- - (instance) conn: the socket object instance of the newly
+- established connection.
+- - (instance) server: the ftp server class instance.
+- """
+- try:
+- asynchat.async_chat.__init__(self, conn=conn) # python2.5
+- except TypeError:
+- asynchat.async_chat.__init__(self, sock=conn) # python2.6
+- self.server = server
+- self.remote_ip, self.remote_port = self.socket.getpeername()[:2]
+- self.in_buffer = []
+- self.in_buffer_len = 0
+- self.set_terminator("\r\n")
+-
+- # session attributes
+- self.fs = self.abstracted_fs()
+- self.authenticated = False
+- self.username = ""
+- self.password = ""
+- self.attempted_logins = 0
+- self.current_type = 'a'
+- self.restart_position = 0
+- self.quit_pending = False
+- self._epsvall = False
+- self.__in_dtp_queue = None
+- self.__out_dtp_queue = None
+-
+- self.__errno_responses = {
+- errno.EPERM: 553,
+- errno.EINVAL: 504,
+- errno.ENOENT: 550,
+- errno.EREMOTE: 450,
+- errno.EEXIST: 521,
+- }
+-
+- # mlsx facts attributes
+- self.current_facts = ['type', 'perm', 'size', 'modify']
+- self.current_facts.append('unique')
+- self.available_facts = self.current_facts[:]
+- self.available_facts += ['unix.mode', 'unix.uid', 'unix.gid']
+- self.available_facts.append('create')
+-
+- # dtp attributes
+- self.data_server = None
+- self.data_channel = None
+-
+- if hasattr(self.socket, 'family'):
+- self.af = self.socket.family
+- else: # python < 2.5
+- ip, port = self.socket.getsockname()[:2]
+- self.af = socket.getaddrinfo(ip, port, socket.AF_UNSPEC,
+- socket.SOCK_STREAM)[0][0]
+-
+- def handle(self):
+- """Return a 220 'Ready' response to the client over the command
+- channel.
+- """
+- if len(self.banner) <= 75:
+- self.respond("220 %s" %str(self.banner))
+- else:
+- self.push('220-%s\r\n' %str(self.banner))
+- self.respond('220 ')
+-
+- def handle_max_cons(self):
+- """Called when limit for maximum number of connections is reached."""
+- msg = "Too many connections. Service temporary unavailable."
+- self.respond("421 %s" %msg)
+- self.log(msg)
+- # If self.push is used, data could not be sent immediately in
+- # which case a new "loop" will occur exposing us to the risk of
+- # accepting new connections. Since this could cause asyncore to
+- # run out of fds (...and exposes the server to DoS attacks), we
+- # immediately close the channel by using close() instead of
+- # close_when_done(). If data has not been sent yet client will
+- # be silently disconnected.
+- self.close()
+-
+- def handle_max_cons_per_ip(self):
+- """Called when too many clients are connected from the same IP."""
+- msg = "Too many connections from the same IP address."
+- self.respond("421 %s" %msg)
+- self.log(msg)
+- self.close_when_done()
+-
+- # --- asyncore / asynchat overridden methods
+-
+- def readable(self):
+- # if there's a quit pending we stop reading data from socket
+- return not self.quit_pending
+-
+- def collect_incoming_data(self, data):
+- """Read incoming data and append to the input buffer."""
+- self.in_buffer.append(data)
+- self.in_buffer_len += len(data)
+- # Flush buffer if it gets too long (possible DoS attacks).
+- # RFC-959 specifies that a 500 response could be given in
+- # such cases
+- buflimit = 2048
+- if self.in_buffer_len > buflimit:
+- self.respond('500 Command too long.')
+- self.log('Command received exceeded buffer limit of %s.' %(buflimit))
+- self.in_buffer = []
+- self.in_buffer_len = 0
+-
+- # commands accepted before authentication
+- unauth_cmds = ('FEAT','HELP','NOOP','PASS','QUIT','STAT','SYST','USER')
+-
+- # commands needing an argument
+- arg_cmds = ('ALLO','APPE','DELE','EPRT','MDTM','MODE','MKD','OPTS','PORT',
+- 'REST','RETR','RMD','RNFR','RNTO','SIZE', 'STOR','STRU',
+- 'TYPE','USER','XMKD','XRMD')
+-
+- # commands needing no argument
+- unarg_cmds = ('ABOR','CDUP','FEAT','NOOP','PASV','PWD','QUIT','REIN',
+- 'SYST','XCUP','XPWD')
+-
+- def found_terminator(self):
+- r"""Called when the incoming data stream matches the \r\n
+- terminator.
+-
+- Depending on the command received it calls the command's
+- corresponding method (e.g. for received command "MKD pathname",
+- ftp_MKD() method is called with "pathname" as the argument).
+- """
+- line = ''.join(self.in_buffer)
+- self.in_buffer = []
+- self.in_buffer_len = 0
+-
+- cmd = line.split(' ')[0].upper()
+- space = line.find(' ')
+- if space != -1:
+- arg = line[space + 1:]
+- else:
+- arg = ""
+-
+- if cmd != 'PASS':
+- self.logline("<== %s" %line)
+- else:
+- self.logline("<== %s %s" %(line.split(' ')[0], '*' * 6))
+-
+- # let's check if user provided an argument for those commands
+- # needing one
+- if not arg and cmd in self.arg_cmds:
+- self.respond("501 Syntax error: command needs an argument.")
+- return
+-
+- # let's do the same for those commands requiring no argument.
+- elif arg and cmd in self.unarg_cmds:
+- self.respond("501 Syntax error: command does not accept arguments.")
+- return
+-
+- # provide a limited set of commands if user isn't
+- # authenticated yet
+- if (not self.authenticated):
+- if cmd in self.unauth_cmds:
+- # we permit STAT during this phase but we don't want
+- # STAT to return a directory LISTing if the user is
+- # not authenticated yet (this could happen if STAT
+- # is used with an argument)
+- if (cmd == 'STAT') and arg:
+- self.respond("530 Log in with USER and PASS first.")
+- else:
+- method = getattr(self, 'ftp_' + cmd)
+- method(arg) # call the proper ftp_* method
+- elif cmd in proto_cmds:
+- self.respond("530 Log in with USER and PASS first.")
+- else:
+- self.respond('500 Command "%s" not understood.' %line)
+-
+- # provide full command set
+- elif (self.authenticated) and (cmd in proto_cmds):
+- if not (self.__check_path(arg, arg)): # and self.__check_perm(cmd, arg)):
+- return
+- method = getattr(self, 'ftp_' + cmd)
+- method(arg) # call the proper ftp_* method
+-
+- else:
+- # recognize those commands having "special semantics"
+- if 'ABOR' in cmd:
+- self.ftp_ABOR("")
+- elif 'STAT' in cmd:
+- self.ftp_STAT("")
+- # unknown command
+- else:
+- self.respond('500 Command "%s" not understood.' %line)
+-
+- def __check_path(self, cmd, line):
+- """Check whether a path is valid."""
+-
+- # Always true, we will only check later, once we have a cursor
+- return True
+-
+- def __check_perm(self, cmd, line, datacr):
+- """Check permissions depending on issued command."""
+- map = {'CWD':'e', 'XCWD':'e', 'CDUP':'e', 'XCUP':'e',
+- 'LIST':'l', 'NLST':'l', 'MLSD':'l', 'STAT':'l',
+- 'RETR':'r',
+- 'APPE':'a',
+- 'DELE':'d', 'RMD':'d', 'XRMD':'d',
+- 'RNFR':'f',
+- 'MKD':'m', 'XMKD':'m',
+- 'STOR':'w'}
+- raise NotImplementedError
+- if cmd in map:
+- if cmd == 'STAT' and not line:
+- return True
+- perm = map[cmd]
+- if not line and (cmd in ('LIST','NLST','MLSD')):
+- path = self.fs.ftp2fs(self.fs.cwd, datacr)
+- else:
+- path = self.fs.ftp2fs(line, datacr)
+- if not self.authorizer.has_perm(self.username, perm, path):
+- self.log('FAIL %s "%s". Not enough privileges.' \
+- %(cmd, self.fs.ftpnorm(line)))
+- self.respond("550 Can't %s. Not enough privileges." %cmd)
+- return False
+- return True
+-
+- def handle_expt(self):
+- """Called when there is out of band (OOB) data for the socket
+- connection. This could happen in case of such commands needing
+- "special action" (typically STAT and ABOR) in which case we
+- append OOB data to incoming buffer.
+- """
+- if hasattr(socket, 'MSG_OOB'):
+- try:
+- data = self.socket.recv(1024, socket.MSG_OOB)
+- except socket.error:
+- pass
+- else:
+- self.in_buffer.append(data)
+- return
+- self.log("Can't handle OOB data.")
+- self.close()
+-
+- def handle_error(self):
+- try:
+- raise
+- except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+- raise
+- except socket.error, err:
+- # fix around asyncore bug (http://bugs.python.org/issue1736101)
+- if err[0] in (errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN, \
+- errno.ECONNABORTED):
+- self.handle_close()
+- return
+- else:
+- logerror(traceback.format_exc())
+- except:
+- logerror(traceback.format_exc())
+- self.close()
+-
+- def handle_close(self):
+- self.close()
+-
+- _closed = False
+- def close(self):
+- """Close the current channel disconnecting the client."""
+- if not self._closed:
+- self._closed = True
+- if self.data_server:
+- self.data_server.close()
+- del self.data_server
+-
+- if self.data_channel:
+- self.data_channel.close()
+- del self.data_channel
+-
+- del self.__out_dtp_queue
+- del self.__in_dtp_queue
+-
+- # remove client IP address from ip map
+- self.server.ip_map.remove(self.remote_ip)
+- asynchat.async_chat.close(self)
+- self.log("Disconnected.")
+-
+- # --- callbacks
+-
+- def on_dtp_connection(self):
+- """Called every time data channel connects (either active or
+- passive).
+-
+- Incoming and outgoing queues are checked for pending data.
+- If outbound data is pending, it is pushed into the data channel.
+- If awaiting inbound data, the data channel is enabled for
+- receiving.
+- """
+- if self.data_server:
+- self.data_server.close()
+- self.data_server = None
+-
+- # check for data to send
+- if self.__out_dtp_queue:
+- data, isproducer, file = self.__out_dtp_queue
+- if file:
+- self.data_channel.file_obj = file
+- if not isproducer:
+- self.data_channel.push(data)
+- else:
+- self.data_channel.push_with_producer(data)
+- if self.data_channel:
+- self.data_channel.close_when_done()
+- self.__out_dtp_queue = None
+-
+- # check for data to receive
+- elif self.__in_dtp_queue:
+- self.data_channel.file_obj = self.__in_dtp_queue
+- self.data_channel.enable_receiving(self.current_type)
+- self.__in_dtp_queue = None
+-
+- def on_dtp_close(self):
+- """Called every time the data channel is closed."""
+- self.data_channel = None
+- if self.quit_pending:
+- self.close_when_done()
+-
+- # --- utility
+-
+- def respond(self, resp):
+- """Send a response to the client using the command channel."""
+- self.push(resp + '\r\n')
+- self.logline('==> %s' % resp)
+-
+- def push_dtp_data(self, data, isproducer=False, file=None):
+- """Pushes data into the data channel.
+-
+- It is usually called for those commands requiring some data to
+- be sent over the data channel (e.g. RETR).
+- If data channel does not exist yet, it queues the data to send
+- later; data will then be pushed into data channel when
+- on_dtp_connection() will be called.
+-
+- - (str/classobj) data: the data to send which may be a string
+- or a producer object).
+- - (bool) isproducer: whether treat data as a producer.
+- - (file) file: the file[-like] object to send (if any).
+- """
+- if self.data_channel:
+- self.respond("125 Data connection already open. Transfer starting.")
+- if file:
+- self.data_channel.file_obj = file
+- if not isproducer:
+- self.data_channel.push(data)
+- else:
+- self.data_channel.push_with_producer(data)
+- if self.data_channel:
+- self.data_channel.close_when_done()
+- else:
+- self.respond("150 File status okay. About to open data connection.")
+- self.__out_dtp_queue = (data, isproducer, file)
+-
+- def log(self, msg):
+- """Log a message, including additional identifying session data."""
+- log("[%s]@%s:%s %s" %(self.username, self.remote_ip,
+- self.remote_port, msg))
+-
+- def logline(self, msg):
+- """Log a line including additional indentifying session data."""
+- logline("%s:%s %s" %(self.remote_ip, self.remote_port, msg))
+-
+- def flush_account(self):
+- """Flush account information by clearing attributes that need
+- to be reset on a REIN or new USER command.
+- """
+- if self.data_channel:
+- if not self.data_channel.transfer_in_progress():
+- self.data_channel.close()
+- self.data_channel = None
+- if self.data_server:
+- self.data_server.close()
+- self.data_server = None
+-
+- self.fs.rnfr = None
+- self.authenticated = False
+- self.username = ""
+- self.password = ""
+- self.attempted_logins = 0
+- self.current_type = 'a'
+- self.restart_position = 0
+- self.quit_pending = False
+- self.__in_dtp_queue = None
+- self.__out_dtp_queue = None
+-
+- def run_as_current_user(self, function, *args, **kwargs):
+- """Execute a function impersonating the current logged-in user."""
+- self.authorizer.impersonate_user(self.username, self.password)
+- try:
+- return function(*args, **kwargs)
+- finally:
+- self.authorizer.terminate_impersonation()
+-
+- # --- connection
+-
+- def try_as_current_user(self, function, args=None, kwargs=None, line=None, errno_resp=None):
+- """run function as current user, auto-respond in exceptions
+- @param args,kwargs the arguments, in list and dict respectively
+- @param errno_resp a dictionary of responses to IOError, OSError
+- """
+- if errno_resp:
+- eresp = self.__errno_responses.copy()
+- eresp.update(errno_resp)
+- else:
+- eresp = self.__errno_responses
+-
+- uline = ''
+- if line:
+- uline = ' "%s"' % _to_unicode(line)
+- try:
+- if args is None:
+- args = ()
+- if kwargs is None:
+- kwargs = {}
+- return self.run_as_current_user(function, *args, **kwargs)
+- except NotImplementedError, err:
+- cmdname = function.__name__
+- why = err.args[0] or 'Not implemented'
+- self.log('FAIL %s() not implemented: %s.' %(cmdname, why))
+- self.respond('502 %s.' %why)
+- raise FTPExceptionSent(why)
+- except EnvironmentError, err:
+- cmdname = function.__name__
+- try:
+- logline(traceback.format_exc())
+- except Exception:
+- pass
+- ret_code = eresp.get(err.errno, '451')
+- why = (err.strerror) or 'Error in command'
+- self.log('FAIL %s() %s errno=%s: %s.' %(cmdname, uline, err.errno, why))
+- self.respond('%s %s.' % (str(ret_code), why))
+-
+- raise FTPExceptionSent(why)
+- except Exception, err:
+- cmdname = function.__name__
+- try:
+- logerror(traceback.format_exc())
+- except Exception:
+- pass
+- why = (err.args and err.args[0]) or 'Exception'
+- self.log('FAIL %s() %s Exception: %s.' %(cmdname, uline, why))
+- self.respond('451 %s.' % why)
+- raise FTPExceptionSent(why)
+-
+- def get_crdata2(self, *args, **kwargs):
+- return self.try_as_current_user(self.fs.get_crdata, args, kwargs, line=args[0])
+-
+- def _make_eport(self, ip, port):
+- """Establish an active data channel with remote client which
+- issued a PORT or EPRT command.
+- """
+- # FTP bounce attacks protection: according to RFC-2577 it's
+- # recommended to reject PORT if IP address specified in it
+- # does not match client IP address.
+- if not self.permit_foreign_addresses:
+- if ip != self.remote_ip:
+- self.log("Rejected data connection to foreign address %s:%s."
+- %(ip, port))
+- self.respond("501 Can't connect to a foreign address.")
+- return
+-
+- # ...another RFC-2577 recommendation is rejecting connections
+- # to privileged ports (< 1024) for security reasons.
+- if not self.permit_privileged_ports:
+- if port < 1024:
+- self.log('PORT against the privileged port "%s" refused.' %port)
+- self.respond("501 Can't connect over a privileged port.")
+- return
+-
+- # close existent DTP-server instance, if any.
+- if self.data_server:
+- self.data_server.close()
+- self.data_server = None
+- if self.data_channel:
+- self.data_channel.close()
+- self.data_channel = None
+-
+- # make sure we are not hitting the max connections limit
+- if self.server.max_cons:
+- if len(self._map) >= self.server.max_cons:
+- msg = "Too many connections. Can't open data channel."
+- self.respond("425 %s" %msg)
+- self.log(msg)
+- return
+-
+- # open data channel
+- self.active_dtp(ip, port, self)
+-
+- def _make_epasv(self, extmode=False):
+- """Initialize a passive data channel with remote client which
+- issued a PASV or EPSV command.
+- If extmode argument is False we assume that client issued EPSV in
+- which case extended passive mode will be used (see RFC-2428).
+- """
+- # close existing DTP-server instance, if any
+- if self.data_server:
+- self.data_server.close()
+- self.data_server = None
+-
+- if self.data_channel:
+- self.data_channel.close()
+- self.data_channel = None
+-
+- # make sure we are not hitting the max connections limit
+- if self.server.max_cons:
+- if len(self._map) >= self.server.max_cons:
+- msg = "Too many connections. Can't open data channel."
+- self.respond("425 %s" %msg)
+- self.log(msg)
+- return
+-
+- # open data channel
+- self.data_server = self.passive_dtp(self, extmode)
+-
+- def ftp_PORT(self, line):
+- """Start an active data channel by using IPv4."""
+- if self._epsvall:
+- self.respond("501 PORT not allowed after EPSV ALL.")
+- return
+- if self.af != socket.AF_INET:
+- self.respond("425 You cannot use PORT on IPv6 connections. "
+- "Use EPRT instead.")
+- return
+- # Parse PORT request for getting IP and PORT.
+- # Request comes in as:
+- # > h1,h2,h3,h4,p1,p2
+- # ...where the client's IP address is h1.h2.h3.h4 and the TCP
+- # port number is (p1 * 256) + p2.
+- try:
+- addr = map(int, line.split(','))
+- assert len(addr) == 6
+- for x in addr[:4]:
+- assert 0 <= x <= 255
+- ip = '%d.%d.%d.%d' %tuple(addr[:4])
+- port = (addr[4] * 256) + addr[5]
+- assert 0 <= port <= 65535
+- except (AssertionError, ValueError, OverflowError):
+- self.respond("501 Invalid PORT format.")
+- return
+- self._make_eport(ip, port)
+-
+- def ftp_EPRT(self, line):
+- """Start an active data channel by choosing the network protocol
+- to use (IPv4/IPv6) as defined in RFC-2428.
+- """
+- if self._epsvall:
+- self.respond("501 EPRT not allowed after EPSV ALL.")
+- return
+- # Parse EPRT request for getting protocol, IP and PORT.
+- # Request comes in as:
+- # # <d>proto<d>ip<d>port<d>
+- # ...where <d> is an arbitrary delimiter character (usually "|") and
+- # <proto> is the network protocol to use (1 for IPv4, 2 for IPv6).
+- try:
+- af, ip, port = line.split(line[0])[1:-1]
+- port = int(port)
+- assert 0 <= port <= 65535
+- except (AssertionError, ValueError, IndexError, OverflowError):
+- self.respond("501 Invalid EPRT format.")
+- return
+-
+- if af == "1":
+- if self.af != socket.AF_INET:
+- self.respond('522 Network protocol not supported (use 2).')
+- else:
+- try:
+- octs = map(int, ip.split('.'))
+- assert len(octs) == 4
+- for x in octs:
+- assert 0 <= x <= 255
+- except (AssertionError, ValueError, OverflowError):
+- self.respond("501 Invalid EPRT format.")
+- else:
+- self._make_eport(ip, port)
+- elif af == "2":
+- if self.af == socket.AF_INET:
+- self.respond('522 Network protocol not supported (use 1).')
+- else:
+- self._make_eport(ip, port)
+- else:
+- if self.af == socket.AF_INET:
+- self.respond('501 Unknown network protocol (use 1).')
+- else:
+- self.respond('501 Unknown network protocol (use 2).')
+-
+- def ftp_PASV(self, line):
+- """Start a passive data channel by using IPv4."""
+- if self._epsvall:
+- self.respond("501 PASV not allowed after EPSV ALL.")
+- return
+- if self.af != socket.AF_INET:
+- self.respond("425 You cannot use PASV on IPv6 connections. "
+- "Use EPSV instead.")
+- else:
+- self._make_epasv(extmode=False)
+-
+- def ftp_EPSV(self, line):
+- """Start a passive data channel by using IPv4 or IPv6 as defined
+- in RFC-2428.
+- """
+- # RFC-2428 specifies that if an optional parameter is given,
+- # we have to determine the address family from that otherwise
+- # use the same address family used on the control connection.
+- # In such a scenario a client may use IPv4 on the control channel
+- # and choose to use IPv6 for the data channel.
+- # But how could we use IPv6 on the data channel without knowing
+- # which IPv6 address to use for binding the socket?
+- # Unfortunately RFC-2428 does not provide satisfing information
+- # on how to do that. The assumption is that we don't have any way
+- # to know which address to use, hence we just use the same address
+- # family used on the control connection.
+- if not line:
+- self._make_epasv(extmode=True)
+- elif line == "1":
+- if self.af != socket.AF_INET:
+- self.respond('522 Network protocol not supported (use 2).')
+- else:
+- self._make_epasv(extmode=True)
+- elif line == "2":
+- if self.af == socket.AF_INET:
+- self.respond('522 Network protocol not supported (use 1).')
+- else:
+- self._make_epasv(extmode=True)
+- elif line.lower() == 'all':
+- self._epsvall = True
+- self.respond('220 Other commands other than EPSV are now disabled.')
+- else:
+- if self.af == socket.AF_INET:
+- self.respond('501 Unknown network protocol (use 1).')
+- else:
+- self.respond('501 Unknown network protocol (use 2).')
+-
+- def ftp_QUIT(self, line):
+- """Quit the current session."""
+- # From RFC-959:
+- # This command terminates a USER and if file transfer is not
+- # in progress, the server closes the control connection.
+- # If file transfer is in progress, the connection will remain
+- # open for result response and the server will then close it.
+- if self.authenticated:
+- msg_quit = self.authorizer.get_msg_quit(self.username)
+- else:
+- msg_quit = "Goodbye."
+- if len(msg_quit) <= 75:
+- self.respond("221 %s" %msg_quit)
+- else:
+- self.push("221-%s\r\n" %msg_quit)
+- self.respond("221 ")
+-
+- if not self.data_channel:
+- self.close_when_done()
+- else:
+- # tell the cmd channel to stop responding to commands.
+- self.quit_pending = True
+-
+-
+- # --- data transferring
+-
+- def ftp_LIST(self, line):
+- """Return a list of files in the specified directory to the
+- client.
+- """
+- # - If no argument, fall back on cwd as default.
+- # - Some older FTP clients erroneously issue /bin/ls-like LIST
+- # formats in which case we fall back on cwd as default.
+- if not line or line.lower() in ('-a', '-l', '-al', '-la'):
+- line = ''
+- datacr = None
+- try:
+- datacr = self.get_crdata2(line, mode='list')
+- iterator = self.try_as_current_user(self.fs.get_list_dir, (datacr,))
+- except FTPExceptionSent:
+- self.fs.close_cr(datacr)
+- return
+-
+- try:
+- self.log('OK LIST "%s". Transfer starting.' % line)
+- producer = BufferedIteratorProducer(iterator)
+- self.push_dtp_data(producer, isproducer=True)
+- finally:
+- self.fs.close_cr(datacr)
+-
+-
+- def ftp_NLST(self, line):
+- """Return a list of files in the specified directory in a
+- compact form to the client.
+- """
+- if not line:
+- line = ''
+-
+- datacr = None
+- try:
+- datacr = self.get_crdata2(line, mode='list')
+- if not datacr:
+- datacr = ( None, None, None )
+- if self.fs.isdir(datacr[1]):
+- nodelist = self.try_as_current_user(self.fs.listdir, (datacr,))
+- else:
+- # if path is a file we just list its name
+- nodelist = [datacr[1],]
+-
+- listing = []
+- for nl in nodelist:
+- if isinstance(nl.path, (list, tuple)):
+- listing.append(nl.path[-1])
+- else:
+- listing.append(nl.path) # assume string
+- except FTPExceptionSent:
+- self.fs.close_cr(datacr)
+- return
+-
+- self.fs.close_cr(datacr)
+- data = ''
+- if listing:
+- listing.sort()
+- data = ''.join([ _to_decode(x) + '\r\n' for x in listing ])
+- self.log('OK NLST "%s". Transfer starting.' %line)
+- self.push_dtp_data(data)
+-
+- # --- MLST and MLSD commands
+-
+- # The MLST and MLSD commands are intended to standardize the file and
+- # directory information returned by the server-FTP process. These
+- # commands differ from the LIST command in that the format of the
+- # replies is strictly defined although extensible.
+-
+- def ftp_MLST(self, line):
+- """Return information about a pathname in a machine-processable
+- form as defined in RFC-3659.
+- """
+- # if no argument, fall back on cwd as default
+- if not line:
+- line = ''
+- datacr = None
+- try:
+- datacr = self.get_crdata2(line, mode='list')
+- perms = self.authorizer.get_perms(self.username)
+- iterator = self.try_as_current_user(self.fs.format_mlsx, (datacr[0], datacr[1].parent,
+- [datacr[1],], perms, self.current_facts), {'ignore_err':False})
+- data = ''.join(iterator)
+- except FTPExceptionSent:
+- self.fs.close_cr(datacr)
+- return
+- else:
+- self.fs.close_cr(datacr)
+- # since TVFS is supported (see RFC-3659 chapter 6), a fully
+- # qualified pathname should be returned
+- data = data.split(' ')[0] + ' %s\r\n' %line
+- # response is expected on the command channel
+- self.push('250-Listing "%s":\r\n' %line)
+- # the fact set must be preceded by a space
+- self.push(' ' + data)
+- self.respond('250 End MLST.')
+-
+- def ftp_MLSD(self, line):
+- """Return contents of a directory in a machine-processable form
+- as defined in RFC-3659.
+- """
+- # if no argument, fall back on cwd as default
+- if not line:
+- line = ''
+-
+- datacr = None
+- try:
+- datacr = self.get_crdata2(line, mode='list')
+- # RFC-3659 requires 501 response code if path is not a directory
+- if not self.fs.isdir(datacr[1]):
+- err = 'No such directory'
+- self.log('FAIL MLSD "%s". %s.' %(line, err))
+- self.respond("501 %s." %err)
+- return
+- listing = self.try_as_current_user(self.fs.listdir, (datacr,))
+- except FTPExceptionSent:
+- self.fs.close_cr(datacr)
+- return
+- else:
+- self.fs.close_cr(datacr)
+- perms = self.authorizer.get_perms(self.username)
+- iterator = self.fs.format_mlsx(datacr[0], datacr[1], listing, perms,
+- self.current_facts)
+- producer = BufferedIteratorProducer(iterator)
+- self.log('OK MLSD "%s". Transfer starting.' %line)
+- self.push_dtp_data(producer, isproducer=True)
+-
+- def ftp_RETR(self, line):
+- """Retrieve the specified file (transfer from the server to the
+- client)
+- """
+- datacr = None
+- try:
+- datacr = self.get_crdata2(line, mode='file')
+- fd = self.try_as_current_user(self.fs.open, (datacr, 'rb'))
+- except FTPExceptionSent:
+- self.fs.close_cr(datacr)
+- return
+-
+- if self.restart_position:
+- # Make sure that the requested offset is valid (within the
+- # size of the file being resumed).
+- # According to RFC-1123 a 554 reply may result in case that
+- # the existing file cannot be repositioned as specified in
+- # the REST.
+- ok = 0
+- try:
+- assert not self.restart_position > self.fs.getsize(datacr)
+- fd.seek(self.restart_position)
+- ok = 1
+- except AssertionError:
+- why = "Invalid REST parameter"
+- except IOError, err:
+- why = _strerror(err)
+- self.restart_position = 0
+- if not ok:
+- self.respond('554 %s' %why)
+- self.log('FAIL RETR "%s". %s.' %(line, why))
+- self.fs.close_cr(datacr)
+- return
+- self.log('OK RETR "%s". Download starting.' %line)
+- producer = FileProducer(fd, self.current_type)
+- self.push_dtp_data(producer, isproducer=True, file=fd)
+- self.fs.close_cr(datacr)
+-
+- def ftp_STOR(self, line, mode='w'):
+- """Store a file (transfer from the client to the server)."""
+- # A resume could occur in case of APPE or REST commands.
+- # In that case we have to open file object in different ways:
+- # STOR: mode = 'w'
+- # APPE: mode = 'a'
+- # REST: mode = 'r+' (to permit seeking on file object)
+- if 'a' in mode:
+- cmd = 'APPE'
+- else:
+- cmd = 'STOR'
+-
+- datacr = None
+- try:
+- datacr = self.get_crdata2(line,mode='create')
+- if self.restart_position:
+- mode = 'r+'
+- fd = self.try_as_current_user(self.fs.create, (datacr, datacr[2], mode + 'b'))
+- assert fd
+- except FTPExceptionSent:
+- self.fs.close_cr(datacr)
+- return
+-
+- if self.restart_position:
+- # Make sure that the requested offset is valid (within the
+- # size of the file being resumed).
+- # According to RFC-1123 a 554 reply may result in case
+- # that the existing file cannot be repositioned as
+- # specified in the REST.
+- ok = 0
+- try:
+- assert not self.restart_position > self.fs.getsize(datacr)
+- fd.seek(self.restart_position)
+- ok = 1
+- except AssertionError:
+- why = "Invalid REST parameter"
+- except IOError, err:
+- why = _strerror(err)
+- self.restart_position = 0
+- if not ok:
+- self.fs.close_cr(datacr)
+- self.respond('554 %s' %why)
+- self.log('FAIL %s "%s". %s.' %(cmd, line, why))
+- return
+-
+- self.log('OK %s "%s". Upload starting.' %(cmd, line))
+- if self.data_channel:
+- self.respond("125 Data connection already open. Transfer starting.")
+- self.data_channel.file_obj = fd
+- self.data_channel.enable_receiving(self.current_type)
+- else:
+- self.respond("150 File status okay. About to open data connection.")
+- self.__in_dtp_queue = fd
+- self.fs.close_cr(datacr)
+-
+-
+- def ftp_STOU(self, line):
+- """Store a file on the server with a unique name."""
+- # Note 1: RFC-959 prohibited STOU parameters, but this
+- # prohibition is obsolete.
+- # Note 2: 250 response wanted by RFC-959 has been declared
+- # incorrect in RFC-1123 that wants 125/150 instead.
+- # Note 3: RFC-1123 also provided an exact output format
+- # defined to be as follow:
+- # > 125 FILE: pppp
+- # ...where pppp represents the unique path name of the
+- # file that will be written.
+-
+- # watch for STOU preceded by REST, which makes no sense.
+- if self.restart_position:
+- self.respond("450 Can't STOU while REST request is pending.")
+- return
+-
+-
+- if line:
+- datacr = self.get_crdata2(line, mode='create')
+- # TODO
+- else:
+- # TODO
+- basedir = self.fs.ftp2fs(self.fs.cwd, datacr)
+- prefix = 'ftpd.'
+- try:
+- fd = self.try_as_current_user(self.fs.mkstemp, kwargs={'prefix':prefix,
+- 'dir': basedir}, line=line )
+- except FTPExceptionSent:
+- self.fs.close_cr(datacr)
+- return
+- except IOError, err: # TODO
+- # hitted the max number of tries to find out file with
+- # unique name
+- if err.errno == errno.EEXIST:
+- why = 'No usable unique file name found'
+- # something else happened
+- else:
+- why = _strerror(err)
+- self.respond("450 %s." %why)
+- self.log('FAIL STOU "%s". %s.' %(self.fs.ftpnorm(line), why))
+- self.fs.close_cr(datacr)
+- return
+-
+- filename = line
+- if not self.authorizer.has_perm(self.username, 'w', filename):
+- self.log('FAIL STOU "%s". Not enough privileges'
+- %self.fs.ftpnorm(line))
+- self.respond("550 Can't STOU: not enough privileges.")
+- self.fs.close_cr(datacr)
+- return
+-
+- # now just acts like STOR except that restarting isn't allowed
+- self.log('OK STOU "%s". Upload starting.' %filename)
+- if self.data_channel:
+- self.respond("125 FILE: %s" %filename)
+- self.data_channel.file_obj = fd
+- self.data_channel.enable_receiving(self.current_type)
+- else:
+- self.respond("150 FILE: %s" %filename)
+- self.__in_dtp_queue = fd
+- self.fs.close_cr(datacr)
+-
+-
+- def ftp_APPE(self, line):
+- """Append data to an existing file on the server."""
+- # watch for APPE preceded by REST, which makes no sense.
+- if self.restart_position:
+- self.respond("550 Can't APPE while REST request is pending.")
+- else:
+- self.ftp_STOR(line, mode='a')
+-
+- def ftp_REST(self, line):
+- """Restart a file transfer from a previous mark."""
+- try:
+- marker = int(line)
+- if marker < 0:
+- raise ValueError
+- except (ValueError, OverflowError):
+- self.respond("501 Invalid parameter.")
+- else:
+- self.respond("350 Restarting at position %s. " \
+- "Now use RETR/STOR for resuming." %marker)
+- self.log("OK REST %s." %marker)
+- self.restart_position = marker
+-
+- def ftp_ABOR(self, line):
+- """Abort the current data transfer."""
+-
+- # ABOR received while no data channel exists
+- if (self.data_server is None) and (self.data_channel is None):
+- resp = "225 No transfer to abort."
+- else:
+- # a PASV was received but connection wasn't made yet
+- if self.data_server:
+- self.data_server.close()
+- self.data_server = None
+- resp = "225 ABOR command successful; data channel closed."
+-
+- # If a data transfer is in progress the server must first
+- # close the data connection, returning a 426 reply to
+- # indicate that the transfer terminated abnormally, then it
+- # must send a 226 reply, indicating that the abort command
+- # was successfully processed.
+- # If no data has been transmitted we just respond with 225
+- # indicating that no transfer was in progress.
+- if self.data_channel:
+- if self.data_channel.transfer_in_progress():
+- self.data_channel.close()
+- self.data_channel = None
+- self.respond("426 Connection closed; transfer aborted.")
+- self.log("OK ABOR. Transfer aborted, data channel closed.")
+- resp = "226 ABOR command successful."
+- else:
+- self.data_channel.close()
+- self.data_channel = None
+- self.log("OK ABOR. Data channel closed.")
+- resp = "225 ABOR command successful; data channel closed."
+- self.respond(resp)
+-
+-
+- # --- authentication
+-
+- def ftp_USER(self, line):
+- """Set the username for the current session."""
+- # we always treat anonymous user as lower-case string.
+- if line.lower() == "anonymous":
+- line = "anonymous"
+-
+- # RFC-959 specifies a 530 response to the USER command if the
+- # username is not valid. If the username is valid is required
+- # ftpd returns a 331 response instead. In order to prevent a
+- # malicious client from determining valid usernames on a server,
+- # it is suggested by RFC-2577 that a server always return 331 to
+- # the USER command and then reject the combination of username
+- # and password for an invalid username when PASS is provided later.
+- if not self.authenticated:
+- self.respond('331 Username ok, send password.')
+- else:
+- # a new USER command could be entered at any point in order
+- # to change the access control flushing any user, password,
+- # and account information already supplied and beginning the
+- # login sequence again.
+- self.flush_account()
+- msg = 'Previous account information was flushed'
+- self.log('OK USER "%s". %s.' %(line, msg))
+- self.respond('331 %s, send password.' %msg)
+- self.username = line
+-
+- def ftp_PASS(self, line):
+- """Check username's password against the authorizer."""
+-
+- if self.authenticated:
+- self.respond("503 User already authenticated.")
+- return
+- if not self.username:
+- self.respond("503 Login with USER first.")
+- return
+-
+- # username ok
+- if self.authorizer.has_user(self.username):
+- if self.username == 'anonymous' \
+- or self.authorizer.validate_authentication(self.username, line):
+- msg_login = self.authorizer.get_msg_login(self.username)
+- if len(msg_login) <= 75:
+- self.respond('230 %s' %msg_login)
+- else:
+- self.push("230-%s\r\n" %msg_login)
+- self.respond("230 ")
+-
+- self.authenticated = True
+- self.password = line
+- self.attempted_logins = 0
+- self.fs.root = self.authorizer.get_home_dir(self.username)
+- self.fs.username=self.username
+- self.fs.password=line
+- self.log("User %s logged in." %self.username)
+- else:
+- self.attempted_logins += 1
+- if self.attempted_logins >= self.max_login_attempts:
+- self.respond("530 Maximum login attempts. Disconnecting.")
+- self.close()
+- else:
+- self.respond("530 Authentication failed.")
+- self.log('Authentication failed (user: "%s").' %self.username)
+- self.username = ""
+-
+- # wrong username
+- else:
+- self.attempted_logins += 1
+- if self.attempted_logins >= self.max_login_attempts:
+- self.log('Authentication failed: unknown username "%s".'
+- %self.username)
+- self.respond("530 Maximum login attempts. Disconnecting.")
+- self.close()
+- elif self.username.lower() == 'anonymous':
+- self.respond("530 Anonymous access not allowed.")
+- self.log('Authentication failed: anonymous access not allowed.')
+- else:
+- self.respond("530 Authentication failed.")
+- self.log('Authentication failed: unknown username "%s".'
+- %self.username)
+- self.username = ""
+-
+- def ftp_REIN(self, line):
+- """Reinitialize user's current session."""
+- # From RFC-959:
+- # REIN command terminates a USER, flushing all I/O and account
+- # information, except to allow any transfer in progress to be
+- # completed. All parameters are reset to the default settings
+- # and the control connection is left open. This is identical
+- # to the state in which a user finds himself immediately after
+- # the control connection is opened.
+- self.log("OK REIN. Flushing account information.")
+- self.flush_account()
+- # Note: RFC-959 erroneously mention "220" as the correct response
+- # code to be given in this case, but this is wrong...
+- self.respond("230 Ready for new user.")
+-
+-
+- # --- filesystem operations
+-
+- def ftp_PWD(self, line):
+- """Return the name of the current working directory to the client."""
+- cwd = self.fs.get_cwd()
+- self.respond('257 "%s" is the current directory.' % cwd)
+-
+- def ftp_CWD(self, line):
+- """Change the current working directory."""
+- # check: a lot of FTP servers go back to root directory if no
+- # arg is provided but this is not specified in RFC-959.
+- # Search for official references about this behaviour.
+- datacr = None
+- try:
+- datacr = self.get_crdata2(line,'cwd')
+- self.try_as_current_user(self.fs.chdir, (datacr,), line=line, errno_resp={2: 530})
+- cwd = self.fs.get_cwd()
+- self.log('OK CWD "%s".' % cwd)
+- self.respond('250 "%s" is the current directory.' % cwd)
+- except FTPExceptionSent:
+- return
+- finally:
+- self.fs.close_cr(datacr)
+-
+- def ftp_CDUP(self, line):
+- """Change into the parent directory."""
+- # Note: RFC-959 says that code 200 is required but it also says
+- # that CDUP uses the same codes as CWD.
+- self.ftp_CWD('..')
+-
+- def ftp_SIZE(self, line):
+- """Return size of file in a format suitable for using with
+- RESTart as defined in RFC-3659.
+-
+- Implementation note:
+- properly handling the SIZE command when TYPE ASCII is used would
+- require to scan the entire file to perform the ASCII translation
+- logic (file.read().replace(os.linesep, '\r\n')) and then
+- calculating the len of such data which may be different than
+- the actual size of the file on the server. Considering that
+- calculating such result could be very resource-intensive it
+- could be easy for a malicious client to try a DoS attack, thus
+- we do not perform the ASCII translation.
+-
+- However, clients in general should not be resuming downloads in
+- ASCII mode. Resuming downloads in binary mode is the recommended
+- way as specified in RFC-3659.
+- """
+- datacr = None
+- try:
+- datacr = self.get_crdata2(line, mode='file')
+- size = self.try_as_current_user(self.fs.getsize,(datacr,), line=line)
+- except FTPExceptionSent:
+- self.fs.close_cr(datacr)
+- return
+- else:
+- self.respond("213 %s" %size)
+- self.log('OK SIZE "%s".' %line)
+- self.fs.close_cr(datacr)
+-
+- def ftp_MDTM(self, line):
+- """Return last modification time of file to the client as an ISO
+- 3307 style timestamp (YYYYMMDDHHMMSS) as defined in RFC-3659.
+- """
+- datacr = None
+-
+- try:
+- if line.find('/', 1) < 0:
+- # root or db, just return local
+- lmt = None
+- else:
+- datacr = self.get_crdata2(line)
+- if not datacr:
+- raise IOError(errno.ENOENT, "%s is not retrievable" %line)
+-
+- lmt = self.try_as_current_user(self.fs.getmtime, (datacr,), line=line)
+- lmt = time.strftime("%Y%m%d%H%M%S", time.localtime(lmt))
+- self.respond("213 %s" %lmt)
+- self.log('OK MDTM "%s".' %line)
+- except FTPExceptionSent:
+- return
+- finally:
+- self.fs.close_cr(datacr)
+-
+- def ftp_MKD(self, line):
+- """Create the specified directory."""
+- try:
+- datacr = self.get_crdata2(line, mode='create')
+- self.try_as_current_user(self.fs.mkdir, (datacr, datacr[2]), line=line)
+- except FTPExceptionSent:
+- self.fs.close_cr(datacr)
+- return
+- else:
+- self.log('OK MKD "%s".' %line)
+- self.respond("257 Directory created.")
+- self.fs.close_cr(datacr)
+-
+- def ftp_RMD(self, line):
+- """Remove the specified directory."""
+- datacr = None
+- try:
+- datacr = self.get_crdata2(line, mode='delete')
+- if not datacr[1]:
+- msg = "Can't remove root directory."
+- self.respond("553 %s" %msg)
+- self.log('FAIL MKD "/". %s' %msg)
+- self.fs.close_cr(datacr)
+- return
+- self.try_as_current_user(self.fs.rmdir, (datacr,), line=line)
+- self.log('OK RMD "%s".' %line)
+- self.respond("250 Directory removed.")
+- except FTPExceptionSent:
+- pass
+- self.fs.close_cr(datacr)
+-
+- def ftp_DELE(self, line):
+- """Delete the specified file."""
+- datacr = None
+- try:
+- datacr = self.get_crdata2(line, mode='delete')
+- self.try_as_current_user(self.fs.remove, (datacr,), line=line)
+- self.log('OK DELE "%s".' %line)
+- self.respond("250 File removed.")
+- except FTPExceptionSent:
+- pass
+- self.fs.close_cr(datacr)
+-
+- def ftp_RNFR(self, line):
+- """Rename the specified (only the source name is specified
+- here, see RNTO command)"""
+- datacr = None
+- try:
+- datacr = self.get_crdata2(line, mode='rfnr')
+- if not datacr[1]:
+- self.respond("550 No such file or directory.")
+- elif not datacr[1]:
+- self.respond("553 Can't rename the home directory.")
+- else:
+- self.fs.rnfr = datacr[1]
+- self.respond("350 Ready for destination name.")
+- except FTPExceptionSent:
+- pass
+- self.fs.close_cr(datacr)
+-
+- def ftp_RNTO(self, line):
+- """Rename file (destination name only, source is specified with
+- RNFR).
+- """
+- if not self.fs.rnfr:
+- self.respond("503 Bad sequence of commands: use RNFR first.")
+- return
+- datacr = None
+- try:
+- datacr = self.get_crdata2(line,'create')
+- oldname = self.fs.rnfr.path
+- if isinstance(oldname, (list, tuple)):
+- oldname = '/'.join(oldname)
+- self.try_as_current_user(self.fs.rename, (self.fs.rnfr, datacr), line=line)
+- self.fs.rnfr = None
+- self.log('OK RNFR/RNTO "%s ==> %s".' % \
+- (_to_unicode(oldname), _to_unicode(line)))
+- self.respond("250 Renaming ok.")
+- except FTPExceptionSent:
+- pass
+- finally:
+- self.fs.rnfr = None
+- self.fs.close_cr(datacr)
+-
+-
+- # --- others
+-
+- def ftp_TYPE(self, line):
+- """Set current type data type to binary/ascii"""
+- line = line.upper()
+- if line in ("A", "AN", "A N"):
+- self.respond("200 Type set to: ASCII.")
+- self.current_type = 'a'
+- elif line in ("I", "L8", "L 8"):
+- self.respond("200 Type set to: Binary.")
+- self.current_type = 'i'
+- else:
+- self.respond('504 Unsupported type "%s".' %line)
+-
+- def ftp_STRU(self, line):
+- """Set file structure (obsolete)."""
+- # obsolete (backward compatibility with older ftp clients)
+- if line in ('f','F'):
+- self.respond('200 File transfer structure set to: F.')
+- else:
+- self.respond('504 Unimplemented STRU type.')
+-
+- def ftp_MODE(self, line):
+- """Set data transfer mode (obsolete)"""
+- # obsolete (backward compatibility with older ftp clients)
+- if line in ('s', 'S'):
+- self.respond('200 Transfer mode set to: S')
+- else:
+- self.respond('504 Unimplemented MODE type.')
+-
+- def ftp_STAT(self, line):
+- """Return statistics about current ftp session. If an argument
+- is provided return directory listing over command channel.
+-
+- Implementation note:
+-
+- RFC-959 do not explicitly mention globbing; this means that FTP
+- servers are not required to support globbing in order to be
+- compliant. However, many FTP servers do support globbing as a
+- measure of convenience for FTP clients and users.
+-
+- In order to search for and match the given globbing expression,
+- the code has to search (possibly) many directories, examine
+- each contained filename, and build a list of matching files in
+- memory. Since this operation can be quite intensive, both CPU-
+- and memory-wise, we limit the search to only one directory
+- non-recursively, as LIST does.
+- """
+- # return STATus information about ftpd
+- if not line:
+- s = []
+- s.append('Connected to: %s:%s' %self.socket.getsockname()[:2])
+- if self.authenticated:
+- s.append('Logged in as: %s' %self.username)
+- else:
+- if not self.username:
+- s.append("Waiting for username.")
+- else:
+- s.append("Waiting for password.")
+- if self.current_type == 'a':
+- type = 'ASCII'
+- else:
+- type = 'Binary'
+- s.append("TYPE: %s; STRUcture: File; MODE: Stream" %type)
+- if self.data_server:
+- s.append('Passive data channel waiting for connection.')
+- elif self.data_channel:
+- bytes_sent = self.data_channel.tot_bytes_sent
+- bytes_recv = self.data_channel.tot_bytes_received
+- s.append('Data connection open:')
+- s.append('Total bytes sent: %s' %bytes_sent)
+- s.append('Total bytes received: %s' %bytes_recv)
+- else:
+- s.append('Data connection closed.')
+-
+- self.push('211-FTP server status:\r\n')
+- self.push(''.join([' %s\r\n' %item for item in s]))
+- self.respond('211 End of status.')
+- # return directory LISTing over the command channel
+- else:
+- datacr = None
+- try:
+- datacr = self.fs.get_cr(line)
+- iterator = self.try_as_current_user(self.fs.get_stat_dir, (line, datacr), line=line)
+- except FTPExceptionSent:
+- pass
+- else:
+- self.push('213-Status of "%s":\r\n' %self.fs.ftpnorm(line))
+- self.push_with_producer(BufferedIteratorProducer(iterator))
+- self.respond('213 End of status.')
+- self.fs.close_cr(datacr)
+-
+- def ftp_FEAT(self, line):
+- """List all new features supported as defined in RFC-2398."""
+- features = ['EPRT','EPSV','MDTM','MLSD','REST STREAM','SIZE','TVFS']
+- s = ''
+- for fact in self.available_facts:
+- if fact in self.current_facts:
+- s += fact + '*;'
+- else:
+- s += fact + ';'
+- features.append('MLST ' + s)
+- features.sort()
+- self.push("211-Features supported:\r\n")
+- self.push("".join([" %s\r\n" %x for x in features]))
+- self.respond('211 End FEAT.')
+-
+- def ftp_OPTS(self, line):
+- """Specify options for FTP commands as specified in RFC-2389."""
+- try:
+- assert (not line.count(' ') > 1), 'Invalid number of arguments'
+- if ' ' in line:
+- cmd, arg = line.split(' ')
+- assert (';' in arg), 'Invalid argument'
+- else:
+- cmd, arg = line, ''
+- # actually the only command able to accept options is MLST
+- assert (cmd.upper() == 'MLST'), 'Unsupported command "%s"' %cmd
+- except AssertionError, err:
+- self.respond('501 %s.' %err)
+- else:
+- facts = [x.lower() for x in arg.split(';')]
+- self.current_facts = [x for x in facts if x in self.available_facts]
+- f = ''.join([x + ';' for x in self.current_facts])
+- self.respond('200 MLST OPTS ' + f)
+-
+- def ftp_NOOP(self, line):
+- """Do nothing."""
+- self.respond("200 I successfully done nothin'.")
+-
+- def ftp_SYST(self, line):
+- """Return system type (always returns UNIX type: L8)."""
+- # This command is used to find out the type of operating system
+- # at the server. The reply shall have as its first word one of
+- # the system names listed in RFC-943.
+- # Since that we always return a "/bin/ls -lA"-like output on
+- # LIST we prefer to respond as if we would on Unix in any case.
+- self.respond("215 UNIX Type: L8")
+-
+- def ftp_ALLO(self, line):
+- """Allocate bytes for storage (obsolete)."""
+- # obsolete (always respond with 202)
+- self.respond("202 No storage allocation necessary.")
+-
+- def ftp_HELP(self, line):
+- """Return help text to the client."""
+- if line:
+- if line.upper() in proto_cmds:
+- self.respond("214 %s" %proto_cmds[line.upper()])
+- else:
+- self.respond("501 Unrecognized command.")
+- else:
+- # provide a compact list of recognized commands
+- def formatted_help():
+- cmds = []
+- keys = proto_cmds.keys()
+- keys.sort()
+- while keys:
+- elems = tuple((keys[0:8]))
+- cmds.append(' %-6s' * len(elems) %elems + '\r\n')
+- del keys[0:8]
+- return ''.join(cmds)
+-
+- self.push("214-The following commands are recognized:\r\n")
+- self.push(formatted_help())
+- self.respond("214 Help command successful.")
+-
+-
+- # --- support for deprecated cmds
+-
+- # RFC-1123 requires that the server treat XCUP, XCWD, XMKD, XPWD
+- # and XRMD commands as synonyms for CDUP, CWD, MKD, LIST and RMD.
+- # Such commands are obsoleted but some ftp clients (e.g. Windows
+- # ftp.exe) still use them.
+-
+- def ftp_XCUP(self, line):
+- """Change to the parent directory. Synonym for CDUP. Deprecated."""
+- self.ftp_CDUP(line)
+-
+- def ftp_XCWD(self, line):
+- """Change the current working directory. Synonym for CWD. Deprecated."""
+- self.ftp_CWD(line)
+-
+- def ftp_XMKD(self, line):
+- """Create the specified directory. Synonym for MKD. Deprecated."""
+- self.ftp_MKD(line)
+-
+- def ftp_XPWD(self, line):
+- """Return the current working directory. Synonym for PWD. Deprecated."""
+- self.ftp_PWD(line)
+-
+- def ftp_XRMD(self, line):
+- """Remove the specified directory. Synonym for RMD. Deprecated."""
+- self.ftp_RMD(line)
+-
+-
+-class FTPServer(asyncore.dispatcher):
+- """This class is an asyncore.disptacher subclass. It creates a FTP
+- socket listening on <address>, dispatching the requests to a <handler>
+- (typically FTPHandler class).
+-
+- Depending on the type of address specified IPv4 or IPv6 connections
+- (or both, depending from the underlying system) will be accepted.
+-
+- All relevant session information is stored in class attributes
+- described below.
+- Overriding them is strongly recommended to avoid running out of
+- file descriptors (DoS)!
+-
+- - (int) max_cons:
+- number of maximum simultaneous connections accepted (defaults
+- to 0 == unlimited).
+-
+- - (int) max_cons_per_ip:
+- number of maximum connections accepted for the same IP address
+- (defaults to 0 == unlimited).
+- """
+-
+- max_cons = 0
+- max_cons_per_ip = 0
+-
+- def __init__(self, address, handler):
+- """Initiate the FTP server opening listening on address.
+-
+- - (tuple) address: the host:port pair on which the command
+- channel will listen.
+-
+- - (classobj) handler: the handler class to use.
+- """
+- asyncore.dispatcher.__init__(self)
+- self.handler = handler
+- self.ip_map = []
+- host, port = address
+-
+- # AF_INET or AF_INET6 socket
+- # Get the correct address family for our host (allows IPv6 addresses)
+- try:
+- info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
+- socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
+- except socket.gaierror:
+- # Probably a DNS issue. Assume IPv4.
+- self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+- self.set_reuse_addr()
+- self.bind((host, port))
+- else:
+- for res in info:
+- af, socktype, proto, canonname, sa = res
+- try:
+- self.create_socket(af, socktype)
+- self.set_reuse_addr()
+- self.bind(sa)
+- except socket.error, msg:
+- if self.socket:
+- self.socket.close()
+- self.socket = None
+- continue
+- break
+- if not self.socket:
+- raise socket.error, msg
+- self.listen(5)
+-
+- def set_reuse_addr(self):
+- # Overridden for convenience. Avoid to reuse address on Windows.
+- if (os.name in ('nt', 'ce')) or (sys.platform == 'cygwin'):
+- return
+- asyncore.dispatcher.set_reuse_addr(self)
+-
+- def serve_forever(self, **kwargs):
+- """A wrap around asyncore.loop(); starts the asyncore polling
+- loop.
+-
+- The keyword arguments in kwargs are the same expected by
+- asyncore.loop() function: timeout, use_poll, map and count.
+- """
+- if not 'count' in kwargs:
+- log("Serving FTP on %s:%s" %self.socket.getsockname()[:2])
+-
+- # backward compatibility for python < 2.4
+- if not hasattr(self, '_map'):
+- if not 'map' in kwargs:
+- map = asyncore.socket_map
+- else:
+- map = kwargs['map']
+- self._map = self.handler._map = map
+-
+- try:
+- # FIX #16, #26
+- # use_poll specifies whether to use select module's poll()
+- # with asyncore or whether to use asyncore's own poll()
+- # method Python versions < 2.4 need use_poll set to False
+- # This breaks on OS X systems if use_poll is set to True.
+- # All systems seem to work fine with it set to False
+- # (tested on Linux, Windows, and OS X platforms)
+- if kwargs:
+- asyncore.loop(**kwargs)
+- else:
+- asyncore.loop(timeout=1.0, use_poll=False)
+- except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+- log("Shutting down FTPd.")
+- self.close_all()
+-
+- def handle_accept(self):
+- """Called when remote client initiates a connection."""
+- sock_obj, addr = self.accept()
+- log("[]%s:%s Connected." %addr[:2])
+-
+- handler = self.handler(sock_obj, self)
+- ip = addr[0]
+- self.ip_map.append(ip)
+-
+- # For performance and security reasons we should always set a
+- # limit for the number of file descriptors that socket_map
+- # should contain. When we're running out of such limit we'll
+- # use the last available channel for sending a 421 response
+- # to the client before disconnecting it.
+- if self.max_cons:
+- if len(self._map) > self.max_cons:
+- handler.handle_max_cons()
+- return
+-
+- # accept only a limited number of connections from the same
+- # source address.
+- if self.max_cons_per_ip:
+- if self.ip_map.count(ip) > self.max_cons_per_ip:
+- handler.handle_max_cons_per_ip()
+- return
+-
+- handler.handle()
+-
+- def writable(self):
+- return 0
+-
+- def handle_error(self):
+- """Called to handle any uncaught exceptions."""
+- try:
+- raise
+- except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+- raise
+- logerror(traceback.format_exc())
+- self.close()
+-
+- def close_all(self, map=None, ignore_all=False):
+- """Stop serving; close all existent connections disconnecting
+- clients.
+-
+- - (dict) map:
+- A dictionary whose items are the channels to close.
+- If map is omitted, the default asyncore.socket_map is used.
+-
+- - (bool) ignore_all:
+- having it set to False results in raising exception in case
+- of unexpected errors.
+-
+- Implementation note:
+-
+- Instead of using the current asyncore.close_all() function
+- which only close sockets, we iterate over all existent channels
+- calling close() method for each one of them, avoiding memory
+- leaks.
+-
+- This is how asyncore.close_all() function should work in
+- Python 2.6.
+- """
+- if map is None:
+- map = self._map
+- for x in map.values():
+- try:
+- x.close()
+- except OSError, x:
+- if x[0] == errno.EBADF:
+- pass
+- elif not ignore_all:
+- raise
+- except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
+- raise
+- except:
+- if not ignore_all:
+- raise
+- map.clear()
+-
+-
+-def test():
+- # cmd line usage (provide a read-only anonymous ftp server):
+- # python -m pyftpdlib.FTPServer
+- authorizer = DummyAuthorizer()
+- authorizer.add_anonymous(os.getcwd(), perm='elradfmw')
+- FTPHandler.authorizer = authorizer
+- address = ('', 8021)
+- ftpd = FTPServer(address, FTPHandler)
+- ftpd.serve_forever()
+-
+-if __name__ == '__main__':
+- test()
+-
+-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+diff --git a/openerp/addons/document_ftp/wizard/ftp_browse.py b/openerp/addons/document_ftp/wizard/ftp_browse.py
+index b1120bc..ccc49d6 100644
+--- a/openerp/addons/document_ftp/wizard/ftp_browse.py
++++ b/openerp/addons/document_ftp/wizard/ftp_browse.py
+@@ -21,7 +21,7 @@
+
+ from osv import osv, fields
+ # from tools.translate import _
+-from .. import ftpserver
++import ftpserver
+
+ class document_ftp_browse(osv.osv_memory):
+ _name = 'document.ftp.browse'
+@@ -46,7 +46,7 @@ class document_ftp_browse(osv.osv_memory):
+ if url[-1] == '/':
+ url = url[:-1]
+ else:
+- url = '%s:%s' %(ftpserver.HOST, ftpserver.PORT)
++ url = '%s:%s' % ftpserver.address
+ res['url'] = 'ftp://%s@%s'%(current_user.login, url)
+ return res
+
diff --git a/openerp.service b/openerp.service
new file mode 100644
index 0000000..5d2a3d7
--- /dev/null
+++ b/openerp.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=OpenERP Server
+
+# Remove postgresql.service if db is running on another host.
+After=syslog.target network.target postgresql.service
+
+[Service]
+Type=simple
+User=openerp
+ExecStartPre=run-parts /etc/openerp/start.d
+ExecStart=/usr/bin/openerp-server \
+ -c /etc/openerp/openerp-server.conf \
+ --logfile=/var/log/openerp/openerp-server.log
+
+[Install]
+WantedBy=multi-user.target
diff --git a/openerp.spec b/openerp.spec
new file mode 100644
index 0000000..92e48af
--- /dev/null
+++ b/openerp.spec
@@ -0,0 +1,227 @@
+%global oe_rel -20120505-233516
+ # tarball's source directory,
+%global oe_src_dir openerp-%{version}%{oe_rel}
+
+Name: openerp
+Version: 6.1
+Release: 1%{?dist}
+ # See LICENSING
+License: AGPLv3 and GPLv3 and BSD and LGPLv2+
+Group: System Environment/Daemons
+Summary: Business Applications Server
+URL: http://www.openerp.com
+BuildArch: noarch
+#Source0: http://nightly.openerp.com/6.1/releases/openerp-%{version}%{?oe_rel}.tar.gz
+Source0: http://nightly.openerp.com/6.1/nightly/src/openerp-%{version}%{oe_rel}.tar.gz
+Source1: openerp.service
+Source2: openerp-gen-cert
+Source3: README.fedora
+Source4: LICENSING
+ # https://bugs.launchpad.net/bugs/993408
+Patch0: openerp-fsf-fix.patch
+ # Patch is not usable upstream.
+Patch1: openerp-unbundle-pyftpdlib.patch
+ # Relicensed by copyright owner, see
+ # https://bugzilla.redhat.com/show_bug.cgi?id=693425#c57
+Patch10: openerp-server-relicense-dict_tools-to-LGPL2.1.patch
+
+Requires: ghostscript
+Requires: postgresql-python
+Requires: pychart
+Requires: pydot
+Requires: pyftpdlib
+Requires: pyparsing
+Requires: pywebdav
+Requires: python-babel
+Requires: python-dateutil
+# See BZ 817268
+# Requires: python-faces
+Requires: python-feedparser
+Requires: python-gdata
+Requires: python-imaging
+Requires: python-ldap
+Requires: python-lxml
+Requires: python-mako
+Requires: python-openid
+Requires: python-psycopg2
+Requires: python-reportlab
+Requires: python-simplejson
+# https://fedorahosted.org/fpc/ticket/171
+#Requires: python-trml2pdf
+Requires: python-vatnumber
+Requires: python-vobject
+Requires: python-werkzeug
+Requires: python-xlwt
+Requires: python-ZSI
+Requires: pytz
+Requires: PyXML
+Requires: PyYAML
+
+BuildRequires: desktop-file-utils
+BuildRequires: libxslt-python
+BuildRequires: pygtk2-devel
+BuildRequires: python
+BuildRequires: python-babel
+BuildRequires: python-setuptools
+BuildRequires: python2-devel
+BuildRequires: systemd-units
+
+Requires(pre): shadow-utils
+Requires(post): systemd-units
+Requires(preun): systemd-units
+Requires(postun): systemd-units
+
+
+%description
+Server package for OpenERP.
+
+OpenERP is a free Enterprise Resource Planning and Customer Relationship
+Management software. It is mainly developed to meet changing needs.
+
+The main functional features are: CRM & SRM, analytic and financial accounting,
+double-entry stock management, sales and purchases management, tasks automation,
+help desk, marketing campaign, ... and vertical modules for very specific
+businesses.
+
+Technical features include a distributed server, flexible work-flows, an object
+database, dynamic GUIs, custom reports, NET-RPC and XML-RPC interfaces, ...
+
+For more information, please visit: http://www.openerp.com
+
+This server package contains the core (server) of OpenERP system and all
+additions of the official distribution. You may need the GTK client to connect
+to this server, or the web-client, which serves to HTML browsers. You can
+also find more additions (aka. modules) for this ERP system in:
+http://www.openerp.com/ or http://apps.openerp.com/
+
+
+%prep
+%setup -q -n %{oe_src_dir}
+%patch0 -p1
+%patch1 -p1
+%patch10 -p1
+
+# https://bugs.launchpad.net/bugs/993414
+find . -name \*.py -a -perm 644 | \
+ xargs sed -i -e '\;/usr/bin/env;d' -e '\;/usr/bin/python;d'
+find . -name \*.html -o -name \*yml -o -name \*.js -o -name \*.po \
+ -o -name \*.css -o -iname readme* -o -name \*.csv \
+ -o -name account_asset_change_duration.py \
+ -o -name base_quality_interrogation.py |
+ xargs chmod 644
+chmod 644 $( find openerp/addons/account_asset -name \*.py )
+chmod 644 openerp/addons/l10n_ch/test/test*.v11
+
+find . -name \*.html | xargs sed -i 's/\r//'
+sed -i 's/\r//' openerp/addons/l10n_ch/test/test*.v11
+sed -i 's/\r//' openerp/addons/account_asset/security/ir.model.access.csv
+
+find . -name .hg_* | xargs rm -f
+rm -f openerp/addons/.bzrignore
+
+# Empty and of no use.
+rm openerp/addons/base_report_designer/openerp_sxw2rml/office.dtd
+
+# Prebuilt binaries, bundled libs and foreign packaging
+rm -rf win32 debian setup.nsi
+rm -rf bin/pychart
+rm -rf openerp/addons/outlook/plugin/openerp-outlook-addin.exe \
+ openerp/addons/thunderbird openerp/addons/plugin_thunderbird
+
+# Client-side plugin, until we can build it under Fedora.
+rm -rf openerp/addons/outlook/plugin/
+
+# Wiki contains other licenses and bundled modules, skipped
+# for now. Web modules are anyway better packaged in the web-client.
+rm -rf openerp/addons/wiki/web openerp/addons/wiki/static\
+ openerp/addons/web_graph openerp/addons/web_calendar
+
+
+%build
+NO_INSTALL_REQS=1 python ./setup.py --quiet build
+
+
+%install
+python ./setup.py --quiet install --root=%{buildroot}
+sed -i "s|%{buildroot}||" %{buildroot}%{_bindir}/openerp-server
+rm %{buildroot}/usr/openerp/.apidoc
+rm -r %{buildroot}%{python_sitelib}/openerp
+mv %{buildroot}/usr/openerp %{buildroot}%{python_sitelib}
+rm -r %{buildroot}/usr/localedata
+
+install -m 644 -D install/openerp-server.conf \
+ %{buildroot}%{_sysconfdir}/openerp/openerp-server.conf
+install -d %{buildroot}%{_sysconfdir}/openerp/start.d
+install -d %{buildroot}%{_sysconfdir}/openerp/stop.d
+
+install -D -m 755 %SOURCE2 %{buildroot}%{_sbindir}/openerp-gen-cert
+install -D -m 644 %SOURCE1 %{buildroot}%{_unitdir}/openerp.service
+install -m 644 %{SOURCE3} %{SOURCE4} .
+
+install -m 644 openerp/import_xml.rng %{buildroot}%{python_sitelib}/openerp
+install -d %{buildroot}%{python_sitelib}/openerp/addons/base/security
+install -m 644 openerp/addons/base/security/* \
+ %{buildroot}%{python_sitelib}/openerp/addons/base/security
+
+install -d %{buildroot}/%{_datadir}/openerp/pixmaps
+install -m 644 -D install/*.png %{buildroot}/%{_datadir}/openerp/pixmaps
+
+install -D -m 644 install/openerp-server.1 \
+ %{buildroot}/%{_mandir}/man1/openerp-server.1
+install -D -m 644 install/openerp_serverrc.5 \
+ %{buildroot}/%{_mandir}/man5/openerp-serverrc.5
+
+install -d %{buildroot}%{_localstatedir}/spool/openerp
+install -d %{buildroot}%{_localstatedir}/run/openerp
+
+
+%files
+%doc LICENSE README README.fedora LICENSING
+%{_bindir}/*
+%{_sbindir}/*
+%{_unitdir}/*
+%{_mandir}/man1/*
+%{_mandir}/man5/*
+%{_datadir}/openerp
+%{python_sitelib}/openerp
+%{python_sitelib}/openerp-%{version}*-py%{python_version}.egg-info
+%attr(0755,openerp,openerp) %{_localstatedir}/run/openerp
+
+%attr(0755,root,openerp) %dir %{_sysconfdir}/openerp
+%dir %{_sysconfdir}/openerp/start.d
+%dir %{_sysconfdir}/openerp/stop.d
+%attr(0660,root,openerp) %config(noreplace) %{_sysconfdir}/openerp/openerp-server.conf
+
+
+%pre
+getent group openerp >/dev/null || groupadd -r openerp
+getent passwd openerp >/dev/null || \
+ useradd -r -g openerp -d /var/run/openerp -s /sbin/nologin \
+ -c "OpenERP Server" openerp
+usermod -d /var/run/openerp openerp >/dev/null 2>&1 || :
+
+%post
+if [ $1 -eq 1 ] ; then
+ # Initial installation
+ /bin/systemctl daemon-reload >/dev/null 2>&1 || :
+fi
+
+%preun
+if [ $1 -eq 0 ] ; then
+ # Package removal, not upgrade
+ /bin/systemctl --no-reload disable \
+ openerp.service > /dev/null 2>&1 || :
+ /bin/systemctl stop openerp.service > /dev/null 2>&1 || :
+fi
+
+%postun
+/bin/systemctl daemon-reload >/dev/null 2>&1 || :
+if [ $1 -ge 1 ] ; then
+ # Package upgrade, not uninstall
+ /bin/systemctl try-restart openrep-server.service >/dev/null 2>&1 || :
+fi
+
+
+%changelog
+* Sat Apr 26 2012 Alec Leamas <leamas at nowhere.com> 6.1-1
+ - Initial package, based on 693425
diff --git a/sources b/sources
index e69de29..d1e9806 100644
--- a/sources
+++ b/sources
@@ -0,0 +1 @@
+572987da13d71942c252339b38d5d575 openerp-6.1-20120505-233516.tar.gz
More information about the scm-commits
mailing list