1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
#!/usr/bin/env python
"""Planet PostgreSQL - list synchronizer
This file contains the functions to synchronize the list of subscribers
to planet with those of a majordomo mailinglist.
Copyright (C) 2008 PostgreSQL Global Development Group
"""
import ConfigParser
import re
import psycopg2
import httplib
from urllib import urlopen, urlencode
class MajordomoInterface:
"""
Simple interface wrapping some majordomo commands through screenscraping
the mj_wwwadm interface.
"""
def __init__(self, confp):
self.mjhost = confp.get('list', 'server')
self.listname = confp.get('list', 'listname')
self.listpwd = confp.get('list', 'password')
def fetch_current_subscribers(self):
"""
Fetch the current list of subscribers by calling out to the majordomo server
and screenscrape the result of the 'who-short' command.
"""
f = urlopen("http://%s/mj/mj_wwwadm?passw=%s&list=%s&func=who-short" %
(self.mjhost, self.listpwd, self.listname))
s = f.read()
f.close()
# Ugly screen-scraping regexp hack
resub = re.compile('list administration<br>\s+</p>\s+<pre>([^<]+)</pre>')
m = resub.findall(s)
if len(m) != 1:
if s.find("<!-- Majordomo who_none format file -->") > 0:
# Nobody on the list yet
return set()
raise Exception("Could not find list of subscribers")
return set([a for a in re.split('[\s\n]+',m[0]) if a])
def RemoveSubscribers(self, remove_subscribers):
"""
Remove the specified subscribers from the list.
"""
victims = "\r\n".join(remove_subscribers)
self.__PostMajordomoForm({
'func': 'unsubscribe-farewell',
'victims': victims
})
def AddSubscribers(self, add_subscribers):
"""
Add the specified subscribers to the list.
"""
victims = "\r\n".join(add_subscribers)
self.__PostMajordomoForm({
'func': 'subscribe-set-welcome',
'victims': victims
})
def __PostMajordomoForm(self, varset):
"""
Post a fake form to the majordomo mj_wwwadm interface with whatever
variables are specified. Add the listname and password on top of what's
already in the set of variables.
"""
var = varset
var.update({
'list': self.listname,
'passw': self.listpwd
})
body = urlencode(var)
h = httplib.HTTP(self.mjhost)
h.putrequest('POST', '/mj/mj_wwwadm')
h.putheader('host', self.mjhost)
h.putheader('content-type','application/x-www-form-urlencoded')
h.putheader('content-length', str(len(body)))
h.endheaders()
h.send(body)
errcode, errmsg, headers = h.getreply()
if errcode != 200:
print "ERROR: Form returned code %i, message %s" % (errcode, errmsg)
print h.file.read()
raise Exception("Aborting")
class Synchronizer:
"""
Perform the synchronization between the planet database and the
majordomo list.
"""
def __init__(self, c, db):
self.db = db
self.mj = MajordomoInterface(c)
def sync(self):
self.subscribers = self.mj.fetch_current_subscribers()
self.fetch_expected_subscribers()
self.diff_subscribers()
self.apply_subscriber_diff()
def diff_subscribers(self):
"""
Generate a list of differences between the current and expected subscribers,
so we know what to modify.
"""
self.remove_subscribers = self.subscribers.difference(self.expected)
self.add_subscribers = self.expected.difference(self.subscribers)
def apply_subscriber_diff(self):
"""
If there are any changes to subscribers to be made (subscribe or unsubscribe),
send these commands to the majordomo admin interface using a http POST
operation with a faked form.
"""
if len(self.remove_subscribers):
self.mj.RemoveSubscribers(self.remove_subscribers)
print "Removed %i subscribers" % len(self.remove_subscribers)
if len(self.add_subscribers):
self.mj.AddSubscribers(self.add_subscribers)
print "Added %i subscribers" % len(self.add_subscribers)
def fetch_expected_subscribers(self):
"""
Fetch the list of addresses that *should* be subscribed to the list by
looking in the database.
"""
curs = self.db.cursor()
curs.execute("""
SELECT email FROM planetadmin.auth_user
INNER JOIN planet.feeds ON planetadmin.auth_user.username=planet.feeds.userid
WHERE planet.feeds.approved
""")
self.expected = set([r[0] for r in curs.fetchall()])
if __name__=="__main__":
c = ConfigParser.ConfigParser()
c.read('planet.ini')
Synchronizer(c, psycopg2.connect(c.get('planet','db'))).sync()
|