- C 74.9%
- Tcl 13.7%
- Shell 10.2%
- Roff 0.7%
- Makefile 0.4%
| assets | self host mailing lists | |
| autosetup | infra: add boilerplate to configure and build mlmmj-webview | |
| genindex | manpages: add manpages | |
| mk | genindex: add a new binary | |
| webview | CGI: use 303 instead of generating a temporary html and refresh | |
| auto.def | Release 0.1.0 | |
| configure | infra: add boilerplate to configure and build mlmmj-webview | |
| Makefile.in | genindex: add a new binary | |
| README.md | README: make it up to date | |
MLMMJ Webview
It provides an index page for mlmmj mailing lists and handles subscribe/unsubscribe requests via a web interface.
It is composed of two parts:
- mlmmj-genindex: a static HTML generator that produces the index and per-list subscription pages from Mustache-like templates.
- mlmmj-webview: a CGI that handles the
/actionPOST endpoint for subscribe/unsubscribe requests. It validates the email format and verifies that the email domain resolves via DNS before processing the request.
Building
./configure
make
mlmmj-genindex
Generates static HTML pages from templates and the mailing list directory structure.
mlmmj-genindex -t /path/to/templates -L /path/to/lists -o /path/to/output
Options:
-t: directory containing template files (index.tpl,subscription.tpl)-L: directory containing mlmmj mailing list directories-o: output directory for generated HTML files
The generator enters Capsicum sandbox mode after opening all required directories.
Mailing list directory structure
Each mailing list is a subdirectory under the -L directory. The following
files are recognized inside each list directory:
desc: required, contains a one-line description of the listprivate: if present, the list is hidden from the indexnoarchive: if present, the archive link is not shown
Templates
Templates use Mustache-like syntax with {{variable}} for HTML-escaped output
and {{{variable}}} for raw output.
The following data is available in templates:
index.tpl
{{#mls}}...{{/mls}}: iterates over the mailing lists array{{name}}: list name (directory name){{desc}}: list description (from thedescfile){{#archives}}...{{/archives}}: conditional block, rendered if archiving is enabled
subscription.tpl
{{name}}: the mailing list name
Example index.tpl
<!DOCTYPE html>
<html>
<head><title>Mailing Lists</title></head>
<body>
<table>
<tbody>{{#mls}}
<tr>
<td><a href="/subscription/{{name}}">{{name}}</a></td>
<td>{{desc}}</td>
<td>{{#archives}}<a href="/archives/{{name}}/">archives</a>{{/archives}}</td>
</tr>
{{/mls}}</tbody>
</table>
</body>
</html>
Example subscription.tpl
<!DOCTYPE html>
<html>
<head><title>Subscription for {{name}}</title></head>
<body>
<h1>Subscription for {{name}}</h1>
<form action="/action" method="post">
<label for="email">Your email address:</label>
<input type="text" name="email" id="email" placeholder="user@example.net">
<input type="hidden" name="ml" value="{{name}}">
<input type="Submit" name="Subscribe" value="Subscribe">
<input type="Submit" name="Unsubscribe" value="Unsubscribe">
</form>
</body>
</html>
mlmmj-webview (CGI)
Handles the /action POST endpoint. It expects the following form fields:
ml: the mailing list nameemail: the email address (validated with kcgi'skvalid_emailand DNS domain check)SubscribeorUnsubscribe: the action to perform
The CGI writes a temporary file in /var/spool/mlmmj-webview/subscribe/ or
/var/spool/mlmmj-webview/unsubscribe/ containing the mailing list name and
email address. It always returns the same response regardless of success or
failure to prevent information leakage.
Configuration with nginx
Here is an example of using mlmmj-webview with nginx and fastcgi:
location /action {
gzip off;
include fastcgi_params;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SCRIPT_FILENAME /usr/local/bin/mlmmj-webview;
fastcgi_param PATH_INFO $uri;
fastcgi_param QUERY_STRING $args;
fastcgi_pass unix:/var/run/fcgiwrap/fcgiwrap.sock;
}
Processing subscriptions
The (un)subscription requests are written to temporary files in
/var/spool/mlmmj-webview/{subscribe,unsubscribe}.
A script should run on a regular basis (e.g. via crontab) to process these files. Here is an example:
#!/bin/sh
set -eu
tmpdir=/usr/local/mlmmj/tmp/
mls=/usr/local/mlmmj/lists
tml=$(mktemp -d ${tmpdir}/subscribe.XXXXX)
for f in $(find /var/spool/mlmmj-webview/subscribe -type f); do
ml=$(head -1 $f)
email=$(tail -1 $f | tr -d '[:space:]')
testemail=$(tail -1 $f | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')
rm -f $f
case $testemail in
*@domain.org)
awk -v mail="${testemail%@domain.org}" 'BEGIN { found=1 } 1ドル == mail { found=0; exit } END { exit found }' /usr/local/mlmmj/virtual && continue
;;
esac
if [ -d ${mls}/${ml} ]; then
echo $email >> ${tml}/${ml}
fi
done
for f in $(find ${tml} -type f); do
for m in $(sort -u ${f}); do
/usr/local/bin/mlmmj-sub -L ${mls}/${f##*/} -a $m -C
done
done
rm -rf "${tml}"
tml=$(mktemp -d ${tmpdir}/unsubscribe.XXXXX)
for f in $(find /var/spool/mlmmj-webview/unsubscribe -type f); do
ml=$(head -1 $f)
email=$(tail -1 $f | tr -d '[:space:]')
testemail=$(tail -1 $f | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')
rm -f $f
case $testemail in
*@domain.org)
awk -v mail="${testemail%@domain.org}" 'BEGIN { found=1 } 1ドル == mail { found=0; exit } END { exit found }' /usr/local/mlmmj/virtual && continue
;;
esac
if [ -d ${mls}/${ml} ]; then
echo $email >> ${tml}/${ml}
fi
done
for f in $(find ${tml} -type f); do
for m in $(sort -u ${f}); do
/usr/local/bin/mlmmj-unsub -L ${mls}/${f##*/} -a $m -C
done
done
rm -rf "${tml}"
A crontab entry could be added in /usr/local/etc/cron.d/mlmmj-webview:
@120 mlmmj /usr/local/mlmmj/bin/mlsubscriptions.sh