summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLester Caine <lester@lsces.co.uk>2026-05-14 20:00:51 +0100
committerLester Caine <lester@lsces.co.uk>2026-05-14 20:00:51 +0100
commit9162909f3173a630f542087add748e8d5d48e82b (patch)
tree94178dd0b396638f007abc1015c0bf3ac782a971
parentef793a806a8d5a479c29c061780c56f62c4d3535 (diff)
downloadboards-9162909f3173a630f542087add748e8d5d48e82b.tar.gz
boards-9162909f3173a630f542087add748e8d5d48e82b.tar.bz2
boards-9162909f3173a630f542087add748e8d5d48e82b.zip
Move legacy mailman library into the relevant package for easier maintenance
-rw-r--r--admin/update_lists.php2
-rwxr-xr-xincludes/classes/BitBoard.php4
-rwxr-xr-xincludes/mailman_lib.php365
-rwxr-xr-xincludes/mailman_lib.py27
-rwxr-xr-xmailing_list.php2
5 files changed, 396 insertions, 4 deletions
diff --git a/admin/update_lists.php b/admin/update_lists.php
index 86ce546..478fc0e 100644
--- a/admin/update_lists.php
+++ b/admin/update_lists.php
@@ -12,7 +12,7 @@
*/
require_once( '../../kernel/includes/setup_inc.php' );
-require_once( UTIL_PKG_INCLUDE_PATH.'mailman_lib.php' );
+require_once( BOARDS_PKG_INCLUDE_PATH.'mailman_lib.php' );
// Is package installed and enabled
$gBitSystem->verifyPackage( 'boards' );
diff --git a/includes/classes/BitBoard.php b/includes/classes/BitBoard.php
index b9c9eb5..da054c2 100755
--- a/includes/classes/BitBoard.php
+++ b/includes/classes/BitBoard.php
@@ -140,7 +140,7 @@ class BitBoard extends LibertyMime {
$result = $this->mDb->associateInsert( BIT_DB_PREFIX."boards_map",['board_content_id'=>$pParamHash['board_store']['content_id'],'topic_content_id'=>$pParamHash['board_store']['content_id']]);
if( !empty( $pParamHash['boards_mailing_list'] ) ) {
global $gBitSystem, $gBitUser;
- require_once( UTIL_PKG_INCLUDE_PATH.'mailman_lib.php' );
+ require_once( BOARDS_PKG_INCLUDE_PATH.'mailman_lib.php' );
if( $gBitSystem->getConfig( 'boards_sync_mail_server' ) ) {
if( !($error = mailman_newlist( [ 'listname' => $pParamHash['boards_mailing_list'], 'listhost' => $gBitSystem->getConfig( 'boards_email_host', $gBitSystem->getConfig( 'kernel_server_name' ) ), 'admin-password'=>$pParamHash['boards_mailing_list_password'], 'listadmin-addr'=>$gBitUser->getField( 'email' ) ] )) ) {
$this->storePreference( 'boards_mailing_list', !empty( $pParamHash['boards_mailing_list'] ) ? $pParamHash['boards_mailing_list'] : null );
@@ -280,7 +280,7 @@ class BitBoard extends LibertyMime {
$result = $this->mDb->query( $query, [ $this->mContentId ] );
if( LibertyMime::expunge() ) {
if( $mailingList ) {
- require_once UTIL_PKG_INCLUDE_PATH.'mailman_lib.php';
+ require_once BOARDS_PKG_INCLUDE_PATH.'mailman_lib.php';
if( $error = mailman_rmlist( $mailingList ) ) {
$this->mErrors['mailing_list'] = $error;
}
diff --git a/includes/mailman_lib.php b/includes/mailman_lib.php
new file mode 100755
index 0000000..377345d
--- /dev/null
+++ b/includes/mailman_lib.php
@@ -0,0 +1,365 @@
+<?php
+// $Header$
+// Copyright (c) bitweaver Group
+// All Rights Reserved.
+// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See http://www.gnu.org/copyleft/lesser.html for details.
+
+/**
+* mailman lib
+* library of functions to manipulate mailman lists
+*
+* @date created 2008-APR-06
+* @author wjames <will@tekimaki.com> spider <spider@viovio.com>
+*/
+
+function mailman_verify_list( $pListName ) {
+ $error = NULL;
+ if( $matches = preg_match( '/[^A-Za-z0-9\-\_]/', $pListName ) ) {
+ $error = tra( "Invalid mailing list name" ).": ".tra( "List names can only contain letters and numbers" );
+ } else {
+ $lists = mailman_list_lists();
+ if( !empty( $lists[strtolower($pListName)] ) ) {
+ $error = tra( "Invalid mailing list name" ).": ".tra( "List already exists" );
+ }
+ }
+ return $error;
+}
+
+function mailman_list_lists() {
+ $ret = [];
+ if( $ret_code = mailman_command( "list_lists", $output) ) {
+ mailman_fatal(tra("Unable to list lists."), $ret_code);
+ }
+ else {
+ foreach( $output as $o ) {
+ if( strpos( $o, "-" ) ) {
+ list( $name, $desc ) = split( "-", $o );
+ $ret[strtolower( trim( $name ) )] = trim( $desc );
+ }
+ }
+ }
+ return( $ret );
+}
+
+function mailman_list_members( $pListName ) {
+ $ret = [];
+ $options = escapeshellarg( $pListName );
+ if( $ret = mailman_command( "list_members", $output, $options ) ) {
+ // mailman_fatal(tra('Unable to get members for list: ').$pListName, $ret);
+ }
+ return( $output );
+}
+
+// pParamHash follows naming convention off config_list --help usage instructions
+function mailman_config_list( $pParamHash ){
+ $options = " -i ".escapeshellarg(UTIL_PKG_INCLUDE_PATH."mailman.cfg");
+ $options .= " ".escapeshellarg( $pParamHash["listname"] );
+ if( $ret = mailman_command( "config_list", $output, $options) ) {
+ return (tra("Unable to configure list: ").$pParamHash["listname"].":".$ret);
+ }
+}
+
+// pParamHash follows naming convention off newlist --help usage instructions
+function mailman_newlist( $pParamHash ) {
+ $error = NULL;
+ if( !($error = mailman_verify_list( $pParamHash["listname"] )) ) {
+ $options = "";
+ if( !empty( $pParamHash["listhost"] ) ) {
+ $options .= " -e ".escapeshellarg( $pParamHash["listhost"] );
+ }
+ $options .= " -q ".escapeshellarg( $pParamHash["listname"] );
+ $options .= " ".escapeshellarg( $pParamHash["listadmin-addr"] )." ";
+ $options .= " ".escapeshellarg( $pParamHash["admin-password"] )." ";
+
+ if( $ret = mailman_command( "newlist", $output, $options ) ) {
+ return (tra("Unable to create list: ").$pParamHash["listname"].":".$ret);
+ }
+
+ $options = " -i ".escapeshellarg(UTIL_PKG_INCLUDE_PATH."mailman.cfg");
+ $options .= " ".escapeshellarg( $pParamHash["listname"] );
+ if( $ret = mailman_command( "config_list", $output, $options) ) {
+ // @TODO if this fails we should roll back the newlist created
+ return (tra("Unable to configure list: ").$pParamHash["listname"].":".$ret);
+ }
+
+ $newList = $pParamHash["listname"];
+ $mailman = mailman_get_mailman_command();
+ $newAliases = "
+## $newList mailing list
+$newList: \"|$mailman post $newList\"
+$newList-admin: \"|$mailman admin $newList\"
+$newList-bounces: \"|$mailman bounces $newList\"
+$newList-confirm: \"|$mailman confirm $newList\"
+$newList-join: \"|$mailman join $newList\"
+$newList-leave: \"|$mailman leave $newList\"
+$newList-owner: \"|$mailman owner $newList\"
+$newList-request: \"|$mailman request $newList\"
+$newList-subscribe: \"|$mailman subscribe $newList\"
+$newList-unsubscribe: \"|$mailman unsubscribe $newList\"";
+
+ // Make sure we unlock the semaphore
+ ignore_user_abort(true);
+ // Get a lock. flock is not reliable (NFS, FAT, etc)
+ // so we use a semaphore instead.
+ $sem_key = ftok(mailman_get_aliases_file(), "m");
+ if( $sem_key != -1 ) {
+ // Get the semaphore
+ $sem = sem_get($sem_key);
+ if( $sem ) {
+ if( sem_acquire($sem) ) {
+ if( $fh = fopen( mailman_get_aliases_file(), "a" ) ) {
+ fwrite( $fh, $newAliases );
+ fclose( $fh );
+ mailman_newalias();
+ } else {
+ $error = "Could not open /etc/aliases for appending.";
+ }
+ if( !sem_release($sem) ) {
+ $error = "Error releasing a sempahore.";
+ }
+ } else {
+ $error = "Unable to aquire a semaphore.";
+ }
+ } else {
+ $error = "Unable to get a semaphore.";
+ }
+ } else {
+ $error = "Couldn't create semaphore key.";
+ }
+ // Let the user cancel again
+ ignore_user_abort(false);
+ }
+ return $error;
+}
+
+function mailman_remove_member( $pListName, $pEmail ) {
+ $ret = "";
+ if( $fullCommand = mailman_get_command( "remove_members" ) ) {
+ $cmd = "echo ".escapeshellarg( $pEmail )." | $fullCommand -f - ".escapeshellarg( $pListName );
+ exec( $cmd, $ret );
+ } else {
+ bit_error_log( "Groups mailman command failed (remove_members) File not found: ".$fullCommand );
+ }
+}
+
+function mailman_setmoderated( $pListName, $pModerated = TRUE ) {
+ $ret = FALSE;
+ if( $fullCommand = mailman_get_command( "withlist" ) ) {
+ $cmd = $fullCommand." -q -l -r mailman_lib.setDefaultModerationFlag ".escapeshellarg( $pListName )." ".( $pModerated ? 1 : 0 );
+ $cmd = '/bin/sh -c "PYTHONPATH='.UTIL_PKG_INCLUDE_PATH." $cmd\"";
+ exec( $cmd, $ret );
+ } else {
+ bit_error_log( "Groups mailman command failed (withlist) File not found: ".$fullCommand );
+ }
+ return $ret;
+}
+
+function mailman_setmoderator( $pListName, $pEmail ) {
+ $ret = "";
+ if( $fullCommand = mailman_get_command( "withlist" ) ) {
+ $cmd = $fullCommand." -q -l -r mailman_lib.setMemberModeratedFlag ".escapeshellarg( $pListName )." ".escapeshellarg( $pEmail );
+ $cmd = '/bin/sh -c "PYTHONPATH='.UTIL_PKG_INCLUDE_PATH." $cmd\"";
+ exec( $cmd, $ret );
+ } else {
+ bit_error_log( "Groups mailman command failed (withlist) File not found: ".$fullCommand );
+ }
+ return $ret;
+}
+
+function mailman_addmember( $pListName, $pEmail, $pType = NULL ) {
+ $ret = "";
+ if( $fullCommand = mailman_get_command( "add_members" ) ) {
+ switch( $pType){
+ case "digest":
+ $cmd = "echo ".escapeshellarg( $pEmail )." | $fullCommand -d - ".escapeshellarg( $pListName );
+ break;
+ case "email":
+ default:
+ $cmd = "echo ".escapeshellarg( $pEmail )." | $fullCommand -r - ".escapeshellarg( $pListName );
+ break;
+ }
+ exec( $cmd, $ret );
+
+ /**
+ * its not enough to rely on the add_members call
+ * add_members is ignored by mailman if the user is already a member
+ * bw relies on mailman_addmember to toggle if a user wants to receive a digest or not
+ * to keep things simple in bw we conveniently handle the toggle here
+ **/
+ if( !empty( $pType ) ){
+ mailman_setsubscriptiontype( $pListName, $pEmail, $pType );
+ }
+ } else {
+ bit_error_log( "Groups mailman command failed (add_members) File not found: ".$fullCommand );
+ }
+}
+
+function mailman_findmember( $pListName, $pEmail ) {
+ $options = " -l ".escapeshellarg( $pListName )." ".escapeshellarg( $pEmail );
+ if( $ret = mailman_command( "find_member", $output, $options ) ) {
+ mailman_fatal(tra("Unable to find member in list: ").$pListName, $ret);
+ }
+ return $output;
+}
+
+function mailman_setsubscriptiontype( $pListName, $pEmail, $pType ) {
+ if( $fullCommand = mailman_get_command( "withlist" ) ) {
+ $cmd = $fullCommand." -q -l -r mailman_lib.setSubscriptionType ".escapeshellarg( $pListName )." ".escapeshellarg( $pEmail )." ".( $pType == "digest" ? 1 : 0 );
+ $cmd = '/bin/sh -c "PYTHONPATH='.UTIL_PKG_INCLUDE_PATH." $cmd\"";
+ exec( $cmd, $ret );
+ }else{
+ bit_error_log( "Groups mailman command failed (withlist) File not found: ".$fullCommand );
+ }
+ return $ret;
+}
+
+function mailman_getsubscriptiontype( $pListName, $pEmail ){
+ // even though setSubscriptionType returns a string or false we return an array because thats what exec returns
+ if( $fullCommand = mailman_get_command( "withlist" ) ) {
+ $cmd = $fullCommand." -l -r mailman_lib.getSubscriptionType ".escapeshellarg( $pListName )." ".escapeshellarg( $pEmail );
+ $cmd = '/bin/sh -c "PYTHONPATH='.UTIL_PKG_INCLUDE_PATH." $cmd\"";
+ exec( $cmd, $ret );
+ }else{
+ bit_error_log( "Groups mailman command failed (withlist) File not found: ".$fullCommand );
+ }
+ return $ret;
+}
+
+function mailman_rmlist( $pListName ) {
+ $error = NULL;
+ if( mailman_verify_list( $pListName ) ) {
+ $options = " -a ".escapeshellarg( $pListName );
+ if( $ret = mailman_command( "rmlist", $output, $options ) ) {
+ mailman_fatal(tra("Unable to remove list: ").$pListName, $ret);
+ }
+
+ $newList = $pListName;
+
+ $mailman = mailman_get_mailman_command();
+
+ $aliasesLines = [
+ "## $newList mailing list",
+ "$newList",
+ "$newList-admin",
+ "$newList-bounces",
+ "$newList-confirm",
+ "$newList-join",
+ "$newList-leave",
+ "$newList-owner",
+ "$newList-request",
+ "$newList-subscribe",
+ "$newList-unsubscribe",
+ ];
+ // Make sure we unlock the semaphore
+ ignore_user_abort(true);
+ // Get a lock. flock is not reliable (NFS, FAT, etc)
+ // so we use a semaphore instead.
+ $sem_key = ftok(mailman_get_aliases_file(), "m");
+ if( $sem_key != -1 ) {
+ // Get the semaphore
+ $sem = sem_get($sem_key);
+ if( $sem ) {
+ if( sem_acquire($sem) ) {
+ if( $fh = fopen( mailman_get_aliases_file(), "r+" ) ) {
+ // cull out all aliase lines for the mailing list and rewrite the file
+ $newContents = "";
+ while( $line = fgets( $fh ) ) {
+ @list( $alias, $value ) = split( ":", $line );
+ $alias = trim( $alias );
+ if( !in_array($alias, $aliasesLines) ) {
+ $newContents .= $line;
+ }
+ }
+
+ // Truncate the file
+ if( ftruncate($fh, 0) != 1) {
+ $error = "Unable to truncate /etc/aliases";
+ } else {
+ if( !rewind($fh) ) {
+ $error = "Unable to seek /etc/aliases";
+ }
+ else {
+ if( empty( $newContents ) ) {
+ $error = "Empty aliases for /etc/aliases";
+ } elseif( !fwrite( $fh, $newContents ) ) {
+ $error = "Could not write new /etc/aliases";
+ }
+ }
+ }
+
+ fclose( $fh );
+ mailman_newalias();
+ } else {
+ $error = "Could not open /etc/aliases for appending.";
+ }
+
+ if( !sem_release($sem) ) {
+ $error = "Error releasing a sempahore.";
+ }
+ } else {
+ $error = "Unable to aquire a semaphore.";
+ }
+ } else {
+ $error = "Unable to get a semaphore.";
+ }
+ } else {
+ $error = "Couldn't create semaphore key.";
+ }
+ // Let the user cancel again
+ ignore_user_abort(false);
+ }
+ return $error;
+}
+
+function mailman_newalias() {
+ global $gBitSystem;
+ exec( $gBitSystem->getConfig("server_newaliases_cmd", "/usr/bin/newaliases" ));
+}
+
+function mailman_get_aliases_file() {
+ global $gBitSystem;
+ return $gBitSystem->getConfig("server_aliases_file", "/etc/aliases");
+}
+
+function mailman_command( $pCommand, &$output, $pOptions=NULL ) {
+ $ret_code = NULL;
+ if( $fullCommand = mailman_get_command( $pCommand ) ) {
+ $cmd = $fullCommand." ".$pOptions;
+ if( !defined( "IS_LIVE" ) || !IS_LIVE ) {
+ bit_error_log( "mailman LOG: ".$cmd );
+ }
+ exec( $cmd, $output, $ret_code );
+ if( $ret_code ) {
+ bit_error_log("Error running command: ". $cmd . " Command returned: ".$ret_code);
+ }
+ } else {
+ bit_error_log( "Groups mailman command failed (".$pCommand."): File not found: ".$fullCommand );
+ }
+ return $ret_code;
+}
+
+function mailman_fatal($pMessage, $pRetCode) {
+ global $gBitSystem;
+ $gBitSystem->fatalError($pMessage.tra(" Command returned: ").$pRetCode);
+ die;
+}
+
+function mailman_get_mailman_command() {
+ global $gBitSystem;
+ $mailman = $gBitSystem->getConfig( "server_mailman_cmd", "/usr/lib/mailman/mail/mailman" );
+ return $mailman;
+}
+
+function mailman_get_command( $pCommand ) {
+ global $gBitSystem;
+ $ret = NULL;
+ // Support for legacy configurations
+ $fullCommand = $gBitSystem->getConfig( "server_mailman_bin" ) ."/".$pCommand;
+ $fullCommand = str_replace( "//", "/", $fullCommand );
+ if( file_exists( $fullCommand ) ) {
+ $ret = $fullCommand;
+ }
+ return $ret;
+}
+
+?>
diff --git a/includes/mailman_lib.py b/includes/mailman_lib.py
new file mode 100755
index 0000000..8301873
--- /dev/null
+++ b/includes/mailman_lib.py
@@ -0,0 +1,27 @@
+from Mailman import mm_cfg
+from Mailman.Errors import NotAMemberError
+from Mailman.mm_cfg import Digests
+
+def setMemberModeratedFlag (mlist, addr):
+ mlist.moderator.append(addr)
+ mlist.Save()
+
+def setDefaultModerationFlag(mlist, val):
+ mlist.default_member_moderation = int(val);
+ for member in mlist.getMembers():
+ mlist.setMemberOption(member, mm_cfg.Moderate, int(val))
+ mlist.Save()
+
+def getSubscriptionType(mlist, addr):
+ try:
+ if mlist.getMemberOption(addr, Digests):
+ print "digest"
+ else:
+ print "email"
+ except NotAMemberError:
+ print 0
+
+def setSubscriptionType(mlist, addr, val):
+ mlist.setMemberOption(addr, mm_cfg.Digests, int(val))
+ mlist.Save()
+
diff --git a/mailing_list.php b/mailing_list.php
index c37da31..c09baae 100755
--- a/mailing_list.php
+++ b/mailing_list.php
@@ -17,7 +17,7 @@ require_once( BOARDS_PKG_CLASS_PATH.'BitBoardTopic.php' );
require_once( BOARDS_PKG_CLASS_PATH.'BitBoardPost.php' );
require_once( BOARDS_PKG_CLASS_PATH.'BitBoard.php' );
require_once( BOARDS_PKG_INCLUDE_PATH.'lookup_inc.php' );
-require_once( UTIL_PKG_INCLUDE_PATH.'mailman_lib.php' );
+require_once( BOARDS_PKG_INCLUDE_PATH.'mailman_lib.php' );
// Is package installed and enabled
$gBitSystem->verifyPackage( 'boards' );