enemies-of-carlotta-1.2.6/0000775000175000017500000000000010577453117013542 5ustar liwliwenemies-of-carlotta-1.2.6/enemies-of-carlotta.1.fr0000664000175000017500000004746210577453116020104 0ustar liwliw.\" This file was generated with po4a. Translate the source file. .\" .TH ENEMIES\-OF\-CARLOTTA 1 .SH NOM enemies\-of\-carlotta \- Gestionnaire simple de listes de diffusion .SH SYNOPSIS \fBenemies\-of\-carlotta\fP [\fIoptions\fP] [\fIadresses\fP] .SH DESCRIPTION \fBenemies\-of\-carlotta\fP est un gestionnaire simple de listes de diffusion. .PP Enemies of Carlotta conserve toutes les données des listes dans le répertoire \fI~/.enemies\-of\-carlotta\fP. Celui\-ci sera créé automatiquement lors de la création de la première liste. Le traitement des courriels par le gestionnaire de listes doit être configuré manuellement. Les détails peuvent varier d'un système à l'autre. Pour QMail et Postfix, voir ci\-dessous. .PP Chaque liste possède un ou plusieurs propriétaires, qui modèrent également les inscriptions ou certains messages, voire tous. Sur les listes non modérées, les propriétaires de la liste sont chargés de répondre aux questions concernant la liste. Sur les listes totalement modérées, ils doivent approuver chaque message avant qu'il ne soit envoyé sur la liste. Sur les liste où \fIposting=auto\fP, les messages des abonnés sont automatiquement envoyés à la liste, et les modérateurs n'ont besoin d'approuver que les messages qui ne proviennent pas des abonnés. .SH OPTIONS .TP \fB\-\-name=\fPfoo@example.com préciser la liste sur laquelle la commande doit être effectuée. La plupart des options doivent être accompagnées du nom de la liste concernée. Avec les options \-\-edit, \-\-subscribe, \-\-unsubscribe et \-\-list, le nom peut être abrégé en enlevant le signe @ et le domaine. .TP \fB\-\-create\fP créer une nouvelle liste de diffusion. Au moins un propriétaire doit être précisé à l'aide de \fB\-\-owner\fP. .TP \fB\-\-owner=\fP\fIadresse\fP ajouter un propriétaire à la liste lors de sa création .TP \fB\-\-moderator=\fP\fIadresse\fP ajouter un modérateur à la liste lors de sa création ou de son édition .TP \fB\-\-language=\fP\fIcode\-de\-la\-langue\fP configurer le code de la langue utilisé pour récupérer les fichiers modèles. Le code peut être vide (l'anglais sera utilisé par défaut) ou composé de deux lettres comme \fBfr\fP ou \fBes\fP. .TP \fB\-\-cleaning\-woman\fP gérer les adresses invalides et nettoyer quelques autres éléments. La commande \fBenemies\-of\-carlotta \-\-cleaning\-woman\fP doit être lancée périodiquement, comme une fois par heure, pour nettoyer l'ensemble des listes de diffusion. .TP \fB\-\-destroy\fP détruire une liste .TP \fB\-\-edit\fP modifier la configuration d'une liste .TP \fB\-\-subscription=\fP\fItype\fP définir le mode d'abonnement à \fIfree\fP (libre) ou \fImoderated\fP (modéré) lors de la création de la liste. À utiliser en combinaison avec \fB\-\-edit\fP ou \fB\-\-create\fP. .TP \fB\-\-posting=\fP\fItype\fP définir le mode d'envoi à \fIfree\fP (libre), \fIauto\fP (automatique) ou \fImoderated\fP (modéré). À utiliser en combinaison avec \fB\-\-edit\fP ou \fB\-\-create\fP. .TP \fB\-\-archived=\fP\fIyes\-ou\-no\fP définir si les messages de la liste doivent être archivés dans le sous\-répertoire \fBarchive\-box\fP du répertoire de la liste, lui\-même présent dans le répertoire \fB~/.enemies\-of\-carlotta\fP. Utiliser \fIyes\fP (oui) ou \fIno\fP (non). .TP \fB\-\-mail\-on\-subscription\-changes=\fP\fIyes\-ou\-no\fP définir si les propriétaires de la liste doivent être avertis lors d'un abonnement ou d'un désabonnement. Utiliser \fIyes\fP (oui) ou \fIno\fP (non). La valeur par défaut est no. .TP \fB\-\-mail\-on\-forced\-unsubscription=\fP\fIyes\-ou\-no\fP définir si les propriétaires de la liste doivent être avertis lorsque quelqu'un est retiré de la liste en raison d'une adresse trop souvent invalide. Utiliser \fIyes\fP (oui) ou \fIno\fP (non). La valeur par défaut est no. .TP \fB\-\-ignore\-bounce=\fP\fIyes\-ou\-no\fP définir si les renvois pour adresse invalide doivent être ignorés. Utiliser \fIyes\fP (oui) ou \fIno\fP (non). La valeur par défaut est no. .TP \fB\-\-list\fP lister l'ensemble des abonnés à une liste. .TP \fB\-\-subscribe\fP ajouter des abonnés à la liste. Les arguments qui ne sont pas des options sont les adresses à abonner. Les adresses ajoutées de cette façon ne recevront pas de demande de confirmation. .TP \fB\-\-unsubscribe\fP enlever des abonnés de la liste. Les arguments qui ne sont pas des options sont les adresses à désabonner. Les adresses enlevées de cette manière ne recevront pas de demande de confirmation. .TP \fB\-\-incoming\fP traiter un message entrant sur l'entrée standard. L'adresse de l'expéditeur de l'enveloppe SMTP doit être précisée par la variable d'environnement \fISENDER\fP et l'adresse du destinataire de l'enveloppe SMTP doit être précisée par la variable d'environnement \fIRECIPIENT\fP. (QMail et Postfix traitent cela automatiquement.) .TP \fB\-\-skip\-prefix=\fP\fIchaîne\fP retirer \fIchaîne\fP du début de l'adresse de destination avant de l'analyser pour déterminer à quelle liste le message est destiné. Ceci permet le traitement des domaines virtuels avec QMail et Postfix, voir ci\-dessous. .TP \fB\-\-domain=\fP\fInom.du.domaine\fP remplacer le nom de domaine par \fInom.du.domaine\fP dans l'adresse de destination pour déterminer à quelle liste le message est destiné. Ceci permet le traitement des domaines virtuels avec QMail et Postfix. .TP \fB\-\-is\-list\fP déterminer si l'adresse indiquée par \fB\-\-name\fP fait référence à une liste valide. Le code de sortie zéro (succès) est retourné si c'est le cas, un (échec) dans le cas contraire. .TP \fB\-\-sendmail=\fP\fIchemin\fP utiliser \fIchemin\fP au lieu de \fB/usr/sbin/sendmail\fP pour envoyer des courriels via une interface en ligne de commande. Cette commande doit respecter l'interface de la ligne de commande sendmail. .TP \fB\-\-smtp\-server=\fP\fIhôte\fP envoyer les courriels en utilisant le serveur SMTP sur \fIhôte\fP (port 25). Le serveur doit être configuré pour permettre à la machine sur laquelle fonctionne la liste de l'utiliser comme relais. Par défaut, l'interface en ligne de commande est utilisée. L'envoi par SMTP n'est utilisé qu'avec cette option. .TP \fB\-\-qmqp\-server=\fP\fIhôte\fP envoyer les courriels en utilisant le serveur QMQP sur \fIhôte\fP (port 628). Le serveur doit être configuré pour permettre à la machine sur laquelle fonctionne la liste de l'utiliser comme relais. Par défaut, l'interface en ligne de commande est utilisée. L'envoi par QMQP n'est utilisé qu'avec cette option. .TP \fB\-\-moderate\fP forcer un message entrant à être modéré, même s'il est envoyé à une liste où l'envoi est libre. Cela peut être utilisé pour filtrer les courriers indésirables\ : les messages entrants peuvent être filtrés par n'importe quel système de filtrage des courriels\ ; dès lors qu'un message semble être indésirable, une modération par un humain peut être demandée. .TP \fB\-\-post\fP forcer un message entrant à être envoyé, même s'il est envoyé à une liste où l'envoi est modéré. Cela peut être utilisé lorsqu'une vérification externe a lieu pour définir si le courriel est acceptable sur la liste, par exemple en vérifiant les signatures électroniques. .TP \fB\-\-quiet\fP ne pas envoyer les messages de débogage des journaux sur la sortie d'erreur standard, contrairement au comportement par défaut. .TP \fB\-\-sender=\fP\fIfoo@example.com\fP .TP \fB\-\-recipient=\fP\fIfoo@example.com\fP écraser les variables d'environnement \fBSENDER\fP et \fBRECIPIENT\fP respectivement, à utiliser en combinaison avec \fB\-\-incoming\fP et \fB\-\-is\-list\fP. .TP \fB\-\-get\fP obtenir les valeurs d'une ou plusieurs variables de configuration. Le nom des variables est indiqué sur la ligne de commande après les options. Chaque valeur est affichée sur une ligne séparée. .TP \fB\-\-set\fP configurer les valeurs d'une ou plusieurs variables de configuration. Les noms et valeurs sont donnés sur la ligne de commande après les options et séparés par un signe d'égalité («\ =\ »). Par exemple, la commande suivante configurerait une liste en français\ : \fBenemies\-of\-carlotta \-\-name=foo@bar \-\-set language=fr\fP .TP \fB\-\-version\fP afficher la version du programme .TP \fB\-\-show\-lists\fP lister les listes connues de enemies\-of\-carlotta .SH CONFIGURATION Chaque liste est représentée par un répertoire, nommé d'après le nom de la liste, sous le répertoire \fI~/.enemies\-of\-carlotta\fP. Ce répertoire contient plusieurs fichiers et répertoires décrits ci\-dessous. En général, il n'est pas nécessaire de toucher à ces répertoires. Cependant, certaines configurations ésotériques peuvent uniquement être faites en éditant le fichier de configuration de la liste. .TP \fBconfig\fP Fichier de configuration de la liste. Le contenu est décrit ci\-dessous. .TP \fBsubscribers\fP Base de données des abonnés. Chaque ligne contient un groupe d'abonnés, dont les cinq premiers espaces délimitent les champs qui sont l'identifiant du groupe, l'état, la date de création du groupe, la date du message de retour qui a fait passer l'état de la liste de «\ ok\ » à «\ bounced\ », et l'identifiant du message de retour. .TP \fBarchive\-box\fP Messages archivés. .TP \fBbounce\-box\fP Groupes des messages de retour qui ne sont pas dans l'état «\ ok\ ». .TP \fBheaders\-to\-add\fP En\-têtes à ajouter aux courriels envoyés à la liste. Ceux\-ci sont copiés aux débuts des en\-têtes existants exactement tels qu'ils sont dans ce fichier, après que les en\-têtes de la liste (comme «\ List\-ID\ ») ont été ajoutés et que ceux mentionnés dans \fBheaders\-to\-remove\fP ont été retirés. .TP \fBheaders\-to\-remove\fP En\-têtes à retirer des messages envoyés à la liste. .TP \fBmoderation\-box\fP Messages en attente d'approbation du modérateur. .TP \fBsubscription\-box\fP Requêtes d'abonnement et de désabonnement en attente de confirmation de la part de l'utilisateur. .TP \fBtemplates\fP Répertoires qui contiennent les modèles spécifiques à une liste (optionnel). Les modèles seront recherchés d'abord dans ce répertoire, s'il existe, puis dans les répertoires du système. Un fichier vide signifie que le message correspondant ne doit pas être envoyé. Cela peut être utilisé par exemple pour activer uniquement pour certaines listes les messages du type «\ veuillez attendre la modération de votre message\ ». .TP \fBplugins\fP Répertoires contenant des grephons, à savoir des fichiers source en Python qui sont chargés automatiquement par EoC au démarrage. Ces grephons peuvent modifier le comportement d'EoC. .PP Le fichier \fBconfig\fP possède un format de type \fImot\-clé\fP=\fIvaleur\fP\ : .PP .RS .nf [list] owners = liw@liw.iki.fi archived = no posting = free subscription = free mail\-on\-subscription\-changes = yes mail\-on\-forced\-unsubscribe = yes language = fr .fi .RE .PP Les mots clés \fBarchived\fP, \fBposting\fP, et \fBsubscription\fP correspondent aux options qui portent les mêmes noms. Les autres mots\-clés sont\ : .TP \fBowners\fP Liste des adresses des propriétaires, définie avec l'option \fI\-\-owner\fP. .TP \fBmoderators\fP Liste des adresses des modérateurs, définie avec l'option \fI\-\-moderator\fP. .TP \fBmail\-on\-subscription\-changes\fP Valeur définissant si les propriétaires doivent être avertis lorsqu'un utilisateur s'abonne ou se désabonne. .TP \fBmail\-on\-forced\-unsubscribe\fP Valeur définissant si les propriétaires doivent être avertis si quelqu'un est retiré d'une liste suite à une adresse trop souvent invalide. .TP \fBignore_bounce\fP Valeur définissant si les messages de retour sont ignorés sur la liste. Utilisé par exemple lorsque la liste devrait avoir un nombre stable d'abonnés. .TP \fBlanguage\fP Suffixe pour les modèles, pour permettre la gestion de plusieurs langues. (Si \fIlanguage\fP est défini à «\ fr\ », alors le modèle «\ foo\ » est d'abord recherché en tant que modèle «\ foo.fr\ ».) .TP \fBpristine\-headers\fP Valeur définissant si les en\-têtes doivent être encodés au format MIME. Si la valeur vaut «\ yes\ », l'encodage aura lieu. .SH EXEMPLES Créer une liste nommée \fImoviefans@example.com\fP, dont le propriétaire est \fIding@example.com\fP (tout sur la même ligne)\ : .sp 1 .nf .RS enemies\-of\-carlotta \-\-name=moviefans@example.com \-\-owner=ding@example.com \-\-create .RE .fi .PP Le système de messagerie doit être configuré pour que ces messages arrivent sur la liste (et à ses adresses de commande). Pour Qmail et Postfix, voir ci\-dessous. .PP Afficher la liste de tous les abonnés à cette liste\ : .sp 1 .nf .RS enemies\-of\-carlotta \-\-name=moviefans@example.com \-\-list .RE .fi .PP Les personnes voulant s'abonner à la liste doivent envoyer un courriel à .sp 1 .nf .RS moviefans\-subscribe@example.com .RE .fi .SH QMAIL Avec Qmail, pour faire en sorte que les courriels entrants soient traités par Enemies of Carlotta, quelques fichiers \fI.qmail\-extension\fP sont nécessaires pour chaque liste. Par exemple, si joe est un nom d'utilisateur, pour initialiser la liste de diffusion joe\-fans, deux fichiers doivent être créés\ : \fI.qmail\-fans\fP et \fI.qmail\-fans\-default\fP, qui contiendront .sp 1 .RS |enemies\-of\-carlotta \-\-incoming .RE .PP Si les messages à destination d'un domaine virtuel, example.com, délivrés via \fI/var/qmail/control/virtualdomains\fP à \fIjoe\-exampledotcom\fP, les fichiers seront nommés \fI.qmail\-exampledotcom\-fans\fP et \&\fI.qmail\-exampledotcom\-fans\-default\fP et contiendront .sp 1 .RS |enemies\-of\-carlotta \-\-incoming \-\-skip\-prefix=joe\-exampledotcom\- .RE .sp 1 (tout sur la même ligne). .SH POSTFIX Avec Postfix, un fichier \fI.forward\fP contenant .sp 1 .RS "|procmail \-p" .RE .sp 1 sera nécessaire, ainsi qu'un fichier \fI.procmailrc\fP contenant .sp 1 .RS :0 .br * ? enemies\-of\-carlotta \-\-name=$RECIPIENT \-\-is\-list .br | enemies\-of\-carlotta \-\-incoming .RE .PP Pour utiliser Enemies of Carlotta avec un domaine virtuel Postfix, une \fIcarte virtuelle d'expressions rationnelles\fP généralement appelée \fI/etc/postfix/virtual_regexp\fP devra être créée (pour l'activer, ajouter \fIvirtual_maps = regexp:/etc/postfix/virtual_regexp\fP dans le fichier \fI/etc/postfix/main.cf\fP). Le fichier d'expressions rationnelles doit faire des choses horribles pour conserver l'adresse de destination. Il faut donc ajouter\ : .sp 1 .RS /^your.virtual.domain$/ dummy .br /^(yourlist|yourlist\-.*)@(your.virtual.domain)$/ joe+virtual\-$1 .RE .sp 1 Soit deux lignes. Si \fIrecipient_delimiter\fP pour Postfix est configuré avec un moins au lieu d'un plus, utiliser \fBjoe\-virtual\fP. Ensuite dans le fichier \&\fI.procmailrc\fP, ajouter les options \fI\-\-skip\-prefix=joe\-virtual\-\fP et \fI\-\-domain=your.virtual.domain\fP pour les deux appels à \fBenemies\-of\-carlotta\fP. .PP Ces manipulations restent complexes. .SH "COMMANDES PAR MESSAGE" Les utilisateurs et propriétaires utilisent Enemies of Carlotta par courriel en utilisant des adresses de commande comme \fBfoo\-subscribe@example.com\fP. Voici une liste de toutes les adresses de commande accessibles aux utilisateurs et propriétaires. Dans tous ces exemples, le nom de la liste de diffusion sera \fBfoo@example.com\fP. .SS "Commandes par message accessibles à tout le monde" Ces commandes peuvent servir à tout le monde et ne demandent aucun privilège spécial. .TP \fBfoo@example.com\fP Envoie un courriel à tous les abonnés. Le message peut avoir à être approuvé d'abord par les modérateurs de la liste, qui ont aussi le pouvoir de le rejeter. .TP \fBfoo\-owner@example.com\fP Envoie un courriel au(x) propriétaire(s) de la liste. .TP \fBfoo\-help@example.com\fP Retourne en réponse à ce courriel un message contenant l'aide de la liste. .TP \fBfoo\-subscribe@example.com\fP Inscrit l'auteur du courriel à la liste. Le gestionnaire de listes répondra en demandant une confirmation. L'inscription n'aura lieu qu'en cas de réponse à cette demande. De cette manière, les personnes malveillantes ne peuvent ajouter votre adresse sur une ou beaucoup de listes de diffusion. .TP \fBfoo\-subscribe\-joe=example.com@example.com\fP Inscrit joe@example.com à la liste. Pour inscrire une adresse différente de celle utilisée pour la demande d'abonnement, utiliser cette forme. La demande de confirmation sera envoyée à joe@example.com puisque cette adresse seulement sera abonnée à la liste. .TP \fBfoo\-unsubscribe@example.com\fP Désinscrit l'auteur du courriel de la liste. Encore une fois, une demande de confirmation sera envoyée pour éviter que des personnes malveillantes désinscrivent d'autres personnes. .TP \fBfoo\-unsubscribe\-joe=example.com@example.com\fP Désinscrit joe@example.com de la liste. Encore une fois, cette dernière adresse recevra la demande de confirmation. .SS "Commandes par message pour les propriétaires de la liste." Les commandes suivantes peuvent être utilisées pour administrer la liste. .TP \fBfoo\-subscribe\-joe=example.com@example.com\fP Si un propriétaire envoie un courriel de ce type, il recevra la demande de confirmation à la place de joe@example.com. Il est souvent préférable de laisser les gens s'inscrire eux\-mêmes, mais parfois cela peut être utile que le propriétaire s'en occupe. .TP \fBfoo\-unsubscribe\-joe=example.com@example.com\fP Les propriétaires peuvent également désinscrire d'autres personnes. .TP \fBfoo\-list@example.com\fP Retourne la liste des abonnés à la liste. Cela ne marche que si l'adresse de l'expéditeur est celle de l'un des propriétaires. L'adresse de l'expéditeur est celle utilisée au niveau SMTP et non celle dans l'en\-tête From:. .TP \fBfoo\-setlist@example.com\fP Permet à un propriétaire de configurer la liste des abonnés en un seul coup. Cela correspond à une suite de commandes \-subscribe et \-unsubscribe, mais en plus simple. Tous ceux qui seront ajoutés à la liste recevront un message de bienvenue, alors que ceux qui seront retirés recevront un message d'adieu. .TP \fBfoo\-setlistsilently@example.com\fP Commande similaire à \-setlist, sans les envois des messages de bienvenue et d'adieu. .SH GREPHONS Enemies of Carlotta accepte des grephons. Des connaissances en Python sont nécessaires pour comprendre cette section. .PP Un grephon est un module Python (un fichier avec un suffixe \fB.py\fP), placé dans le répertoire \fB~/.enemies\-of\-carlotta/plugins\fP. Les grephons sont chargés automatiquement au démarrage, si la version déclarée de l'interface correspond à celle utilisée par Enemies of Carlotta. La version de l'interface est déclarée par la variable globale \fBPLUGIN_INTERFACE_VERSION\fP du module. .PP Les grephons peuvent définir des fonctions d'ancrage appelées au bon endroit dans le code d'EoC. Pour le moment, la seule fonction d'ancrage est \fBsend_mail_to_subscribers_hook\fP, qui peut modifier un courriel avant qu'il ne soit envoyé aux abonnés. La fonction devrait ressembler à\ : .PP .ti +5 def send_mail_to_subscribers_hook(list, text): .PP L'argument \fIlist\fP est une référence à l'objet \fIMailingList\fP qui correspond à la liste concernée, et \fItext\fP est le texte complet du courriel tel qu'il existe actuellement. La fonction devrait retourner le nouveau contenu de ce courriel. .SH FICHIERS .TP \fI~/.enemies\-of\-carlotta\fP Ensemble des fichiers en rapport avec les listes de diffusion. .TP \fI~/.enemies\-of\-carlotta/secret\fP Mots de passe secrets utilisés pour générer des adresses signées pour le contrôle des messages de retour et la validation des abonnements. .TP \fI~/.enemies\-of\-carlotta/foo@example.com\fP Répertoire contenant les données appartenant à la liste de diffusion foo@example.com. À l'exception du fichier de \fIconfig\fP qui se trouve dans ce répertoire, rien ne devrait être à modifier. .TP \fI~/.enemies\-of\-carlotta/foo@example.com/config\fP Fichier de configuration pour la liste de diffusion. Ce fichier devra être édité pour modifier les options de modération ou les propriétaires de la liste. .SH "VOIR AUSSI" Page web d'\fI«\ Enemies of Carlotta\ »\fP\ : \fIhttp://www.iki.fi/liw/eoc/\fP. .SH "TRADUCTION" Pierre Machard , 2003 .br Thomas Huriaux , 2005 enemies-of-carlotta-1.2.6/Makefile0000664000175000017500000000222510577453116015202 0ustar liwliwprefix = /usr/local bindir = $(prefix)/bin mandir = $(prefix)/share/man man1dir = $(mandir)/man1 man1dires = $(mandir)/es/man1 man1dirfr = $(mandir)/fr/man1 sharedir = $(prefix)/share/enemies-of-carlotta command = enemies-of-carlotta all: check check: python testrun.py install: install -d $(DESTDIR)$(bindir) install -d $(DESTDIR)$(sharedir) install -d $(DESTDIR)$(man1dir) install -d $(DESTDIR)$(man1dires) install -d $(DESTDIR)$(man1dirfr) sed 's,^SHAREDIR=.*,SHAREDIR="$(sharedir)",' enemies-of-carlotta \ > $(DESTDIR)$(bindir)/$(command) chmod 755 $(DESTDIR)$(bindir)/$(command) sh fix-config $(sharedir) < eoc.py > $(DESTDIR)$(sharedir)/eoc.py install -m 0755 qmqp.py $(DESTDIR)$(sharedir)/qmqp.py python -c 'import py_compile; py_compile.compile("$(DESTDIR)$(sharedir)/eoc.py")' python -c 'import py_compile; py_compile.compile("$(DESTDIR)$(sharedir)/qmqp.py")' install -m 0644 enemies-of-carlotta.1 $(DESTDIR)$(man1dir)/$(command).1 install -m 0644 enemies-of-carlotta.1.es $(DESTDIR)$(man1dires)/$(command).1 install -m 0644 enemies-of-carlotta.1.fr $(DESTDIR)$(man1dirfr)/$(command).1 install -m 0644 templates/????* $(DESTDIR)$(sharedir) enemies-of-carlotta-1.2.6/NEWS0000664000175000017500000001555210577453116014250 0ustar liwliwNEWS file for Enemies of Carlotta, a mailing list manager Significant user-visible changes from version 1.2.5 to version 1.2.6: * When listname-setlistsilently@example.org is mailed from an address which isn't the admin, EoC died. Now fixed. Bug found and patched by Dominic Hargreaves. Significant user-visible changes from version 1.2.4 to version 1.2.5: * The change in 1.2.3 used the wrong log printing function, this release uses the right one. Significant user-visible changes from version 1.2.3 to version 1.2.4: * A fix to CVE-2006-5875, a security problem where EoC did not quote shell command line arguments properly. Thanks to Antti-Juhani Kaijanaho for finding the problem. Significant user-visible changes from version 1.2.2 to version 1.2.3: * When there is a problem with MIME header encodings, don't kill EoC, just print a warning. Significant user-visible changes from version 1.2.1 to version 1.2.2: * Manual page: documents the values to the --posting option, and has instructions for Courier-MTA users. * When someone is told to wait for moderation, the message now contains a few words of explanation that it might have happened due to spam. * A German translation courtesy of Johannes Berg. * Fixes to mail template headers (missing MIME-Type added, typos in Content-Type headers). * Bug fixes to avoid problems when mail headers are wrong (and encoding them correctly fails), or when mail headers contain colons, or when there's problems sending mail. Also, List-* headers are removed from incoming e-mails. * CRLF line endings in incoming messages are converted to LF. Significant user-visible changes from version 1.2 to version 1.2.1: * Bugfix: Checking for base64 encodings now works. Significant user-visible changes from version 1.1.5 to version 1.2: * When the list manager sends a mail with an attached message (e.g., a bounce), they are marked as "Content-disposition: inline" so that they are shown by default (by sensible mail user agents). * A footer is not appended to messages that are base64 encoded. * Updated translations for templates and manual pages. Significant user-visible changes from version 1.1.4 to version 1.1.5: * Debug logs no longer go to stderr, just to the log file. This should prevent sudden deaths from MTA buffers becoming full. * Bugs about treating addresses in case-insensitive manners fixed. * Lists can now be configured to ignore bounces. (By Jaakko Niemi.) * Moderators for posting can be separate from list owners. (By Pascal Hakim.) * Header MIME encoding can be disabled per list. Significant user-visible changes from version 1.1.3 to version 1.1.4: * Fixed race condition when multiple moderators approve or reject the same message at the same time. This could result in the message being sent multiple times. Significant user-visible changes from version 1.1.2 to version 1.1.3: * Mails sent by EoC now get their headers MIME encoded (if the version of Python in use has the email.Header module). * A bunch of new command line options: --get, --set, --language, --mail-on-forced-unsubscribe, --mail-on-subscription-changes, --version * Making an empty template file prevents the corresponding mail from being sent at all. This can be used, for example, to prevent "please wait for moderation" mails. * The beginnings of a plugin architecture, where plugins can modify EoC's operation or the mails it sends. There is only one supported hook at the moment, though, but more will be added as needed. * --show-lists now shows lists in alphabetical order. Significant user-visible changes from version 1.1.1 to version 1.1.2: * This is still a DEVELOPMENT release. There have been many changes and the documentation and translations are probably out of date. There may be bugs lurking as well. * Headers of messages sent to the list can be manipulated in simple ways: some headers can be removed and others added. * Error messages are now user-friendlier than Python's stack traces. * When EoC attaches a message to something it sends to a user, it does so by making it a proper MIME attachment. * Message templates are now in UTF-8. * Manual page in Spanish. Significant user-visible changes from version 1.1.0 to version 1.1.1: * This is still a DEVELOPMENT release. There have been many changes and the documentation and translations are probably out of date. There may be bugs lurking as well. * Manual page mentions the "templates" sub-directory of the list specific directory. * QMQP implementation is now installed by "make install". * Bashism in the Makefile have been removed. * Bugs regarding lists with names that are not all lower case have been fixed. * This release is dedicated to Jaakko Niemi who found most bugs in 1.1.0. Significant user-visible changes from version 1.0.3 to version 1.1.0: * This is a DEVELOPMENT release. There have been many changes and the documentation and translations are probably out of date. There may be bugs lurking as well. * QMQP support from Jaakko Niemi. * Command line syntax errors result in an error message rather than a stack trace. * Upper and lower case are no longer treated as being different characters in e-mail addresses. * New options --sender and --recipient. * Subscription and posting moderation requests now use the rejection address to make life easier for mutt users. * Swedish translation. * Mails sent by EoC now have a "real name" in the From header, to lessen the chance they are caught by spam filters. Significant changes from version 1.0.2 to version 1.0.3: * Added option --post, to bypass moderation. * The manual page now documents all mail command addresses. * Bug fix: Qmail section in the manual page had extra quotation marks. They have been removed. * Bug fix: Log file didn't get more than the first message of each run. Now fixed so that it gets all messages. (Stderr got everything even before, though.) Significant changes from version 1.0.1 to version 1.0.2: * Manual page fix: the example for creating a new list now actually works. * Added option --show-lists, to list all lists. Significant changes from version 1.0 to version 1.0.1: * Bug fixes only. * --help option implemented. * --create: If ~/.enemies-of-carlotta exists, but a file named "secret" inside it doesn't, create the file instead of crashing. * Bounce handling fixed. enemies-of-carlotta-1.2.6/COPYING0000664000175000017500000004311010577453116014573 0ustar liwliw GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) 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 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 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 Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. enemies-of-carlotta-1.2.6/eoc.py0000664000175000017500000017353610577453116014700 0ustar liwliw"""Mailing list manager. This is a simple mailing list manager that mimicks the ezmlm-idx mail address commands. See manual page for more information. """ VERSION = "1.2.6" PLUGIN_INTERFACE_VERSION = "1" import getopt import md5 import os import shutil import smtplib import string import sys import time import ConfigParser try: import email.Header have_email_module = 1 except ImportError: have_email_module = 0 import imp import qmqp # The following values will be overriden by "make install". TEMPLATE_DIRS = ["./templates"] DOTDIR = "dot-eoc" class EocException(Exception): def __init__(self, arg=None): self.msg = repr(arg) def __str__(self): return self.msg class UnknownList(EocException): def __init__(self, list_name): self.msg = "%s is not a known mailing list" % list_name class BadCommandAddress(EocException): def __init__(self, address): self.msg = "%s is not a valid command address" % address class BadSignature(EocException): def __init__(self, address): self.msg = "address %s has an invalid digital signature" % address class ListExists(EocException): def __init__(self, list_name): self.msg = "Mailing list %s alreadys exists" % list_name class ListDoesNotExist(EocException): def __init__(self, list_name): self.msg = "Mailing list %s does not exist" % list_name class MissingEnvironmentVariable(EocException): def __init__(self, name): self.msg = "Environment variable %s does not exist" % name class MissingTemplate(EocException): def __init__(self, template): self.msg = "Template %s does not exit" % template # Names of commands EoC recognizes in e-mail addresses. SIMPLE_COMMANDS = ["help", "list", "owner", "setlist", "setlistsilently", "ignore"] SUB_COMMANDS = ["subscribe", "unsubscribe"] HASH_COMMANDS = ["subyes", "subapprove", "subreject", "unsubyes", "bounce", "probe", "approve", "reject", "setlistyes", "setlistsilentyes"] COMMANDS = SIMPLE_COMMANDS + SUB_COMMANDS + HASH_COMMANDS def md5sum_as_hex(s): return md5.new(s).hexdigest() def forkexec(argv, text): """Run a command (given as argv array) and write text to its stdin""" (r, w) = os.pipe() pid = os.fork() if pid == -1: raise Exception("fork failed") elif pid == 0: os.dup2(r, 0) os.close(r) os.close(w) fd = os.open("/dev/null", os.O_RDWR) os.dup2(fd, 1) os.dup2(fd, 2) os.execvp(argv[0], argv) sys.exit(1) else: os.close(r) os.write(w, text) os.close(w) (pid2, exit) = os.waitpid(pid, 0) if pid != pid2: raise Exception("os.waitpid for %d returned for %d" % (pid, pid2)) if exit != 0: raise Exception("subprocess failed, exit=0x%x" % exit) return exit environ = None def set_environ(new_environ): global environ environ = new_environ def get_from_environ(key): global environ if environ: env = environ else: env = os.environ if env.has_key(key): return env[key].lower() raise MissingEnvironmentVariable(key) class AddressParser: """A parser for incoming e-mail addresses.""" def __init__(self, lists): self.set_lists(lists) self.set_skip_prefix(None) self.set_forced_domain(None) def set_lists(self, lists): """Set the list of canonical list names we should know about.""" self.lists = lists def set_skip_prefix(self, skip_prefix): """Set the prefix to be removed from an address.""" self.skip_prefix = skip_prefix def set_forced_domain(self, forced_domain): """Set the domain part we should force the address to have.""" self.forced_domain = forced_domain def clean(self, address): """Remove cruft from the address and convert the rest to lower case.""" if self.skip_prefix: n = self.skip_prefix and len(self.skip_prefix) if address[:n] == self.skip_prefix: address = address[n:] if self.forced_domain: parts = address.split("@", 1) address = "%s@%s" % (parts[0], self.forced_domain) return address.lower() def split_address(self, address): """Split an address to a local part and a domain.""" parts = address.lower().split("@", 1) if len(parts) != 2: return (address, "") else: return parts # Does an address refer to a list? If not, return None, else return a list # of additional parts (separated by hyphens) in the address. Note that [] # is not the same as None. def additional_address_parts(self, address, listname): addr_local, addr_domain = self.split_address(address) list_local, list_domain = self.split_address(listname) if addr_domain != list_domain: return None if addr_local.lower() == list_local.lower(): return [] n = len(list_local) if addr_local[:n] != list_local or addr_local[n] != "-": return None return addr_local[n+1:].split("-") # Parse an address we have received that identifies a list we manage. # The address may contain command and signature parts. Return the name # of the list, and a sequence of the additional parts (split at hyphens). # Raise exceptions for errors. Note that the command will be valid, but # cryptographic signatures in the address is not checked. def parse(self, address): address = self.clean(address) for listname in self.lists: parts = self.additional_address_parts(address, listname) if parts == None: pass elif parts == []: return listname, parts elif parts[0] in HASH_COMMANDS: if len(parts) != 3: raise BadCommandAddress(address) return listname, parts elif parts[0] in COMMANDS: return listname, parts raise UnknownList(address) class MailingListManager: def __init__(self, dotdir, sendmail="/usr/sbin/sendmail", lists=[], smtp_server=None, qmqp_server=None): self.dotdir = dotdir self.sendmail = sendmail self.smtp_server = smtp_server self.qmqp_server = qmqp_server self.make_dotdir() self.secret = self.make_and_read_secret() if not lists: lists = filter(lambda s: "@" in s, os.listdir(dotdir)) self.set_lists(lists) self.simple_commands = ["help", "list", "owner", "setlist", "setlistsilently", "ignore"] self.sub_commands = ["subscribe", "unsubscribe"] self.hash_commands = ["subyes", "subapprove", "subreject", "unsubyes", "bounce", "probe", "approve", "reject", "setlistyes", "setlistsilentyes"] self.commands = self.simple_commands + self.sub_commands + \ self.hash_commands self.environ = None self.load_plugins() # Create the dot directory for us, if it doesn't exist already. def make_dotdir(self): if not os.path.isdir(self.dotdir): os.makedirs(self.dotdir, 0700) # Create the "secret" file, with a random value used as cookie for # verification addresses. def make_and_read_secret(self): secret_name = os.path.join(self.dotdir, "secret") if not os.path.isfile(secret_name): f = open("/dev/urandom", "r") secret = f.read(32) f.close() f = open(secret_name, "w") f.write(secret) f.close() else: f = open(secret_name, "r") secret = f.read() f.close() return secret # Load the plugins from DOTDIR/plugins/*.py. def load_plugins(self): self.plugins = [] dirname = os.path.join(DOTDIR, "plugins") try: plugins = os.listdir(dirname) except OSError: return plugins.sort() plugins = map(os.path.splitext, plugins) plugins = filter(lambda p: p[1] == ".py", plugins) plugins = map(lambda p: p[0], plugins) for name in plugins: pathname = os.path.join(dirname, name + ".py") f = open(pathname, "r") module = imp.load_module(name, f, pathname, (".py", "r", imp.PY_SOURCE)) f.close() if module.PLUGIN_INTERFACE_VERSION == PLUGIN_INTERFACE_VERSION: self.plugins.append(module) # Call function named funcname (a string) in all plugins, giving as # arguments all the remaining arguments preceded by ml. Return value # of each function is the new list of arguments to the next function. # Return value of this function is the return value of the last function. def call_plugins(self, funcname, list, *args): for plugin in self.plugins: if plugin.__dict__.has_key(funcname): args = apply(plugin.__dict__[funcname], (list,) + args) if type(args) != type((0,)): args = (args,) return args # Set the list of listnames. The list of lists needs to be sorted in # length order so that test@example.com is matched before # test-list@example.com def set_lists(self, lists): temp = map(lambda s: (len(s), s), lists) temp.sort() self.lists = map(lambda t: t[1], temp) # Return the list of listnames. def get_lists(self): return self.lists # Decode an address that has been encoded to be part of a local part. def decode_address(self, parts): return string.join(string.join(parts, "-").split("="), "@") # Is local_part@domain an existing list? def is_list_name(self, local_part, domain): return ("%s@%s" % (local_part, domain)) in self.lists # Compute the verification checksum for an address. def compute_hash(self, address): return md5sum_as_hex(address + self.secret) # Is the verification signature in a parsed address bad? If so, return true, # otherwise return false. def signature_is_bad(self, dict, hash): local_part, domain = dict["name"].split("@") address = "%s-%s-%s@%s" % (local_part, dict["command"], dict["id"], domain) correct = self.compute_hash(address) return correct != hash # Parse a command address we have received and check its validity # (including signature, if any). Return a dictionary with keys # "command", "sender" (address that was encoded into address, if # any), "id" (group ID). def parse_recipient_address(self, address, skip_prefix, forced_domain): ap = AddressParser(self.get_lists()) ap.set_lists(self.get_lists()) ap.set_skip_prefix(skip_prefix) ap.set_forced_domain(forced_domain) listname, parts = ap.parse(address) dict = { "name": listname } if parts == []: dict["command"] = "post" else: command, args = parts[0], parts[1:] dict["command"] = command if command in SUB_COMMANDS: dict["sender"] = self.decode_address(args) elif command in HASH_COMMANDS: dict["id"] = args[0] hash = args[1] if self.signature_is_bad(dict, hash): raise BadSignature(address) return dict # Does an address refer to a mailing list? def is_list(self, name, skip_prefix=None, domain=None): try: self.parse_recipient_address(name, skip_prefix, domain) except BadCommandAddress: return 0 except BadSignature: return 0 except UnknownList: return 0 return 1 # Create a new list and return it. def create_list(self, name): if self.is_list(name): raise ListExists(name) self.set_lists(self.lists + [name]) return MailingList(self, name) # Open an existing list. def open_list(self, name): if self.is_list(name): return self.open_list_exact(name) else: x = name + "@" for list in self.lists: if list[:len(x)] == x: return self.open_list_exact(list) raise ListDoesNotExist(name) def open_list_exact(self, name): for list in self.get_lists(): if list.lower() == name.lower(): return MailingList(self, list) raise ListDoesNotExist(name) # Process an incoming message. def incoming_message(self, skip_prefix, domain, moderate, post): debug("Processing incoming message.") debug("$SENDER = <%s>" % get_from_environ("SENDER")) debug("$RECIPIENT = <%s>" % get_from_environ("RECIPIENT")) dict = self.parse_recipient_address(get_from_environ("RECIPIENT"), skip_prefix, domain) dict["force-moderation"] = moderate dict["force-posting"] = post debug("List is <%(name)s>, command is <%(command)s>." % dict) list = self.open_list_exact(dict["name"]) list.obey(dict) # Clean up bouncing address and do other janitorial work for all lists. def cleaning_woman(self, send_mail=None): now = time.time() for listname in self.lists: list = self.open_list_exact(listname) if send_mail: list.send_mail = send_mail list.cleaning_woman(now) # Send a mail to the desired recipients. def send_mail(self, envelope_sender, recipients, text): debug("send_mail:\n sender=%s\n recipients=%s\n text=\n %s" % (envelope_sender, str(recipients), "\n ".join(text[:text.find("\n\n")].split("\n")))) if recipients: if self.smtp_server: try: smtp = smtplib.SMTP(self.smtp_server) smtp.sendmail(envelope_sender, recipients, text) smtp.quit() except: error("Error sending SMTP mail, mail probably not sent") sys.exit(1) elif self.qmqp_server: try: q = qmqp.QMQP(self.qmqp_server) q.sendmail(envelope_sender, recipients, text) q.quit() except: error("Error sending QMQP mail, mail probably not sent") sys.exit(1) else: status = forkexec([self.sendmail, "-oi", "-f", envelope_sender] + recipients, text) if status: error("%s returned %s, mail sending probably failed" % (self.sendmail, status)) sys.exit((status >> 8) & 0xff) else: debug("send_mail: no recipients, not sending") class MailingList: posting_opts = ["auto", "free", "moderated"] def __init__(self, mlm, name): self.mlm = mlm self.name = name self.cp = ConfigParser.ConfigParser() self.cp.add_section("list") self.cp.set("list", "owners", "") self.cp.set("list", "moderators", "") self.cp.set("list", "subscription", "free") self.cp.set("list", "posting", "free") self.cp.set("list", "archived", "no") self.cp.set("list", "mail-on-subscription-changes", "no") self.cp.set("list", "mail-on-forced-unsubscribe", "no") self.cp.set("list", "ignore-bounce", "no") self.cp.set("list", "language", "") self.cp.set("list", "pristine-headers", "") self.dirname = os.path.join(self.mlm.dotdir, name) self.make_listdir() self.cp.read(self.mkname("config")) self.subscribers = SubscriberDatabase(self.dirname, "subscribers") self.moderation_box = MessageBox(self.dirname, "moderation-box") self.subscription_box = MessageBox(self.dirname, "subscription-box") self.bounce_box = MessageBox(self.dirname, "bounce-box") def make_listdir(self): if not os.path.isdir(self.dirname): os.mkdir(self.dirname, 0700) self.save_config() f = open(self.mkname("subscribers"), "w") f.close() def mkname(self, relative): return os.path.join(self.dirname, relative) def save_config(self): f = open(self.mkname("config"), "w") self.cp.write(f) f.close() def read_stdin(self): data = sys.stdin.read() # Convert CRLF to plain LF data = "\n".join(data.split("\r\n")) # Skip Unix mbox "From " mail start indicator if data[:5] == "From ": data = string.split(data, "\n", 1)[1] return data def invent_boundary(self): return "%s/%s" % (md5sum_as_hex(str(time.time())), md5sum_as_hex(self.name)) def command_address(self, command): local_part, domain = self.name.split("@") return "%s-%s@%s" % (local_part, command, domain) def signed_address(self, command, id): unsigned = self.command_address("%s-%s" % (command, id)) hash = self.mlm.compute_hash(unsigned) return self.command_address("%s-%s-%s" % (command, id, hash)) def ignore(self): return self.command_address("ignore") def nice_7bit(self, str): for c in str: if (ord(c) < 32 and not c.isspace()) or ord(c) >= 127: return False return True def mime_encode_headers(self, text): try: headers, body = text.split("\n\n", 1) list = [] for line in headers.split("\n"): if line[0].isspace(): list[-1] += line else: list.append(line) headers = [] for header in list: if self.nice_7bit(header): headers.append(header) else: if ": " in header: name, content = header.split(": ", 1) else: name, content = header.split(":", 1) hdr = email.Header.Header(content, "utf-8") headers.append(name + ": " + hdr.encode()) return "\n".join(headers) + "\n\n" + body except: info("Cannot MIME encode header, using original ones, sorry") return text def template(self, template_name, dict): lang = self.cp.get("list", "language") if lang: template_name_lang = template_name + "." + lang else: template_name_lang = template_name if not dict.has_key("list"): dict["list"] = self.name dict["local"], dict["domain"] = self.name.split("@") if not dict.has_key("list"): dict["list"] = self.name for dir in [os.path.join(self.dirname, "templates")] + TEMPLATE_DIRS: pathname = os.path.join(dir, template_name_lang) if not os.path.exists(pathname): pathname = os.path.join(dir, template_name) if os.path.exists(pathname): f = open(pathname, "r") data = f.read() f.close() return data % dict raise MissingTemplate(template_name) def send_template(self, envelope_sender, sender, recipients, template_name, dict): dict["From"] = "EoC <%s>" % sender dict["To"] = string.join(recipients, ", ") text = self.template(template_name, dict) if not text: return if self.cp.get("list", "pristine-headers") != "yes": text = self.mime_encode_headers(text) self.mlm.send_mail(envelope_sender, recipients, text) def send_info_message(self, recipients, template_name, dict): self.send_template(self.command_address("ignore"), self.command_address("help"), recipients, template_name, dict) def owners(self): return self.cp.get("list", "owners").split() def moderators(self): return self.cp.get("list", "moderators").split() def is_list_owner(self, address): return address in self.owners() def obey_help(self): self.send_info_message([get_from_environ("SENDER")], "help", {}) def obey_list(self): recipient = get_from_environ("SENDER") if self.is_list_owner(recipient): addr_list = self.subscribers.get_all() addr_text = string.join(addr_list, "\n") self.send_info_message([recipient], "list", { "addresses": addr_text, "count": len(addr_list), }) else: self.send_info_message([recipient], "list-sorry", {}) def obey_setlist(self, origmail): recipient = get_from_environ("SENDER") if self.is_list_owner(recipient): id = self.moderation_box.add(recipient, origmail) if self.parse_setlist_addresses(origmail) == None: self.send_bad_addresses_in_setlist(id) self.moderation_box.remove(id) else: confirm = self.signed_address("setlistyes", id) self.send_info_message(self.owners(), "setlist-confirm", { "confirm": confirm, "origmail": origmail, "boundary": self.invent_boundary(), }) else: self.send_info_message([recipient], "setlist-sorry", {}) def obey_setlistsilently(self, origmail): recipient = get_from_environ("SENDER") if self.is_list_owner(recipient): id = self.moderation_box.add(recipient, origmail) if self.parse_setlist_addresses(origmail) == None: self.send_bad_addresses_in_setlist(id) self.moderation_box.remove(id) else: confirm = self.signed_address("setlistsilentyes", id) self.send_info_message(self.owners(), "setlist-confirm", { "confirm": confirm, "origmail": origmail, "boundary": self.invent_boundary(), }) else: self.send_info_message([recipient], "setlist-sorry", {}) def parse_setlist_addresses(self, text): body = text.split("\n\n", 1)[1] lines = body.split("\n") lines = filter(lambda line: line != "", lines) badlines = filter(lambda line: "@" not in line, lines) if badlines: return None else: return lines def send_bad_addresses_in_setlist(self, id): addr = self.moderation_box.get_address(id) origmail = self.moderation_box.get(id) self.send_info_message([addr], "setlist-badlist", { "origmail": origmail, "boundary": self.invent_boundary(), }) def obey_setlistyes(self, dict): if self.moderation_box.has(dict["id"]): text = self.moderation_box.get(dict["id"]) addresses = self.parse_setlist_addresses(text) if addresses == None: self.send_bad_addresses_in_setlist(id) else: removed_subscribers = [] self.subscribers.lock() old = self.subscribers.get_all() for address in old: if address.lower() not in map(string.lower, addresses): self.subscribers.remove(address) removed_subscribers.append(address) else: for x in addresses: if x.lower() == address.lower(): addresses.remove(x) self.subscribers.add_many(addresses) self.subscribers.save() for recipient in addresses: self.send_info_message([recipient], "sub-welcome", {}) for recipient in removed_subscribers: self.send_info_message([recipient], "unsub-goodbye", {}) self.send_info_message(self.owners(), "setlist-done", {}) self.moderation_box.remove(dict["id"]) def obey_setlistsilentyes(self, dict): if self.moderation_box.has(dict["id"]): text = self.moderation_box.get(dict["id"]) addresses = self.parse_setlist_addresses(text) if addresses == None: self.send_bad_addresses_in_setlist(id) else: self.subscribers.lock() old = self.subscribers.get_all() for address in old: if address not in addresses: self.subscribers.remove(address) else: addresses.remove(address) self.subscribers.add_many(addresses) self.subscribers.save() self.send_info_message(self.owners(), "setlist-done", {}) self.moderation_box.remove(dict["id"]) def obey_owner(self, text): sender = get_from_environ("SENDER") recipients = self.cp.get("list", "owners").split() self.mlm.send_mail(sender, recipients, text) def obey_subscribe_or_unsubscribe(self, dict, template_name, command, origmail): requester = get_from_environ("SENDER") subscriber = dict["sender"] if not subscriber: subscriber = requester if subscriber.find("@") == -1: info("Trying to (un)subscribe address without @: %s" % subscriber) return if self.cp.get("list", "ignore-bounce") == "yes": info("Will not (un)subscribe address: %s from static list" %subscriber) return if requester in self.owners(): confirmers = self.owners() else: confirmers = [subscriber] id = self.subscription_box.add(subscriber, origmail) confirm = self.signed_address(command, id) self.send_info_message(confirmers, template_name, { "confirm": confirm, "origmail": origmail, "boundary": self.invent_boundary(), }) def obey_subscribe(self, dict, origmail): self.obey_subscribe_or_unsubscribe(dict, "sub-confirm", "subyes", origmail) def obey_unsubscribe(self, dict, origmail): self.obey_subscribe_or_unsubscribe(dict, "unsub-confirm", "unsubyes", origmail) def obey_subyes(self, dict): if self.subscription_box.has(dict["id"]): if self.cp.get("list", "subscription") == "free": recipient = self.subscription_box.get_address(dict["id"]) self.subscribers.lock() self.subscribers.add(recipient) self.subscribers.save() sender = self.command_address("help") self.send_template(self.ignore(), sender, [recipient], "sub-welcome", {}) self.subscription_box.remove(dict["id"]) if self.cp.get("list", "mail-on-subscription-changes")=="yes": self.send_info_message(self.owners(), "sub-owner-notification", { "address": recipient, }) else: recipients = self.cp.get("list", "owners").split() confirm = self.signed_address("subapprove", dict["id"]) deny = self.signed_address("subreject", dict["id"]) subscriber = self.subscription_box.get_address(dict["id"]) origmail = self.subscription_box.get(dict["id"]) self.send_template(self.ignore(), deny, recipients, "sub-moderate", { "confirm": confirm, "deny": deny, "subscriber": subscriber, "origmail": origmail, "boundary": self.invent_boundary(), }) recipient = self.subscription_box.get_address(dict["id"]) self.send_info_message([recipient], "sub-wait", {}) def obey_subapprove(self, dict): if self.subscription_box.has(dict["id"]): recipient = self.subscription_box.get_address(dict["id"]) self.subscribers.lock() self.subscribers.add(recipient) self.subscribers.save() self.send_info_message([recipient], "sub-welcome", {}) self.subscription_box.remove(dict["id"]) if self.cp.get("list", "mail-on-subscription-changes")=="yes": self.send_info_message(self.owners(), "sub-owner-notification", { "address": recipient, }) def obey_subreject(self, dict): if self.subscription_box.has(dict["id"]): recipient = self.subscription_box.get_address(dict["id"]) self.send_info_message([recipient], "sub-reject", {}) self.subscription_box.remove(dict["id"]) def obey_unsubyes(self, dict): if self.subscription_box.has(dict["id"]): recipient = self.subscription_box.get_address(dict["id"]) self.subscribers.lock() self.subscribers.remove(recipient) self.subscribers.save() self.send_info_message([recipient], "unsub-goodbye", {}) self.subscription_box.remove(dict["id"]) if self.cp.get("list", "mail-on-subscription-changes")=="yes": self.send_info_message(self.owners(), "unsub-owner-notification", { "address": recipient, }) def store_into_archive(self, text): if self.cp.get("list", "archived") == "yes": archdir = os.path.join(self.dirname, "archive") if not os.path.exists(archdir): os.mkdir(archdir, 0700) id = md5sum_as_hex(text) f = open(os.path.join(archdir, id), "w") f.write(text) f.close() def list_headers(self): local, domain = self.name.split("@") list = [] list.append("List-Id: <%s.%s>" % (local, domain)) list.append("List-Help: " % (local, domain)) list.append("List-Unsubscribe: " % (local, domain)) list.append("List-Subscribe: " % (local, domain)) list.append("List-Post: " % (local, domain)) list.append("List-Owner: " % (local, domain)) list.append("Precedence: bulk"); return string.join(list, "\n") + "\n" def read_file(self, basename): try: f = open(os.path.join(self.dirname, basename), "r") data = f.read() f.close() return data except IOError: return "" def headers_to_add(self): headers_to_add = self.read_file("headers-to-add").rstrip() if headers_to_add: return headers_to_add + "\n" else: return "" def remove_some_headers(self, mail, headers_to_remove): endpos = mail.find("\n\n") if endpos == -1: endpos = mail.find("\n\r\n") if endpos == -1: return mail headers = mail[:endpos].split("\n") body = mail[endpos:] headers_to_remove = [x.lower() for x in headers_to_remove] remaining = [] add_continuation_lines = 0 for header in headers: if header[0] in [' ','\t']: # this is a continuation line if add_continuation_lines: remaining.append(header) else: pos = header.find(":") if pos == -1: # malformed message, try to remove the junk add_continuation_lines = 0 continue name = header[:pos].lower() if name in headers_to_remove: add_continuation_lines = 0 else: add_continuation_lines = 1 remaining.append(header) return "\n".join(remaining) + body def headers_to_remove(self, text): headers_to_remove = self.read_file("headers-to-remove").split("\n") headers_to_remove = map(lambda s: s.strip().lower(), headers_to_remove) return self.remove_some_headers(text, headers_to_remove) def append_footer(self, text): if "base64" in text or "BASE64" in text: import StringIO for line in StringIO.StringIO(text): if line.lower().startswith("content-transfer-encoding:") and \ "base64" in line.lower(): return text return text + self.template("footer", {}) def send_mail_to_subscribers(self, text): text = self.remove_some_headers(text, ["list-id", "list-help", "list-unsubscribe", "list-subscribe", "list-post", "list-owner", "precedence"]) text = self.headers_to_add() + self.list_headers() + \ self.headers_to_remove(text) text = self.append_footer(text) text, = self.mlm.call_plugins("send_mail_to_subscribers_hook", self, text) if have_email_module and \ self.cp.get("list", "pristine-headers") != "yes": text = self.mime_encode_headers(text) self.store_into_archive(text) for group in self.subscribers.groups(): bounce = self.signed_address("bounce", group) addresses = self.subscribers.in_group(group) self.mlm.send_mail(bounce, addresses, text) def post_into_moderate(self, poster, dict, text): id = self.moderation_box.add(poster, text) recipients = self.moderators() if recipients == []: recipients = self.owners() confirm = self.signed_address("approve", id) deny = self.signed_address("reject", id) self.send_template(self.ignore(), deny, recipients, "msg-moderate", { "confirm": confirm, "deny": deny, "origmail": text, "boundary": self.invent_boundary(), }) self.send_info_message([poster], "msg-wait", {}) def should_be_moderated(self, posting, poster): if posting == "moderated": return 1 if posting == "auto": if poster.lower() not in \ map(string.lower, self.subscribers.get_all()): return 1 return 0 def obey_post(self, dict, text): if dict.has_key("force-moderation") and dict["force-moderation"]: force_moderation = 1 else: force_moderation = 0 if dict.has_key("force-posting") and dict["force-posting"]: force_posting = 1 else: force_posting = 0 posting = self.cp.get("list", "posting") if posting not in self.posting_opts: error("You have a weird 'posting' config. Please, review it") poster = get_from_environ("SENDER") if force_moderation: self.post_into_moderate(poster, dict, text) elif force_posting: self.send_mail_to_subscribers(text) elif self.should_be_moderated(posting, poster): self.post_into_moderate(poster, dict, text) else: self.send_mail_to_subscribers(text) def obey_approve(self, dict): if self.moderation_box.lock(dict["id"]): if self.moderation_box.has(dict["id"]): text = self.moderation_box.get(dict["id"]) self.send_mail_to_subscribers(text) self.moderation_box.remove(dict["id"]) self.moderation_box.unlock(dict["id"]) def obey_reject(self, dict): if self.moderation_box.lock(dict["id"]): if self.moderation_box.has(dict["id"]): self.moderation_box.remove(dict["id"]) self.moderation_box.unlock(dict["id"]) def split_address_list(self, addrs): domains = {} for addr in addrs: userpart, domain = addr.split("@") if domains.has_key(domain): domains[domain].append(addr) else: domains[domain] = [addr] result = [] if len(domains.keys()) == 1: for addr in addrs: result.append([addr]) else: result = domains.values() return result def obey_bounce(self, dict, text): if self.subscribers.has_group(dict["id"]): self.subscribers.lock() addrs = self.subscribers.in_group(dict["id"]) if len(addrs) == 1: if self.cp.get("list", "ignore-bounce") == "yes": info("Address <%s> bounced, ignoring bounce as configured." % addrs[0]) self.subscribers.unlock() return debug("Address <%s> bounced, setting state to bounce." % addrs[0]) bounce_id = self.bounce_box.add(addrs[0], text[:4096]) self.subscribers.set(dict["id"], "status", "bounced") self.subscribers.set(dict["id"], "timestamp-bounced", "%f" % time.time()) self.subscribers.set(dict["id"], "bounce-id", bounce_id) else: debug("Group %s bounced, splitting." % dict["id"]) for new_addrs in self.split_address_list(addrs): self.subscribers.add_many(new_addrs) self.subscribers.remove_group(dict["id"]) self.subscribers.save() else: debug("Ignoring bounce, group %s doesn't exist (anymore?)." % dict["id"]) def obey_probe(self, dict, text): id = dict["id"] if self.subscribers.has_group(id): self.subscribers.lock() if self.subscribers.get(id, "status") == "probed": self.subscribers.set(id, "status", "probebounced") self.subscribers.save() def obey(self, dict): text = self.read_stdin() if dict["command"] in ["help", "list", "subscribe", "unsubscribe", "subyes", "subapprove", "subreject", "unsubyes", "post", "approve"]: sender = get_from_environ("SENDER") if not sender: debug("Ignoring bounce message for %s command." % dict["command"]) return if dict["command"] == "help": self.obey_help() elif dict["command"] == "list": self.obey_list() elif dict["command"] == "owner": self.obey_owner(text) elif dict["command"] == "subscribe": self.obey_subscribe(dict, text) elif dict["command"] == "unsubscribe": self.obey_unsubscribe(dict, text) elif dict["command"] == "subyes": self.obey_subyes(dict) elif dict["command"] == "subapprove": self.obey_subapprove(dict) elif dict["command"] == "subreject": self.obey_subreject(dict) elif dict["command"] == "unsubyes": self.obey_unsubyes(dict) elif dict["command"] == "post": self.obey_post(dict, text) elif dict["command"] == "approve": self.obey_approve(dict) elif dict["command"] == "reject": self.obey_reject(dict) elif dict["command"] == "bounce": self.obey_bounce(dict, text) elif dict["command"] == "probe": self.obey_probe(dict, text) elif dict["command"] == "setlist": self.obey_setlist(text) elif dict["command"] == "setlistsilently": self.obey_setlistsilently(text) elif dict["command"] == "setlistyes": self.obey_setlistyes(dict) elif dict["command"] == "setlistsilentyes": self.obey_setlistsilentyes(dict) elif dict["command"] == "ignore": pass def get_bounce_text(self, id): bounce_id = self.subscribers.get(id, "bounce-id") if self.bounce_box.has(bounce_id): bounce_text = self.bounce_box.get(bounce_id) bounce_text = string.join(map(lambda s: "> " + s + "\n", bounce_text.split("\n")), "") else: bounce_text = "Bounce message not available." return bounce_text one_week = 7.0 * 24.0 * 60.0 * 60.0 def handle_bounced_groups(self, now): for id in self.subscribers.groups(): status = self.subscribers.get(id, "status") t = float(self.subscribers.get(id, "timestamp-bounced")) if status == "bounced": if now - t > self.one_week: sender = self.signed_address("probe", id) recipients = self.subscribers.in_group(id) self.send_template(sender, sender, recipients, "bounce-warning", { "bounce": self.get_bounce_text(id), "boundary": self.invent_boundary(), }) self.subscribers.set(id, "status", "probed") elif status == "probed": if now - t > 2 * self.one_week: debug(("Cleaning woman: probe didn't bounce " + "for group <%s>, setting status to ok.") % id) self.subscribers.set(id, "status", "ok") self.bounce_box.remove( self.subscribers.get(id, "bounce-id")) elif status == "probebounced": sender = self.command_address("help") for address in self.subscribers.in_group(id): if self.cp.get("list", "mail-on-forced-unsubscribe") \ == "yes": self.send_template(sender, sender, self.owners(), "bounce-owner-notification", { "address": address, "bounce": self.get_bounce_text(id), "boundary": self.invent_boundary(), }) self.bounce_box.remove( self.subscribers.get(id, "bounce-id")) self.subscribers.remove(address) debug("Cleaning woman: removing <%s>." % address) self.send_template(sender, sender, [address], "bounce-goodbye", {}) def join_nonbouncing_groups(self, now): to_be_joined = [] for id in self.subscribers.groups(): status = self.subscribers.get(id, "status") age1 = now - float(self.subscribers.get(id, "timestamp-bounced")) age2 = now - float(self.subscribers.get(id, "timestamp-created")) if status == "ok": if age1 > self.one_week and age2 > self.one_week: to_be_joined.append(id) if to_be_joined: addrs = [] for id in to_be_joined: addrs = addrs + self.subscribers.in_group(id) self.subscribers.add_many(addrs) for id in to_be_joined: self.bounce_box.remove(self.subscribers.get(id, "bounce-id")) self.subscribers.remove_group(id) def remove_empty_groups(self): for id in self.subscribers.groups()[:]: if len(self.subscribers.in_group(id)) == 0: self.subscribers.remove_group(id) def cleaning_woman(self, now): if self.subscribers.lock(): self.handle_bounced_groups(now) self.join_nonbouncing_groups(now) self.subscribers.save() class SubscriberDatabase: def __init__(self, dirname, name): self.dict = {} self.filename = os.path.join(dirname, name) self.lockname = os.path.join(dirname, "lock") self.loaded = 0 self.locked = 0 def lock(self): if os.system("lockfile -l 60 %s" % self.lockname) == 0: self.locked = 1 self.load() return self.locked def unlock(self): os.remove(self.lockname) self.locked = 0 def load(self): if not self.loaded and not self.dict: f = open(self.filename, "r") for line in f.xreadlines(): parts = line.split() self.dict[parts[0]] = { "status": parts[1], "timestamp-created": parts[2], "timestamp-bounced": parts[3], "bounce-id": parts[4], "addresses": parts[5:], } f.close() self.loaded = 1 def save(self): assert self.locked assert self.loaded f = open(self.filename + ".new", "w") for id in self.dict.keys(): f.write("%s " % id) f.write("%s " % self.dict[id]["status"]) f.write("%s " % self.dict[id]["timestamp-created"]) f.write("%s " % self.dict[id]["timestamp-bounced"]) f.write("%s " % self.dict[id]["bounce-id"]) f.write("%s\n" % string.join(self.dict[id]["addresses"], " ")) f.close() os.remove(self.filename) os.rename(self.filename + ".new", self.filename) self.unlock() def get(self, id, attribute): self.load() if self.dict.has_key(id) and self.dict[id].has_key(attribute): return self.dict[id][attribute] return None def set(self, id, attribute, value): assert self.locked self.load() if self.dict.has_key(id) and self.dict[id].has_key(attribute): self.dict[id][attribute] = value def add(self, address): return self.add_many([address]) def add_many(self, addresses): assert self.locked assert self.loaded for addr in addresses[:]: if addr.find("@") == -1: info("Address '%s' does not contain an @, ignoring it." % addr) addresses.remove(addr) for id in self.dict.keys(): old_ones = self.dict[id]["addresses"] for addr in addresses: for x in old_ones: if x.lower() == addr.lower(): old_ones.remove(x) self.dict[id]["addresses"] = old_ones id = self.new_group() self.dict[id] = { "status": "ok", "timestamp-created": self.timestamp(), "timestamp-bounced": "0", "bounce-id": "..notexist..", "addresses": addresses, } return id def new_group(self): keys = self.dict.keys() if keys: keys = map(lambda x: int(x), keys) keys.sort() return "%d" % (keys[-1] + 1) else: return "0" def timestamp(self): return "%.0f" % time.time() def get_all(self): self.load() list = [] for values in self.dict.values(): list = list + values["addresses"] return list def groups(self): self.load() return self.dict.keys() def has_group(self, id): self.load() return self.dict.has_key(id) def in_group(self, id): self.load() return self.dict[id]["addresses"] def remove(self, address): assert self.locked self.load() for id in self.dict.keys(): group = self.dict[id] for x in group["addresses"][:]: if x.lower() == address.lower(): group["addresses"].remove(x) if len(group["addresses"]) == 0: del self.dict[id] def remove_group(self, id): assert self.locked self.load() del self.dict[id] class MessageBox: def __init__(self, dirname, boxname): self.boxdir = os.path.join(dirname, boxname) if not os.path.isdir(self.boxdir): os.mkdir(self.boxdir, 0700) def filename(self, id): return os.path.join(self.boxdir, id) def add(self, address, message_text): id = self.make_id(message_text) filename = self.filename(id) f = open(filename + ".address", "w") f.write(address) f.close() f = open(filename + ".new", "w") f.write(message_text) f.close() os.rename(filename + ".new", filename) return id def make_id(self, message_text): return md5sum_as_hex(message_text) # XXX this might be unnecessarily long def remove(self, id): filename = self.filename(id) if os.path.isfile(filename): os.remove(filename) os.remove(filename + ".address") def has(self, id): return os.path.isfile(self.filename(id)) def get_address(self, id): f = open(self.filename(id) + ".address", "r") data = f.read() f.close() return data.strip() def get(self, id): f = open(self.filename(id), "r") data = f.read() f.close() return data def lockname(self, id): return self.filename(id) + ".lock" def lock(self, id): if os.system("lockfile -l 600 %s" % self.lockname(id)) == 0: return 1 else: return 0 def unlock(self, id): try: os.remove(self.lockname(id)) except os.error: pass class DevNull: def write(self, str): pass log_file_handle = None def log_file(): global log_file_handle if log_file_handle is None: try: log_file_handle = open(os.path.join(DOTDIR, "logfile.txt"), "a") except: log_file_handle = DevNull() return log_file_handle def timestamp(): tuple = time.localtime(time.time()) return time.strftime("%Y-%m-%d %H:%M:%S", tuple) + " [%d]" % os.getpid() quiet = 0 # No logging to stderr of debug messages. Some MTAs have a limit on how # much data they accept via stderr and debug logs will fill that quickly. def debug(msg): log_file().write(timestamp() + " " + msg + "\n") # Log to log file first, in case MTA's stderr buffer fills up and we lose # logs. def info(msg): log_file().write(timestamp() + " " + msg + "\n") sys.stderr.write(msg + "\n") def error(msg): info(msg) sys.exit(1) def usage(): sys.stdout.write("""\ Usage: enemies-of-carlotta [options] command Mailing list manager. Options: --name=listname@domain --owner=address@domain --moderator=address@domain --subscription=free/moderated --posting=free/moderated/auto --archived=yes/no --ignore-bounce=yes/no --language=language code or empty --mail-on-forced-unsubscribe=yes/no --mail-on-subscription-changes=yes/no --skip-prefix=string --domain=domain.name --smtp-server=domain.name --quiet --moderate Commands: --help --create --subscribe --unsubscribe --list --is-list --edit --incoming --cleaning-woman --show-lists For more detailed information, please read the enemies-of-carlotta(1) manual page. """) sys.exit(0) def no_act_send_mail(sender, recipients, text): print "NOT SENDING MAIL FOR REAL!" print "Sender:", sender print "Recipients:", recipients print "Mail:" print "\n".join(map(lambda s: " " + s, text.split("\n"))) def set_list_options(list, owners, moderators, subscription, posting, archived, language, ignore_bounce, mail_on_sub_changes, mail_on_forced_unsub): if owners: list.cp.set("list", "owners", string.join(owners, " ")) if moderators: list.cp.set("list", "moderators", string.join(moderators, " ")) if subscription != None: list.cp.set("list", "subscription", subscription) if posting != None: list.cp.set("list", "posting", posting) if archived != None: list.cp.set("list", "archived", archived) if language != None: list.cp.set("list", "language", language) if ignore_bounce != None: list.cp.set("list", "ignore-bounce", ignore_bounce) if mail_on_sub_changes != None: list.cp.set("list", "mail-on-subscription-changes", mail_on_sub_changes) if mail_on_forced_unsub != None: list.cp.set("list", "mail-on-forced-unsubscribe", mail_on_forced_unsub) def main(args): try: opts, args = getopt.getopt(args, "h", ["name=", "owner=", "moderator=", "subscription=", "posting=", "archived=", "language=", "ignore-bounce=", "mail-on-forced-unsubscribe=", "mail-on-subscription-changes=", "skip-prefix=", "domain=", "sendmail=", "smtp-server=", "qmqp-server=", "quiet", "moderate", "post", "sender=", "recipient=", "no-act", "set", "get", "help", "create", "destroy", "subscribe", "unsubscribe", "list", "is-list", "edit", "incoming", "cleaning-woman", "show-lists", "version", ]) except getopt.GetoptError, detail: error("Error parsing command line options (see --help):\n%s" % detail) operation = None list_name = None owners = [] moderators = [] subscription = None posting = None archived = None ignore_bounce = None skip_prefix = None domain = None sendmail = "/usr/sbin/sendmail" smtp_server = None qmqp_server = None moderate = 0 post = 0 sender = None recipient = None language = None mail_on_forced_unsub = None mail_on_sub_changes = None no_act = 0 global quiet for opt, arg in opts: if opt == "--name": list_name = arg elif opt == "--owner": owners.append(arg) elif opt == "--moderator": moderators.append(arg) elif opt == "--subscription": subscription = arg elif opt == "--posting": posting = arg elif opt == "--archived": archived = arg elif opt == "--ignore-bounce": ignore_bounce = arg elif opt == "--skip-prefix": skip_prefix = arg elif opt == "--domain": domain = arg elif opt == "--sendmail": sendmail = arg elif opt == "--smtp-server": smtp_server = arg elif opt == "--qmqp-server": qmqp_server = arg elif opt == "--sender": sender = arg elif opt == "--recipient": recipient = arg elif opt == "--language": language = arg elif opt == "--mail-on-forced-unsubscribe": mail_on_forced_unsub = arg elif opt == "--mail-on-subscription-changes": mail_on_sub_changes = arg elif opt == "--moderate": moderate = 1 elif opt == "--post": post = 1 elif opt == "--quiet": quiet = 1 elif opt == "--no-act": no_act = 1 else: operation = opt if operation is None: error("No operation specified, see --help.") if list_name is None and operation not in ["--incoming", "--help", "-h", "--cleaning-woman", "--show-lists", "--version"]: error("%s requires a list name specified with --name" % operation) if operation in ["--help", "-h"]: usage() if sender or recipient: environ = os.environ.copy() if sender: environ["SENDER"] = sender if recipient: environ["RECIPIENT"] = recipient set_environ(environ) mlm = MailingListManager(DOTDIR, sendmail=sendmail, smtp_server=smtp_server, qmqp_server=qmqp_server) if no_act: mlm.send_mail = no_act_send_mail if operation == "--create": if not owners: error("You must give at least one list owner with --owner.") list = mlm.create_list(list_name) set_list_options(list, owners, moderators, subscription, posting, archived, language, ignore_bounce, mail_on_sub_changes, mail_on_forced_unsub) list.save_config() debug("Created list %s." % list_name) elif operation == "--destroy": shutil.rmtree(os.path.join(DOTDIR, list_name)) debug("Removed list %s." % list_name) elif operation == "--edit": list = mlm.open_list(list_name) set_list_options(list, owners, moderators, subscription, posting, archived, language, ignore_bounce, mail_on_sub_changes, mail_on_forced_unsub) list.save_config() elif operation == "--subscribe": list = mlm.open_list(list_name) list.subscribers.lock() for address in args: if address.find("@") == -1: error("Address '%s' does not contain an @." % address) list.subscribers.add(address) debug("Added subscriber <%s>." % address) list.subscribers.save() elif operation == "--unsubscribe": list = mlm.open_list(list_name) list.subscribers.lock() for address in args: list.subscribers.remove(address) debug("Removed subscriber <%s>." % address) list.subscribers.save() elif operation == "--list": list = mlm.open_list(list_name) for address in list.subscribers.get_all(): print address elif operation == "--is-list": if mlm.is_list(list_name, skip_prefix, domain): debug("Indeed a mailing list: <%s>" % list_name) else: debug("Not a mailing list: <%s>" % list_name) sys.exit(1) elif operation == "--incoming": mlm.incoming_message(skip_prefix, domain, moderate, post) elif operation == "--cleaning-woman": mlm.cleaning_woman() elif operation == "--show-lists": listnames = mlm.get_lists() listnames.sort() for listname in listnames: print listname elif operation == "--get": list = mlm.open_list(list_name) for name in args: print list.cp.get("list", name) elif operation == "--set": list = mlm.open_list(list_name) for arg in args: if "=" not in arg: error("Error: --set arguments must be of form name=value") name, value = arg.split("=", 1) list.cp.set("list", name, value) list.save_config() elif operation == "--version": print "EoC, version %s" % VERSION print "Home page: http://liw.iki.fi/liw/eoc/" else: error("Internal error: unimplemented option <%s>." % operation) if __name__ == "__main__": try: main(sys.argv[1:]) except EocException, detail: error("Error: %s" % detail) enemies-of-carlotta-1.2.6/fix-config0000755000175000017500000000021710577453116015516 0ustar liwliw#!/bin/sh sed 's:^TEMPLATE_DIRS *=.*:TEMPLATE_DIRS = ["'$1'"]:' | sed 's:^DOTDIR *=.*:DOTDIR = os.path.expanduser("~/.enemies-of-carlotta"):' enemies-of-carlotta-1.2.6/templates/0000775000175000017500000000000010577453117015540 5ustar liwliwenemies-of-carlotta-1.2.6/templates/bounce-goodbye0000664000175000017500000000113510577453116020363 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Your mail bounces, goodbye from %(list)s Content-type: text/plain; charset=us-ascii MIME-Version: 1.0 Hello, You have been a subscriber to the %(list)s mailing list. Unfortunately, mail sent to you by the mailing list manager has bounced so much that you have been automatically removed. Once your mail problems are solved, feel free to re-subscribe. For instructions on using the mailing list manager software, send mail to %(local)s-help@%(domain)s . If you have problems, please contact the human owners of the list at %(local)s-owner@%(domain)s . Thank you. enemies-of-carlotta-1.2.6/templates/sub-owner-notification0000664000175000017500000000071110577453117022067 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Address added to %(list)s Content-type: text/plain; charset=us-ascii MIME-Version: 1.0 Hello, This mail has been sent by the mailing list manager operating the %(list)s mailing list to the human owners of the list. The following address has been added to the list: %(address)s Unless there is something really strange going on, this mail is only for your information and you need take no action. Thanks. enemies-of-carlotta-1.2.6/templates/bounce-warning0000664000175000017500000000171510577453116020404 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Your mail is bouncing, %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=us-ascii Hello, You are subscribed to the %(list)s mailig list. Mail sent to you by the mailing list software has bounced at least once in the past week or so. If this was a temporary error, you can ignore this warning. However, if your mail continues to bounce, you will eventually be automatically unsubscribed from the list. Sorry about the inconvenience. The first bounce message is attached. For instructions on using the mailing list manager software, send mail to %(local)s-help@%(domain)s . If you have problems, please contact the human owners of the list at %(local)s-owner@%(domain)s . Thank you. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=bounce.txt %(bounce)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/list-sorry.sv0000664000175000017500000000030210577453117020234 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Begäran om prenumerantlista för %(list)s nekad Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Tyvärr, du är inte ägaren till listan %(list)s. enemies-of-carlotta-1.2.6/templates/list-sorry.de0000664000175000017500000000036110577453117020201 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Kein Zugriff auf Abonnentenliste der Liste %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Da Sie nicht Verwalter der Liste %(list)s sind können Sie die Abonnentenliste nicht einsehen. enemies-of-carlotta-1.2.6/templates/setlist-confirm.es0000664000175000017500000000232110577453117021211 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Por favor, modere la lista de suscriptores de %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hola, Este mensaje se lo ha enviado el gestor de listas de correo que opera sobre %(list)s . Uno de los miembros de la lista (o alguien que pretende serlo) ha pedido que se cambie el registro de suscriptores. Se adjunta el mensaje de la petición, con el nuevo conjunto de suscriptores. Si acepta el cambio, responda a este mensaje. En caso contrario, limítese a ignorarlo. Tenga en cuenta que se cambiará la lista de suscriptores completa por la lista que aparece debajo. Olvidaré todos los suscriptores antiguos a menos que también estén en la lista nueva. Si desea instrucciones sobre el uso del software gestor de listas, envíe un mensaje a %(local)s-help@%(domain)s . Si tiene problemas, por favor, póngase en contacto con las personas que gestionan la lista en %(local)s-owner@%(domain)s . Gracias. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/sub-owner-notification.fi0000664000175000017500000000062410577453117022467 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Tilaaja lisätty: %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Tämän viestin on lähettänyt postituslistaa %(list)s käsittelevä ohjelmisto listan omistajille. Seuraava osoite on lisätty listalle: %(address)s Tämä tiedoksi teille. Teidän ei tarvitse tehdä mitään asialle, paitsi jos jotain oikein omituista on tapahtunut. enemies-of-carlotta-1.2.6/templates/footer.sv0000664000175000017500000000014010577453116017402 0ustar liwliw -- Om du vill avsluta prenumerationen, skicka ett brev till %(local)s-unsubscribe@%(domain)s. enemies-of-carlotta-1.2.6/templates/bounce-warning.de0000664000175000017500000000212410577453116020766 0ustar liwliwFrom: %(From)s To: %(To)s Subject: E-Mail an Sie von %(list)s kann nicht ausgestellt werden Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hallo, Sie sind auf der Liste %(list)s angemeldet. E-Mail von der Liste konnte mindestens einmal in der letzten Woche nicht zugestellt werden. Falls nur ein temporärer Fehler vorlag können Sie diese E-Mail einfach ignorieren, falls andererseits weiterhin Zustellprobleme auftreten werden Sie automatisch aus der Liste entfernt werden. Bitte entschuldigen Sie die evtl. daraus entstehenden Unannehmlichkeiten. Die erste Zustell-Fehlermeldung ist angehängt. Um eine Anleitung zur Benutzung der Listen-Software zu erhalten, senden Sie bitte eine E-Mail an %(local)s-help@%(domain)s. Falls Sie Hilfe benötigen, können Sie auch an den Verwalter der Liste unter %(local)s-owner@%(domain)s schreiben. Vielen Dank. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=bounce.txt %(bounce)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/sub-wait.de0000664000175000017500000000130710577453117017606 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Bitte warten Sie auf Moderation Ihres Abonnements der Liste %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hallo, Diese E-Mail wurde von der Listen-Software der Liste $(list)s versendet. Sie haben bestätigt, dass Sie die Liste abonnieren möchten. Abonnement dieser List ist moderiert, daher muss der Verwalter der Liste erst zustimmen. Er wurde darum gebeten, dies kann aber eine Weile dauern. Um eine Anleitung zur Benutzung der Listen-Software zu erhalten, senden Sie bitte eine E-Mail an %(local)s-help@%(domain)s. Falls Sie Hilfe benötigen, können Sie auch an den Verwalter der Liste unter %(local)s-owner@%(domain)s schreiben. Vielen Dank. enemies-of-carlotta-1.2.6/templates/setlist-confirm.fr0000664000175000017500000000255410577453117021221 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Veuillez modérer la liste des abonnés pour %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Bonjour, Ce courriel a été envoyé par le gestionnaire de la liste de diffusion %(list)s . L'un des propriétaires de la liste (ou quelqu'un se faisant passer pour l'un d'entres eux) a demandé à ce que la liste des abonnés de cette liste soit modifiée. Le courriel en question, avec la nouvelle liste des abonnés se trouve ci-dessous. Si vous acceptez les changements, veuillez répondre à ce courriel. Dans le cas contraire, ignorez-le. Veuillez noter que la totalité des abonnés à la liste sera remplacée par la liste des adresses ci-dessous. Tous les anciens abonnements seront supprimés, à moins qu'il ne se trouve dans la nouvelle liste. Pour obtenir des informations sur l'utilisation du logiciel de gestion de la liste de diffusion, envoyez un courriel à %(local)s-help@%(domain)s . Si vous rencontrez des problèmes, veuillez prendre contact avec la personne responsable de la liste de diffusion à l'adresse : %(local)s-owner@%(domain)s . Merci. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/footer.fr0000664000175000017500000000012010577453116017357 0ustar liwliw -- Pour ne plus recevoir de message : mailto:%(local)s-unsubscribe@%(domain)s enemies-of-carlotta-1.2.6/templates/setlist-sorry.es0000664000175000017500000000031510577453117020733 0ustar liwliwFrom: %(From)s To: %(To)s Subject: No puede cambiar la lista de suscriptores de %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Lo siento, no está en la lista de gestores de %(list)s. enemies-of-carlotta-1.2.6/templates/sub-moderate.es0000664000175000017500000000140510577453117020460 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Por favor, modere esta suscripción a %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hola, Este mensaje lo ha enviado el gestor que opera la lista de correo %(list)s a sus administradores humanos. ¿Debería permitirse a la siguiente dirección suscribirse a la lista? %(subscriber)s En caso afirmativo, responda a este mensaje o envíe uno a %(confirm)s Si desea rechazar al suscriptor, envíe un mensaje a %(deny)s Gracias. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/footer0000664000175000017500000000010410577453116016753 0ustar liwliw -- To unsubscribe, send mail to %(local)s-unsubscribe@%(domain)s. enemies-of-carlotta-1.2.6/templates/sub-owner-notification.fr0000664000175000017500000000100410577453117022471 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Adresse ajoutée à %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Bonjour, Ce courriel a été envoyé par le gestionnaire de la liste de diffusion %(list)s aux personnes qui sont responsables de la liste. L'adresse suivante a été ajoutée à la liste : %(address)s À moins que quelque chose de particulier vous semble suspect, ce message vous est uniquement envoyé à titre informatif et ne nécessite pas traitement particulier. Merci. enemies-of-carlotta-1.2.6/templates/bounce-owner-notification.fr0000664000175000017500000000156410577453116023165 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Adresse supprimée de la liste %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Bonjour, Ce courriel a été envoyé par le gestionnaire de la liste de diffusion %(list)s aux personnes qui sont responsables de la liste. L'adresse suivante a été enlevée de la liste en raison de problème dans la livraison du courriel destiné à : %(address)s Le message qui n'a pu être délivré se trouve en bas de ce courriel. À moins qu'il n'y ait quelque chose de vraiment particulier, ce courriel n'est présent qu'à titre informatif; vous n'avez pas à vous en souciez. Merci. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=bounce.txt %(bounce)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/bounce-owner-notification0000664000175000017500000000141310577453116022550 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Address removed from %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=us-ascii Hello, This mail has been sent by the mailing list manager operating the %(list)s mailing list to the human owners of the list. The following address has been removed from the list due to bouncing: %(address)s The bounce message is attached at the bottom of this mail. Unless there is something really strange going on, this mail is only for your information and you need take no action. Thanks. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=bounce.txt %(bounce)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/footer.es0000664000175000017500000000011510577453116017363 0ustar liwliw -- Para desuscribirse, mande un mensaje a %(local)s-unsubscribe@%(domain)s enemies-of-carlotta-1.2.6/templates/unsub-goodbye.fr0000664000175000017500000000102110577453117020645 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Confirmation de votre désabonnement de %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Votre désabonnement de la liste de diffusion %(list)s est confirmé. Pour obtenir des informations sur l'utilisation du logiciel de gestion de la liste de diffusion, envoyez un courriel à %(local)s-help@%(domain)s . Si vous rencontrez des problèmes, veuillez prendre contact avec la personne responsable de la liste de diffusion à l'adresse : %(local)s-owner@%(domain)s . Merci. enemies-of-carlotta-1.2.6/templates/msg-moderate.sv0000664000175000017500000000133210577453117020475 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Var god moderera meddelande till %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hej! Det här brevet skickades av epostlistehanteraren som sköter listan %(list)s till de som modererar listan. Ska meddelandet som visas nedan fÃ¥ skickas till listan? I sÃ¥ fall, svara pÃ¥ det här brevet eller skicka ett tomt meddelande till %(confirm)s Om inte, skicka ett meddelande till %(deny)s Tack. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/bounce-warning.sv0000664000175000017500000000173510577453116021035 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Din epost studsar, %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hej! Du prenumererar pÃ¥ epostlistan %(list)s. Epost skickad till dig av epostlistehanteraren har studsat Ã¥tminstone en gÃ¥ng under den senaste veckan eller sÃ¥. Om detta berodde pÃ¥ ett tillfälligt fel kan du bortse frÃ¥n den här varningen. Fortsätter dina brev att studsa kommer du i slutändan automatiskt att bli borttagen frÃ¥n listan över prenumeranter. Vi beklagar detta. Den första studsen är bifogad. För instruktioner om hur man använder epostlistehanteraren, skicka ett brev till %(local)s-help@%(domain)s. Om du har problem, kontakta personerna som äger listan pÃ¥ %(local)s-owner@%(domain)s. Tack. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=bounce.txt %(bounce)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/unsub-confirm0000664000175000017500000000230510577453117020252 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Please confirm UNsubscription to %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=us-ascii Hello, This mail has been sent by the mailing list manager operating the %(list)s mailing list. Someone has asked you to be removed as a subscriber from the list. See the mail they sent at the bottom. If you did not send it yourself, please contact the sender or their administrator and ask what is going on. If you reply to this mail, you confirm that you wish to be removed as a subscriber. Usually, you can just use the normal reply feature in your mail program. Alternatively, you can send mail to the address below: %(confirm)s You don't need to do anything to continue your subscription. For instructions on using the mailing list manager software, send mail to %(local)s-help@%(domain)s . If you have problems, please contact the human owners of the list at %(local)s-owner@%(domain)s . Thank you. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/msg-wait0000664000175000017500000000135610577453117017220 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Please wait for moderation to %(list)s Content-type: text/plain; charset=us-ascii MIME-Version: 1.0 Hello, This mail has been sent by the mailing list manager operating the %(list)s mailing list. You, or someone (perhaps a spammer) pretending to be you, has sent a message to the mailing list. If it wasn't you, please just ignore this message. If it was you, please read further. Your message to the list has been sent to the moderators for approval. This might take a while. Please be patient. For instructions on using the mailing list manager software, send mail to %(local)s-help@%(domain)s . If you have problems, please contact the human owners of the list at %(local)s-owner@%(domain)s . Thank you. enemies-of-carlotta-1.2.6/templates/unsub-owner-notification0000664000175000017500000000073010577453117022433 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Address removed from %(list)s Content-type: text/plain; charset=us-ascii MIME-Version: 1.0 Hello, This mail has been sent by the mailing list manager operating the %(list)s mailing list to the human owners of the list. The following address has unsubscribed itself from the list: %(address)s Unless there is something really strange going on, this mail is only for your information and you need take no action. Thanks. enemies-of-carlotta-1.2.6/templates/list.es0000664000175000017500000000036410577453117017047 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Suscriptores de la lista %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Tal como me pidió, estos son los suscriptores de la lista %(list)s . %(addresses)s Total: %(count)s direcciones. enemies-of-carlotta-1.2.6/templates/unsub-owner-notification.es0000664000175000017500000000073610577453117023047 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Dirección eliminada de %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hola, Este mensaje lo ha enviado el gestor de listas de correo que opera sobre %(list)s a sus administradores humanos. Se ha cancelado la suscripción a la lista de la siguiente dirección: %(address)s A menos que pase algo realmente extraño, este mensaje sólo es informativo y no necesita tomar ninguna acción al respecto. Gracias. enemies-of-carlotta-1.2.6/templates/setlist-confirm.sv0000664000175000017500000000214010577453117021231 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Var god kontrollera ny prenumerantlista för %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hej! Det här brevet skickades av epostlistehanteraren som sköter epostlistan %(list)s. En av listägarna (eller nÃ¥gon som lÃ¥tsas vara en) har begärt att prenumerantlistan ändras. Brevet innehÃ¥llande begäran är bifogat. Om du accepterar ändringen, svara pÃ¥ det här brevet. I annat fall, ignorera det bara. Observera att hela prenumerantlistan kommer att ändras till nedanstÃ¥ende adresslista. Alla tidigare prenumeranter som inte finns med pÃ¥ den nya listan kommer att glömmas. För instruktioner om hur man använder epostlistehanteraren, skicka ett brev till to %(local)s-help@%(domain)s. Om du har problem, kontakta personerna som äger listan pÃ¥ %(local)s-owner@%(domain)s. Tack. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/setlist-badlist.fr0000664000175000017500000000165610577453117021210 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Mauvaise adresse de liste pour %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Bonjour, Ce courriel a été envoyé par le gestionnaire de la liste de diffusion %(list)s . L'adresse à laquelle vous avez envoyé ce message contenait une erreur de syntaxe. Vous devez expressément avoir une adresse par ligne, et rien d'autre. Pour obtenir des informations sur l'utilisation du logiciel de gestion de la liste de diffusion, envoyez un courriel à %(local)s-help@%(domain)s . Si vous rencontrez des problèmes, veuillez prendre contact avec la personne responsable de la liste de diffusion à l'adresse : %(local)s-owner@%(domain)s . Merci --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/sub-moderate.sv0000664000175000017500000000136010577453117020501 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Godkänn ny prenumeration pÃ¥ %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hej! Det här brevet skickades av epostlistehanteraren som sköter listan %(list)s till listans ägare. Ska följande adress fÃ¥ lov att prenumerera pÃ¥ listan? %(subscriber)s I sÃ¥ fall, svara pÃ¥ det här brevet eller skicka ett tomt brev till %(confirm)s Om du vill avvisa ansökan, skicka ett tomt brev till %(deny)s Tack. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/setlist-sorry.sv0000664000175000017500000000031310577453117020752 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Du kan inte ställa in prenumerantlistan för %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Tyvärr, du är inte nÃ¥gon av ägarna till %(list)s. enemies-of-carlotta-1.2.6/templates/sub-confirm.fr0000664000175000017500000000302510577453117020315 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Veuillez confirmer votre abonnement à %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Bonjour, Ce courriel a été envoyé par le gestionnaire de la liste de diffusion de la liste de diffusion %(list)s . Quelqu'un vient de demander de vous abonner à cette liste de diffusion. Regardez le message qui se trouve à la fin. Si vous n'avez pas envoyez vous-même directement ce message, veuillez prendre contact avec l'expéditeur de ce message ou bien avec l'administrateur pour demander des explications. En répondant à ce courriel, vous confirmerez que vous souhaitez vous abonner à cette liste. Le contenu du message n'a pas d'importance. Typiquement, vous pouvez utiliser la fonction répondre de votre logiciel de lecture des courriers électroniques. Vous pouvez également envoyer un courriel à l'adresse ci-dessous : %(confirm)s Si vous ne souhaitez pas vous abonner, ignorez simplement ce courriel. Pour obtenir des informations sur l'utilisation du logiciel de gestion de la liste de diffusion, envoyez un courriel à %(local)s-help@%(domain)s Si vous rencontrez des problèmes, veuillez prendre contact avec la personne responsable de la liste de diffusion à l'adresse : %(local)s-owner@%(domain)s . Merci. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/list-sorry.fr0000664000175000017500000000032510577453117020220 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Impossible d'obtenir la liste des abonnés à %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Désolé, vous n'êtes pas le responsable de la liste %(list)s. enemies-of-carlotta-1.2.6/templates/sub-welcome.sv0000664000175000017500000000071510577453117020337 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Välkommen till %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Välkommen till epostlistan %(list)s! Om du vill avsluta prenumerationen, skicka ett tomt brev till %(local)s-unsubscribe@%(domain)s. För instruktioner om hur man använder epostlistehanteraren, skicka ett brev till %(local)s-help@%(domain)s. Om du har problem, kontakta personerna som äger listan pÃ¥ %(local)s-owner@%(domain)s. Tack. enemies-of-carlotta-1.2.6/templates/sub-wait.es0000664000175000017500000000131410577453117017623 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Por favor, espere por la moderación de %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hola, Este mensaje se lo ha enviado el gestor de listas de correo que opera sobre %(list)s . Ha confirmado que desea suscribirse a la lista. La suscripción está moderada, sin embargo, y hace falta que el administrador de la lista procese manualmente la petición. Esto puede llevar un tiempo. Por favor, sea paciente. Si desea instrucciones sobre el uso del software gestor de listas, envíe un mensaje a %(local)s-help@%(domain)s . Si tiene problemas, por favor, póngase en contacto con las personas que gestionan la lista en %(local)s-owner@%(domain)s . Gracias. enemies-of-carlotta-1.2.6/templates/unsub-confirm.fr0000664000175000017500000000267410577453117020671 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Veuillez confirmer votre DÉsabonnement à %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Bonjour, Ce courriel a été envoyé par le gestionnaire de la liste de diffusion %(list)s . Quelqu'un a demandé à ce que vous soyez désabonné de la liste. Regardez le message qui ce trouve en dessous. Si vous n'avez pas envoyé ce message vous-même, veuillez prendre contact avec l'expéditeur ou son administrateur et demandez des explications. En répondant à ce courriel, vous confirmerez que vous souhaitez vous désabonner. Généralement, il suffit simplement d'utiliser la fonction répondre de votre logiciel de lecture des courriers électroniques. Vous pouvez également envoyez un courriel à l'adresse indiquée ci-dessous : %(confirm)s Si vous souhaitez toujours être abonné, aucune action n'est nécessaire. Pour obtenir des informations sur l'utilisation du logiciel de gestion de la liste de diffusion, envoyez un courriel à %(local)s-help@%(domain)s . Si vous rencontrez des problèmes, veuillez prendre contact avec la personne responsable de la liste de diffusion à l'adresse : %(local)s-owner@%(domain)s . Merci. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/bounce-goodbye.fi0000664000175000017500000000110510577453116020755 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Postisi ei toimi, näkemiin listalta %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Olet ollut tilaajana %(list)s -listalla. Valitettavasti sinulle lähetetyt sähköpostit aiheuttavat virheilmoituksia siinä määrin, että sinut on nyt automaattisesti poistettu listalta. Kunhan sähköpostiongelmasi on korjattu, olet tervetullut takaisin tilaajaksi. Postituslistaohjelmiston käyttöohjeet saa pyytämällä osoitteesta %(local)s-help@%(domain)s . Listan omistajat saat kiinni osoitteesta %(local)s-owner@%(domain)s . enemies-of-carlotta-1.2.6/templates/sub-wait.fr0000664000175000017500000000144110577453117017624 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Veuillez attendre la modération pour %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Bonjour, Ce courriel a été envoyé par le gestionnaire de la liste de diffusion %(list)s . Vous avez confirmé que vous souhaitiez vous abonner à la liste. L'inscription à la liste est malgré tout modérée, et le propriétaire de la liste devra manuellement accepter votre requête. Cela peut prendre un certain temps. Soyez patient. Pour obtenir des informations sur l'utilisation du logiciel de gestion de la liste de diffusion, envoyez un courriel à %(local)s-help@%(domain)s . Si vous rencontrez des problèmes, veuillez prendre contact avec la personne responsable de la liste de diffusion à l'adresse : %(local)s-owner@%(domain)s . Merci. enemies-of-carlotta-1.2.6/templates/sub-reject0000664000175000017500000000066010577453117017530 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Subscription denied to %(list)s Content-type: text/plain; charset=us-ascii MIME-Version: 1.0 Hello, This mail has been sent by the mailing list manager operating the %(list)s mailing list. You tried to subscribe to the list, but the moderator has rejected your subscription. Sorry. If you have problems, please contact the human owners of the list at %(local)s-owner@%(domain)s . Thank you. enemies-of-carlotta-1.2.6/templates/sub-wait0000664000175000017500000000121410577453117017214 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Please wait for moderation to %(list)s Content-type: text/plain; charset=us-ascii MIME-Version: 1.0 Hello, This mail has been sent by the mailing list manager operating the %(list)s mailing list. You have confirmed that you want to subscribe to the list. List subscription is moderated, however, and the list owner needs to manually process your request. This might take a while. Please be patient. For instructions on using the mailing list manager software, send mail to %(local)s-help@%(domain)s . If you have problems, please contact the human owners of the list at %(local)s-owner@%(domain)s . Thank you. enemies-of-carlotta-1.2.6/templates/msg-moderate.fi0000664000175000017500000000134410577453117020446 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Hyväksy viesti listalle %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Tämän viestin on lähettänyt postituslistaa %(list)s käsittelevä ohjelmisto listan moderaattoreille. Pitäisikö alla oleva viesti hyväksyä ja lähettää kaikille tilaajille? Jos pitäisi, vastaa tähän viestiin tai lähetä viesti osoitteeseen %(confirm)s Jos ei pitäisi, lähetä viesti osoitteeseen %(deny)s Kiitos. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/unsub-goodbye.es0000664000175000017500000000065010577453117020654 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Despedida de %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Nos despedimos de usted desde la lista de correo %(list)s. Si desea instrucciones sobre el uso del software gestor de listas, envíe un mensaje a %(local)s-help@%(domain)s. Si tiene problemas, por favor, póngase en contacto con las personas que gestionan la lista en %(local)s-owner@%(domain)s. Gracias. enemies-of-carlotta-1.2.6/templates/unsub-confirm.de0000664000175000017500000000245010577453117020642 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Bitte bestaetigen Sie Ihre Abmeldung von der Liste %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hallo, Diese E-Mail wurde von der Listen-Software der Liste $(list)s versendet. Sie haben (oder jemand anderes für Sie hat) darum gebeten Ihr Abonnement der Liste zu kündigen. Die Anfrage ist an diese E-Mail angehängt. Falls Sie sie nicht selber versendet haben wenden Sie sich an den Absender oder dessen Systemadministrator. Falls Sie auf diese E-Mail antworten bestätigen Sie dass Sie Ihr Abonnement kündigen möchsten. Der Inhalt der Antwort ist unerheblich. Alternativ können Sie auch einfach eine E-Mail an die Adresse %(confirm)s senden. Falls Sie Ihr Abonnement nicht kündigen möchten ignorieren Sie bitte diese E-Mail. Um eine Anleitung zur Benutzung der Listen-Software zu erhalten, senden Sie bitte eine E-Mail an %(local)s-help@%(domain)s. Falls Sie Hilfe benötigen, können Sie auch an den Verwalter der Liste unter %(local)s-owner@%(domain)s schreiben. Vielen Dank. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/setlist-sorry.de0000664000175000017500000000036710577453117020723 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Sie koennen die Abonnentenliste von %(list)s nicht aendern Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Da Sie nicht Verwalter der Liste %(list)s sind können Sie die Abonnentenliste nicht ändern. enemies-of-carlotta-1.2.6/templates/sub-wait.fi0000664000175000017500000000102110577453117017605 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Odota omistajan vahvistusta: %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Tämän viestin on lähettänyt postituslistaa %(list)s operoiva ohjelmisto. Olet vahvistanut, että haluat tilata listan. Listan tilaus on rajoitettua ja listan omistajien tarvitsee vielä käsitellä tilauspyyntösi käsin. Postituslistaohjelmiston käyttöohjeet saa pyytämällä osoitteesta %(local)s-help@%(domain)s . Listan omistajat saat kiinni osoitteesta %(local)s-owner@%(domain)s . enemies-of-carlotta-1.2.6/templates/unsub-owner-notification.de0000664000175000017500000000077410577453117023032 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Eine Abonnentement der Liste %(list)s wurde gekuendigt Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hallo, Diese E-Mail wurde von der Listen-Software der Liste %(list)s an die Verwalter der Liste gesendet. Die Person mit der Adresse %(address)s wurde auf eigenen Wunsch von der Abonnentenliste entfernt. Falls keine Besonderheiten vorliegen dient diese E-Mail nur Ihrer Information und Sie brauchen nichts weiter zu unternehmen. Vielen Dank. enemies-of-carlotta-1.2.6/templates/setlist-done0000664000175000017500000000107710577453117020102 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Subscriber list has been changed for %(list)s Content-type: text/plain; charset=us-ascii MIME-Version: 1.0 Hello, This mail has been sent by the mailing list manager operating the %(list)s mailing list. For your information: On request from a list owner, the subscriber list for the list has been replaced with a new one. For instructions on using the mailing list manager software, send mail to %(local)s-help@%(domain)s . If you have problems, please contact the human owners of the list at %(local)s-owner@%(domain)s . Thank you. enemies-of-carlotta-1.2.6/templates/unsub-goodbye.sv0000664000175000017500000000057210577453117020700 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Farväl frÃ¥n %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Din prenumeration pÃ¥ %(list)s har härmed avslutats. För instruktioner om hur man använder epostlistehanteraren, skicka ett brev till %(local)s-help@%(domain)s. Om du har problem, kontakta personerna som äger listan pÃ¥ %(local)s-owner@%(domain)s. Tack. enemies-of-carlotta-1.2.6/templates/unsub-goodbye.de0000664000175000017500000000067610577453117020645 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Auf Wiedersehen von %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Ihr Abonnement der Liste %(list)s wurde entfernt. Auf Wiedersehen. Um eine Anleitung zur Benutzung der Listen-Software zu erhalten, senden Sie bitte eine E-Mail an %(local)s-help@%(domain)s. Falls Sie Hilfe benötigen, können Sie auch an den Verwalter der Liste unter %(local)s-owner@%(domain)s schreiben. Vielen Dank. enemies-of-carlotta-1.2.6/templates/bounce-owner-notification.de0000664000175000017500000000147010577453116023142 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Eine Adresse wurde von %(list)s entfernt Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hallo, Diese E-Mail wurde von der Listen-Software der Liste %(list)s an die Verwalter der Liste gesendet. Die folgende Adresse wurde von der Liste entfernt, da E-Mail an sie wiederholt nicht ausgestellt werden konnte: %(address)s Die Fehlermeldung ist an diese E-Mail angehängt. Falls keine besonderen Vorkommnisse daraus zu erkennen sind dient diese E-Mail nur Ihrer Information und Sie brauchen nichts zu unternehmen. Vielen Dank. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=bounce.txt %(bounce)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/unsub-owner-notification.fr0000664000175000017500000000102510577453117023037 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Adresse supprimée sur %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Bonjour, Ce courriel a été envoyé par le gestionnaire de la liste de diffusion %(list)s aux personnes qui sont responsables de la liste. L'adresse suivante s'est désabonnée d'elle-même de la liste : %(address)s À moins que quelque chose de particulier vous semble suspect, ce message vous est uniquement envoyé à titre informatif et ne nécessite pas traitement particulier. Merci. enemies-of-carlotta-1.2.6/templates/msg-moderate.de0000664000175000017500000000136210577453117020440 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Moderation auf der Liste %(list)s erbeten Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hallo, Diese E-Mail wurde von der Listen-Software der Liste %(list)s an die Verwalter der Liste gesendet. Soll die angehängte Nachricht an die Liste versendet werden? Falls ja, so antworten Sie bitte auf diese E-Mail oder senden eine Bestätigung an %(confirm)s. Falls nein, senden Sie bitte ein Nachricht an %(deny)s. Vielen Dank. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/sub-reject.fi0000664000175000017500000000051710577453117020126 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Tilaus evätty: %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Tämän viestin on lähettänyt postituslistaa %(list)s operoiva ohjelmisto. Yritit tilata listan, mutta listan omistajat eväsivät pyyntösi. Listan omistajat saat kiinni osoitteesta %(local)s-owner@%(domain)s . enemies-of-carlotta-1.2.6/templates/sub-confirm.sv0000664000175000017500000000244010577453117020336 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Bekräfta prenumeration pÃ¥ %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hej! Det här brevet skickades av epostlistehanteraren som sköter epostlistan %(list)s. Du, eller nÃ¥gon i ditt ställe, har begärt att du läggs till som prenumerant pÃ¥ listan. Meddelandet i frÃ¥ga finns längst ner. Om du inte skickade det själv, kontakta avsändaren eller dennes administratör och frÃ¥ga vad som pÃ¥gÃ¥r. Genom att besvara det här brevet bekräftar du att du vill vara med som prenumerant pÃ¥ listan. InnehÃ¥llet i svaret spelar ingen roll. Vanligtvis gÃ¥r det bra att använda den vanliga svara-funktionen i ditt epostprogram. Alternativt kan du skicka ett tomt meddelande till följande adress: %(confirm)s Om du inte vill prenumerera, strunta bara i det här brevet. För instruktioner om hur man använder epostlistehanteraren, skicka ett brev till to %(local)s-help@%(domain)s. Om du har problem, kontakta personerna som äger listan pÃ¥ %(local)s-owner@%(domain)s. Tack. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/setlist-sorry.fr0000664000175000017500000000035410577453117020736 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Vous ne pouvez pas définir la liste des abonnés pour %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Désolé, mais vous n'êtes pas l'un des propriétaires de la liste %(list)s. enemies-of-carlotta-1.2.6/templates/sub-welcome.es0000664000175000017500000000076510577453117020323 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Bienvenido a %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Bienvenido de la lista de c orreo %(list)s . Si desea cancelar la suscripción, envíe un mensaje a %(local)s-unsubscribe@%(domain)s . Si desea instrucciones sobre el uso del software gestor de listas, envíe un mensaje a %(local)s-help@%(domain)s. Si tiene problemas, por favor, póngase en contacto con las personas que gestionan la lista en %(local)s-owner@%(domain)s. Gracias. enemies-of-carlotta-1.2.6/templates/setlist-badlist.sv0000664000175000017500000000141110577453117021216 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Felaktig adresslista för %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hej! Det här brevet skickades av epostlistehanteraren som sköter listan %(list)s. Adresslistan du skickade är syntaktiskt felaktig. Du mÃ¥ste skriva exakt en adress per rad och inget annat. För instruktioner om hur man använder epostlistehanteraren, skicka ett brev till to %(local)s-help@%(domain)s. Om du har problem, kontakta personerna som äger listan pÃ¥ %(local)s-owner@%(domain)s. Tack. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/sub-owner-notification.de0000664000175000017500000000072110577453117022457 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Neuer Abonnent der Liste %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hallo, Diese E-Mail wurde von der Listen-Software der Liste %(list)s an die Verwalter der Liste gesendet. Die folgende Adresse wurde auf die Abonnentenliste hinzugefügt: %(address)s Falls keine Besonderheiten vorliegen dient diese E-Mail nur Ihrer Information und Sie brauchen nichts weiter zu unternehmen. Vielen Dank. enemies-of-carlotta-1.2.6/templates/sub-wait.sv0000664000175000017500000000123510577453117017646 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Var god vänta pÃ¥ godkännande av prenumerationen pÃ¥ %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hej! Det här brevet skickades av epostlistehanteraren som sköter epostlistan %(list)s. Du har bekräftat att du vill prenumerera pÃ¥ listan. Prenumerationen är dock inte öppen för alla, och därför mÃ¥ste listans ägare manuellt godkänna din begäran. Det kan ta en stund, sÃ¥ ha tÃ¥lamod! För instruktioner om hur man använder epostlistehanteraren, skicka ett brev till to %(local)s-help@%(domain)s. Om du har problem, kontakta personerna som äger listan pÃ¥ %(local)s-owner@%(domain)s. Tack. enemies-of-carlotta-1.2.6/templates/sub-reject.sv0000664000175000017500000000063110577453117020155 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Prenumeration pÃ¥ %(list)s avvisad Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hej! Det här brevet skickades av epostlistehanteraren som sköter epostlistan %(list)s. Du försökte prenumerera pÃ¥ listan, men en moderator har avslagit din begäran. Beklagar. Om du har problem, kontakta personerna som äger listan pÃ¥ %(local)s-owner@%(domain)s. Tack. enemies-of-carlotta-1.2.6/templates/sub-confirm0000664000175000017500000000233610577453117017713 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Please confirm subscription to %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hello, This mail has been sent by the mailing list manager operating the %(list)s mailing list. Someone has asked you to be added as a subscriber to the list. See the mail they sent at the bottom. If you did not send it yourself, please contact the sender or their administrator and ask what is going on. If you reply to this mail, you confirm that you wish to be added as a subscriber. The contents of the reply doesn't matter. Usually, you can just use the normal reply feature in your mail program. Alternatively, you can send mail to the address below: %(confirm)s If you don't want to subscribe, just ignore this mail. For instructions on using the mailing list manager software, send mail to %(local)s-help@%(domain)s . If you have problems, please contact the human owners of the list at %(local)s-owner@%(domain)s . Thank you. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/bounce-goodbye.es0000664000175000017500000000122410577453116020770 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Su mensaje rebota, adiós desde %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hola, Hasta ahora ha sido suscriptor de la lista %(list)s. Desafortunadamente, ha rebotado tanto un mensaje que le envió el administrador de la lista que ha sido desuscrito de forma automática. Una vez corrija sus problemas de correo, será bienvenido a resuscribirse. Si desea instrucciones sobre el uso del software gestor de listas, envíe un mensaje a %(local)s-help@%(domain)s . Si tiene problemas, por favor, póngase en contacto con las personas que gestionan la lista en %(local)s-owner@%(domain)s . Gracias. enemies-of-carlotta-1.2.6/templates/list.sv0000664000175000017500000000035710577453117017072 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Prenumeranter pÃ¥ %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Här är listan över prenumeranter pÃ¥ listan %(list)s, enligt begäran. %(addresses)s Totalt: %(count)s adresser. enemies-of-carlotta-1.2.6/templates/msg-moderate.fr0000664000175000017500000000140310577453117020453 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Veuillez modérer le message pour %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Bonjour, Ce courriel a été envoyé par le gestionnaire de la liste de diffusion %(list)s aux personnes qui sont responsables de la liste. Le message qui ce trouve à la fin doit-il être envoyé à la liste de diffusion ? Si oui, répondez à ce message ou envoyez un courriel à %(confirm)s Si non, envoyez un courriel à %(deny)s Merci. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/help.sv0000664000175000017500000000201510577453116017037 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Hjälp för epostlistan %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hej! Detta är hjälptexten till epostlistan %(list)s. Listan sköts av epostlistehanteraren EoC. Den förstÃ¥r följande kommandoadresser: %(local)s-help@%(domain)s Skickar den här hjälptexten. %(local)s-subscribe@%(domain)s Prenumerera pÃ¥ listan. Du kommer att fÃ¥ ett meddelande som ber dig bekräfta att du vill prenumerera. %(local)s-subscribe-foo=bar@%(domain)s Anmäl adressen foo@bar som prenumerant. foo@bar kommer att fÃ¥ en bekräftelseförfrÃ¥gan. %(local)s-unsubscribe@%(domain)s Säg upp prenumerationen pÃ¥ listan. Du kommer att bli ombedd att bekräfta. %(local)s-unsubscribe-foo=bar@%(domain)s Säg upp prenumerationen för foo@bar. foo@bar kommer att fÃ¥ bekräfta. Om du har problem som inte den här hjälptexten kan lösa, kontakta listans ägare pÃ¥ adressen %(local)s-owner@%(domain)s. Tack. enemies-of-carlotta-1.2.6/templates/help0000664000175000017500000000176310577453116016421 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Help for the %(list)s mailing list Content-type: text/plain; charset=us-ascii MIME-Version: 1.0 Hello, this is the help text for the %(list)s mailing list. The list is operated by the EoC mailing list manager. It understands the following command addresses: %(local)s-help@%(domain)s Sends this help text. %(local)s-subscribe@%(domain)s Subscribe to the list. You will get a confirmation request. %(local)s-subscribe-foo=bar@%(domain)s Subscribe the address foo@bar to the list. foo@bar will get the confirmation request. %(local)s-unsubscribe@%(domain)s Unsubscribe from the list. You will get a confirmation request. %(local)s-unsubscribe-foo=bar@%(domain)s Unsubscribe the address foo@bar from the list. foo@bar will get the confirmation request. If you have problems that are not solved by this help text, please contact the human owner of the list at %(local)s-owner@%(domain)s. Thank you. enemies-of-carlotta-1.2.6/templates/setlist-done.fi0000664000175000017500000000072210577453117020473 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Tilaajalista vaihdettu listalle %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Tämän viestin on lähettänyt postituslistaa %(list)s operoiva ohjelmisto. Tiedoksi: Listan (erään) omistajan pyynnöstä tilaajalista on vaihdettu uuteen. Postituslistaohjelmiston käyttöohjeet saa pyytämällä osoitteesta %(local)s-help@%(domain)s . Listan omistajat saat kiinni osoitteesta %(local)s-owner@%(domain)s . enemies-of-carlotta-1.2.6/templates/bounce-goodbye.fr0000664000175000017500000000163310577453116020774 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Votre courriel ne peut être délivré, vous êtes désabonné de la liste %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Bonjour, Vous vous étiez abonné à la liste de diffusion %(list)s. Malheureusement, les courriels qui vous ont été envoyés par le gestionnaire de la liste de diffusion ne sont jamais arrivés à destination. Ce phénomène s'étant reproduit un grand nombre de fois, vous avez été automatiquement désabonné de la liste. Une fois que vos problèmes de courriels seront corrigés, n'hésitez pas à vous réabonner. Pour obtenir des informations sur l'utilisation du logiciel de gestion de la liste de diffusion, envoyez un courriel à %(local)s-help@%(domain)s . Si vous rencontrez des problèmes, veuillez prendre contact avec la personne responsable de la liste de diffusion à l'adresse : %(local)s-owner@%(domain)s . Merci. enemies-of-carlotta-1.2.6/templates/setlist-confirm.fi0000664000175000017500000000175210577453117021207 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Varmista tilaajaluettelon muutos listalle %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Tämän viestin on lähettänyt postituslistaa %(list)s operoiva ohjelmisto. Listan omistaja (tai sellaisena esiintyvä) on pyytänyt tilaajalistaa vaihdettavaksi liitteenä olevan meilin mukaiseksi. Jos hyväksyt tämän muutokset, vastaa tähän meiliin. Muuten jätä se huomiotta. Huomaa, että koko tilaajalista vaihtuu alla olevan listan mukaiseksi. Listalla jo olevat osoitteet unohdetaan, jos niitä ei ole alla. Postituslistaohjelmiston käyttöohjeet saa pyytämällä osoitteesta %(local)s-help@%(domain)s . Listan omistajat saat kiinni osoitteesta %(local)s-owner@%(domain)s . --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/msg-moderate0000664000175000017500000000130710577453117020050 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Please moderate message to %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=us-ascii Hello, This mail has been sent by the mailing list manager operating the %(list)s mailing list to the human moderators of the list. Should the message at the bottom be allowed to be sent to the list? If so, reply to this mail or send mail to %(confirm)s If not, send mail to %(deny)s Thanks. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/help.de0000664000175000017500000000203410577453116017000 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Hilfe zur Liste %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hallo, Dies ist die Hilfe zur Liste %(list)s. Diese Liste wird von der Listen-Software EoC betrieben. Sie reagiert auf die folgenden Befehlsadressen: %(local)s-help@%(domain)s Diese Hilfe anfordern. %(local)s-subscribe@%(domain)s Die Liste abonnieren. Sie werden um eine Bestätigung gebeten. %(local)s-subscribe-foo=bar@%(domain)s Die Liste unter der Adresse foo@bar abonnieren. Eine Bestätigung wird von foo@bar erbeten. %(local)s-unsubscribe@%(domain)s Mitgliedschaft auf der Liste kündigen. Sie werden um eine Bestätigung gebeten. %(local)s-unsubscribe-foo=bar@%(domain)s Mitgliedschaft von foo@bar kündigen. Eine Bestätigung wird von foo@bar erbeten. Falls Sie Probleme haben die nicht in dieser Hilfe beschrieben sind wenden Sie sich bitte an den Verwalter der Liste unter der Adresse %(local)s-owner@%(domain)s. Vielen Dank. enemies-of-carlotta-1.2.6/templates/setlist-sorry0000664000175000017500000000030010577453117020317 0ustar liwliwFrom: %(From)s To: %(To)s Subject: You can't set the subscriber list for %(list)s Content-type: text/plain; charset=us-ascii MIME-Version: 1.0 Sorry, you are not the list owner for %(list)s. enemies-of-carlotta-1.2.6/templates/list0000664000175000017500000000032710577453116016437 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Subscribers for %(list)s Content-type: text/plain; charset=us-ascii MIME-Version: 1.0 Subscribers for the %(list)s list, as requested. %(addresses)s Total: %(count)s addresses. enemies-of-carlotta-1.2.6/templates/msg-wait.fi0000664000175000017500000000130510577453117017607 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Odota omistajan vahvistusta: %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Tämän viestin on lähettänyt postituslistaa %(list)s operoiva ohjelmisto. Sinä, tai joku (mahdollisesti roskapostittaja) joka väittää olevansa sinä, on lähettänyt postituslistalle viestin. Jos se et ollut sinä, voit unohtaa tämän viestin. Jos se olit sinä, jatka lukemista. Listalle lähettämäsi viesti on lähetetty listan omistajille hyväksyntää varten. Tämä voi kestää hetken. Postituslistaohjelmiston käyttöohjeet saa pyytämällä osoitteesta %(local)s-help@%(domain)s . Listan omistajat saat kiinni osoitteesta %(local)s-owner@%(domain)s . enemies-of-carlotta-1.2.6/templates/bounce-warning.fr0000664000175000017500000000236710577453116021016 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Votre courriel ne peut être délivré, %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Bonjour, Vous êtes abonné à la liste de diffusion %(list)s . Les courriels qui vous ont été envoyés par le gestionnaire de la liste de diffusion ne sont jamais arrivés à destination. Ce phénomène s'est produit au moins une fois depuis la semaine dernière. S'il s'agit d'une erreur temporaire, vous pouvez ignorer ce message d'avertissement. Cependant, si les échecs se reproduisent, il se peut que vous soyez automatiquement désabonné de la liste de diffusion. Nous en sommes désolé. Le premier message qui n'a pu être délivré est cité ci-dessous. Pour obtenir des informations sur l'utilisation du logiciel de gestion de la liste de diffusion, envoyez un courriel à %(local)s-help@%(domain)s . Si vous rencontrez des problèmes, veuillez prendre contact avec la personne responsable de la liste de diffusion à l'adresse : %(local)s-owner@%(domain)s . Merci. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=bounce.txt %(bounce)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/sub-moderate.fi0000664000175000017500000000132610577453117020451 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Käsittele tilauspyyntö: %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Tämän viestin on lähettänyt postituslistaa %(list)s käsittelevä ohjelmisto listan omistajille. Pitäisikö seuraava osoite lisätä tilaajalistalle? %(subscriber)s Jos pitäisi, vastaa tähän viestiin tai lähetä viesti osoitteeseen %(confirm)s Jos ei pitäisi, lähetä viesti osoitteeseen %(deny)s --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/sub-confirm.es0000664000175000017500000000241110577453117020313 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Por favor, confirme su suscripción a %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hola, Este mensaje se lo ha enviado el gestor automático que opera la lista de correo %(list)s . Alguien ha pedido que usted sea añadido como suscriptor de la lista. Al final verá el mensaje enviado. Si no lo envió usted mismo, por favor, póngase en contacto con quien lo hizo o con su administrador, para saber qué sucede. Si responde a este mensaje, confirmará que desea ser añadido como suscriptor. No importa el contenido de la respuesta. Por lo común, basta con usar la función normal de respuesta de su programa de correo. De forma alternativa, puede enviar un mensaje a la siguiente dirección: %(confirm)s Si desea instrucciones sobre el uso del software gestor de listas, envíe un mensaje a %(local)s-help@%(domain)s . Si tiene problemas, por favor, póngase en contacto con las personas que gestionan la lista en %(local)s-owner@%(domain)s . Gracias. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/setlist-done.es0000664000175000017500000000115410577453117020504 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Se ha cambiado la lista de suscriptores de %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hola, Este mensaje se lo ha enviado el gestor de listas de correo que opera sobre %(list)s . Para su información: Bajo petición de uno de los gestores de la lista, se ha cambiado la lista de suscriptores por una nueva. Si desea instrucciones sobre el uso del software gestor de listas, envíe un mensaje a %(local)s-help@%(domain)s . Si tiene problemas, por favor, póngase en contacto con las personas que gestionan la lista en %(local)s-owner@%(domain)s . Gracias. enemies-of-carlotta-1.2.6/templates/sub-welcome.de0000664000175000017500000000102710577453117020274 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Willkommen auf der Liste %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Willkommen auf der Liste %(list)s! Falls Sie Ihr Abonnement wieder kündigen wollen senden Sie bitte eine E-Mail an %(local)s-unsubscribe@%(domain)s. Um eine Anleitung zur Benutzung der Listen-Software zu erhalten, senden Sie bitte eine E-Mail an %(local)s-help@%(domain)s. Falls Sie Hilfe benötigen, können Sie auch an den Verwalter der Liste unter %(local)s-owner@%(domain)s schreiben. Vielen Dank. enemies-of-carlotta-1.2.6/templates/list.de0000664000175000017500000000040110577453117017020 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Abonnenten der Liste %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Wie angefordert erhalten Sie hiermit die Abonnentenliste der Liste %(list)s. %(addresses)s Insgesamt sind dies %(count)s Adressen. enemies-of-carlotta-1.2.6/templates/setlist-confirm0000664000175000017500000000223110577453117020603 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Please moderate subscriber list for %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=us-ascii Hello, This mail has been sent by the mailing list manager operating the %(list)s mailing list. One of the owners of the list (or someone pretending to be one) has asked for the subscriber list for the list to be changed. The request mail, with the new set of describers, is attached. If you accept the change, please respond to this mail. Otherwise please just ignore it. Note that the whole subscriber list will be changed to the list of addresses below. All old subscriptions will be forgotten, unless they are also on the new list of subscribers. For instructions on using the mailing list manager software, send mail to %(local)s-help@%(domain)s . If you have problems, please contact the human owners of the list at %(local)s-owner@%(domain)s . Thank you. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/sub-welcome.fr0000664000175000017500000000107710577453117020320 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Bienvenue sur %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Bienvenue sur la liste de diffusion %(list)s . Si vous souhaitez vous désabonner, envoyez un courriel à %(local)s-unsubscribe@%(domain)s. Pour obtenir des informations sur l'utilisation du logiciel de gestion de la liste de diffusion, envoyez un courriel à %(local)s-help@%(domain)s . Si vous rencontrez des problèmes, veuillez prendre contact avec la personne responsable de la liste de diffusion à l'adresse : %(local)s-owner@%(domain)s . Merci. enemies-of-carlotta-1.2.6/templates/setlist-badlist.fi0000664000175000017500000000137210577453117021172 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Virheelinen tilaajalista listalle %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Tämän viestin on lähettänyt postituslistaa %(list)s operoiva ohjelmisto. Lähettämäsi osoiteluettelo oli muodollisesti epäpätevä. Luettelossa pitää olla täsmälleen yksi osoite per rivi, eikä muuta. Postituslistaohjelmiston käyttöohjeet saa pyytämällä osoitteesta %(local)s-help@%(domain)s . Listan omistajat saat kiinni osoitteesta %(local)s-owner@%(domain)s . --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/bounce-warning.es0000664000175000017500000000175410577453116021015 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Sus mensajes están rebotando, %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hola, Usted es suscriptor de la lista de correo %(list)s. Al menos uno de los mensajes que le envió la lista durante la última semana rebotó. Si fue un problema temporal, puede ignorar este aviso. Sin embargo, si su correo continúa rebotando, acabará siendo desuscrito de forma automática de esta lista. Sentimos el inconveniente. Encontrará el primer mensaje que rebotó adjunto. Si desea instrucciones sobre el uso del software gestor de listas, envíe un mensaje a %(local)s-help@%(domain)s . Si tiene problemas, por favor, póngase en contacto con las personas que gestionan la lista en %(local)s-owner@%(domain)s . --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=bounce.txt %(bounce)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/footer.de0000664000175000017500000000017610577453116017353 0ustar liwliw -- Um Ihre Mitgliedschaft auf dieser Liste zu kuendigen schicken Sie bitte eine E-Mail an %(local)s-unsubscribe@%(domain)s. enemies-of-carlotta-1.2.6/templates/sub-welcome.fi0000664000175000017500000000065210577453117020305 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Tervetuloa listalle %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Tervetuloa postituslistalle %(list)s . Jos haluat poistua listalta, lähetä postia osoitteeseen %(local)s-unsubscribe@%(domain)s . Postituslistaohjelmiston käyttöohjeet saa pyytämällä osoitteesta %(local)s-help@%(domain)s . Listan omistajat saat kiinni osoitteesta %(local)s-owner@%(domain)s . enemies-of-carlotta-1.2.6/templates/help.es0000664000175000017500000000206510577453116017023 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Ayuda de la lista de correo %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hola, éste es el texto de ayuda de la lista de correo %(list)s. La lista la maneja el gestor de listas de correo EoC, que entiende las siguientes órdenes mediante direcciones de correo: %(local)s-help@%(domain)s Envía este texto de ayuda. %(local)s-subscribe@%(domain)s Suscribirse a la lista. Recibirá una petición de confirmación. %(local)s-subscribe-foo=bar@%(domain)s Suscribir la dirección foo@bar a la lista. foo@bar recibirá la petición de confirmación. %(local)s-unsubscribe@%(domain)s Desuscribirse de la lista. Recibirá una petición de confirmación. %(local)s-unsubscribe-foo=bar@%(domain)s Desuscribir la dirección foo@bar de la lista. foo@bar recibirá la petición de confirmación. Si tiene algún problema que no resuelva este texto de ayuda, póngase en contacto por favor con la persona que supervisa la lista en %(local)s-owner@%(domain)s . Gracias. enemies-of-carlotta-1.2.6/templates/sub-confirm.de0000664000175000017500000000243610577453117020303 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Bitte bestaetigen Sie Ihr Abonnement der Liste %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hallo, Diese E-Mail wurde von der Listen-Software der Liste $(list)s versendet. Sie haben (oder jemand anderes hat für Sie) darum gebeten dass Sie die Liste %(list)s abonnieren. Die Anfrage ist an diese E-Mail angehängt. Falls Sie sie nicht selber versendet haben wenden Sie sich an den Absender oder dessen Systemadministrator. Falls Sie auf diese E-Mail antworten bestätigen Sie dass Sie die Liste abonnieren möchsten. Der Inhalt der Antwort ist unerheblich. Alternativ können Sie auch einfach eine E-Mail an die Adresse %(confirm)s senden. Falls Sie die Liste nicht abonnieren möchten ignorieren Sie bitte diese E-Mail. Um eine Anleitung zur Benutzung der Listen-Software zu erhalten, senden Sie bitte eine E-Mail an %(local)s-help@%(domain)s. Falls Sie Hilfe benötigen, können Sie auch an den Verwalter der Liste unter %(local)s-owner@%(domain)s schreiben. Vielen Dank. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/msg-wait.sv0000664000175000017500000000140110577453117017636 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Var god vänta pÃ¥ moderering av din postning till %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hej! Det här brevet skickades av epostlistehanteraren som sköter epostlistan %(list)s. Du, eller nÃ¥gon (kanske en spammer) som föreställer dig, har skickat ett meddelande till epostlistan. Om det inte var du, var sÃ¥ god och glöm det här brevet. Om det var du, läs vidare. Ditt meddelande till listan har skickats till moderatorerna för god- kännande. Det kan ta ett litet tag. Ha tÃ¥lamod! För instruktioner om hur man använder epostlistehanteraren, skicka ett brev till to %(local)s-help@%(domain)s. Om du har problem, kontakta personerna som äger listan pÃ¥ %(local)s-owner@%(domain)s. Tack. enemies-of-carlotta-1.2.6/templates/setlist-sorry.fi0000664000175000017500000000025610577453117020726 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Et voi vaihtaa tilaajalistaa listalle %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Et ole listan %(list)s omistaja. enemies-of-carlotta-1.2.6/templates/bounce-owner-notification.sv0000664000175000017500000000140210577453116023175 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Adress borttagen frÃ¥n %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hej! Det här brevet skickades av epostlistehanteraren som sköter listan %(list)s till listans ägare. Följande adress har tagits bort frÃ¥n listan p.g.a. att posten studsar: %(address)s Studsmeddelandet är bifogat med det här brevet. SÃ¥vida inte nÃ¥got riktigt märkligt pÃ¥gÃ¥r är det här brevet bara till för att informera dig och du behöver inte vidta nÃ¥gon Ã¥tgärd. Tack. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=bounce.txt %(bounce)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/list-sorry0000664000175000017500000000026510577453116017614 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Subscriber list denied for %(list)s Content-type: text/plain; charset=us-ascii MIME-Version: 1.0 Sorry, you are not the list owner for %(list)s. enemies-of-carlotta-1.2.6/templates/sub-reject.de0000664000175000017500000000072010577453117020114 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Abonnement der Liste %(list)s verweigert Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hallo, Diese E-Mail wurde von der Listen-Software der Liste $(list)s versendet. Sie haben versucht die Liste zu abonnieren, aber der Verwalter der Liste hat Ihr Abonnement verweigert. Entschuldigung. Falls Sie Hilfe benötigen, können Sie an den Verwalter der Liste unter %(local)s-owner@%(domain)s schreiben. Vielen Dank. enemies-of-carlotta-1.2.6/templates/setlist-done.de0000664000175000017500000000112510577453117020463 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Die Abonnentenliste von %(list)s wurde ersetzt Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hallo, Diese E-Mail wurde von der Listen-Software der Liste $(list)s versendet. Zu Ihrer Information: Auf Bitte des Listenverwalters wurde die Abonnentenliste dieser Liste durch eine neue ersetzt. Um eine Anleitung zur Benutzung der Listen-Software zu erhalten, senden Sie bitte eine E-Mail an %(local)s-help@%(domain)s. Falls Sie Hilfe benötigen, können Sie auch an den Verwalter der Liste unter %(local)s-owner@%(domain)s schreiben. Vielen Dank. enemies-of-carlotta-1.2.6/templates/sub-welcome0000664000175000017500000000067710577453117017717 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Welcome to %(list)s Content-type: text/plain; charset=us-ascii MIME-Version: 1.0 Welcome to the %(list)s mailing list. If you wish to unsubscribe, send mail to %(local)s-unsubscribe@%(domain)s . For instructions on using the mailing list manager software, send mail to %(local)s-help@%(domain)s . If you have problems, please contact the human owners of the list at %(local)s-owner@%(domain)s . Thank you. enemies-of-carlotta-1.2.6/templates/list.fi0000664000175000017500000000032710577453117017035 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Tilaajaluettelo: %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Pyynnöstä luettelen listan %(list)s tilaajat. %(addresses)s Yhteensä %(count)s osoitetta. enemies-of-carlotta-1.2.6/templates/sub-reject.es0000664000175000017500000000071310577453117020135 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Suscripción a %(list)s rechazada Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hola, Este mensaje se lo ha enviado el gestor de listas de correo que opera sobre %(list)s . Ha intentado suscribirse a la lista, pero el moderador rechazó la suscripción. Lo siento. Si tiene problemas, haga el favor de ponerse en contacto con los administradores humanos de la lista en %(local)s-owner@%(domain)s . Gracias. enemies-of-carlotta-1.2.6/templates/setlist-badlist0000664000175000017500000000144210577453117020573 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Bad address list for %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=us-ascii Hello, This mail has been sent by the mailing list manager operating the %(list)s mailing list. The address list you sent was syntactically incorrect. You need to have exactly one address per line, and nothing else. For instructions on using the mailing list manager software, send mail to %(local)s-help@%(domain)s . If you have problems, please contact the human owners of the list at %(local)s-owner@%(domain)s . Thank you. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/msg-wait.fr0000664000175000017500000000132010577453117017615 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Veuillez attendre la modération pour votre envoi sur %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Bonjour, Ce courriel a été envoyé par le gestionnaire de la liste de diffusion %(list)s . Votre message à destination de la liste a été envoyé aux modérateurs pour qu'ils l'approuvent. Cela peut prendre un certain temps. Soyez patient. Pour obtenir des informations sur l'utilisation du logiciel de gestion de la liste de diffusion, envoyez un courriel à %(local)s-help@%(domain)s . Si vous rencontrez des problèmes, veuillez prendre contact avec la personne responsable de la liste de diffusion à l'adresse : %(local)s-owner@%(domain)s . Merci. enemies-of-carlotta-1.2.6/templates/bounce-owner-notification.es0000664000175000017500000000142310577453116023157 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Dirección eliminada de %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hola, El gestor que opera la lista de correo %(list)s ha enviado este mensaje a los operadores humanos de la lista. La siguiente dirección ha sido eliminada de la lista debido a rebotes: %(address)s Al final de este mensaje se adjunta el mensaje que rebotó. A menos que esté pasando algo realmente extraño, este mensaje es sólo informativo, y no necesita tomar ninguna acción al respecto. Gracias. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=bounce.txt %(bounce)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/bounce-warning.fi0000664000175000017500000000156410577453116021003 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Postisi aiheuttaa virheilmoituksia, %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Olet tilaajana listalla %(list)s . Sinulle lähetetty sähköposti on aiheuttanut ainakin yhden virheilmoituksen viimeisen viikon aikana. Jos tämä oli tilapäinen ongelma, voit unohtaa koko asian. Jos virheet kuitenkin jatkuvat, sinut poistetaan lopulta listalta. Valitamme. Ensimmäinen virheilmoitus on tämän viestin liitteenä. Postituslistaohjelmiston käyttöohjeet saa pyytämällä osoitteesta %(local)s-help@%(domain)s . Listan omistajat saat kiinni osoitteesta %(local)s-owner@%(domain)s . --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=bounce.txt %(bounce)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/unsub-confirm.fi0000664000175000017500000000224410577453117020651 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Vahvista poistumispyyntö: %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Tämän viestin on lähettänyt postituslistaa %(list)s operoiva ohjelmisto. Joku on pyytänyt, että sinut poistetaan listalta. Pyyntö on tämän viestin lopussa. Jos et lähettänyt sitä itse, ole hyvä ja kysy lähettäjältä tai heidän ylläpidoltaan mistä on kyse. Jos vastaat tähän viestiin, vahvistat, että haluat poistua listalta. Vastausviestisi sisällöllä ei ole väliä. Vastaamisen pitäisi onnistua sähköpostiohjelmasi normaalilla vastaustoiminnolla. Vaihtoehtoisesti voit lähettää viestin tähän osoitteeseen: %(confirm)s Jos et halua tilata listaa, sinun ei tarvitse tehdä mitään. Postituslistaohjelmiston käyttöohjeet saa pyytämällä osoitteesta %(local)s-help@%(domain)s . Listan omistajat saat kiinni osoitteesta %(local)s-owner@%(domain)s . --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/unsub-owner-notification.sv0000664000175000017500000000072210577453117023063 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Adress borttagen frÃ¥n %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hej! Det här brevet skickades av epostlistehanteraren som sköter listan %(list)s till listans ägare. Följande adress har avslutat sin prenumeration pÃ¥ listan: %(address)s SÃ¥vida inte nÃ¥got riktigt märkligt pÃ¥gÃ¥r är det här brevet bara till för att informera dig och du behöver inte vidta nÃ¥gon Ã¥tgärd. Tack.enemies-of-carlotta-1.2.6/templates/sub-owner-notification.es0000664000175000017500000000070610577453117022501 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Dirección añadida a %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hola, Este es un mensaje del gestor de listas de correo que opera sobre %(list)s a sus administradores humanos. Se ha añadido a la lista la siguiente dirección: %(address)s A menos que pase algo realmente extraño, este mensaje es sólo informativo y no hace falta que tome ninguna acción al respecto. Gracias. enemies-of-carlotta-1.2.6/templates/msg-moderate.es0000664000175000017500000000134410577453117020457 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Por favor, modere este mensaje enviado a %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hola, Este mensaje lo ha enviado el gestor de listas de correo que lleva %(list)s a los moderadores humanos que la supervisan. ¿Debería permitirse enviar a la lista el mensaje que sigue? En caso afirmativo, responda a este mensaje o envíe un mensaje a %(confirm)s Si no, envíe un mensaje a %(deny)s Gracias. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/bounce-owner-notification.fi0000664000175000017500000000125010577453116023144 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Osoite poistettu listalta %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Tämän meilin on lähettänyt postituslistaohjelmisto, joka hoitaa listaa %(list)s listan omistajille. Osoite %(address)s on poistettu listalta toimimattomuuden takia. Virheilmoitus on tämän viestin liitteenä. Tämä meili on vain tiedoksi, jos mitään erityisen omituista ei ole tapahtumassa. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=bounce.txt %(bounce)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/footer.fi0000664000175000017500000000007210577453116017354 0ustar liwliw -- Poistu listalta: %(local)s-unsubscribe@%(domain)s . enemies-of-carlotta-1.2.6/templates/help.fi0000664000175000017500000000173510577453116017015 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Ohjeita listalle %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Tämä on postituslistan %(list)s ohjeteksti. Listaa pyörittää postituslistaohjelmisto nimeltä EoC. Se ymmärtää seuraavat komento-osoitteet: %(local)s-help@%(domain)s Lähettää tämän ohjetekstin. %(local)s-subscribe@%(domain)s Tilauspyyntö. Vastauksena tulee vahvistuspyyntö. %(local)s-subscribe-foo=bar@%(domain)s Tilauspyyntö siten, että listalle lisätään osoite foo@bar . foo@bar saa myös vahvistuspyynnön. %(local)s-unsubscribe@%(domain)s Listalta poistumispyyntö. Tämäkin pitää vahvistaa. %(local)s-unsubscribe-foo=bar@%(domain)s Pyyntö poistaa foo@bar listalta. foo@bar saa vahvistuspyynnön. Jos sinulla on ongelmia, jotka eivät ratkea tämän ohjetekstin avulla, voit kysyä apua listan omistajilta (jotka ovat ihmisiä) osoitteesta %(local)s-owner@%(domain)s. enemies-of-carlotta-1.2.6/templates/msg-wait.de0000664000175000017500000000113310577453117017600 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Ihre Nachricht an die Liste %(list)s wird moderiert Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hallo, Diese E-Mail wurde von der Listen-Software der Liste $(list)s versendet. Ihre Nachricht an die Liste wurde an die Moderatoren gesendet. Es kann eine Weile dauern bis diese entschieden haben. Um eine Anleitung zur Benutzung der Listen-Software zu erhalten, senden Sie bitte eine E-Mail an %(local)s-help@%(domain)s. Falls Sie Hilfe benötigen, können Sie auch an den Verwalter der Liste unter %(local)s-owner@%(domain)s schreiben. Vielen Dank. enemies-of-carlotta-1.2.6/templates/unsub-confirm.sv0000664000175000017500000000247210577453117020706 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Bekräfta avslut pÃ¥ prenumeration pÃ¥ %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hej! Det här brevet skickades av epostlistehanteraren som sköter epostlistan %(list)s. Du, eller nÃ¥gon i ditt ställe, har begärt att din prenumeration pÃ¥ listan ska avslutas. Meddelandet i frÃ¥ga finns längst ner. Om du inte skickade det själv, kontakta avsändaren eller dennes administratör och frÃ¥ga vad som pÃ¥gÃ¥r. Genom att besvara det här brevet bekräftar du att du inte längre vill prenumerera pÃ¥ listan. InnehÃ¥llet i svaret spelar ingen roll. Vanligtvis gÃ¥r det bra att använda den vanliga svara-funktionen i ditt epostprogram. Alternativt kan du skicka ett tomt meddelande till följande adress: %(confirm)s Om du vill fortsätta prenumerera behöver du inte göra nÃ¥gonting alls. För instruktioner om hur man använder epostlistehanteraren, skicka ett brev till to %(local)s-help@%(domain)s. Om du har problem, kontakta personerna som äger listan pÃ¥ %(local)s-owner@%(domain)s. Tack. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/sub-confirm.fi0000664000175000017500000000226010577453117020304 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Vahvista tilauspyyntö listalle %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Tämän viestin on lähettänyt postituslistaa %(list)s operoiva ohjelmisto. Joku on pyytänyt, että sinut lisätään listan tilaajaksi. Pyyntö on tämän viestin lopussa. Jos et lähettänyt sitä itse, ole hyvä ja kysy lähettäjältä tai heidän ylläpidoltaan mistä on kyse. Jos vastaat tähän viestiin, vahvistat, että haluat tilata listan. Vastausviestisi sisällöllä ei ole väliä. Vastaamisen pitäisi onnistua sähköpostiohjelmasi normaalilla vastaustoiminnolla. Vaihtoehtoisesti voit lähettää viestin tähän osoitteeseen: %(confirm)s Jos et halua tilata listaa, sinun ei tarvitse tehdä mitään. Postituslistaohjelmiston käyttöohjeet saa pyytämällä osoitteesta %(local)s-help@%(domain)s . Listan omistajat saat kiinni osoitteesta %(local)s-owner@%(domain)s . --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/setlist-done.fr0000664000175000017500000000130510577453117020502 0ustar liwliwFrom: %(From)s To: %(To)s Subject: La liste des abonnés de la liste %(list)s a changé Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Bonjour, Ce courriel a été envoyé par le gestionnaire de la liste de diffusion %(list)s . Pour votre information : à la demande d'un des propriétaires de la liste, la liste des abonnés pour la liste a été remplacée par une nouvelle. Pour obtenir des informations sur l'utilisation du logiciel de gestion de la liste de diffusion, envoyez un courriel à %(local)s-help@%(domain)s . Si vous rencontrez des problèmes, veuillez prendre contact avec la personne responsable de la liste de diffusion à l'adresse : %(local)s-owner@%(domain)s . Merci. enemies-of-carlotta-1.2.6/templates/help.fr0000664000175000017500000000223210577453116017017 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Message d'aide de la liste de diffusion %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Bonjour, Voici le message d'aide pour la liste de diffusion %(list)s. La liste fonctionne grâce au gestionnaire de liste de diffusion « EoC ». Il est capable de traiter les commandes suivantes : %(local)s-help@%(domain)s Envoie ce message d'aide. %(local)s-subscribe@%(domain)s Vous abonne à la liste. Vous recevrez une demande de confirmation. %(local)s-subscribe-foo=bar@%(domain)s Abonne l'adresse foo@bar à la liste de diffusion. foo@bar recevra une demande de confirmation. %(local)s-unsubscribe@%(domain)s Vous désabonne de la liste de diffusion. Vous recevrez une demande de confirmation. %(local)s-unsubscribe-foo=bar@%(domain)s Désabonne l'adresse foo@bar de la liste de diffusion. foo@bar recevra une demande de confirmation. Si vous rencontrez des problèmes qui ne sont pas traités dans ce message, veuillez prendre contact avec la personne responsable de la liste de diffusion à l'adresse : %(local)s-owner@%(domain)s. Merci. enemies-of-carlotta-1.2.6/templates/setlist-done.sv0000664000175000017500000000103110577453117020517 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Prenumerantlista har ändrats för %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hej! Det här brevet skickades av epostlistehanteraren som sköter listan %(list)s. För din kännedom: PÃ¥ begäran frÃ¥n en listägare har listan med prenumeranter ersatts med en ny. För instruktioner om hur man använder epostlistehanteraren, skicka ett brev till to %(local)s-help@%(domain)s. Om du har problem, kontakta personerna som äger listan pÃ¥ %(local)s-owner@%(domain)s. Tack. enemies-of-carlotta-1.2.6/templates/unsub-confirm.es0000664000175000017500000000245610577453117020667 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Por favor, confirme la cancelación de suscripción a %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hola, Este mensaje lo ha enviado el gestor de listas de correo que opera sobre %(list)s . Alguien ha pedido que se cancele su suscripción a la lista. Lea el mensaje que se envió al final del todo. Si no lo envió usted mismo, por favor, póngase en contacto con quien lo hizo, o sus administradores, y pregúnteles qué está pasando. Si responde a este mensaje, confirmará que desea la cancelación. Por lo común, basta con usar la función normal de respuesta de su programa de correo. De forma alternativa, puede enviar mensajes a las siguientes direcciones: %(confirm)s Si desea continuar con su suscripción, no hace falta que haga nada. Si desea instrucciones sobre el uso del software gestor de listas, envíe un mensaje a %(local)s-help@%(domain)s. Si tiene problemas, por favor, póngase en contacto con las personas que gestionan la lista en %(local)s-owner@%(domain)s. Gracias. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/unsub-goodbye.fi0000664000175000017500000000051210577453117020640 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Näkemiin listalta %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Näkemiin postituslistalta %(list)s . Postituslistaohjelmiston käyttöohjeet saa pyytämällä osoitteesta %(local)s-help@%(domain)s . Listan omistajat saat kiinni osoitteesta %(local)s-owner@%(domain)s . enemies-of-carlotta-1.2.6/templates/setlist-confirm.de0000664000175000017500000000240210577453117021172 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Bitte bestaetigen Sie die Adressenliste fuer %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hallo, Diese E-Mail wurde von der Listen-Software der Liste $(list)s versendet. Einer der Verwalter der Liste (oder jemand der vorgegeben hat ein Verwalter zu sein) hat darum gebeten die Abonnentenliste der Liste zu ändern. Die Anfrage (inklusive der neuen Abonnentenliste) ist an diese E-Mail angehängt. Falls Sie diese Änderung akzeptieren antworten Sie bitte auf diese Nachricht. Ansonsten ignorieren Sie sie bitte einfach. Beachten Sie dass die gesamte Abonnentenliste durch diese Änderung ersetzt wird, alle alten Abonennten werden gelöscht falls sie nicht auch auf der neuen Liste verzeichnet sind. Um eine Anleitung zur Benutzung der Listen-Software zu erhalten, senden Sie bitte eine E-Mail an %(local)s-help@%(domain)s. Falls Sie Hilfe benötigen, können Sie auch an den Verwalter der Liste unter %(local)s-owner@%(domain)s schreiben. Vielen Dank. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/sub-reject.fr0000664000175000017500000000100210577453117020125 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Rejet de l'abonnement à %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Bonjour, Ce courriel vous a été envoyé par le gestionnaire de la liste de diffusion %(list)s . Vous avez demandé à vous abonner à la liste, mais le modérateur a rejet votre demande d'abonnement. Désolé. Si vous rencontrez des problèmes, veuillez prendre contact avec la personne responsable de la liste de diffusion à l'adresse : %(local)s-owner@%(domain)s . Merci. enemies-of-carlotta-1.2.6/templates/unsub-goodbye0000664000175000017500000000056510577453117020253 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Goodbye from %(list)s Content-type: text/plain; charset=us-ascii MIME-Version: 1.0 Goodbye from the %(list)s mailing list. For instructions on using the mailing list manager software, send mail to %(local)s-help@%(domain)s . If you have problems, please contact the human owners of the list at %(local)s-owner@%(domain)s . Thank you. enemies-of-carlotta-1.2.6/templates/list-sorry.fi0000664000175000017500000000026210577453117020207 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Et saa tilaajaluetteloa listalle %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Valitan, et ole listan %(list)s omistaja. enemies-of-carlotta-1.2.6/templates/sub-moderate0000664000175000017500000000137210577453117020055 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Please moderate subscription to %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=us-ascii Hello, This mail has been sent by the mailing list manager operating the %(list)s mailing list to the human owners of the list. Should the following address be allowed to subscribe to the list? %(subscriber)s If so, reply to this mail or send mail to %(confirm)s If you wish to reject the subscriber, send mail to %(deny)s Thanks. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/sub-owner-notification.sv0000664000175000017500000000071110577453117022516 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Address added to %(list)s Content-type: text/plain; charset=us-ascii MIME-Version: 1.0 Hello, This mail has been sent by the mailing list manager operating the %(list)s mailing list to the human owners of the list. The following address has been added to the list: %(address)s Unless there is something really strange going on, this mail is only for your information and you need take no action. Thanks. enemies-of-carlotta-1.2.6/templates/bounce-goodbye.de0000664000175000017500000000132310577453116020751 0ustar liwliwFrom: %(From)s To: %(To)s Subject: E-Mail an Sie konnte nicht zugestellt werden; Sie sind nicht mehr auf der Liste %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hallo, Sie waren Mitglied der Liste %(list)s. Leider konnte E-Mail der Liste an Sie wiederholt nicht zugestellt werden (z.B. weil Ihr Postfach voll war), so dass Sie jetzt von der Liste entfernt wurden. Sobald das Problem behoben ist können Sie die Liste gerne wieder abonnieren. Um eine Anleitung zur Benutzung der Listen-Software zu erhalten, senden Sie bitte eine E-Mail an %(local)s-help@%(domain)s. Falls Sie Hilfe benötigen, können Sie auch an den Verwalter der Liste unter %(local)s-owner@%(domain)s schreiben. Vielen Dank. enemies-of-carlotta-1.2.6/templates/setlist-badlist.es0000664000175000017500000000153410577453117021203 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Dirección incorrecta para %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hola, Este mensaje se lo ha enviado el gestor de listas de correo que opera sobre %(list)s . La sintaxis de la dirección de lista a la que ha enviado el mensaje es incorrecta. Debe haber una única dirección por línea, y nada más. Si desea instrucciones sobre el uso del software gestor de listas, envíe un mensaje a %(local)s-help@%(domain)s . Si tiene problemas, por favor, póngase en contacto con las personas que gestionan la lista en %(local)s-owner@%(domain)s . Gracias. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/setlist-badlist.de0000664000175000017500000000151710577453117021165 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Fehlerhafte Adressenliste (%(list)s) Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hallo, Diese E-Mail wurde von der Listen-Software der Liste $(list)s versendet. Die Adressenliste die Sie gesendet haben war leider nicht lesbar. Sie müssen genau eine Adresse und nichts anderes pro Zeile angeben. Um eine Anleitung zur Benutzung der Listen-Software zu erhalten, senden Sie bitte eine E-Mail an %(local)s-help@%(domain)s. Falls Sie Hilfe benötigen, können Sie auch an den Verwalter der Liste unter %(local)s-owner@%(domain)s schreiben. Vielen Dank. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/msg-wait.es0000664000175000017500000000116010577453117017617 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Por favor, aguarde a la moderación de %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hola, Este mensaje se lo ha enviado el gestor de listas de correo que opera sobre %(list)s . He enviado a los moderadores el mensaje que envió a la lista para que lo aprueben. Esto puede tardar un poco. Por favor, sea paciente. Si desea instrucciones sobre el uso del software gestor de listas, envíe un mensaje a %(local)s-help@%(domain)s . Si tiene problemas, por favor, póngase en contacto con las personas que gestionan la lista en %(local)s-owner@%(domain)s . Gracias. enemies-of-carlotta-1.2.6/templates/sub-moderate.fr0000664000175000017500000000144510577453117020464 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Veuillez modérer l'abonnement à %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Bonjour, Ce courriel a été envoyé par le gestionnaire de la liste de diffusion %(list)s aux personnes qui sont responsables de la liste. L'adresse suivante doit-elle être autorisée à s'abonner à la liste ? %(subscriber)s Si oui, répondez à ce courriel ou envoyez un message à %(confirm)s Si vous souhaitez rejeter l'abonnement, envoyez un message à %(deny)s Merci. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/sub-moderate.de0000664000175000017500000000142210577453117020440 0ustar liwliwFrom: %(From)s To: %(To)s Reply-To: %(confirm)s Subject: Bitte moderieren Sie eine Anmeldung an die Liste %(list)s Content-type: multipart/mixed; boundary="%(boundary)s" MIME-Version: 1.0 This is a multipart message in MIME format --%(boundary)s Content-type: text/plain; charset=utf-8 Hallo, Diese E-Mail wurde von der Listen-Software der Liste %(list)s an die Verwalter der Liste gesendet. Darf die folgende Adresse auf die Liste angemeldet werden? %(subscriber)s Falls ja, antworten Sie bitte auf diese E-Mail oder senden Sie eine E-Mail an %(confirm)s. Falls nicht, so senden Sie bitte eine E-Mail an %(deny)s. Vielen Dank. --%(boundary)s Content-type: message/rfc822 Content-disposition: inline; filename=original.txt %(origmail)s--%(boundary)s-- enemies-of-carlotta-1.2.6/templates/list.fr0000664000175000017500000000036210577453117017045 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Liste des abonnés pour la liste %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Voici la liste des abonnés à la liste de diffusion %(list)s %(addresses)s Total: %(count)s adresses. enemies-of-carlotta-1.2.6/templates/bounce-goodbye.sv0000664000175000017500000000114310577453116021011 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Prenumerationen pÃ¥ %(list)s avbruten p.g.a. för mÃ¥nga studsar Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Hej! Du har prenumererat pÃ¥ epostlistan %(list)s. Tyvärr har post skickad till dig av epostlistehanteraren studsat sÃ¥ mycket att du har blivit automatiskt borttagen. När problemen är lösta är du välkommen att prenumerera pÃ¥ nytt. För instruktioner om hur man använder epostlistehanteraren, skicka ett brev till to %(local)s-help@%(domain)s. Om du har problem, kontakta personerna som äger listan pÃ¥ %(local)s-owner@%(domain)s. Tack. enemies-of-carlotta-1.2.6/templates/list-sorry.es0000664000175000017500000000030210577453117020213 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Lista de suscriptores de %(list)s denegada Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Lo siento, no está en la lista de dueños de %(list)s . enemies-of-carlotta-1.2.6/templates/unsub-owner-notification.fi0000664000175000017500000000064610577453117023036 0ustar liwliwFrom: %(From)s To: %(To)s Subject: Osoite poistettu listalta %(list)s Content-type: text/plain; charset=utf-8 MIME-Version: 1.0 Tämän viestin on lähettänyt postituslistaa %(list)s käsittelevä ohjelmisto listan omistajille. Seuraava osoite on poistanut itsensä listalta: %(address)s Tämä tiedoksi teille. Teidän ei tarvitse tehdä mitään asialle, paitsi jos jotain oikein omituista on tapahtunut. enemies-of-carlotta-1.2.6/testrun.py0000664000175000017500000000037210577453117015622 0ustar liwliwimport unittest import os suite = unittest.TestSuite() for n in filter(lambda fn: fn[-8:] == "Tests.py", os.listdir(".")): suite.addTest(unittest.defaultTestLoader.loadTestsFromName(n[:-3])) runner = unittest.TextTestRunner() runner.run(suite) enemies-of-carlotta-1.2.6/enemies-of-carlotta.10000664000175000017500000004462610577453116017475 0ustar liwliw.TH ENEMIES\-OF\-CARLOTTA 1 .SH NAME enemies\-of\-carlotta \- a simple mailing list manager .SH SYNOPSIS .B enemies\-of\-carlotta .IR "" [ options "] [" addresses ] .SH "DESCRIPTION" .B enemies\-of\-carlotta is a simple mailing list manager. If you don't know what a mailing list manager is, you should learn what they are before trying to use one. A manual page is unfortunately too short to explain it. .PP Enemies of Carlotta keeps all data about the lists in the .I ~/.enemies\-of\-carlotta directory. It will be created automatically when you create your first list. You need to arrange manually for the mails to be processed by the list manager. The details differ from mail system to another. For QMail and Postfix, see below. .PP Each list has one or more owners, who also moderate subscriptions or moderate some or all postings. On completely unmoderated lists the list owners are responsible for answering questions about the list. On completely moderated lists, they have to approve each message before it is sent to the list. On lists with .IR posting=auto , messages from subscribers are sent automatically to the list, and the moderators have to approve the rest. .SH OPTIONS .TP .BR \-\-name= foo@example.com Specify the list the command is to operate on. Most of the remaining options require you to set the list name with this option. With the \-\-edit, \-\-subscribe, \-\-unsubscribe, and \-\-list options, the name can be abbreviated to by leaving out the @ sign and domain. .TP .BI \-\-create Create a new list. You must specify at least one owner with .BR \-\-owner . .TP .BI \-\-owner= address Specify a list owner when creating or editing a list. .TP .BI \-\-moderator= address Specificy a list moderator when creating or editing a list. .TP .BI \-\-language= language\-code Set the language code used for looking up template files. The code should be empty (the default, meaning English), or a two\-letter code such as .B fi or .BR es . .TP .B \-\-cleaning\-woman Deal with bouncing addresses and do other cleanup. You must run .B "enemies\-of\-carlotta \-\-cleaning\-woman" periodically, such as once per hour. It will clean up all your lists. .TP .BI \-\-destroy Destroy the list. .TP .BI \-\-edit Modify the list configuration. .TP .BI \-\-subscription= type When creating a list, set its subscription mode to .I free or .IR moderated . Use with .BR \-\-edit , or .BR \-\-create . .TP .BI \-\-posting= type When creating a list, set its posting mode to .IR free (anyone can post), .IR auto (only subscribers can post, mails from others need to be moderated), or .IR moderated (all mails are moderated). Use with .BR \-\-edit , or .BR \-\-create . .TP .BI \-\-archived= yes\-or\-no Should list messages be archived to the .B archive\-box directory in the list directory under the .B "~/.enemies\-of\-carlotta" directory. Use .I yes or .IR no . .TP .BI \-\-mail\-on\-subscription\-changes= yes\-or\-no Should the list owners be notified when someone subscribes to or unsubscribes from the list? Use .I yes or .IR no . Default is no. .TP .BI \-\-mail\-on\-forced\-unsubscription= yes\-or\-no Should list owners be notified when someone is forcibly dropped from the list due to too much bouncing? Use .I yes or .IR no . Default is no. .TP .BI \-\-ignore\-bounce= yes\-or\-no Should bounces be ignored? Use .I yes or .IR no . Default is no. .TP .BI \-\-list List the subscribers of a list. .TP .BI \-\-subscribe Add subscribers to a list. The non\-option arguments are the addresses to be subscribed. Note that addresses added this way won't be sent confirmation requests. .TP .BI \-\-unsubscribe Remove subscribers from a list. The non\-option arguments are the addresses to be unsubscribed. Note that addresses removed this way won't be sent confirmation requests. .TP .B \-\-incoming Deal with an incoming message in the standard input. The SMTP envelope sender address must be given in the .I SENDER environment variable, and the SMTP envelope recipient address in the .I RECIPIENT environment variable. (QMail and Postfix do this automatically.) .TP .BI \-\-skip\-prefix= string Before analyzing the recipient address to see which list it refers, remove .I string from its beginning. This helps deal with QMail and Postfix virtual domains, see above. .TP .BI \-\-domain= domain.name Before analyzing the recipient address to see which list it refers, replace the domain name part with .IR domain.name . This helps deal with Postfix virtual domains. .TP .BI \-\-is\-list Does the address specified with .B \-\-name refer to a valid list? This sets the exit code to zero (success) if it does, or one (failure) if it does not. .TP .BI \-\-sendmail= pathname Use .I pathname instead of .B /usr/sbin/sendmail for sending mail via a command line interface. Note that the command must obey the sendmail command line interface. .TP .BI \-\-smtp\-server= hostname Send mail using the SMTP server at .I hostname (port 25). The server must be configured to allow the list host to relay mail through it. Note that a command line interface is used by default; SMTP sending is used only if you use this option. .TP .BI \-\-qmqp\-server= hostname Send mail using the QMQP server at .I hostname (port 628). The server must be configured to allow the list host to relay mail through it. Note that a command line interface is used by default; QMQP sending is used only if you use this option. .TP .BI \-\-moderate Force an incoming message to be moderated, even if it is going to a list where posting is free. This can be used for spam filtering: you pipe incoming messages through whatever spam filter you choose to use and if the mssage looks like spam, you ask it to be moderated by a human. .TP .BI \-\-post Force an incoming message to be posted, even if it is going to a list where posting is moderated. This can be used when there is an external check for whether a mail is acceptable to the list, e.g., by checking digital signatures. .TP .BI \-\-quiet By default, debugging log messages are sent to the standard error output stream. With this option, they aren't. .TP .BI \-\-sender= foo@example.com .TP .BI \-\-recipient= foo@example.com These two options are used with .B \-\-incoming and .B \-\-is\-list to override the environment variables .B SENDER and .BR RECIPIENT , respectively. .TP .BI \-\-get Get the values of one or more configuration variables. The name of the variables are given on the command line after the options. Each value is printed on a separate line. .TP .BI \-\-set Set the values of one or more configuration variables. The names and values are given on the command line after the options and separated by an equals sign ("="). For example, the following would set the language of a list to Finnish: .B "enemies\-of\-carlotta \-\-name=foo@bar \-\-set language=fi" .TP .BI \-\-version Print out the version of the program. .TP .BI \-\-show\-lists List the lists enemies\-of\-carlotta knows about. .SH CONFIGURATION Each list is represented by a directory, named after the list, in .IR ~/.enemies\-of\-carlotta . That directory contains several files and directories, described below. In general, it is not necessary to touch these at all. However, some esoteric configuration can only be done by hand editing of the list configuration file. .TP .B config The list configuration file. Contents are described below. .TP .B subscribers Subscriber database. Each line contains a subscriber group, with the first five space delimited fields being group identifier, status, timestamp for when the group was created, timestamp for the bounce that made it switch from status 'ok' to 'bounced', and the bounce identifier. .TP .B archive\-box Archived messages. .TP .B bounce\-box Bounce messages groups not in state 'ok'. .TP .B headers\-to\-add These headers are added to the mails sent to the list. They are copied to the beginning of the existing headers exactly as they are in the file, after list headers ("List\-ID" and such) have been added and those mentioned in .B headers\-to\-remove have been removed. .TP .B headers\-to\-remove These headers are removed from mails sent to the list. .TP .B moderation\-box Messages waiting for moderator approval. .TP .B subscription\-box Subscription and unsubscription requests waiting to be confirmed by the user. .TP .B templates Directory containing list specific templates (optional). If this directory exists, templates are searched from it before going for system wide templates. An empty file here means the corresponding message is not sent at all. This can, for example, to be used to turn off the "please wait for moderator" mails on a per\-list basis. .TP .B plugins Directory containing plugins, Python source files that are loaded automatically by EoC upon startup. The plugins may change how EoC operates. .PP The .B config file has a .IR keyword = value format: .PP .RS .nf [list] owners = liw@liw.iki.fi archived = no posting = free subscription = free mail\-on\-subscription\-changes = yes mail\-on\-forced\-unsubscribe = yes language = fi .fi .RE .PP The keywords .BR archived , .BR posting , and .B subscription correspond to the options with the same names. Other keywords are: .TP .B owners List of addresses for the owners. Set with the .I \-\-owner option. .TP .B moderators List of addresses for the moderators. Set with the .I \-\-moderator option. .TP .B mail\-on\-subscription\-changes Should the owners be mailed when users subscribe or unsubscribe? .TP .B mail\-on\-forced\-unsubscribe Should the owners be mailed when people are removed from the list due to excessive bouncing? .TP .B ignore_bounce Bounce messages are ignored on this list. Useful for example if list should have static subscriber list. .TP .B language Suffix for templates, to allow support for multiple languages. (If .I language is set to "fi", then the template named "foo" is first searched as "foo.fi".) .TP .B pristine\-headers Do not MIME encode the headers. Set to "yes" to not encode, anything else (including empty or unset) means encoding will happen. .SH EXAMPLES To create a list called .IR moviefans@example.com , owned by .IR ding@example.com , use the following command (all on one line): .sp 1 .nf .RS enemies\-of\-carlotta \-\-name=moviefans@example.com \-\-owner=ding@example.com \-\-create .RE .fi .PP Note that you need to arrange mail to arrive at the list (and its command addresses) by configuring your mail system. For Qmail and Postfix, see below. .PP To see the subscribers on that list: .sp 1 .nf .RS enemies\-of\-carlotta \-\-name=moviefans@example.com \-\-list .RE .fi .PP People wanting to subscribe to the list should mail .sp 1 .nf .RS moviefans\-subscribe@example.com .RE .fi .SH QMAIL With QMail, to arrange for incoming mail to be processed by Enemies of Carlotta, you need to create a couple of .I .qmail\-extension files per list. For example, if your username is joe and you wish to run the joe\-fans mailing list, you need to create two files, .I .qmail\-fans and .IR .qmail\-fans\-default , containing .sp 1 .RS |enemies\-of\-carlotta \-\-incoming .RE .PP If you're running a virtual domain, example.com, and the mails are being delivered to via .I /var/qmail/control/virtualdomains to .IR joe\-exampledotcom , the files would be called .I .qmail\-exampledotcom\-fans and .I .qmail\-exampledotcom\-fans\-default and would contain .sp 1 .RS |enemies\-of\-carlotta \-\-incoming \-\-skip\-prefix=joe\-exampledotcom\- .RE .sp 1 (all on one line, of course, in case the manual page formatter breaks it on several lines). .SH COURIER-MTA For Courier-MTA, the instructions are similar to the Qmail ones above. If your user name is joe and you wish to run the joe-fans email list, you need to create the two files .courier-fans and .courier-fans-default in your home directory with the content .sp 1 .RS |enemies-of-carlotta --is-list --name $RECIPIENT || exit 67 .br |enemies-of-carlotta --incoming .RE .sp 1 (The former file needs only the second line, but the first line does no harm and it is easier to keep track of things when the files have the same content. Note that $RECIPIENT should be included verbatim, it is not a metavariable for you to expand.) .PP If you are running a virtual domain configured so that all mail to the domain @example.com is delivered to joe-exampledotcom, you need to create the files .courier-exampledotcom-fans and .courier-exampledotcom-fans-default containing the two following lines: .sp 1 .RS |enemies-of-carlotta --is-list --name $RECIPIENT --skip-prefix=joe-exampledotcom || exit 67 .br |enemies-of-carlotta --incoming --skip-prefix=joe-exampledotcom .RE .sp 1 If the virtual domain is for list use only, then it is sufficient to create only the file .courier-exampledotcom-default containing the latter two lines. .SH POSTFIX With Postfix, you need to set up a .I .forward file containing .sp 1 .RS "|procmail \-p" .RE .sp 1 and then a .I .procmailrc file containing .sp 1 .RS :0 .br * ? enemies\-of\-carlotta \-\-name=$RECIPIENT \-\-is\-list .br | enemies\-of\-carlotta \-\-incoming .RE .PP To use Enemies of Carlotta with a Postfix virtual domain, you need to set up a .IR "virtual regular expression map" , typically called .I /etc/postfix/virtual_regexp (add .I "virtual_maps = regexp:/etc/postfix/virtual_regexp" to your .I /etc/postfix/main.cf file to enable it). The regexp file needs to do ugly things to preserve the recipient address. Add the following to the regexp file: .sp 1 .RS /^your\.virtual\.domain$/ dummy .br /^(yourlist|yourlist\-.*)@(your\.virtual\.domain)$/ joe+virtual\-$1 .RE .sp 1 That's two lines. Use .B joe-virtual instead, if .I recipient_delimiter for your Postfix is configured to a minus instead of a plus.. Then, in your .I .procmailrc file, add the .I "\-\-skip\-prefix=joe\-virtual\-" and .I \-\-domain=your.virtual.domain options to both calls to .BR enemies\-of\-carlotta . .PP (Yes, I think these things are much too complicated, too.) .SH "MAIL COMMANDS" Users and list owners use Enemies of Carlotta via e\-mail using command addresses such as .BR foo\-subscribe@example.com . Here is a list of all command addresses list users and owners can give. In all these examples, the name of the mailing list is .BR foo@example.com . .SS "Mail commands anyone can use" These commands are meant for everyone's use. They don't require any special priviledges. .TP .BR foo@example.com Send mail to all list subscribers. The message may have to be manually approved by the list moderators first, and they have the power to reject a message. .TP .BR foo\-owner@example.com Send mail to the list owner or owners instead. .TP .BR foo\-help@example.com Sending mail to this address makes the list manager reply with the help message for the list. .TP .BR foo\-subscribe@example.com Send mail to this address to subscribe to a list. The list manager will respond with a confirmation request. You won't be subscribed unless you reply to the confirmation request. This way, malicious people can't put your address on a mailing list, or many mailing lists. .TP .BR foo\-subscribe\-joe=example.com@example.com This is a second form of the subscription address. If you want to subscribe to the list with another address than the one you're sending mail from, use this one. In this case, the address to be subscribed is joe@example.com. Note that the confirmation request is sent to Joe, since it is his address that is to be added to the list. .TP .BR foo\-unsubscribe@example.com To unsubscribe from a list, send mail to this address from the address that is subscribed to the list. Again, you will receive a confirmation request, to prevent malicious people from unsubscribing you against your will. .TP .BR foo\-unsubscribe\-joe=example.com@example.com To unsubscribe Joe, use this address. Again, it is Joe who gets to confirm. .SS "Mail commands for the list owners" These are commands that list owners can use to administer their list. .TP .BR foo\-subscribe\-joe=example.com@example.com If a list owner sends mail like this, it is they who get the confirmation request, not Joe. It is generally better for people to subscribe themselves, but sometimes list owners want to do it, when they have permission from the person and feel helpful. .TP .BR foo\-unsubscribe\-joe=example.com@example.com List owners can also unsubscribe other people. .TP .BR foo\-list@example.com To see who are on the list, this is the address to use. It only works if the sender address is one of the list owners. The sender address is the one used on the SMTP level, not the one in the From: header. .TP .BR foo\-setlist@example.com This lets a list owner set the whole subscriber list at once. This is similar to using lots and lots and lots of \-subscribe and \-unsubscribe commands, only less painful. Everyone who is added to the list gets a welcome message, and everyone who is removed from the list gets a goodbye message. .TP .BR foo\-setlistsilently@example.com This is similar to \-setlist, but no welcome and goodbye messages are sent. .SH PLUGINS Enemies of Carlotta supports plugins. If you don't know what Python programming is, you may want to skip this section. .PP A plugin is a Python module (file named with a .B .py suffix), placed in the .B ~/.enemies\-of\-carlotta/plugins directory. The plugins are loaded automatically upon startup, if their declared interface version matches the one implemented by Enemies of Carlotta. The interface version is declared by the module global variable .BR PLUGIN_INTERFACE_VERSION . .PP Plugins can define hook functions that are called by appropriate places in the EoC code. At the moment, the only hook function is .BR send_mail_to_subscribers_hook , which can manipulate a mail message before it is sent to the subscribers. The function must look like this: .PP .ti +5 def send_mail_to_subscribers_hook(list, text): .PP The .I list argument is a reference to the .I MailingList object that corresponds to the list in question, and .I text is the complete text of the mail message as it exists. The function must return the new contents of the mail message. .SH FILES .TP .I ~/.enemies\-of\-carlotta All files related to your mailing lists. .TP .I ~/.enemies\-of\-carlotta/secret Secret password used to generate signed addresses for bounce checking and subscription verification. .TP .I ~/.enemies\-of\-carlotta/foo@example.com Directory containing data pertaining to the foo@example.com list. Except for the .I config file in this directory, you shouldn't edit anything by hand. .TP .I ~/.enemies\-of\-carlotta/foo@example.com/config Configuration file for the mailing list. You may need to edit this file by hand if you wish to change moderation status or list owners. .SH "SEE ALSO" You may want to visit the .I "Enemies of Carlotta" home page at .IR http://www.iki.fi/liw/eoc/ . enemies-of-carlotta-1.2.6/enemies-of-carlotta0000664000175000017500000000074010577453116017323 0ustar liwliw#!/usr/bin/python # # This file works as the Enemies of Carlotta startup wrapper. The real # program is in eoc.py, stored in SHAREDIR (see below). It is large enough # that the time Python takes to parse and compile it to bytecode is # significant, therefore we use a very short wrapper (this file) and # install eoc.py in a way that allows it to be compiled, thus reducing # startup time. SHAREDIR="." import sys sys.path.insert(0, SHAREDIR) import eoc eoc.main(sys.argv[1:]) enemies-of-carlotta-1.2.6/enemies-of-carlotta.1.es0000664000175000017500000005075210577453116020100 0ustar liwliw.TH ENEMIES\-OF\-CARLOTTA 1 .SH NAME enemies\-of\-carlotta \- sencillo gestor de listas de correo .SH SYNOPSIS .B enemies\-of\-carlotta .IR "" [ opciones "] [" direcciones ] .SH "DESCRIPCIÓN" .B enemies\-of\-carlotta es un gestor sencillo para listas de correo. Si no sabe qué es un gestor de listas de correo, es mejor que aprenda lo que son, antes de intentar usar uno concreto. Por desgracia, no hay espacio para eso en una página de manual. .PP Enemies of Carlotta mantiene todos los datos sobre las listas de correo en un directorio llamado .I ~/.enemies\-of\-carlotta . Se creará automáticamente en cuanto usted cree la primera lista. Tendrá que hacer arreglos a mano para que el gestor de listas pueda procesar los mensajes. Los detalles varían de un servidor de correo a otro. Para qmail y Postfix, véase más adelante. .PP Cada lista tiene uno o más propietarios, que también moderan suscripciones o incluso algunos o todos los envíos a la lista. En listas sin moderación alguna, el propietario de la lista es el responsable de contestar las dudas acerca de la lista. En listas con moderación completa, tienen que aprobar cada mensaje, antes de que éste pueda enviarse a la lista. En listas con la opción .IR posting=auto , los mensajes de los suscriptores se envían automáticamente a la lista, y los moderadores tienen que aprobar el resto de mensajes. .SH OPCIONES .TP .BR \-\-name= lista@ejemplo.com Especifica sobre qué lista ha de actuar la orden especificada. Casi todas las restantes opciones precisan que especifique antes el nombre de la lista con la opción antedicha. Con las opciones \-\-edit, \-\-subscribe, \-\-unsubscribe, y \-\-list , el nombre puede abreviarse quitando el signo @ y el dominio que le sigue. .TP .BI \-\-create Crear una lista nueva. Ha de especificar al menos un propietario con la opción .BR \-\-owner . .TP .BI \-\-owner= dirección Al crear una lista, especifica un propietario de la lista. .TP .BI \-\-language= código\-idioma Establece el código de idioma que se usa para buscar plantillas. El código debería estar vacío (opción por defecto, es decir inglés), o un código de dos letras como .B fi o .BR es . .TP .B \-\-cleaning\-woman Se encarga de las direcciones de rebote y hace otras limpiezas varias. Ha de ejecutar periódicamente .B "enemies\-of\-carlotta \-\-cleaning\-woman" , algo así como una vez por hora. Efectuará una limpieza de todas sus listas. .TP .BI \-\-destroy Eliminar la lista. .TP .BI \-\-edit Modificar la configuración de la lista. .TP .BI \-\-subscription= tipo Al crear una lista, establece su modo de suscripción a .I free (libre) o bien .IR moderated (moderado). Úselo con .BR \-\-edit , o con .BR \-\-create . .TP .BI \-\-posting= tipo Al crear una lista, establece su modo de envío de mensajes a .IR free (libre), .IR auto (auto), o bien .IR moderated (moderado). Úselo con .BR \-\-edit , o con .BR \-\-create . .TP .BI \-\-archived= yes\-o\-no Especifica si los mensajes de la lista deben archivarse en el directorio .B archive\-box en el directorio de la lista que a su vez existe dentro del directorio .B "~/.enemies\-of\-carlotta" . Utilice .I yes o bien .IR no . .TP .BI \-\-mail\-on\-subscription\-changes= yes\-o\-no ¿Debería notificarse a los dueños de la lista cuando alguien se suscribe o desuscribe de ella? Use .I yes o .IR no . Por defecto es no. .TP .BI \-\-mail\-on\-forced\-unsubscription= yes\-o\-no ¿Debería notificarse a los dueños de la lista cuando se elimina a alguien de la lista forzosamente por exceso de rebotes? Use .I yes o .IR no . Por defecto es no. .TP .BI \-\-list Muestra los suscriptores de una lista de correo. .TP .BI \-\-subscribe Añade suscriptores a una lista de correo. Los argumentos que no son opciones, son las direcciones que hay que suscribir a la lista. Observe que las direcciones que se añadan mediante este procedimiento no recibirán una confirmación de suscripción, sino que se las suscribirá directamente. .TP .BI \-\-unsubscribe Elimina suscriptores de una lista de correo. Los argumentos que no son opciones, son las direcciones que hay que desuscribir de la lista. Observe que las direcciones que se eliminen mediante este procedimiento no recibirán una confirmación de desuscripción, sino que se las eliminará directamente. .TP .B \-\-incoming Encargarse de un mensaje que se recibe por la entrada estándar. La dirección del remitente del envoltorio SMTP (envelope sender address) debe especificarse mediante la variable de entorno .I SENDER , y la dirección del destinatario del envoltorio SMTP (SMTP envelope recipient address) debe especificarse en la variable de entorno .I RECIPIENT . (qmail y Postfix lo hacen automáticamente). .TP .BI \-\-skip\-prefix= cadena Antes de analizar la dirección del destinatario para ver a qué lista se refiere, eliminar .I cadena de su comienzo. Esta característica ayuda en el caso de los dominios virtuales de qmail y Postfix; véase más arriba. .TP .BI \-\-domain= nombre.dominio Antes de analizar la dirección del destinatario para ver a qué lista se refiere, sustituir la parte del dominio por .IR nombre. dominio . Esta característica es útil en el caso de los dominios virtuales de Postfix. .TP .BI \-\-is\-list ¿Se refiere la lista especificada en la opción .B \-\-name a una lista válida? Devuelve un estado de salida de cero (éxito) si es válida, o un estado de uno (fallo) si no es válida. .TP .BI \-\-sendmail= ruta\-hasta\-el\-programa Utilice .I ruta\-hasta\-el\-programa en lugar de .B /usr/sbin/sendmail para enviar correo por medio de una interfaz de línea de órdenes. Nótese que la orden alternativa debe seguir las convenciones de la interfaz de línea de órdenes sendmail. .TP .BI \-\-smtp\-server= nombre.de.servidor Enviar el correo usando el servidor SMTP .I nombre.de.servidor (puerto 25). El server ha de estar configurado para permitir que la lista pueda efectuar la retransmisión de correo a través de él. Nótese que la opción por defecto es usar la interfaz de línea de órdenes. Esta opción de enviar por SMTP sólo se utilizará si la especifica explícitamente. .TP .BI \-\-qmqp\-server= nombredemaquina Enviar correo usando el servidor QMQP que hay en .I nombredemaquina (puerto 628). El servidor debe estar configurado para permitir que la máquina de la lista reenvíe correo a través suyo. Tenga en cuenta que por defecto se usa una interfaz de línea de órdenes para el envío; sólo se utilizará QMQP si especifica esta opción. .TP .BI \-\-moderate Forzar la moderación de mensajes para un mensaje dado, incluso si va a ir a parar a una lista de mensajes donde se puede envíar libremente. Puede usar esta opción para el filtrado de correo electrónico no solicitado (spam): sus mensajes entrantes pasan por el filtro de spam que usted especifique y si el mensaje califica como spam, se solicita la moderación del mensaje por parte de una persona. .TP .BI \-\-post Forzar el envío de un mensaje entrante a una lista dada, incluso si va a ir a parar a una lista que tenga el envío moderado. Puede usar esta opción cuando hay una comprobación externa de si un correo es aceptable en una lista; por ejempo, si dispone de un comprobador de firmas digitales. .TP .BI \-\-quiet De forma predeterminada, los mensajes de registro de depuración se envían al flujo de salida de error estándar. Con esta opción, se anula dicho comportamiento. .TP .BI \-\-sender= foo@ejemplo.com .TP .BI \-\-recipient= foo@ejemplo.com Estas dos opciones se usan junto a .B \-\-incoming y .B \-\-is\-list para imponerse a las variables de entorno .B SENDER y .BR RECIPIENT , respectivamente. .TP .BI \-\-get Obtiene los valores de una o más variables de configuración. El nombre de las variables se da en la línea de órdenes tras las opciones. Cada valor se imprime en una línea aparte. .TP .BI \-\-set Establece los valores de una o más variables de configuración. Los nombres y valores se dan en la línea de órdenes tras las opciones y separadas por signos 'igual' ("="). Por ejemplo, lo siguiente establecería el finlandés como idioma de una lista: .B "enemies\-of\-carlotta \-\-name=foo@bar \-\-set language=fi" .TP .BI \-\-version Muestra la versión del programa. .TP .BI \-\-show\-lists Muestra las listas conocidas para enemies\-of\-carlotta. .SH CONFIGURACIÓN Cada lista está representada por un directorio, que recibe el nombre de la lista, y que está dentro de .IR ~/.enemies\-of\-carlotta . Dicho directorio contiene varios ficheros y directorios, que se describen más abajo. En general, no es necesario tocarlos para nada. Sin embargo, determinadas configuraciones, un tanto esotéricas, sólo pueden establecerse editando a mano el fichero de configuración de la lista. .TP .B config El fichero de configuración de la lista. Su contenido se describe más abajo. .TP .B subscribers Base de datos de suscriptores. Cada línea contiene un grupo de suscriptores, siendo los cinco primeros campos delimitados por espacios el identificador del grupo, el estado la marca temporal de cuándo se creó el grupo, la marca temporal de cuándo cambió su estado de 'ok' a 'bounced' (rebotado), y el identificador de la devolución (bounce). .TP .B archive\-box Mensajes de la lista archivados. .TP .B bounce\-box Grupos de mensajes rebotados (bounce) y que no están en estado 'ok'. .TP .B headers\-to\-add Cabeceras a añadir a los mensajes enviados a esta lista. Se copian al principio de cualquier cabecera existente exactamente tal como estén en el fichero, tras haber añadido las cabeceras de la lista ("List\-ID", etc) y eliminado las mencionadas en .B headers\-to\-remove . .TP .B headers\-to\-remove Estas cabeceras se eliminan de los mensajes enviados a la lista. .TP .B moderation\-box Mensajes en espera de aprobación por parte del moderador. .TP .B subscription\-box Solicitudes de suscripción y desuscripción en espera de confirmación por parte del usuario. .TP .B templates Directorio que contiene plantillas (opcionales) específicas a la lista. Si existe este directorio, se buscan las plantillas allí antes de ir en busca de plantillas globales. Un fichero vacío indica que el mensaje correspondiente no será enviado. Esto puede usarse, por ejemplo, para desactivar los mensajes «espere por la moderación» en determinadas listas. .TP .B plugins Directorio que contiene plugins. Son archivos fuente en Python que carga EoC automáticamente al arrancar. Los plugins pueden variar la manera en que opera EoC. .PP El fichero .B config tiene un formato .IR palabra_clave = valor : .PP .RS .nf [list] owners = liw@liw.iki.fi archived = no posting = free subscription = free mail\-on\-subscription\-changes = yes mail\-on\-forced\-unsubscribe = yes language = es .fi .RE .PP Las palabras clave .BR archived , .BR posting , y .B subscription corresponden a las opciones de su mismo nombre. Otras palabras clave son: .TP .B owners Lista de las direcciones de los propietarios. Especifíquelas con la opción .I \-\-owner . .TP .B moderators Lista de las direcciones de los moderadores. Especifíquelas con la opción .I \-\-moderator . .TP .B mail\-on\-subscription\-changes Especifica si hay que mandar un correo a los propietarios de la lista cada vez que un usuario se suscribe o se desuscribe. .TP .B mail\-on\-forced\-unsubscribe Especifica si hay que mandar un correo a los propietarios de la lista cada vez que un usuario es dado de baja por excesivo rebote de mensajes. .TP .B ignore_bounce Los rebotes son ignorados en esta lista. Útil por ejemplo si la lista debe tener una lista fija de suscriptores. .TP .B language Sufijo para las plantillas, para permitir el suporte de múltiples lenguas. (Si .I language tiene el valor "es", entonces a la plantilla llamada "aficionados" se la busca en primer lugar como "aficionados.es".) .TP .B pristine\-headers No usar codificación MIME para las cabeceras. Establecer a "yes" para no hacerlo, cualquier otra cosa (incluyendo vacío o sin establecer) significa que se utilizará la codificación. .SH EJEMPLOS Para crear una lista llamada .IR cinefilos@ejemplo.com , cuyo propietario sea .IR dingo@ejemplo.com , utilice la siguiente orden (todo en una línea): .sp 1 .nf .RS enemies\-of\-carlotta \-\-name=cinefilos@ejemplo.com \-\-owner=dingo@ejemplo.com \-\-create .RE .PP Observe que debe configurar su servidor de correo en concreto para que el correo llegue a la lista. Para qmail y postfix, véase infra. .PP To see the subscribers on that list: .sp 1 .RS enemies\-of\-carlotta \-\-name=cinefilos@ejemplo.com \-\-list .RE .PP Quien quiera suscribirse a la lista ha de escribir un correo a: .sp 1 .RS cinefilos\-subscribe@ejemplo.com .RE .SH QMAIL Con qmail, para conseguir que el correo entrante sea procesado por Enemies of Carlotta, tiene que crear dos ficheros .I .qmail\-extension por cada lista. Por ejemplo, si su nombre de usuario es pepe y quiere ejecutar la lista pepe\-aficionados, ha de crear dos ficheros, .I .qmail\-aficionados y .IR .qmail\-aficionados\-default , que contengan la línea .sp 1 .RS |enemies\-of\-carlotta \-\-incoming .RE .PP Si tiene configurado un dominio virtual, ejemplo.com, y los correos se entregan vía .I /var/qmail/control/virtualdomains a pepe\-ejemplodotcom , los ficheros se llamarían .I .qmail\-ejemplodotcom\-aficionados y .I .qmail\-ejemplodotcom\-aficionados-default y contendrían .sp 1 .RS |enemies\-of\-carlotta \-\-incoming \-\-skip\-prefix=pepe\-ejemplodotcom\- .RE .sp 1 (todo en una línea, claro, por si acaso su lector de páginas de manual formatea la orden anterior en varias líneas). .SH POSTFIX Con postfix, ha de configurar un fichero .I .forward que contenga .sp 1 .RS "|procmail \-p" .RE .sp 1 y además un fichero .I .procmailrc que contenga .sp 1 .RS :0 .br * ? enemies\-of\-carlotta \-\-name=$RECIPIENT \-\-is\-list .br | enemies\-of\-carlotta \-\-incoming .RE .PP Para usar Enemies of Carlotta con un dominio virtual de Postfix, ha de configurar un .I "mapa virtual de expresiones regulares", que típicamente está en .I /etc/postfix/virtual_regexp (añada .I "virtual_maps = regexp:/etc/postfix/virtual_regexp" a su fichero .I /etc/postfix/main.cf para activar esta carcterística). El fichero de expresiones regulares ha de hacer cosas extrañas para conservar la dirección del destinatario. Añada lo siguiente al fichero de expresiones regulares: .sp 1 .RS /^su\.dominio\.virtual$/ dummy .br /^(sulista|sulista\-.*)@(su\.dominio\.virtual)$/ pepe+virtual\-$1 .RE .sp 1 (Lo anterior estaba en dos líneas). Utilize .B pepe-virtual en lugar del anterior si el .I recipient_delimiter de su Postfix está configurado para usar un signo menos en vez de más. Luego, en su fichero .I .procmailrc añada la opción .I "\-\-skip\-prefix=pepe\-virtual\-" y también .I \-\-domain=your.virtual.domain para las dos llamadas a .BR enemies\-of\-carlotta . .PP (Sí, nosotros también pensamos que estas configuraciones son demasiado complicadas). .SH "ÓRDENES PARA EL CORREO" Los usuarios y los propietarios de las listas utilizan Enemies of Carlotta a través del correo electrónico, usando para ello direcciones a modo de órdenes, como por ejemplo .BR aficionados\-subscribe@ejemplo.com . He aquí una lista de todas las órdenes que pueden usar tanto usuarios como propietarios de listas de correo. En todos estos ejemplos, el nombre de la lista de correo es .BR aficionados@ejemplo.com . .SS "Órdenes a través de correo que pueden usar todos" Estas órdenes están pensadas para el uso general. No precisan de ningún privilegio especial. .TP .BR aficionados@ejemplo.com Enviar correo a todos los suscritos a la lista. El mensaje pueden haberlo aprobado previamente de forma manual los administradores de la lista, que están facultados para rechazar los mensajes. .TP .BR aficionados\-owner@ejemplo.com Enviar un correo al propietario o propietarios de la lista. .TP .BR aficionados\-help@ejemplo.com Enviar un correo a esta dirección hace que el gestor de listas de correo nos devuelva un correo con la ayuda existente sobre la lista en cuestión. .TP .BR aficionados\-subscribe@ejemplo.com Envíe un mensaje a esta dirección para suscribirse a la lista. El gestor de listas de correo le responderá con una confirmación de suscripción. No se le suscribirá a la lista a menos que responda a la petición de confirmación. De esta forma, un usuario malicioso no podrá poner su dirección en una o en muchas listas de correo. .TP .BR aficionados\-subscribe\-pepe=ejemplo.com@ejemplo.com Esta es una manera alternativa de la dirección de suscripción. Si desea suscribirse a la lista de correo con una dirección distinta a aquella desde la que envía el mensaje, utilice esta modalidad. En este caso, la dirección para suscribirse es pepe@ejemplo.com. Nótese que la petición de confirmación se envía a Pepe, puesto que es su dirección la que va a añadirse a la lista. .TP .BR aficionados\-unsubscribe@ejemplo.com Para desuscribirse de una lista, envíe un correo a esta dirección desde la dirección que desea desuscribir de la lista. De nuevo recibirá una petición de confirmación, para evitar que un usuario malicioso le desuscriba de una lista de correo contra su voluntad. .TP .BR aficionados\-unsubscribe\-pepe=ejemplo.com@ejemplo.com Para desuscribir a Pepe, use esta dirección. De nuevo, es Pepe quien recibirá la petición de confirmación. .SS "Órdenes a través de correo que pueden usar los propietarios de las listas" Se trata de órdenes que pueden usar los propietarios de listas para administrar su lista. .TP .BR aficionados\-subscribe\-pepe=ejemplo.com@ejemplo.com Si un propietario de una lista envía un correo a la dirección anterior, él recibirá la petición de confirmación, y no Pepe. Generalmente es mejor que los usuarios se suscriban ellos mismos, pero a veces los propietarios de listas pueden desear esta característica, cuando tienen permiso de la persona afectada y quieren resultar más útiles. .TP .BR aficionados\-unsubscribe\-pepe=ejemplo.com@ejemplo.com Los propietarios de listas también pueden desuscribir a otros usuarios. .TP .BR aficionados\-list@ejemplo.com Para ver quién está en la lista, envíe un correo a esta dirección. Sólo funciona si la dirección del remitente del correo coincide con un propietario de la lista. La dirección "sender address" se usa a nivel del protocolo SMTP, y no es la del encabezamiento "From:" .TP .BR aficionados\-setlist@ejemplo.com Esta orden permite al propietario de una lista especificar de una sola vez toda la lista de suscriptores. Es equivalente a utilizar montones y montones de órdenes \-subscribe y \-unsubscribe, sólo que menos tedioso. Todo el que resulte añadido a la lista recibe un mensaje de bienvenida, y todo el que quede eliminado de la lista recibe un mensaje de despedida. .TP .BR aficionados\-setlistsilently@ejemplo.com Semejante a -setlist, pero no se envían mensajes ni de bienvenida, ni de despedida. .SH PLUGINS Enemies of Carlotta admite plugins. Si no sabe programar en Python, probablemente se puede saltar esta sección. .PP Un plugin es un módulo de Python (fichero con un sufijo .B .py en el nombre), situado en el directorio .B ~/.enemies-of-carlotta/plugins . Los plugins se cargan automáticamente durante el arranque, si la versión declarada de su interfaz se ajusta con la implementada por Enemies of Carlotta. La versión de la interfaz se declara en la variable global del módulo .BR PLUGIN_INTERFACE_VERSION . .PP Los plugin pueden definir funciones que serán invocadas desde los lugares apropiados del código EoC. Por el momento, la única función de enganche (hook) disponible es .BR send_mail_to_subscribers_hook , que puede manipular un mensaje antes de que sea enviado a los suscriptores. La función debe parecerse a esto: .PP .ti +5 def send_mail_to_subscribers_hook(list, text): .PP El argumento .I list es una referencia al objeto .I MailingList que corresponde a la lista en cuestión, y .I text es el texto completo del mensaje de correo en su forma actual. La función debe devolver el nuevo contenido del mensaje de correo. .SH FICHEROS .TP .I ~/.enemies\-of\-carlotta Aquí están todos los ficheros relacionados con sus listas de correo. .TP .I ~/.enemies\-of\-carlotta/secret Contraseña secreta que se usa para generar direcciones firmadas para comprobación de rebotes de correo y verificación de suscripción. .TP .I ~/.enemies\-of\-carlotta/aficionados@ejemplo.com Directorio que contiene los datos relativos a la lista aficionados@ejemplo.com. Excepto el fichero .I config de este directorio, no debe editar a mano nada de lo contenido en él. .TP .I ~/.enemies\-of\-carlotta/aficionados@ejemplo.com/config Fichero de configuración de la lista de correo. Quizá tenga que editar este fichero a mano si desea cambiar el estado de moderación de la lista o sus propietarios. .SH "VÉASE TAMBIÉN" Visite la página de .I "Enemies of Carlotta" alojada en .IR http://www.iki.fi/liw/eoc/ . .TP La traducción de esta página ha corrido a cargo de Iván Juanes .BR kerberos@gulic.org y de Ricardo Cárdenes .BR heimy@gulic.org como parte de los proyectos del grupo Gulic. enemies-of-carlotta-1.2.6/eocTests.py0000664000175000017500000012607010577453116015712 0ustar liwliwimport unittest import shutil import os import re import time import string import eoc DOTDIR = "dot-dir-for-testing" eoc.DOTDIR = DOTDIR eoc.quiet = 1 def no_op(*args): pass class AddressCleaningTestCases(unittest.TestCase): def setUp(self): self.ap = eoc.AddressParser(["foo@EXAMPLE.com", "bar@lists.example.com"]) def verify(self, address, wanted, skip_prefix=None, forced_domain=None): self.ap.set_skip_prefix(skip_prefix) self.ap.set_forced_domain(forced_domain) address = self.ap.clean(address) self.failUnlessEqual(address, wanted) def testSimpleAddress(self): self.verify("foo@example.com", "foo@example.com") def testUpperCaseAddress(self): self.verify("FOO@EXAMPLE.COM", "foo@example.com") def testPrefixRemoval(self): self.verify("foo@example.com", "foo@example.com", skip_prefix="user-") self.verify("user-foo@example.com", "foo@example.com", skip_prefix="user-") def testForcedDomain(self): self.verify("foo@example.com", "foo@example.com", forced_domain="example.com") self.verify("foo@whatever.example.com", "foo@example.com", forced_domain="example.com") def testPrefixRemovalWithForcedDomain(self): self.verify("foo@example.com", "foo@example.com", skip_prefix="user-", forced_domain="example.com") self.verify("foo@whatever.example.com", "foo@example.com", skip_prefix="user-", forced_domain="example.com") self.verify("user-foo@example.com", "foo@example.com", skip_prefix="user-", forced_domain="example.com") self.verify("user-foo@whatever.example.com", "foo@example.com", skip_prefix="user-", forced_domain="example.com") class AddressParserTestCases(unittest.TestCase): def setUp(self): self.ap = eoc.AddressParser(["foo@EXAMPLE.com", "bar@lists.example.com"]) def verify_parser(self, address, wanted_listname, wanted_parts): listname, parts = self.ap.parse(address) self.failUnlessEqual(listname, wanted_listname) self.failUnlessEqual(parts, wanted_parts) def testParser(self): self.verify_parser("foo@example.com", "foo@EXAMPLE.com", []) self.verify_parser("foo-subscribe@example.com", "foo@EXAMPLE.com", ["subscribe"]) self.verify_parser("foo-subscribe-joe=example.com@example.com", "foo@EXAMPLE.com", ["subscribe", "joe=example.com"]) self.verify_parser("foo-bounce-123-ABCDEF@example.com", "foo@EXAMPLE.com", ["bounce", "123", "abcdef"]) class ParseRecipientAddressBase(unittest.TestCase): def setUp(self): self.lists = ["foo@example.com", "bar@lists.example.com", "foo-announce@example.com"] self.mlm = eoc.MailingListManager(DOTDIR, lists=self.lists) def environ(self, sender, recipient): eoc.set_environ({ "SENDER": sender, "RECIPIENT": recipient, }) class ParseUnsignedAddressTestCases(ParseRecipientAddressBase): def testEmpty(self): self.failUnlessRaises(eoc.UnknownList, self.mlm.parse_recipient_address, "", None, None) def verify(self, address, skip_prefix, forced_domain, wanted_dict): dict = self.mlm.parse_recipient_address(address, skip_prefix, forced_domain) self.failUnlessEqual(dict, wanted_dict) def testSimpleAddresses(self): self.verify("foo@example.com", None, None, { "name": "foo@example.com", "command": "post" }) self.verify("FOO@EXAMPLE.COM", None, None, { "name": "foo@example.com", "command": "post" }) self.verify("prefix-foo@example.com", "prefix-", None, { "name": "foo@example.com", "command": "post" }) self.verify("bar@example.com", None, "lists.example.com", { "name": "bar@lists.example.com", "command": "post" }) self.verify("prefix-bar@example.com", "prefix-", "lists.example.com", { "name": "bar@lists.example.com", "command": "post" }) def testSubscription(self): self.verify("foo-subscribe@example.com", None, None, { "name": "foo@example.com", "command": "subscribe", "sender": "", }) self.verify("foo-subscribe-joe-user=example.com@example.com", None, None, { "name": "foo@example.com", "command": "subscribe", "sender": "joe-user@example.com", }) self.verify("foo-unsubscribe@example.com", None, None, { "name": "foo@example.com", "command": "unsubscribe", "sender": "", }) self.verify("foo-unsubscribe-joe-user=example.com@example.com", None, None, { "name": "foo@example.com", "command": "unsubscribe", "sender": "joe-user@example.com", }) def testPost(self): for name in self.lists: self.verify(name, None, None, { "name": name, "command": "post" }) def testSimpleCommands(self): for name in self.lists: for command in ["help", "list", "owner"]: localpart, domain = name.split("@") address = "%s-%s@%s" % (localpart, command, domain) self.verify(address, None, None, { "name": name, "command": command }) class ParseWellSignedAddressTestCases(ParseRecipientAddressBase): def try_good_signature(self, command): s = "foo-announce-%s-1" % command hash = self.mlm.compute_hash("%s@%s" % (s, "example.com")) local_part = "%s-%s" % (s, hash) dict = self.mlm.parse_recipient_address("%s@example.com" % local_part, None, None) self.failUnlessEqual(dict, { "name": "foo-announce@example.com", "command": command, "id": "1", }) def testProperlySignedCommands(self): self.try_good_signature("subyes") self.try_good_signature("subapprove") self.try_good_signature("subreject") self.try_good_signature("unsubyes") self.try_good_signature("bounce") self.try_good_signature("approve") self.try_good_signature("reject") self.try_good_signature("probe") class ParseBadlySignedAddressTestCases(ParseRecipientAddressBase): def try_bad_signature(self, command_part): self.failUnlessRaises(eoc.BadSignature, self.mlm.parse_recipient_address, "foo-announce-" + command_part + "-123-badhash@example.com", None, None) def testBadlySignedCommands(self): self.try_bad_signature("subyes") self.try_bad_signature("subapprove") self.try_bad_signature("subreject") self.try_bad_signature("unsubyes") self.try_bad_signature("bounce") self.try_bad_signature("approve") self.try_bad_signature("reject") self.try_bad_signature("probe") class DotDirTestCases(unittest.TestCase): def setUp(self): self.secret_name = os.path.join(DOTDIR, "secret") def tearDown(self): shutil.rmtree(DOTDIR) def dotdir_is_ok(self): self.failUnless(os.path.isdir(DOTDIR)) self.failUnless(os.path.isfile(self.secret_name)) def testNoDotDirExists(self): self.failIf(os.path.exists(DOTDIR)) mlm = eoc.MailingListManager(DOTDIR) self.dotdir_is_ok() def testDotDirDoesExistButSecretDoesNot(self): self.failIf(os.path.exists(DOTDIR)) os.makedirs(DOTDIR) self.failUnless(os.path.isdir(DOTDIR)) self.failIf(os.path.exists(self.secret_name)) mlm = eoc.MailingListManager(DOTDIR) self.dotdir_is_ok() class RemoveSomeHeadersTest(unittest.TestCase): def testRemoveSomeHeaders(self): mlm = eoc.MailingListManager(DOTDIR) ml = eoc.MailingList(mlm, "list@example.com") mail = """\ Header-1: this is a simple header Header-2: this is a complex header with a colon: yes it is Header-3: odd numbered headers are simple Body. """ mail2 = ml.remove_some_headers(mail, ["Header-2"]) self.failUnlessEqual(mail2, """\ Header-1: this is a simple header Header-3: odd numbered headers are simple Body. """) class ListBase(unittest.TestCase): def setUp(self): if os.path.exists(DOTDIR): shutil.rmtree(DOTDIR) self.mlm = eoc.MailingListManager(DOTDIR) def tearDown(self): self.mlm = None shutil.rmtree(DOTDIR) class ListCreationTestCases(ListBase): def setUp(self): ListBase.setUp(self) self.names = None def listdir(self, listname): return os.path.join(DOTDIR, listname) def listdir_has_file(self, listdir, filename): self.failUnless(os.path.isfile(os.path.join(listdir, filename))) self.names.remove(filename) def listdir_has_dir(self, listdir, dirname): self.failUnless(os.path.isdir(os.path.join(listdir, dirname))) self.names.remove(dirname) def listdir_may_have_dir(self, listdir, dirname): if dirname in self.names: self.listdir_has_dir(listdir, dirname) def listdir_is_ok(self, listname): listdir = self.listdir(listname) self.failUnless(os.path.isdir(listdir)) self.names = os.listdir(listdir) self.listdir_has_file(listdir, "config") self.listdir_has_file(listdir, "subscribers") self.listdir_has_dir(listdir, "bounce-box") self.listdir_has_dir(listdir, "subscription-box") self.listdir_may_have_dir(listdir, "moderation-box") self.listdir_may_have_dir(listdir, "templates") # Make sure there are no extras. self.failUnlessEqual(self.names, []) def testCreateNew(self): self.failIf(os.path.exists(self.listdir("foo@example.com"))) ml = self.mlm.create_list("foo@example.com") self.failUnlessEqual(ml.__class__, eoc.MailingList) self.failUnlessEqual(ml.dirname, self.listdir("foo@example.com")) self.listdir_is_ok("foo@example.com") def testCreateExisting(self): list = self.mlm.create_list("foo@example.com") self.failUnlessRaises(eoc.ListExists, self.mlm.create_list, "foo@example.com") self.listdir_is_ok("foo@example.com") class ListOptionTestCases(ListBase): def check(self, ml, wanted): self.failUnlessEqual(ml.cp.sections(), ["list"]) cpdict = {} for key, value in ml.cp.items("list"): cpdict[key] = value self.failUnlessEqual(cpdict, wanted) def testDefaultOptionsOnCreateAndOpenExisting(self): self.mlm.create_list("foo@example.com") ml = self.mlm.open_list("foo@example.com") self.check(ml, { "owners": "", "moderators": "", "subscription": "free", "posting": "free", "archived": "no", "mail-on-subscription-changes": "no", "mail-on-forced-unsubscribe": "no", "ignore-bounce": "no", "language": "", "pristine-headers": "", }) def testChangeOptions(self): # Create a list, change some options, and save the result. ml = self.mlm.create_list("foo@example.com") self.failUnlessEqual(ml.cp.get("list", "owners"), "") self.failUnlessEqual(ml.cp.get("list", "posting"), "free") ml.cp.set("list", "owners", "owner@example.com") ml.cp.set("list", "posting", "moderated") ml.save_config() # Re-open the list and check that the new instance has read the # values from the disk correctly. ml2 = self.mlm.open_list("foo@example.com") self.check(ml2, { "owners": "owner@example.com", "moderators": "", "subscription": "free", "posting": "moderated", "archived": "no", "mail-on-subscription-changes": "no", "mail-on-forced-unsubscribe": "no", "ignore-bounce": "no", "language": "", "pristine-headers": "", }) class SubscriberDatabaseTestCases(ListBase): def has_subscribers(self, ml, addrs): subs = ml.subscribers.get_all() subs.sort() self.failUnlessEqual(subs, addrs) def testAddAndRemoveSubscribers(self): addrs = ["joe@example.com", "MARY@example.com", "bubba@EXAMPLE.com"] addrs.sort() ml = self.mlm.create_list("foo@example.com") self.failUnlessEqual(ml.subscribers.get_all(), []) self.failUnless(ml.subscribers.lock()) ml.subscribers.add_many(addrs) self.has_subscribers(ml, addrs) ml.subscribers.save() self.failIf(ml.subscribers.locked) ml = None ml2 = self.mlm.open_list("foo@example.com") self.has_subscribers(ml2, addrs) ml2.subscribers.lock() ml2.subscribers.remove(addrs[0]) self.has_subscribers(ml2, addrs[1:]) ml2.subscribers.save() ml3 = self.mlm.open_list("foo@example.com") self.has_subscribers(ml3, addrs[1:]) def testSubscribeTwice(self): ml = self.mlm.create_list("foo@example.com") self.failUnlessEqual(ml.subscribers.get_all(), []) ml.subscribers.lock() ml.subscribers.add("user@example.com") ml.subscribers.add("USER@example.com") self.failUnlessEqual(map(string.lower, ml.subscribers.get_all()), map(string.lower, ["USER@example.com"])) def testSubscriberAttributesAndGroups(self): addrs = ["joe@example.com", "mary@example.com"] addrs.sort() ml = self.mlm.create_list("foo@example.com") self.failUnlessEqual(ml.subscribers.groups(), []) ml.subscribers.lock() id = ml.subscribers.add_many(addrs) self.failUnlessEqual(ml.subscribers.groups(), ["0"]) self.failUnlessEqual(ml.subscribers.get(id, "status"), "ok") ml.subscribers.set(id, "status", "bounced") self.failUnlessEqual(ml.subscribers.get(id, "status"), "bounced") subs = ml.subscribers.in_group(id) subs.sort() self.failUnlessEqual(subs, addrs) class ModerationBoxTestCases(ListBase): def testModerationBox(self): ml = self.mlm.create_list("foo@example.com") listdir = os.path.join(DOTDIR, "foo@example.com") boxdir = os.path.join(listdir, "moderation-box") self.failUnlessEqual(boxdir, ml.moderation_box.boxdir) self.failUnless(os.path.isdir(boxdir)) mailtext = "From: foo\nTo: bar\n\nhello\n" id = ml.moderation_box.add("foo", mailtext) self.failUnless(ml.moderation_box.has(id)) self.failUnlessEqual(ml.moderation_box.get_address(id), "foo") self.failUnlessEqual(ml.moderation_box.get(id), mailtext) filename = os.path.join(boxdir, id) self.failUnless(os.path.isfile(filename)) self.failUnless(os.path.isfile(filename + ".address")) ml.moderation_box.remove(id) self.failIf(ml.moderation_box.has(id)) self.failUnless(not os.path.exists(filename)) class IncomingBase(unittest.TestCase): def setUp(self): if os.path.isdir(DOTDIR): shutil.rmtree(DOTDIR) self.mlm = eoc.MailingListManager(DOTDIR) self.ml = None ml = self.mlm.create_list("foo@EXAMPLE.com") ml.cp.set("list", "owners", "listmaster@example.com") ml.save_config() ml.subscribers.lock() ml.subscribers.add("USER1@example.com") ml.subscribers.add("user2@EXAMPLE.com") ml.subscribers.save() self.write_file_in_listdir(ml, "headers-to-add", "X-Foo: foo\n") self.write_file_in_listdir(ml, "headers-to-remove", "Received\n") self.sent_mail = [] def tearDown(self): shutil.rmtree(DOTDIR) def write_file_in_listdir(self, ml, basename, contents): f = open(os.path.join(ml.dirname, basename), "w") f.write(contents) f.close() def configure_list(self, subscription, posting): list = self.mlm.open_list("foo@example.com") list.cp.set("list", "subscription", subscription) list.cp.set("list", "posting", posting) list.save_config() def environ(self, sender, recipient): eoc.set_environ({ "SENDER": sender, "RECIPIENT": recipient, }) def catch_sendmail(self, sender, recipients, text): self.sent_mail.append({ "sender": sender, "recipients": recipients, "text": text, }) def send(self, sender, recipient, text="", force_moderation=0, force_posting=0): self.environ(sender, recipient) dict = self.mlm.parse_recipient_address(recipient, None, None) dict["force-moderation"] = force_moderation dict["force-posting"] = force_posting self.ml = self.mlm.open_list(dict["name"]) if "\n\n" not in text: text = "\n\n" + text text = "Received: foobar\n" + text self.ml.read_stdin = lambda t=text: t self.mlm.send_mail = self.catch_sendmail self.sent_mail = [] self.ml.obey(dict) def sender_matches(self, mail, sender): pat = "(?P
" + sender + ")" m = re.match(pat, mail["sender"], re.I) if m: return m.group("address") else: return None def replyto_matches(self, mail, replyto): pat = "(.|\n)*(?P
" + replyto + ")" m = re.match(pat, mail["text"], re.I) if m: return m.group("address") else: return None def receiver_matches(self, mail, recipient): return map(string.lower, mail["recipients"]) == [recipient.lower()] def body_matches(self, mail, body): if body: pat = re.compile("(.|\n)*" + body + "(.|\n)*") m = re.match(pat, mail["text"]) return m else: return 1 def headers_match(self, mail, header): if header: pat = re.compile("(.|\n)*" + header + "(.|\n)*", re.I) m = re.match(pat, mail["text"]) return m else: return 1 def match(self, sender, replyto, receiver, body=None, header=None, anti_header=None): ret = None for mail in self.sent_mail: if replyto is None: m1 = self.sender_matches(mail, sender) m3 = self.receiver_matches(mail, receiver) m4 = self.body_matches(mail, body) m5 = self.headers_match(mail, header) m6 = self.headers_match(mail, anti_header) no_anti_header = anti_header == None or m6 == None if m1 != None and m3 and m4 and m5 and no_anti_header: ret = m1 self.sent_mail.remove(mail) break else: m1 = self.sender_matches(mail, sender) m2 = self.replyto_matches(mail, replyto) m3 = self.receiver_matches(mail, receiver) m4 = self.body_matches(mail, body) m5 = self.headers_match(mail, header) m6 = self.headers_match(mail, anti_header) no_anti_header = anti_header == None or m6 == None if m1 != None and m2 != None and m3 and m4 and m5 and \ no_anti_header: ret = m2 self.sent_mail.remove(mail) break self.failUnless(ret != None) return ret def no_more_mail(self): self.failUnlessEqual(self.sent_mail, []) class SimpleCommandAddressTestCases(IncomingBase): def testHelp(self): self.send("outsider@example.com", "foo-help@example.com") self.match("foo-ignore@example.com", None, "outsider@example.com", "Subject: Help for") self.no_more_mail() def testOwner(self): self.send("outsider@example.com", "foo-owner@example.com", "abcde") self.match("outsider@example.com", None, "listmaster@example.com", "abcde") self.no_more_mail() def testIgnore(self): self.send("outsider@example.com", "foo-ignore@example.com", "abcde") self.no_more_mail() class OwnerCommandTestCases(IncomingBase): def testList(self): self.send("listmaster@example.com", "foo-list@example.com") self.match("foo-ignore@example.com", None, "listmaster@example.com", "[uU][sS][eE][rR][12]@" + "[eE][xX][aA][mM][pP][lL][eE]\\.[cC][oO][mM]\n" + "[uU][sS][eE][rR][12]@" + "[eE][xX][aA][mM][pP][lL][eE]\\.[cC][oO][mM]\n") self.no_more_mail() def testListDenied(self): self.send("outsider@example.com", "foo-list@example.com") self.match("foo-ignore@example.com", None, "outsider@example.com", "Subject: Subscriber list denied") self.no_more_mail() def testSetlist(self): self.send("listmaster@example.com", "foo-setlist@example.com", "From: foo\n\nnew1@example.com\nuser1@example.com\n") a = self.match("foo-ignore@example.com", "foo-setlistyes-[^@]*@example.com", "listmaster@example.com", "Subject: Please moderate subscriber list") self.no_more_mail() self.send("listmaster@example.com", a) self.match("foo-ignore@example.com", None, "listmaster@example.com", "Subject: Subscriber list has been changed") self.match("foo-ignore@example.com", None, "new1@example.com", "Subject: Welcome to") self.match("foo-ignore@example.com", None, "user2@EXAMPLE.com", "Subject: Goodbye from") self.no_more_mail() def testSetlistSilently(self): self.send("listmaster@example.com", "foo-setlistsilently@example.com", "From: foo\n\nnew1@example.com\nuser1@example.com\n") a = self.match("foo-ignore@example.com", "foo-setlistsilentyes-[^@]*@example.com", "listmaster@example.com", "Subject: Please moderate subscriber list") self.no_more_mail() self.send("listmaster@example.com", a) self.match("foo-ignore@example.com", None, "listmaster@example.com", "Subject: Subscriber list has been changed") self.no_more_mail() def testSetlistDenied(self): self.send("outsider@example.com", "foo-setlist@example.com", "From: foo\n\nnew1@example.com\nnew2@example.com\n") self.match("foo-ignore@example.com", None, "outsider@example.com", "Subject: You can't set the subscriber list") self.no_more_mail() def testSetlistBadlist(self): self.send("listmaster@example.com", "foo-setlist@example.com", "From: foo\n\nBlah blah blah.\n") self.match("foo-ignore@example.com", None, "listmaster@example.com", "Subject: Bad address list") self.no_more_mail() def testOwnerSubscribesSomeoneElse(self): # Send subscription request. List sends confirmation request. self.send("listmaster@example.com", "foo-subscribe-outsider=example.com@example.com") a = self.match("foo-ignore@example.com", "foo-subyes-[^@]*@example.com", "listmaster@example.com", "Please confirm subscription") self.no_more_mail() # Confirm sub. req. List sends welcome. self.send("listmaster@example.com", a) self.match("foo-ignore@example.com", None, "outsider@example.com", "Welcome to the") self.no_more_mail() def testOwnerUnubscribesSomeoneElse(self): # Send unsubscription request. List sends confirmation request. self.send("listmaster@example.com", "foo-unsubscribe-outsider=example.com@example.com") a = self.match("foo-ignore@example.com", "foo-unsubyes-[^@]*@example.com", "listmaster@example.com", "Subject: Please confirm UNsubscription") self.no_more_mail() # Confirm sub. req. List sends welcome. self.send("listmaster@example.com", a) self.match("foo-ignore@example.com", None, "outsider@example.com", "Goodbye") self.no_more_mail() class SubscriptionTestCases(IncomingBase): def confirm(self, recipient): # List has sent confirmation request. Respond to it. a = self.match("foo-ignore@example.com", "foo-subyes-[^@]*@example.com", recipient, "Please confirm subscription") self.no_more_mail() # Confirm sub. req. List response will be analyzed later. self.send("something.random@example.com", a) def got_welcome(self, recipient): self.match("foo-ignore@example.com", None, recipient, "Welcome to the") self.no_more_mail() def approve(self, user_recipient): self.match("foo-ignore@example.com", None, user_recipient) a = self.match("foo-ignore@example.com", "foo-subapprove-[^@]*@example.com", "listmaster@example.com") self.send("listmaster@example.com", a) def reject(self, user_recipient): self.match("foo-ignore@example.com", None, user_recipient) a = self.match("foo-ignore@example.com", "foo-subreject-[^@]*@example.com", "listmaster@example.com") self.send("listmaster@example.com", a) def testSubscribeToUnmoderatedWithoutAddressNotOnList(self): self.configure_list("free", "free") self.send("outsider@example.com", "foo-subscribe@example.com") self.confirm("outsider@example.com") self.got_welcome("outsider@example.com") def testSubscribeToUnmoderatedWithoutAddressAlreadyOnList(self): self.configure_list("free", "free") self.send("user1@example.com", "foo-subscribe@example.com") self.confirm("user1@example.com") self.got_welcome("user1@example.com") def testSubscribeToUnmoderatedWithAddressNotOnList(self): self.configure_list("free", "free") self.send("somebody.else@example.com", "foo-subscribe-outsider=example.com@example.com") self.confirm("outsider@example.com") self.got_welcome("outsider@example.com") def testSubscribeToUnmoderatedWithAddressAlreadyOnList(self): self.configure_list("free", "free") self.send("somebody.else@example.com", "foo-subscribe-user1=example.com@example.com") self.confirm("user1@example.com") self.got_welcome("user1@example.com") def testSubscribeToModeratedWithoutAddressNotOnListApproved(self): self.configure_list("moderated", "moderated") self.send("outsider@example.com", "foo-subscribe@example.com") self.confirm("outsider@example.com") self.approve("outsider@example.com") self.got_welcome("outsider@example.com") def testSubscribeToModeratedWithoutAddressNotOnListRejected(self): self.configure_list("moderated", "moderated") self.send("outsider@example.com", "foo-subscribe@example.com") self.confirm("outsider@example.com") self.reject("outsider@example.com") def testSubscribeToModeratedWithoutAddressAlreadyOnListApproved(self): self.configure_list("moderated", "moderated") self.send("user1@example.com", "foo-subscribe@example.com") self.confirm("user1@example.com") self.approve("user1@example.com") self.got_welcome("user1@example.com") def testSubscribeToModeratedWithoutAddressAlreadyOnListRejected(self): self.configure_list("moderated", "moderated") self.send("user1@example.com", "foo-subscribe@example.com") self.confirm("user1@example.com") self.reject("user1@example.com") def testSubscribeToModeratedWithAddressNotOnListApproved(self): self.configure_list("moderated", "moderated") self.send("somebody.else@example.com", "foo-subscribe-outsider=example.com@example.com") self.confirm("outsider@example.com") self.approve("outsider@example.com") self.got_welcome("outsider@example.com") def testSubscribeToModeratedWithAddressNotOnListRejected(self): self.configure_list("moderated", "moderated") self.send("somebody.else@example.com", "foo-subscribe-outsider=example.com@example.com") self.confirm("outsider@example.com") self.reject("outsider@example.com") def testSubscribeToModeratedWithAddressAlreadyOnListApproved(self): self.configure_list("moderated", "moderated") self.send("somebody.else@example.com", "foo-subscribe-user1=example.com@example.com") self.confirm("user1@example.com") self.approve("user1@example.com") self.got_welcome("user1@example.com") def testSubscribeToModeratedWithAddressAlreadyOnListRejected(self): self.configure_list("moderated", "moderated") self.send("somebody.else@example.com", "foo-subscribe-user1=example.com@example.com") self.confirm("user1@example.com") self.reject("user1@example.com") class UnsubscriptionTestCases(IncomingBase): def confirm(self, recipient): # List has sent confirmation request. Respond to it. a = self.match("foo-ignore@example.com", "foo-unsubyes-[^@]*@example.com", recipient, "Please confirm UNsubscription") self.no_more_mail() # Confirm sub. req. List response will be analyzed later. self.send("something.random@example.com", a) def got_goodbye(self, recipient): self.match("foo-ignore@example.com", None, recipient, "Goodbye from") self.no_more_mail() def testUnsubscribeWithoutAddressNotOnList(self): self.send("outsider@example.com", "foo-unsubscribe@example.com") self.confirm("outsider@example.com") self.got_goodbye("outsider@example.com") def testUnsubscribeWithoutAddressOnList(self): self.send("user1@example.com", "foo-unsubscribe@example.com") self.confirm("user1@example.com") self.got_goodbye("user1@example.com") def testUnsubscribeWithAddressNotOnList(self): self.send("somebody.else@example.com", "foo-unsubscribe-outsider=example.com@example.com") self.confirm("outsider@example.com") self.got_goodbye("outsider@example.com") def testUnsubscribeWithAddressOnList(self): self.send("somebody.else@example.com", "foo-unsubscribe-user1=example.com@example.com") self.confirm("user1@example.com") self.got_goodbye("user1@example.com") class PostTestCases(IncomingBase): msg = u"Subject: something \u00c4\n\nhello, world\n".encode("utf8") def approve(self, user_recipient): self.match("foo-ignore@example.com", None, user_recipient) a = self.match("foo-ignore@example.com", "foo-approve-[^@]*@example.com", "listmaster@example.com") self.send("listmaster@example.com", a) def reject(self, user_recipient): self.match("foo-ignore@example.com", None, user_recipient) a = self.match("foo-ignore@example.com", "foo-reject-[^@]*@example.com", "listmaster@example.com") self.send("listmaster@example.com", a) def check_headers_are_encoded(self): ok_chars = "\t\r\n" for code in range(32, 127): ok_chars = ok_chars + chr(code) for mail in self.sent_mail: text = mail["text"] self.failUnless("\n\n" in text) headers = text.split("\n\n")[0] for c in headers: if c not in ok_chars: print headers self.failUnless(c in ok_chars) def check_mail_to_list(self): self.check_headers_are_encoded() self.match("foo-bounce-.*@example.com", None, "USER1@example.com", body="hello, world", header="X-Foo: FOO", anti_header="Received:") self.match("foo-bounce-.*@example.com", None, "user2@EXAMPLE.com", body="hello, world", header="x-foo: foo", anti_header="Received:") self.no_more_mail() def check_that_moderation_box_is_empty(self): ml = self.mlm.open_list("foo@example.com") self.failUnlessEqual(os.listdir(ml.moderation_box.boxdir), []) def testSubscriberPostsToUnmoderated(self): self.configure_list("free", "free") self.send("user1@example.com", "foo@example.com", self.msg) self.check_mail_to_list() def testOutsiderPostsToUnmoderated(self): self.configure_list("free", "free") self.send("outsider@example.com", "foo@example.com", self.msg) self.check_mail_to_list() def testSubscriberPostToAutomoderated(self): self.configure_list("free", "auto") self.check_that_moderation_box_is_empty() self.send("user1@example.com", "foo@example.com", self.msg) self.check_mail_to_list() self.check_that_moderation_box_is_empty() def testOutsiderPostsToAutomoderatedRejected(self): self.configure_list("free", "auto") self.check_that_moderation_box_is_empty() self.send("outsider@example.com", "foo@example.com", self.msg) self.reject("outsider@example.com") self.check_that_moderation_box_is_empty() def testOutsiderPostsToAutomoderatedApproved(self): self.configure_list("free", "auto") self.check_that_moderation_box_is_empty() self.send("outsider@example.com", "foo@example.com", self.msg) self.approve("outsider@example.com") self.check_mail_to_list() self.check_that_moderation_box_is_empty() def testSubscriberPostsToModeratedRejected(self): self.configure_list("free", "moderated") self.check_that_moderation_box_is_empty() self.send("user1@example.com", "foo@example.com", self.msg) self.reject("user1@example.com") self.check_that_moderation_box_is_empty() def testOutsiderPostsToMderatedApproved(self): self.configure_list("free", "moderated") self.check_that_moderation_box_is_empty() self.send("outsider@example.com", "foo@example.com", self.msg) self.approve("outsider@example.com") self.check_mail_to_list() self.check_that_moderation_box_is_empty() def testSubscriberPostsWithRequestToBeModerated(self): self.configure_list("free", "free") self.check_that_moderation_box_is_empty() self.send("user1@example.com", "foo@example.com", self.msg, force_moderation=1) self.match("foo-ignore@example.com", None, "user1@example.com", "Subject: Please wait") a = self.match("foo-ignore@example.com", "foo-approve-[^@]*@example.com", "listmaster@example.com") self.no_more_mail() self.send("listmaster@example.com", a) self.check_mail_to_list() self.check_that_moderation_box_is_empty() def testSubscriberPostsWithModerationOverride(self): self.configure_list("moderated", "moderated") self.send("user1@example.com", "foo@example.com", self.msg, force_posting=1) self.check_mail_to_list() self.check_that_moderation_box_is_empty() class BounceTestCases(IncomingBase): def check_subscriber_status(self, must_be): ml = self.mlm.open_list("foo@example.com") for id in ml.subscribers.groups(): self.failUnlessEqual(ml.subscribers.get(id, "status"), must_be) def bounce_sent_mail(self): for m in self.sent_mail[:]: self.send("something@example.com", m["sender"], "eek") self.failUnlessEqual(len(self.sent_mail), 0) def send_mail_to_list_then_bounce_everything(self): self.send("user@example.com", "foo@example.com", "hello") for m in self.sent_mail[:]: self.send("foo@example.com", m["sender"], "eek") self.failUnlessEqual(len(self.sent_mail), 0) def testBounceOnceThenRecover(self): self.check_subscriber_status("ok") self.send_mail_to_list_then_bounce_everything() self.check_subscriber_status("bounced") ml = self.mlm.open_list("foo@example.com") for id in ml.subscribers.groups(): bounce_id = ml.subscribers.get(id, "bounce-id") self.failUnless(bounce_id) self.failUnless(ml.bounce_box.has(bounce_id)) bounce_ids = [] now = time.time() ml = self.mlm.open_list("foo@example.com") ml.subscribers.lock() for id in ml.subscribers.groups(): timestamp = float(ml.subscribers.get(id, "timestamp-bounced")) self.failUnless(abs(timestamp - now) < 10.0) ml.subscribers.set(id, "timestamp-bounced", "69.0") bounce_ids.append(ml.subscribers.get(id, "bounce-id")) ml.subscribers.save() self.mlm.cleaning_woman(no_op) self.check_subscriber_status("probed") for bounce_id in bounce_ids: self.failUnless(ml.bounce_box.has(bounce_id)) self.mlm.cleaning_woman(no_op) ml = self.mlm.open_list("foo@example.com") self.failUnlessEqual(len(ml.subscribers.groups()), 2) self.check_subscriber_status("ok") for bounce_id in bounce_ids: self.failUnless(not ml.bounce_box.has(bounce_id)) def testBounceProbeAlso(self): self.check_subscriber_status("ok") self.send_mail_to_list_then_bounce_everything() self.check_subscriber_status("bounced") ml = self.mlm.open_list("foo@example.com") for id in ml.subscribers.groups(): bounce_id = ml.subscribers.get(id, "bounce-id") self.failUnless(bounce_id) self.failUnless(ml.bounce_box.has(bounce_id)) bounce_ids = [] now = time.time() ml = self.mlm.open_list("foo@example.com") ml.subscribers.lock() for id in ml.subscribers.groups(): timestamp = float(ml.subscribers.get(id, "timestamp-bounced")) self.failUnless(abs(timestamp - now) < 10.0) ml.subscribers.set(id, "timestamp-bounced", "69.0") bounce_ids.append(ml.subscribers.get(id, "bounce-id")) ml.subscribers.save() self.sent_mail = [] self.mlm.cleaning_woman(self.catch_sendmail) self.check_subscriber_status("probed") for bounce_id in bounce_ids: self.failUnless(ml.bounce_box.has(bounce_id)) self.bounce_sent_mail() self.check_subscriber_status("probebounced") self.mlm.cleaning_woman(no_op) ml = self.mlm.open_list("foo@example.com") self.failUnlessEqual(len(ml.subscribers.groups()), 0) for bounce_id in bounce_ids: self.failUnless(not ml.bounce_box.has(bounce_id)) def testCleaningWomanJoinsAndBounceSplitsGroups(self): # Check that each group contains one address and set the creation # timestamp to an ancient time. ml = self.mlm.open_list("foo@example.com") bouncedir = os.path.join(ml.dirname, "bounce-box") ml.subscribers.lock() for id in ml.subscribers.groups(): addrs = ml.subscribers.in_group(id) self.failUnlessEqual(len(addrs), 1) bounce_id = ml.subscribers.get(id, "bounce-id") self.failUnlessEqual(bounce_id, "..notexist..") bounce_id = "bounce-" + id ml.subscribers.set(id, "bounce-id", bounce_id) bounce_path = os.path.join(bouncedir, bounce_id) self.failUnless(not os.path.isfile(bounce_path)) f = open(bounce_path, "w") f.close() f = open(bounce_path + ".address", "w") f.close() self.failUnless(os.path.isfile(bounce_path)) ml.subscribers.set(id, "timestamp-created", "1") ml.subscribers.save() # Check that --cleaning-woman joins the two groups into one. self.failUnlessEqual(len(ml.subscribers.groups()), 2) self.mlm.cleaning_woman(no_op) ml = self.mlm.open_list("foo@example.com") self.failUnlessEqual(len(ml.subscribers.groups()), 1) self.failUnlessEqual(os.listdir(bouncedir), []) # Check that a bounce splits the single group. self.send_mail_to_list_then_bounce_everything() ml = self.mlm.open_list("foo@example.com") self.failUnlessEqual(len(ml.subscribers.groups()), 2) # Check that a --cleaning-woman immediately after doesn't join. # (The groups are new, thus shouldn't be joined for a week.) self.failUnlessEqual(len(ml.subscribers.groups()), 2) self.mlm.cleaning_woman(no_op) ml = self.mlm.open_list("foo@example.com") self.failUnlessEqual(len(ml.subscribers.groups()), 2) enemies-of-carlotta-1.2.6/README0000664000175000017500000000127710577453116014430 0ustar liwliwREADME for Enemies of Carlotta, a mailing list manager by Lars Wirzenius, liw@iki.fi Enemies of Carlotta is a simple mailing list manager that mimicks the ezmlm (http://www.ezmlm.org/) commands. Please see the manual page or the home page (http://liw.iki.fi/liw/eoc/) for documentation, and join the mailing list (send mail to eoc-subscribe@liw.iki.fi). You need Python 2.3 or newer, and lockfile (from procmail). If you are willing to live without having headers encoded with MIME, Python 2.1 or 2.2 should also work. To install, edit Makefile, the variables bindir and man1dir, and then run "make install" as root. Enemies of Carlotta is licensed using the GNU General Public License, version 2. enemies-of-carlotta-1.2.6/qmqp.py0000664000175000017500000000741410577453116015077 0ustar liwliw#!/usr/bin/python # This module that implements sending mail via QMQP. See # # http://cr.yp.to/proto/qmqp.html # # for a description of the protocol. # # This module was written by Jaakko Niemi for # Enemies of Carlotta. It is licensed the same way as Enemies of Carlotta: # GPL version 2. import socket, string class QMQPException(Exception): '''Base class for all exceptions raised by this module.''' class QMQPTemporaryError(QMQPException): '''Class for temporary errors''' def __init__(self, msg): self.msg = msg def __str__(self): return "QMQP-Server said: %s" % self.msg class QMQPPermanentError(QMQPException): '''Class for permanent errors''' def __init__(self, msg): self.msg = msg def __str__(self): return "QMQP-Server said: %s" % self.msg class QMQPConnectionError(QMQPException): '''Class for connection errors''' def __init__(self, msg): self.msg = msg def __str__(self): return "Error was: %s" % self.msg class QMQP: '''I handle qmqp connection to a server''' file = None def __init__(self, host = 'localhost'): '''Start''' if host: resp = self.connect(host) def encode(self, stringi): ret = '%d:%s,' % (len(stringi), stringi) return ret def decode(self, stringi): stringi = string.split(stringi, ':', 1) stringi[1] = string.rstrip(stringi[1], ',') if len(stringi[1]) is not int(stringi[0]): print 'malformed netstring encounterd' return stringi[1] def connect(self, host = 'localhost'): for sres in socket.getaddrinfo(host, 628, 0, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = sres try: self.sock = socket.socket(af, socktype, proto) self.sock.connect(sa) except socket.error: print 'connect failed' if self.sock: self.sock.close() self.sock = None continue break if not self.sock: raise socket.error return 0 def send(self, stringi): if self.sock: try: self.sock.sendall(stringi) except socket.error, err: self.sock.close() raise QMQPConnectionError, err else: print 'not connected' def getreply(self): if self.file is None: self.file = self.sock.makefile('rb') while 1: line = self.file.readline() if line == '': self.sock.close() print 'ERORORR' break line = self.decode(line) return line def quit(self): self.sock.close() def sendmail(self, from_addr, to_addrs, msg): recipients = '' msg = self.encode(msg) from_addr = self.encode(from_addr) # I don't understand why len(to_addrs) <= 1 needs to be handled differently. # Anyway, it doesn't seem to work with Postfix. --liw # if len(to_addrs) > 1: # for t in to_addrs: # recipients = recipients + self.encode(t) # else: # recipients = self.encode(to_addrs[0]) for t in to_addrs: recipients = recipients + self.encode(t) output = self.encode(msg + from_addr + recipients) self.send(output) ret = self.getreply() if ret[0] == 'K': return ret[1:] if ret[0] == 'Z': raise QMQPTemporaryError, ret[1:] if ret[0] == 'D': raise QMQPPermanentError, ret[1:] if __name__ == '__main__': a = QMQP() maili = 'asfasdfsfdasfasd' envelope_sender = 'liw@liw.iki.fi' recips = [ 'liw@liw.iki.fi' ] retcode = a.sendmail(envelope_sender, recips, maili) print retcode a.quit() enemies-of-carlotta-1.2.6/ChangeLog0000664000175000017500000005551310577453116015324 0ustar liwliw2005-12-03 Lars Wirzenius * I'm switching EoC development to bzr and using commit messages instead of a Changelog. This is the last ChangeLog entry. 2005-12-03 Lars Wirzenius * enemies-of-carlotta.1: Changed - to \- since that is more correct. Added the missing word "pipe" in the description of --moderate. Added note about + vs - in the Postfix regexp example. Documented --show-lists. * eoc.py: Don't append a footer if the message is base64 encoded. 2005-04-16 Lars Wirzenius * templates/*: Changed content dispositions from attachment to inline so that they are shown by default in mailers. 2005-04-16 Lars Wirzenius * templates/bounce-owner-notification, templates/bounce-warning, templates/setlist-confirm: Say "attached" instead of "appended" or "below". 2005-04-16 Lars Wirzenius * eoc.py: nice_7bit: Don't treat white space characters as requiring MIME encoding. 2005-04-10 Lars Wirzenius * Making release version 1.1.5. 2005-04-10 Lars Wirzenius * eoc.py, templates/help*: Use "EoC" instead of "Enemies of Carlotta" so as not to scare off users who don't get the movie reference. Did not change filenames, since those are only visible to the listmaster and the listmaster presumably can read the home page. 2005-04-10 Lars Wirzenius * eoc.py: Cleaning woman now removes groups without subscribers. They can happen when we add a subscriber to a list when they are there already and were the only member in their group. 2005-04-10 Lars Wirzenius * eoc.py, eocTests.py, enemies-of-carlotta.1: Added option "pristine-headers" to disable header MIME encoding. 2005-04-10 Lars Wirzenius * enemies-of-carlotta.1, eoc.py, eocTests.py, templates/msg-moderate: Applied patches from Pascal Hakim to implement post moderators separately from list owners. If there are no moderators, the list owners are also moderators. 2005-04-10 Lars Wirzenius * eoc.py, eocTest.py, enemies-of-carlotta.1: Applied patch from Jaakko Niemi to implement a feature to optionally ignore bounces. 2005-04-10 Lars Wirzenius * eoc.py, eocTests.py: Fixes for treating addresses in a case in-sensitive manner (but storing in the form that the user gave). 2005-04-10 Lars Wirzenius * eoc.py: Don't log debug() to stderr. Log info() and error() first to file, and to stderr only after that. This should avoid problems when MTA's buffer for our stderr output fills up. 2005-04-10 Lars Wirzenius * templates/sub-owner-notification.fi, templates/unsub-owner-notification.fi: Added the missing word "tehdä". Pointed out by Leena Romppainen. 2005-04-10 Lars Wirzenius * Makefile: Installation improvements. Added a mandir variable and made man1dir, man1dires, and man1dirfr dependent on it. This allows the manual dirs to easily be relocated. Also added a command variable to allow renaming of the command in bindir. 2004-12-06 Lars Wirzenius * Making release version 1.1.4. 2004-12-06 Lars Wirzenius * eoc.py: Introduced locking of individual messages to MessageBox. Used this to prevent more than one request for post approval processing from happening at the same time. This should fix the problem found by Pascal Hakim, where several moderators are so fast to reply to a moderation request that the list gets two or more copies of the same message. 2004-12-06 Lars Wirzenius * TODO: Added. 2004-12-05 Lars Wirzenius * enemies-of-carlotta.1.es: Updated Spanish manual page from Ricardo Javier Cardenes. 2004-12-04 Lars Wirzenius * README.CVS: Wrote. 2004-11-27 Lars Wirzenius * Making release version 1.1.3. 2004-11-27 Lars Wirzenius * eoc.py: If email.Header module is missing, then work as otherwise, but don't MIME encode headers. This should restore the ability to run on Python 2.1 and 2.2. * README: Noted. 2004-11-27 Lars Wirzenius * eoc.py: --show-lists now alphabetizes the output. 2004-11-27 Lars Wirzenius * templates/bounce-probe*, templates/sub-already*, templates/unsub-already*: Unused, removed. 2004-11-27 Lars Wirzenius * eoc.py: Added the beginnings of a plugin feature. The only hook for now is "send_mail_to_subscribers_hook" which allows the plugin to manipulate the mail before it is sent to the subscribers. * eoc.py: Added --no-act option to make testing easier. * enemies-of-carlotta.1: Documented the plugins. 2004-11-27 Lars Wirzenius * eoc.py: Made it so that an empty template means the mail is not sent at all. This can be used to prevent, say, "please wait for moderation" messages from being sent on a per-list basis. * enemies-of-carlotta.1: Documented this. 2004-11-27 Lars Wirzenius * eoc.py: Added --version option. 2004-11-27 Lars Wirzenius * eoc.py: Allow setting of more list options with --create or --edit: added new options --language, --mail-on-forced-unsubscribe, --mail-on-subscription-changes. * enemies-of-carlotta.1: Documented them. 2004-11-27 Lars Wirzenius * eoc.py: Added command line options --get and --set. * enemies-of-carlotta.1: Documented them. 2004-11-27 Lars Wirzenius * eocTests.py: Added a test to check that the headers of sent mails are only 7-bit ASCII characters (tab, newline, carriage return, plus 32-126). * eoc.py: Encode outgoing mails to use only 7-bit characters in the headers. This is required for the mails to work correctly with MIME compliant mail readers. Note that this change makes use of the email.Header module in Python 2.3, so with this change the minimum supported Python version is 2.3. * README: Noted Python version requirement change. 2004-09-18 Lars Wirzenius * Making release version 1.1.2. 2004-09-18 Lars Wirzenius * enemies-of-carlotta.1.es: Added manual page translation to Spanish by Ivan Juanes. Nobody expectes the Spanish manual page. * Makefile: Install the Spanish manual page. 2004-09-18 Lars Wirzenius * eoc.py, templates/*: Implemented suggestion from Magnus Holmgren to make attached messages real MIME attachments rather than just inserting the raw message code into a text/plain message. Also converted all templates to UTF-8, since that makes it easier to edit them for me. Hopefully nothing broke. 2004-09-18 Lars Wirzenius * eoc.py: When an EoC exception occurs, print out a sensible error message about it rather than letting Python print out a stack trace. 2004-09-18 Lars Wirzenius * eoc.py, eocTests.py, enemies-of-carlotta.1: Added support for adding headers to and removing headers from mails sent to the list. See the files headers-to-add and headers-to-remove in the list directories. 2004-09-12 Lars Wirzenius * eoc.py, eocTest.py: Added support for $listdir/headers-to-add (but it still needs to be documented). 2004-09-03 Lars Wirzenius * eoc.py, eocTest.py: Some refactoring and new unit tests. 2004-08-28 Lars Wirzenius * Making release version 1.1.1. This release is dedicated to Jaakko Niemi, the winner of the bug finding competition for 1.1.0. 2004-08-28 Lars Wirzenius * enemies-of-carlotta.1: Added note about the "templates" directory in the list specific directory using a patch sent by Jaakko Niemi, who thereby won the bug finding competition. 2004-08-26 Lars Wirzenius * eoc.py: in AddressParser, return the canonical name of the list, instead of converting it to lower case. Also, when opening a list, open using the canonical name rather than one converted to lower case or derived from the incoming mail address. Thanks to Jaakko Niemi for pointing these problems out. * eocTests.py: Improved testing cases when the name of a list is not all lower case. 2004-08-26 Lars Wirzenius * eoc.py, eocTests.py: Bugfix for bug found by Jaakko Niemi. If the list has been created with a name containing upper case, --is-list won't work. Fixed by making AddressParser convert all list names it gets to lower case. * Makefile: Bugfix for bug found by Jaakko Niemi. Used bashism in the install target, which made install not work under dash and other shells. Fixed by manually expanding a {foo,bar} construct. * Makefile: Bugfix for bug found by Jaakko Niemi. qmqp.py was not installed by "make install". Oops. 2004-08-24 Lars Wirzenius * eoc.py: Changed md5sum_as_hex to use .hexdigest() instead of doing the hex conversion manually. I should read more manuals. Thanks to Magnus Holmgren for pointing this out. 2004-08-23 Lars Wirzenius * Making release version 1.1.0. 2004-07-25 Lars Wirzenius * eoc.py: Added a realname for the From line in message templates. This is meant to avoid some spam filters. 2004-07-25 Lars Wirzenius * eoc.py, eocTests.py: Refactoring. Moved address parsing into its own class, for simplicity, and started work on making EoC specific exceptions be more user friendly as far as error messages are concerned. 2004-07-25 Lars Wirzenius * eocTests.py: Added some test cases for recipient address parsing. 2004-07-09 Lars Wirzenius * eoc.py: Refactoring changes to make code nicer. No functional changes. 2004-07-09 Lars Wirzenius * eoc.py: Removed a bunch of documentation from the beginning of the file. It was never finished, and was partly outdated, and it's better put in README anyway, when I have a moment to flesh it out. * eocTests.py: Simplified (shortened) the implementation of a test case in the anticipation of adding more cases to it. 2004-03-31 Lars Wirzenius * templates/*.sv: Added Swedish translation from Magnus Holmgren. 2004-02-21 Lars Wirzenius * eoc.py: Use the rejection address for subscription and posting moderation requests. This is to make it easier for mutt users to mail to the rejection address: they can just answer "no" to the question about using Reply-To. Thanks to Antti-Juhani Kaijanaho for pointing this out. 2004-02-21 Lars Wirzenius * enemies-of-carlotta.1: Updated. * eoc.py: Added --sender and --recipient options on suggestion from Tommi Virtanen. 2004-02-21 Lars Wirzenius * eoc.py: Convert addresses to lower case so that random capitalizations (especially in domain names) don't break things for us. 2004-02-21 Lars Wirzenius * eoc.py: Added some safeguards against subscribing addresses without @ characters. 2004-02-21 Lars Wirzenius * eoc.py: Catch GetoptError exception and print an error message. This is nicer for the user than the stack trace. 2004-02-21 Lars Wirzenius * qmqp.py: Added QMQP sending module by Jaakko Niemi. Thanks! Did change encoding of single recipient so that Postfix will accept that. * eoc.py: Changes to allow use of QMQP. Also, when logging a sent mail, do it with a bit more white space so that it will be easier to read the log file. 2004-01-13 Lars Wirzenius * enemies-of-carlotta.1: Removed the word "also" from the description of where subscription confirmation requests are sent, since it was incorrect. Also added a suggestion that --cleaning-woman should be run once per hour. * Makefile: Added patch from Jacek Konieczny to add DESTDIR support. 2003-09-07 Lars Wirzenius * Making release version 1.0.3. 2003-09-07 Lars Wirzenius * enemies-of-carlotta.1: Removed erroneous quotation marks in the Qmail section. Added section documenting all mail commands. 2003-09-06 Lars Wirzenius * eoc.py: Bugfix. log_file() was broken in that it would replace an already open output stream with one writing to /dev/null. Fixed that. Also, not writing to /dev/null anymore in cases where DOTDIR doesn't exist, but rather using a special purpose output file stream simulator DevNull. 2003-07-16 Lars Wirzenius * eoc.py, eocTests.py, enemies-of-carlotta.1: Added command line option --post for bypassing moderation status on a list. 2003-07-14 Lars Wirzenius * Making release version 1.0.2. 2003-06-20 Lars Wirzenius * enemies-of-carlotta.1: Fixed the example for creating a mailing list. * eoc.py: Added option --show-lists, from Stefan (who gave no last name). 2003-05-11 Lars Wirzenius * Making release version 1.0.1. 2003-05-11 Lars Wirzenius * eoc.py: Bounce handling was totally broken, because the final step was missing. What used to happen: EoC sends mail to subscribers, it bounces, states goes from "ok" to "bounced"; after one week, cleaning woman sends probe; two weeks after bounce, cleaning woman unsubscribes. What was missing: if probe bounces, state is set to "probebounced", and cleaning woman only unsubscribes if state is "probebounced", otherwise it resets state to "ok". This was quite an embarrassing bug. 2003-05-11 Lars Wirzenius * eoc.py: --help option implemented. * eoc.py: Don't create dotdir if only --help is given. * eoc.py: If dotdir exists, but secret doesn't, create secret instead of crashing. 2003-04-13 Lars Wirzenius * Making release version 1.0. * There have been no changes. I had planned to improve documentation, but the release party is tomorrow and, well, I want this out. Anyway, I'll have a better idea what to put into the manual if people first send me hate mail about the difficult parts. 2003-04-13 Lars Wirzenius * Making release version 0.23. 2003-04-13 Lars Wirzenius * eoc.py: -reject now actually removes the rejected message. Oops. 2003-04-13 Lars Wirzenius * eoc.py: Changed mail command -setlist to welcome new subscribers and say goodbye to old ones. * eoc.py: Added mail command -setlistsilently, which is the same as -setlist, but preserves the old behavior of not welcoming or saying goodbye. * templates/setlist-confirm{,.es,.fi,.fr}: Removed the sentence saying that new subscribers won't be welcomed. Hopefully I recognized it correctly in French and Spanish. 2003-03-22 Lars Wirzenius * Making release 0.22. 2003-03-22 Lars Wirzenius * eoc.py: Messages sent by EoC that used to have an empty SMTP sender (which made them look like bounce messages) now have foo-ignore@example (for the foo@example.com list). This avoids having to deal with different ways to specify an empty sender and is also more correct since the messages sent by EoC aren't really bounces. 2003-03-16 Lars Wirzenius * Making release version 0.21. 2003-03-15 Lars Wirzenius * templates/*.es and *.fr: Translations to Spanish for new templates by Ricardo Javier Cardenes, and to French by Pierre Machard. 2003-03-15 Lars Wirzenius * eoc.py: Implemented -setlist command to allow list owner to change the whole subscriber list as one operation. This should be useful for people maintaining the list of subscriber outside EoC's control, e.g., when the list is generated from a database. * templates/setlist-badlist, templates/setlist-confirm, templates/setlist-done, templates/setlist-sorry: New templates for this feature. Need to be translated. * templates/setlist-*.fi: Translated to Finnish. 2003-03-15 Lars Wirzenius * eoc.py: If the sender of a subscription or unsubscription request is a list owner, the list owners are requested to do the confirmation, instead of the address being subscribed. The welcome or goodbye message is still sent to the subscriber. 2003-03-15 Lars Wirzenius * eoc.py: When someone is sending mail to a moderated list, inform them that their message has been sent to the list owners for approval. * templates/msg-wait: New mail template. Needs translations. * templates/msg-wait.fi: Translated to Finnish. 2003-03-15 Lars Wirzenius * enemies-of-carlotta.1.fr: Proofread translation by Gérard Delafond, sent by Pierre Machard. 2003-03-15 Lars Wirzenius * templates/footer.es, templates/footer.es: Translations re-worded so that they only use 7 bit ASCII characters, to avoid charset problems. Thanks to Pierre Machard and Ricardo Javier Cardenes. 2003-03-14 Lars Wirzenius * eoc.py: When subscribing to a list with subscription moderation, the would-be subscriber is notified that a request has been sent to the moderator and that they need to be patient. * eocTests.py: Related changes. * templates/sub-wait: New mail template for this. Needs translation. * templates/sub-wait.fi: Translated sub-wait to Finnish. * templates/*.fr: Added Content-type headers to all templates. 2003-03-14 Lars Wirzenius * templates/footer.fi: Reworded to use only us-ascii letters. 2003-03-08 Lars Wirzenius * BENCHMARKS, eoc-benchmark, eoc-benchmark-procmailrc: Added. 2003-02-25 Lars Wirzenius * enemies-of-carlotta.1.fr, templates/*.fr: Added translations sent by Pierre Machard (pierre at machard.org). * templates/*.es: Added translations sent by Ricardo Javier Cardenes (ricardo at conysis.com). * Making release 0.20. 2003-02-22 Lars Wirzenius * eoc.py: Added configuration option "language". * templates/*.fi: Translated templates to Finnish so that I can test the "language" configuration option. * enemies-of-carlotta.1: Added "CONFIGURATION" section. * Making relase 0.19. 2003-02-18 Lars Wirzenius * eoc.py: Use os.path.isfile instead of os.path.exists to see whether a MessageBox contains a file. 2003-02-16 Lars Wirzenius * eoc.py: Command line options --edit, --subscribe, --unsubscribe, and --list now allow the list name to be abbreviated by leaving out the domain (and @). I don't want to allow shorter abbreviations to make it less likely that you specify the wrong list by mistake. * eoc.py: When a bouncing address is restored to "ok" status, its bounce message is removed from the bounce-box. * eoc.py: Added configuration options mail-on-forced-unsubscribe and mail-on-subscription-changes. * Making release 0.18. 2003-02-09 Lars Wirzenius * eoc.py: Applied patch from Ricardo Javier Cardenes to implement posting option "auto", which will let messages from subscribers automatically into the list and send others to the moderator. * Making release 0.17. 2003-01-12 Lars Wirzenius * Releasing version 0.16.1. * eoc.py: Bounce message quoting had a stupid bug: it didn't add newlines. 2003-01-11 Lars Wirzenius * Releasing version 0.16. * eoc.py: First bounce message is now saved (up to 4096 bytes) and quoted in the bounce-warning message. * eoc.py: When state changes to bounce, it is noted in the log file. * eoc.py, eocTests.py: Addresses now can't be added twice to the list. * eoc.py: Added missing exception MissingTemplate. 2002-12-11 Lars Wirzenius * Releasing version 0.15. 2002-12-08 Lars Wirzenius * Added --moderate option for asking a message to be moderated. To be used for spam filtering, at least. 2002-12-08 Lars Wirzenius * eocTests.py: Set the quiet flag, so that "make check" doesn't output debuggning messages. 2002-12-08 Lars Wirzenius * eoc.py: Added --smtp-server option for sending via SMTP, not /usr/sbin/sendmail. * enemies-of-carlotta.1: Documented all options. * eocTests.py: Don't require a dot-eoc directory for running the tests. 2002-12-08 Lars Wirzenius * Releasing version 0.14. 2002-12-08 Lars Wirzenius * eoc.py: Also look in the list's template directory for templates. This is useful for doing list specific customizations, e.g., for the footer. 2002-12-07 Lars Wirzenius * enemies-of-carlotta: Wrote startup wrapper for faster startup. * Makefile: Install startup wrapper as the binary, and eoc.py into the share directory, plus compile eoc.py on installtion.§ 2002-11-04 Lars Wirzenius * eoc.py: Cleaning woman now logs addresses it removes and sends a final goodbye message when it does. * templates/bounce-probe: Fixed spelling mistake. * templates/bounce-goodbye: Wrote. 2002-10-26 Lars Wirzenius * Added support for a footer to be appended to each mail. 2002-10-26 Lars Wirzenius * A bounce splits a group with many addresses. If the original group contained addresses in several domains, it is split into groups according to domains, otherwise into groups with single addresses. * --cleaning-woman joins groups that haven't bounced for a week into bigger groups. 2002-10-26 Lars Wirzenius * Releasing version 0.13. 2002-10-26 Lars Wirzenius * eoc.py: Bug fix. When there were over ten subscribers, the generation of group ids was wrong, since a list of group ids was sorted lexically, instead of numerically. * eocTests.py: Added test case to test this. 2002-10-26 Lars Wirzenius * eoc.py: Added --quiet and --sendmail options. * eocTests.py: Related changes. 2002-10-21 Lars Wirzenius * Releasing version 0.12. 2002-10-21 Lars Wirzenius * --incoming always reads stdin. 2002-10-11 Lars Wirzenius * Makefile: added $(prefix), installation of templates. * fix-config: directory is given on the command line. * eoc.py: Added some more debugging output. 2002-09-22 Lars Wirzenius * Starting upstream ChangeLog. Sooner or later someone else is going to be maintaining the Debian packaging stuff, and then it's sensible to keep things separate. * Removed debian/*. I can live without them myself, and I don't want to maintain them when there are perfectly too Debian developers available. :) * Makefile: Added an install target. Very simplistic. * COPYING, README: Added these files. This is beginning to seem like something that can be shown in public. * Releasing version 0.11.