From 4ea4d3abc25728288c7e405d1b57d292d4eb29fe Mon Sep 17 00:00:00 2001
From: Thomas Gambier <thomas.gambier@nexedi.com>
Date: Thu, 17 Jan 2019 14:43:03 +0100
Subject: [PATCH] Pure-FTPd SR

This SR has been developed for a demo. The purpose is to have a running pureftpd server which will call a script "/opt/pureftpd/upload_script" each time a file is uploaded. Pureftpd is used because it is already used in the current working environment of our partner and they don't want to change for now.

The script /opt/pureftpd/upload_script" is left out of the SR on purpose because it will be written by people outside Nexedi at first and contains sensitive information. Also, it is on purpose in /opt directory of the machine because this SR will be deployed on only one machine for now and the script will be shared by all pureftpd instances.

/reviewed-on https://lab.nexedi.com/nexedi/slapos/merge_requests/503
---
 component/pure-ftpd/buildout.cfg              |   9 +-
 software/pureftpd/README.md                   |   8 ++
 software/pureftpd/buildout.hash.cfg           |  18 +++
 software/pureftpd/instance-input-schema.json  |  12 ++
 software/pureftpd/instance-output-schema.json |  28 +++++
 software/pureftpd/instance.cfg.in             | 118 ++++++++++++++++++
 software/pureftpd/software.cfg                |  37 ++++++
 software/pureftpd/software.cfg.json           |  14 +++
 8 files changed, 241 insertions(+), 3 deletions(-)
 create mode 100644 software/pureftpd/README.md
 create mode 100644 software/pureftpd/buildout.hash.cfg
 create mode 100644 software/pureftpd/instance-input-schema.json
 create mode 100644 software/pureftpd/instance-output-schema.json
 create mode 100644 software/pureftpd/instance.cfg.in
 create mode 100644 software/pureftpd/software.cfg
 create mode 100644 software/pureftpd/software.cfg.json

diff --git a/component/pure-ftpd/buildout.cfg b/component/pure-ftpd/buildout.cfg
index 891440658..0f4c1ed6d 100644
--- a/component/pure-ftpd/buildout.cfg
+++ b/component/pure-ftpd/buildout.cfg
@@ -7,8 +7,11 @@ url = https://download.pureftpd.org/pub/pure-ftpd/releases/pure-ftpd-1.0.46.tar.
 md5sum = efce5529c1f0a39dafdd532c619503f1 
 
 # See https://download.pureftpd.org/pub/pure-ftpd/doc/README for more configurations
+# We need the trick about UPLOAD_PIPE_FILE and UPLOAD_PIPE_LOCK so that the files are created inside the $CWD/var/run
+# WARNING: this means that both pure-ftpd and pure-uploadscript binaries must be launched in $HOME !
 configure-options = 
  --with-uploadscript
