3
4
Fork
You've already forked mlmmj-webview
0
Tiny web frontend for mlmmj
  • C 74.9%
  • Tcl 13.7%
  • Shell 10.2%
  • Roff 0.7%
  • Makefile 0.4%
Find a file
2026年03月16日 10:11:14 +01:00
assets self host mailing lists 2021年04月06日 11:57:03 +02:00
autosetup infra: add boilerplate to configure and build mlmmj-webview 2021年03月23日 10:53:28 +01:00
genindex manpages: add manpages 2026年03月16日 09:29:08 +01:00
mk genindex: add a new binary 2025年07月22日 09:08:09 +02:00
webview CGI: use 303 instead of generating a temporary html and refresh 2026年03月16日 10:11:02 +01:00
auto.def Release 0.1.0 2026年03月16日 10:11:14 +01:00
configure infra: add boilerplate to configure and build mlmmj-webview 2021年03月23日 10:53:28 +01:00
Makefile.in genindex: add a new binary 2025年07月22日 09:08:09 +02:00
README.md README: make it up to date 2026年03月16日 09:18:06 +01:00

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 /action POST 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 list
  • private: if present, the list is hidden from the index
  • noarchive: 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 the desc file)
    • {{#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 name
  • email: the email address (validated with kcgi's kvalid_email and DNS domain check)
  • Subscribe or Unsubscribe: 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