From aaa437bf26c51cf3cebd0771416fa218176f1ce3 Mon Sep 17 00:00:00 2001 From: Alexander Khryukin Date: Fri, 13 Sep 2013 21:09:39 +0000 Subject: [PATCH] init build --- .abf.yml | 2 + LICENSING | 102 + README.omv | 72 + openerp-fsf-fix.patch | 819 +++++ openerp-gen-cert | 53 + ...rver-relicense-dict_tools-to-LGPL2.1.patch | 25 + openerp-unbundle-pyftpdlib.patch | 3075 +++++++++++++++++ openerp.service | 16 + openerp.spec | 203 ++ 9 files changed, 4367 insertions(+) create mode 100644 .abf.yml create mode 100644 LICENSING create mode 100644 README.omv create mode 100644 openerp-fsf-fix.patch create mode 100755 openerp-gen-cert create mode 100644 openerp-server-relicense-dict_tools-to-LGPL2.1.patch create mode 100644 openerp-unbundle-pyftpdlib.patch create mode 100644 openerp.service create mode 100644 openerp.spec diff --git a/.abf.yml b/.abf.yml new file mode 100644 index 0000000..97fd237 --- /dev/null +++ b/.abf.yml @@ -0,0 +1,2 @@ +sources: + openerp-6.1-20130408-234645.tar.gz: 13a0a1f3e199601bf4b98b361017f36f8c1bd8d9 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.omv b/README.omv new file mode 100644 index 0000000..9c7ccf2 --- /dev/null +++ b/README.omv @@ -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..6564ca2 --- /dev/null +++ b/openerp-fsf-fix.patch @@ -0,0 +1,819 @@ +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 26ec91c..143d8c3 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 a8dc71f..7f6443d 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' +-# Hacked by Fabien Pinckaers (C) 2008 +-# +-# 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' " +-__web__ = 'http://code.google.com/p/pyftpdlib/' +- +- +-proto_cmds = { +- 'ABOR': 'Syntax: ABOR (abort transfer).', +- 'ALLO': 'Syntax: ALLO bytes (obsolete; allocate storage).', +- 'APPE': 'Syntax: APPE file-name (append data to an existent file).', +- 'CDUP': 'Syntax: CDUP (go to parent directory).', +- 'CWD' : 'Syntax: CWD dir-name (change current working directory).', +- 'DELE': 'Syntax: DELE file-name (delete file).', +- 'EPRT': 'Syntax: EPRT |proto|ip|port| (set server in extended active mode).', +- 'EPSV': 'Syntax: EPSV [ proto/"ALL"] (set server in extended passive mode).', +- 'FEAT': 'Syntax: FEAT (list all new features supported).', +- 'HELP': 'Syntax: HELP [ cmd] (show help).', +- 'LIST': 'Syntax: LIST [ path-name] (list files).', +- 'MDTM': 'Syntax: MDTM file-name (get last modification time).', +- 'MLSD': 'Syntax: MLSD [ dir-name] (list files in a machine-processable form)', +- 'MLST': 'Syntax: MLST [ path-name] (show a path in a machine-processable form)', +- 'MODE': 'Syntax: MODE mode (obsolete; set data transfer mode).', +- 'MKD' : 'Syntax: MDK dir-name (create directory).', +- 'NLST': 'Syntax: NLST [ path-name] (list files in a compact form).', +- 'NOOP': 'Syntax: NOOP (just do nothing).', +- 'OPTS': 'Syntax: OPTS ftp-command [ option] (specify options for FTP commands)', +- 'PASS': 'Syntax: PASS user-name (set user password).', +- 'PASV': 'Syntax: PASV (set server in passive mode).', +- 'PORT': 'Syntax: PORT 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 marker (restart file position).', +- 'RETR': 'Syntax: RETR file-name (retrieve a file).', +- 'RMD' : 'Syntax: RMD dir-name (remove directory).', +- 'RNFR': 'Syntax: RNFR file-name (file renaming (source name)).', +- 'RNTO': 'Syntax: RNTO file-name (file renaming (destination name)).', +- 'SIZE': 'Syntax: HELP file-name (get file size).', +- 'STAT': 'Syntax: STAT [ path name] (status information [list files]).', +- 'STOR': 'Syntax: STOR file-name (store a file).', +- 'STOU': 'Syntax: STOU [ file-name] (store a file with a unique name).', +- 'STRU': 'Syntax: STRU type (obsolete; set file structure).', +- 'SYST': 'Syntax: SYST (get operating system type).', +- 'TYPE': 'Syntax: TYPE [A | I] (set transfer type).', +- 'USER': 'Syntax: USER user-name (set username).', +- 'XCUP': 'Syntax: XCUP (obsolete; go to parent directory).', +- 'XCWD': 'Syntax: XCWD dir-name (obsolete; change current directory).', +- 'XMKD': 'Syntax: XMDK dir-name (obsolete; create directory).', +- 'XPWD': 'Syntax: XPWD (obsolete; get current dir).', +- 'XRMD': 'Syntax: XRMD 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: +- # # protoipport +- # ...where is an arbitrary delimiter character (usually "|") and +- # 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
, dispatching the requests to a +- (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..638f6b8 --- /dev/null +++ b/openerp.spec @@ -0,0 +1,203 @@ +%define oe_rel 20130408-234645 + # tarball's source directory, +%define oe_src_dir openerp-%{version}-%{oe_rel} + +Name: openerp +Version: 6.1 +Release: 1 + # See LICENSING +License: AGPLv3 and GPLv3 and BSD and LGPLv2+ +Group: Networking/WWW +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.omv +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: PyYAML + +Obsoletes: openerp-mpl <= %{version} +Provides: openerp6 + +BuildRequires: desktop-file-utils +BuildRequires: python-libxslt +BuildRequires: pkgconfig(pygtk-2.0) +BuildRequires: python-openbabel +BuildRequires: python-setuptools + +BuildRequires: python-devel +BuildRequires: systemd-units + +Requires(pre): rpm-helper +Requires(post): rpm-helper +Requires(preun): rpm-helper +Requires(postun): rpm-helper + + +%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 input_complete \ + -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/ + +%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} +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 + +rm -f %{buildroot}%{python_sitelib}/openerp/addons/wiki/controllers/wiki.py +rm -f %{buildroot}%{python_sitelib}/openerp/addons/wiki/widgets/wiki.py* + +%pre +%_pre_useradd %{name} /var/run/openerp /bin/false +%_pre_groupadd %{name} + +%post +%_post_service %{name} + +%preun +%_preun_service %{name} + +%postun +%_postun_userdel %{name} +%_postun_groupdel %{name} + +%files +%doc LICENSE README README.omv 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