-
-#environment =
-#    Probably it is missing dependencies to be set here. 
+ --with-puredb
+ --with-nonroot
+environment=
+  CFLAGS=-DUPLOAD_PIPE_FILE='"/proc/self/cwd/var/run/pure-ftpd.upload.pipe"' -DUPLOAD_PIPE_LOCK='"/proc/self/cwd/var/run/pure-ftpd.upload.lock"'
diff --git a/software/pureftpd/README.md b/software/pureftpd/README.md
new file mode 100644
index 000000000..35651013e
--- /dev/null
+++ b/software/pureftpd/README.md
@@ -0,0 +1,8 @@
+Pureftpd with upload script
+
+https://www.pureftpd.org/project/pure-ftpd
+
+# Features
+
+ * After each upload, call the script /opt/pureftpd/upload_script
+
diff --git a/software/pureftpd/buildout.hash.cfg b/software/pureftpd/buildout.hash.cfg
new file mode 100644
index 000000000..8830bec43
--- /dev/null
+++ b/software/pureftpd/buildout.hash.cfg
@@ -0,0 +1,18 @@
+# THIS IS NOT A BUILDOUT FILE, despite purposedly using a compatible syntax.
+# The only allowed lines here are (regexes):
+# - "^#" comments, copied verbatim
+# - "^[" section beginings, copied verbatim
+# - lines containing an "=" sign which must fit in the following categorie.
+#   - "^\s*filename\s*=\s*path\s*$" where "path" is relative to this file
+#     Copied verbatim.
+#   - "^\s*hashtype\s*=.*" where "hashtype" is one of the values supported
+#     by the re-generation script.
+#     Re-generated.
+# - other lines are copied verbatim
+# Substitution (${...:...}), extension ([buildout] extends = ...) and
+# section inheritance (< = ...) are NOT supported (but you should really
+# not need these here).
+
+[instance-profile]
+filename = instance.cfg.in
+md5sum = c352c6f11b7a00dca0a544f7ecddeb52
diff --git a/software/pureftpd/instance-input-schema.json b/software/pureftpd/instance-input-schema.json
new file mode 100644
index 000000000..6df5507f3
--- /dev/null
+++ b/software/pureftpd/instance-input-schema.json
@@ -0,0 +1,12 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "description": "Parameters to instantiate Pure-FTPd",
+  "additionalProperties": false,
+  "properties": {
+    "port": {
+      "title": "FTP port",
+      "description": "Port number to listen to - default to 8021",
+      "type": "number"
+    }
+  }
+}
diff --git a/software/pureftpd/instance-output-schema.json b/software/pureftpd/instance-output-schema.json
new file mode 100644
index 000000000..657398f94
--- /dev/null
+++ b/software/pureftpd/instance-output-schema.json
@@ -0,0 +1,28 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "description": "Values returned by Pure-FTPd instantiation",
+  "additionalProperties": false,
+  "properties": {
+    "url": {
+      "description": "URL of the FTP service",
+      "pattern": "^ftp://",
+      "type": "string"
+    },
+    "username": {
+      "description": "Default username",
+      "type": "string",
+      "optional": true
+    },
+    "password": {
+      "description": "Password for default username",
+      "type": "string",
+      "optional": true
+    },
+    "videos": {
+      "description": "Location of the videos",
+      "type": "string",
+      "optional": true
+    }
+  },
+  "type": "object"
+}
diff --git a/software/pureftpd/instance.cfg.in b/software/pureftpd/instance.cfg.in
new file mode 100644
index 000000000..035f3b545
--- /dev/null
+++ b/software/pureftpd/instance.cfg.in
@@ -0,0 +1,118 @@
+[buildout]
+extends =
+  {{ monitor_rendered }}
+
+parts =
+  promises
+  publish-connection-parameter
+  monitor-base
+eggs-directory = {{ buildout['eggs-directory'] }}
+develop-eggs-directory = {{ buildout['develop-eggs-directory'] }}
+
+[slap-configuration]
+recipe = slapos.cookbook:slapconfiguration
+computer = ${slap-connection:computer-id}
+partition = ${slap-connection:partition-id}
+url = ${slap-connection:server-url}
+key = ${slap-connection:key-file}
+cert = ${slap-connection:cert-file}
+configuration.port = 8021
+
+[directory]
+recipe = slapos.cookbook:mkdirectory
+home = ${buildout:directory}
+etc = ${:home}/etc
+var = ${:home}/var
+run = ${:var}/run
+log = ${:var}/log
+srv = ${:home}/srv
+service = ${:etc}/service
+promise = ${:etc}/promise
+plugin = ${:etc}/plugin
+pureftpd-dir = ${:srv}/pureftpd/
+
+[check-port-listening-promise]
+recipe = slapos.cookbook:promise.plugin
+eggs =
+  slapos.toolbox
+output = ${directory:plugin}/${:_buildout_section_name_}
+content =
+  from slapos.promise.plugin.check_port_listening import RunPromise
+
+[pureftpd-listen-promise]
+<= check-port-listening-promise
+config-hostname = ${pureftpd:ipv6}
+config-port = ${pureftpd:ftp-port}
+
+[pureftpd-userinfo]
+recipe = slapos.cookbook:userinfo
+
+[pureftpd-password]
+recipe = slapos.cookbook:generate.password
+username = nexedi_cdn
+bytes = 12
+
+[pureftpd]
+ipv6 = ${slap-configuration:ipv6-random}
+ipv4 = ${slap-configuration:ipv4-random}
+host = ${:ipv6}
+ftp-port = ${slap-configuration:configuration.port}
+url = ftp://[${:host}]:${:ftp-port}
+data-dir = ${directory:pureftpd-dir}
+pid-file=${directory:run}/pureftpd.pid
+auth-user-file=${auth-user-file:output}
+
+recipe =slapos.recipe.template:jinja2
+# WARNING pure-uploadscript must be launched AFTER pure-ftpd so keep them in the same wrapper
+# and make sure they are both killed if one of them is killed.
+template = inline:
+  #!{{ bash_location }}/bin/bash
+  {{ pureftpd_bin }} --uploadscript --customerproof --bind ${:host},${:ftp-port} --login puredb:${:auth-user-file} --pidfile ${:pid-file} &
+  while ! [ -p ${directory:run}/pure-ftpd.upload.pipe ]
+  do
+    sleep 1
+  done
+  {{ pureuploadscript_bin }} -r /opt/pureftpd/upload_script &
+  wait -n
+  kill 0
+rendered = ${directory:service}/pureftpd
+wrapper-path = ${:rendered}
+
+[pure-pw]
+# command line to add a user, invoke with:
+#   pure-pw useradd bob
+# it will prompt for password twice
+recipe = slapos.cookbook:wrapper
+wrapper-path =${buildout:bin-directory}/${:_buildout_section_name_}
+command-line =
+  {{ purepw_bin }} useradd ${pureftpd-password:username} -d ${pureftpd:data-dir} -u ${pureftpd-userinfo:pw-uid} -g ${pureftpd-userinfo:gr-gid} -f ${auth-user-file:passwd-file} "$@"
+
+
+[auth-user-file]
+recipe = plone.recipe.command
+passwd-file = ${directory:etc}/pureftpd.passwd
+output = ${directory:etc}/pureftpd.pdb
+command = 
+  if [ -f ${:passwd-file} ] && {{ purepw_bin }} show ${pureftpd-password:username} -f ${:passwd-file}
+  then
+    ( echo ${pureftpd-password:passwd} ; echo ${pureftpd-password:passwd}) | {{ purepw_bin }} passwd ${pureftpd-password:username} -f ${:passwd-file}
+  else
+    ( echo ${pureftpd-password:passwd} ; echo ${pureftpd-password:passwd}) | ${pure-pw:wrapper-path}
+  fi
+  {{ purepw_bin }} mkdb ${:output} -f ${:passwd-file}
+update-command = ${:command}
+
+
+[promises]
+recipe =
+instance-promises =
+  ${pureftpd-listen-promise:output}
+
+
+[publish-connection-parameter]
+recipe = slapos.cookbook:publish
+<= monitor-publish
+url = ${pureftpd:url}
+username = ${pureftpd-password:username}
+password = ${pureftpd-password:passwd}
+videos = To see the videos, go to http://nexedi-cdndemo-cdn-p.hexaglobe.net/ and enter the name "${pureftpd-userinfo:pw-name}-[video_name_without_extension]"
diff --git a/software/pureftpd/software.cfg b/software/pureftpd/software.cfg
new file mode 100644
index 000000000..33b9a4eba
--- /dev/null
+++ b/software/pureftpd/software.cfg
@@ -0,0 +1,37 @@
+[buildout]
+extends =
+  ../../stack/slapos.cfg
+  ../../component/pure-ftpd/buildout.cfg
+  ../../component/bash/buildout.cfg
+  buildout.hash.cfg
+  ../../stack/monitor/buildout.cfg
+
+parts =
+  slapos-cookbook
+  instance-profile
+
+
+# force to install plone.recipe.command and slapos.toolbox as it will be used during instanciation 
+[slapos-cookbook]
+eggs +=
+  plone.recipe.command
+  slapos.toolbox
+
+
+[instance-profile]
+recipe = slapos.recipe.template:jinja2
+template = ${:_profile_base_location_}/${:filename}
+rendered = ${buildout:directory}/instance.cfg
+mode = 0644
+extensions = jinja2.ext.do
+context =
+  section buildout buildout
+  key bash_location bash:location
+  raw monitor_rendered ${monitor-template:rendered}
+  raw pureftpd_bin ${pure-ftpd:location}/sbin/pure-ftpd
+  raw pureuploadscript_bin ${pure-ftpd:location}/sbin/pure-uploadscript
+  raw purepw_bin ${pure-ftpd:location}/bin/pure-pw
+
+
+[versions]
+slapos.recipe.template = 4.3
diff --git a/software/pureftpd/software.cfg.json b/software/pureftpd/software.cfg.json
new file mode 100644
index 000000000..0e4f038c5
--- /dev/null
+++ b/software/pureftpd/software.cfg.json
@@ -0,0 +1,14 @@
+{
+  "name": "Pure-FTPd",
+  "description": "Pure-FTPd as a FTP server with virtual users and uploadscript",
+  "serialisation": "json-in-xml",
+  "software-type": {
+    "default": {
+      "title": "Default",
+      "description": "Pure-FTPd, with a default user",
+      "request": "instance-input-schema.json",
+      "response": "instance-output-schema.json",
+      "index": 0
+    }
+  }
+}
-- 
2.30.9