1 |
slords |
1.1 |
Mailman security is in part enforced by requiring it execute |
2 |
|
|
SGID. When the mail process or the web server attempts to execute a |
3 |
|
|
mailman script a C program is invoked to verify the group |
4 |
|
|
permission. Mailman as it is shipped only allows one group to be |
5 |
|
|
specified at build time. For users who build and install on their own |
6 |
|
|
machine this is not a limitation. However, when making a binary |
7 |
|
|
package to be installed on an arbitrary machine it is hard to predict |
8 |
|
|
the correct group to use for that installation. Therefore this patch |
9 |
|
|
allows us to specify at build time a list of groups that will be |
10 |
|
|
iterated over, if the mailman process is executing as any of one of |
11 |
|
|
the group in the set of groups then the permission check passes. Since |
12 |
|
|
the groups we build with are limited to a small number of safe groups |
13 |
|
|
this does not lower the security much while at the same time provides |
14 |
|
|
a much more friendly way to package a binary installation that will |
15 |
|
|
run in a wider range of installations. |
16 |
|
|
|
17 |
|
|
It was necessary to add the macro MM_FIND_GROUP_LIST to the |
18 |
|
|
configure.in file replacing the original use of MM_FIND_GROUP_NAME, |
19 |
|
|
the former operates on a list of group names while the later on a |
20 |
|
|
single name. MM_FIND_GROUP_LIST includes a filter parameter that was |
21 |
|
|
added with the notion of supporting the with-permcheck option. If |
22 |
|
|
filter is true then only group names that exist on the build machine |
23 |
|
|
are permitted in the list, otherwise all names are permitted. However, |
24 |
|
|
note that whenever MM_FIND_GROUP_LIST is invoked it is currently |
25 |
|
|
hardcoded to disable filtering and is not tied to with-permcheck, this |
26 |
|
|
was done because of the observation that if one is passing a list of |
27 |
|
|
groups it is likely one is doing so to support installations that have |
28 |
|
|
a group not present on the build machine, but one might still want to |
29 |
|
|
take advantage of the other with-permcheck functionality. |
30 |
|
|
|
31 |
|
|
diff -u mailman-2.1.2/configure.in.orig mailman-2.1.2/configure.in |
32 |
|
|
--- mailman-2.1.2/configure.in.orig 2003-04-21 23:34:51.000000000 -0400 |
33 |
|
|
+++ mailman-2.1.2/configure.in 2003-05-02 16:32:45.000000000 -0400 |
34 |
|
|
@@ -208,26 +208,101 @@ |
35 |
|
|
fi |
36 |
|
|
|
37 |
|
|
# new macro for finding group names |
38 |
|
|
-AC_DEFUN(MM_FIND_GROUP_NAME, [ |
39 |
|
|
+# returns a comma separated list of quoted group names |
40 |
|
|
+# the list is returned in the same order as specified with any duplicates removed |
41 |
|
|
+# the filter flag must be "yes" or "no", e.g. this is permcheck |
42 |
|
|
+# "no" ==> none existing groups are not filtered out |
43 |
|
|
+# "yes" ==> only those groups that are in the group database are included |
44 |
|
|
+# in the list |
45 |
|
|
+AC_DEFUN(MM_FIND_GROUP_LIST, [ |
46 |
|
|
# $1 == variable name |
47 |
|
|
-# $2 == user id to check for |
48 |
|
|
+# $2 == white space separated list of groups to check, |
49 |
|
|
+# list may contain mix of id's and names |
50 |
|
|
+# $3 == filter, if == 'yes' then remove any non-existing groups |
51 |
|
|
AC_SUBST($1) |
52 |
|
|
changequote(,) |
53 |
|
|
if test -z "$$1" |
54 |
|
|
then |
55 |
|
|
cat > conftest.py <<EOF |
56 |
|
|
import grp |
57 |
|
|
-gid = '' |
58 |
|
|
+group_names = [] |
59 |
|
|
+seen = {} |
60 |
|
|
+filter = "$3" |
61 |
|
|
+ |
62 |
|
|
for group in "$2".split(): |
63 |
|
|
try: |
64 |
|
|
+ gid = int(group) |
65 |
|
|
+ try: |
66 |
|
|
+ gname = grp.getgrgid(gid)[0] |
67 |
|
|
+ except KeyError: |
68 |
|
|
+ gname = '' |
69 |
|
|
+ except ValueError: |
70 |
|
|
try: |
71 |
|
|
- gname = grp.getgrgid(int(group))[0] |
72 |
|
|
- break |
73 |
|
|
- except ValueError: |
74 |
|
|
gname = grp.getgrnam(group)[0] |
75 |
|
|
+ except KeyError: |
76 |
|
|
+ if filter == "yes": |
77 |
|
|
+ gname = '' |
78 |
|
|
+ else: |
79 |
|
|
+ gname = group |
80 |
|
|
+ if gname: |
81 |
|
|
+ if gname not in seen: |
82 |
|
|
+ seen[gname] = 1 |
83 |
|
|
+ group_names.append(gname) |
84 |
|
|
+ |
85 |
|
|
+if group_names: |
86 |
|
|
+ val = '"' + '", "'.join(group_names) + '"' |
87 |
|
|
+ #val = "'"+val+"'" |
88 |
|
|
+else: |
89 |
|
|
+ val = '' |
90 |
|
|
+ |
91 |
|
|
+fp = open("conftest.out", "w") |
92 |
|
|
+fp.write("%s\n" % val) |
93 |
|
|
+fp.close() |
94 |
|
|
+EOF |
95 |
|
|
+ $PYTHON conftest.py |
96 |
|
|
+ $1=`cat conftest.out` |
97 |
|
|
+fi |
98 |
|
|
+changequote([, ]) |
99 |
|
|
+rm -f conftest.out conftest.py]) |
100 |
|
|
+ |
101 |
|
|
+ |
102 |
|
|
+# new macro for finding group names |
103 |
|
|
+AC_DEFUN(MM_FIND_GROUP_NAME, [ |
104 |
|
|
+# Given a list of tokens, either a name or a number (gid) |
105 |
|
|
+# return the first one in the list that is found in the |
106 |
|
|
+# group database. The return value is always a name, possibly |
107 |
|
|
+# translated from a gid. If permcheck is "no" then the group |
108 |
|
|
+# database is not checked, instead the first token in the list |
109 |
|
|
+# which is a name is returned (e.g. the default value). If permcheck |
110 |
|
|
+# is no and only gid's are in the list then the null string is returned. |
111 |
|
|
+# $1 == variable name |
112 |
|
|
+# $2 == group id to check for |
113 |
|
|
+# $3 == permcheck, either "yes" or "no" |
114 |
|
|
+AC_SUBST($1) |
115 |
|
|
+changequote(,) |
116 |
|
|
+if test -z "$$1" |
117 |
|
|
+then |
118 |
|
|
+ cat > conftest.py <<EOF |
119 |
|
|
+import grp |
120 |
|
|
+gname='' |
121 |
|
|
+if "$3" == "yes": |
122 |
|
|
+ for group in "$2".split(): |
123 |
|
|
+ try: |
124 |
|
|
+ try: |
125 |
|
|
+ gname = grp.getgrgid(int(group))[0] |
126 |
|
|
+ break |
127 |
|
|
+ except ValueError: |
128 |
|
|
+ gname = grp.getgrnam(group)[0] |
129 |
|
|
+ break |
130 |
|
|
+ except KeyError: |
131 |
|
|
+ gname = '' |
132 |
|
|
+else: |
133 |
|
|
+ for group in "$2".split(): |
134 |
|
|
+ try: |
135 |
|
|
+ int(group) |
136 |
|
|
+ except ValueError: |
137 |
|
|
+ gname = group |
138 |
|
|
break |
139 |
|
|
- except KeyError: |
140 |
|
|
- gname = '' |
141 |
|
|
fp = open("conftest.out", "w") |
142 |
|
|
fp.write("%s\n" % gname) |
143 |
|
|
fp.close() |
144 |
|
|
@@ -241,25 +316,41 @@ |
145 |
|
|
|
146 |
|
|
# new macro for finding UIDs |
147 |
|
|
AC_DEFUN(MM_FIND_USER_NAME, [ |
148 |
|
|
+# Given a list of tokens, either a name or a number (uid) |
149 |
|
|
+# return the first one in the list that is found in the |
150 |
|
|
+# password database. The return value is always a name, possibly |
151 |
|
|
+# translated from a uid. If permcheck is "no" then the password |
152 |
|
|
+# database is not checked, instead the first token in the list |
153 |
|
|
+# which is a name is returned (e.g. the default value). If permcheck |
154 |
|
|
+# is no and only uid's are in the list then the null string is returned. |
155 |
|
|
# $1 == variable name |
156 |
|
|
# $2 == user id to check for |
157 |
|
|
+# $3 == permcheck, either "yes" or "no" |
158 |
|
|
AC_SUBST($1) |
159 |
|
|
changequote(,) |
160 |
|
|
if test -z "$$1" |
161 |
|
|
then |
162 |
|
|
cat > conftest.py <<EOF |
163 |
|
|
import pwd |
164 |
|
|
-uid = '' |
165 |
|
|
-for user in "$2".split(): |
166 |
|
|
- try: |
167 |
|
|
+uname='' |
168 |
|
|
+if "$3" == "yes": |
169 |
|
|
+ for user in "$2".split(): |
170 |
|
|
try: |
171 |
|
|
- uname = pwd.getpwuid(int(user))[0] |
172 |
|
|
- break |
173 |
|
|
+ try: |
174 |
|
|
+ uname = pwd.getpwuid(int(user))[0] |
175 |
|
|
+ break |
176 |
|
|
+ except ValueError: |
177 |
|
|
+ uname = pwd.getpwnam(user)[0] |
178 |
|
|
+ break |
179 |
|
|
+ except KeyError: |
180 |
|
|
+ uname = '' |
181 |
|
|
+else: |
182 |
|
|
+ for user in "$2".split(): |
183 |
|
|
+ try: |
184 |
|
|
+ int(user) |
185 |
|
|
except ValueError: |
186 |
|
|
- uname = pwd.getpwnam(user)[0] |
187 |
|
|
+ uname = user |
188 |
|
|
break |
189 |
|
|
- except KeyError: |
190 |
|
|
- uname = '' |
191 |
|
|
fp = open("conftest.out", "w") |
192 |
|
|
fp.write("%s\n" % uname) |
193 |
|
|
fp.close() |
194 |
|
|
@@ -285,7 +376,7 @@ |
195 |
|
|
# User `mailman' must exist |
196 |
|
|
AC_SUBST(MAILMAN_USER) |
197 |
|
|
AC_MSG_CHECKING(for user name \"$USERNAME\") |
198 |
|
|
-MM_FIND_USER_NAME(MAILMAN_USER, $USERNAME) |
199 |
|
|
+MM_FIND_USER_NAME(MAILMAN_USER, $USERNAME, $with_permcheck) |
200 |
|
|
if test -z "$MAILMAN_USER" |
201 |
|
|
then |
202 |
|
|
if test "$with_permcheck" = "yes" |
203 |
|
|
@@ -316,7 +407,7 @@ |
204 |
|
|
# Target group must exist |
205 |
|
|
AC_SUBST(MAILMAN_GROUP) |
206 |
|
|
AC_MSG_CHECKING(for group name \"$GROUPNAME\") |
207 |
|
|
-MM_FIND_GROUP_NAME(MAILMAN_GROUP, $GROUPNAME) |
208 |
|
|
+MM_FIND_GROUP_NAME(MAILMAN_GROUP, $GROUPNAME, $with_permcheck) |
209 |
|
|
if test -z "$MAILMAN_GROUP" |
210 |
|
|
then |
211 |
|
|
if test "$with_permcheck" = "yes" |
212 |
|
|
@@ -339,11 +430,11 @@ |
213 |
|
|
prefix = "$prefixcheck" |
214 |
|
|
groupname = "$GROUPNAME" |
215 |
|
|
mailmangroup = "$MAILMAN_GROUP" |
216 |
|
|
-try: |
217 |
|
|
- mailmangid = grp.getgrnam(mailmangroup)[2] |
218 |
|
|
-except KeyError: |
219 |
|
|
- mailmangid = -1 |
220 |
|
|
problems = [] |
221 |
|
|
+try: mailmangid = grp.getgrnam(mailmangroup)[2] |
222 |
|
|
+except KeyError: |
223 |
|
|
+ problems.append("group doesn't exist: " + mailmangroup) |
224 |
|
|
+ mailmangid = 41 |
225 |
|
|
try: statdata = os.stat(prefix) |
226 |
|
|
except OSError: |
227 |
|
|
problems.append("Directory doesn't exist: " + prefix) |
228 |
|
|
@@ -393,7 +484,7 @@ |
229 |
|
|
then |
230 |
|
|
with_mail_gid="mailman other mail daemon" |
231 |
|
|
fi |
232 |
|
|
-MM_FIND_GROUP_NAME(MAIL_GROUP, $with_mail_gid) |
233 |
|
|
+MM_FIND_GROUP_LIST(MAIL_GROUP, $with_mail_gid, $with_permcheck) |
234 |
|
|
if test -z "$MAIL_GROUP" |
235 |
|
|
then |
236 |
|
|
if test "$with_permcheck" = "yes" |
237 |
|
|
@@ -420,7 +511,7 @@ |
238 |
|
|
with_cgi_gid="www www-data nobody" |
239 |
|
|
fi |
240 |
|
|
|
241 |
|
|
-MM_FIND_GROUP_NAME(CGI_GROUP, $with_cgi_gid) |
242 |
|
|
+MM_FIND_GROUP_LIST(CGI_GROUP, $with_cgi_gid, $with_permcheck) |
243 |
|
|
if test -z "$CGI_GROUP" |
244 |
|
|
then |
245 |
|
|
if test "$with_permcheck" = "yes" |
246 |
|
|
diff -u mailman-2.1.2/src/cgi-wrapper.c.orig mailman-2.1.2/src/cgi-wrapper.c |
247 |
|
|
--- mailman-2.1.2/src/cgi-wrapper.c.orig 2002-08-23 16:39:47.000000000 -0400 |
248 |
|
|
+++ mailman-2.1.2/src/cgi-wrapper.c 2003-05-02 16:28:11.000000000 -0400 |
249 |
|
|
@@ -28,11 +28,11 @@ |
250 |
|
|
/* Group name that CGI scripts run as. See your web server's documentation |
251 |
|
|
* for details. |
252 |
|
|
*/ |
253 |
|
|
-#define LEGAL_PARENT_GROUP CGI_GROUP |
254 |
|
|
+#define LEGAL_PARENT_GROUPS CGI_GROUP |
255 |
|
|
|
256 |
|
|
const char* logident = LOG_IDENT; |
257 |
|
|
char* script = SCRIPTNAME; |
258 |
|
|
-const char* parentgroup = LEGAL_PARENT_GROUP; |
259 |
|
|
+const char* parentgroups[] = {LEGAL_PARENT_GROUPS}; |
260 |
|
|
|
261 |
|
|
|
262 |
|
|
int |
263 |
|
|
@@ -42,7 +42,7 @@ |
264 |
|
|
char* fake_argv[3]; |
265 |
|
|
|
266 |
|
|
running_as_cgi = 1; |
267 |
|
|
- check_caller(logident, parentgroup); |
268 |
|
|
+ check_caller(logident, parentgroups, sizeof(parentgroups) / sizeof(parentgroups[0])); |
269 |
|
|
|
270 |
|
|
/* For these CGI programs, we can ignore argc and argv since they |
271 |
|
|
* don't contain anything useful. `script' will always be the driver |
272 |
|
|
diff -u mailman-2.1.2/src/common.c.orig mailman-2.1.2/src/common.c |
273 |
|
|
--- mailman-2.1.2/src/common.c.orig 2002-09-04 21:29:57.000000000 -0400 |
274 |
|
|
+++ mailman-2.1.2/src/common.c 2003-05-02 16:28:11.000000000 -0400 |
275 |
|
|
@@ -116,13 +116,14 @@ |
276 |
|
|
/* Is the parent process allowed to call us? |
277 |
|
|
*/ |
278 |
|
|
void |
279 |
|
|
-check_caller(const char* ident, const char* parentgroup) |
280 |
|
|
+check_caller(const char* ident, const char** parentgroups, size_t numgroups) |
281 |
|
|
{ |
282 |
|
|
GID_T mygid = getgid(); |
283 |
|
|
struct group *mygroup = getgrgid(mygid); |
284 |
|
|
char* option; |
285 |
|
|
char* server; |
286 |
|
|
char* wrapper; |
287 |
|
|
+ int i; |
288 |
|
|
|
289 |
|
|
if (running_as_cgi) { |
290 |
|
|
option = "--with-cgi-gid"; |
291 |
|
|
@@ -136,22 +137,45 @@ |
292 |
|
|
} |
293 |
|
|
|
294 |
|
|
if (!mygroup) |
295 |
|
|
- fatal(ident, GROUP_NAME_NOT_FOUND, |
296 |
|
|
- "Failure to find group name %s. Try adding this group\n" |
297 |
|
|
- "to your system, or re-run configure, providing an\n" |
298 |
|
|
- "existing group name with the command line option %s.", |
299 |
|
|
- parentgroup, option); |
300 |
|
|
+ fatal(ident, GROUP_ID_NOT_FOUND, |
301 |
|
|
+ "Failure to lookup via getgrgid() the group info for group id %d that this Mailman %s wrapper is executing under.\n" |
302 |
|
|
+ "This is probably due to an incorrectly configured system and is not a Mailman problem", |
303 |
|
|
+ mygid, wrapper); |
304 |
|
|
+ |
305 |
|
|
+ for (i = 0; i < numgroups; i++) { |
306 |
|
|
+ if (strcmp(parentgroups[i], mygroup->gr_name) == 0) break; |
307 |
|
|
+ } |
308 |
|
|
+ |
309 |
|
|
+ if (i >= numgroups) { |
310 |
|
|
+ char *groupset = NULL; |
311 |
|
|
+ size_t size = 0; |
312 |
|
|
+ |
313 |
|
|
+ for (i = 0; i < numgroups; i++) { |
314 |
|
|
+ size += strlen(parentgroups[i]) + 2; |
315 |
|
|
+ } |
316 |
|
|
+ |
317 |
|
|
+ groupset = malloc(size); |
318 |
|
|
+ |
319 |
|
|
+ if (groupset) { |
320 |
|
|
+ groupset[0] = 0; |
321 |
|
|
+ for (i = 0; i < numgroups; i++) { |
322 |
|
|
+ strcat(groupset, parentgroups[i]); |
323 |
|
|
+ if (i < numgroups-1) strcat(groupset, ", "); |
324 |
|
|
+ } |
325 |
|
|
+ } |
326 |
|
|
|
327 |
|
|
- if (strcmp(parentgroup, mygroup->gr_name)) |
328 |
|
|
fatal(ident, GROUP_MISMATCH, |
329 |
|
|
- "Group mismatch error. Mailman expected the %s\n" |
330 |
|
|
- "wrapper script to be executed as group \"%s\", but\n" |
331 |
|
|
- "the system's %s server executed the %s script as\n" |
332 |
|
|
- "group \"%s\". Try tweaking the %s server to run the\n" |
333 |
|
|
- "script as group \"%s\", or re-run configure, \n" |
334 |
|
|
- "providing the command line option `%s=%s'.", |
335 |
|
|
- wrapper, parentgroup, server, wrapper, mygroup->gr_name, |
336 |
|
|
- server, parentgroup, option, mygroup->gr_name); |
337 |
|
|
+ "Group mismatch error. Mailman expected the %s wrapper script to be\n" |
338 |
|
|
+ "executed as one of the following groups:\n" |
339 |
|
|
+ "[%s],\n" |
340 |
|
|
+ "but the system's %s server executed the %s script as group: \"%s\".\n" |
341 |
|
|
+ "Try tweaking the %s server to run the script as one of these groups:\n" |
342 |
|
|
+ "[%s],\n" |
343 |
|
|
+ "or re-run configure providing the command line option:\n" |
344 |
|
|
+ "'%s=%s'.", |
345 |
|
|
+ wrapper, groupset, server, wrapper, mygroup->gr_name, |
346 |
|
|
+ server, groupset, option, mygroup->gr_name); |
347 |
|
|
+ } |
348 |
|
|
} |
349 |
|
|
|
350 |
|
|
|
351 |
|
|
diff -u mailman-2.1.2/src/common.h.orig mailman-2.1.2/src/common.h |
352 |
|
|
--- mailman-2.1.2/src/common.h.orig 2002-10-21 14:48:03.000000000 -0400 |
353 |
|
|
+++ mailman-2.1.2/src/common.h 2003-05-02 16:28:11.000000000 -0400 |
354 |
|
|
@@ -33,7 +33,7 @@ |
355 |
|
|
#define GID_T GETGROUPS_T |
356 |
|
|
|
357 |
|
|
extern void fatal(const char*, int, char*, ...); |
358 |
|
|
-extern void check_caller(const char*, const char*); |
359 |
|
|
+extern void check_caller(const char* ident, const char**, size_t); |
360 |
|
|
extern int run_script(const char*, int, char**, char**); |
361 |
|
|
|
362 |
|
|
/* Global variable used as a flag. */ |
363 |
|
|
@@ -51,7 +51,7 @@ |
364 |
|
|
#define MAIL_USAGE_ERROR 5 |
365 |
|
|
#define MAIL_ILLEGAL_COMMAND 6 |
366 |
|
|
#define ADDALIAS_USAGE_ERROR 7 |
367 |
|
|
-#define GROUP_NAME_NOT_FOUND 8 |
368 |
|
|
+#define GROUP_ID_NOT_FOUND 8 |
369 |
|
|
|
370 |
|
|
|
371 |
|
|
/* |
372 |
|
|
diff -u mailman-2.1.2/src/mail-wrapper.c.orig mailman-2.1.2/src/mail-wrapper.c |
373 |
|
|
--- mailman-2.1.2/src/mail-wrapper.c.orig 2002-08-23 16:40:27.000000000 -0400 |
374 |
|
|
+++ mailman-2.1.2/src/mail-wrapper.c 2003-05-02 16:28:11.000000000 -0400 |
375 |
|
|
@@ -23,9 +23,9 @@ |
376 |
|
|
/* Group name that your mail programs run as. See your mail server's |
377 |
|
|
* documentation for details. |
378 |
|
|
*/ |
379 |
|
|
-#define LEGAL_PARENT_GROUP MAIL_GROUP |
380 |
|
|
+#define LEGAL_PARENT_GROUPS MAIL_GROUP |
381 |
|
|
|
382 |
|
|
-const char* parentgroup = LEGAL_PARENT_GROUP; |
383 |
|
|
+const char* parentgroups[] = {LEGAL_PARENT_GROUPS}; |
384 |
|
|
const char* logident = "Mailman mail-wrapper"; |
385 |
|
|
|
386 |
|
|
|
387 |
|
|
@@ -74,7 +74,7 @@ |
388 |
|
|
fatal(logident, MAIL_ILLEGAL_COMMAND, |
389 |
|
|
"Illegal command: %s", argv[1]); |
390 |
|
|
|
391 |
|
|
- check_caller(logident, parentgroup); |
392 |
|
|
+ check_caller(logident, parentgroups, sizeof(parentgroups) / sizeof(parentgroups[0])); |
393 |
|
|
|
394 |
|
|
/* If we got here, everything must be OK */ |
395 |
|
|
status = run_script(argv[1], argc, argv, env); |
396 |
|
|
diff -u mailman-2.1.2/src/Makefile.in.orig mailman-2.1.2/src/Makefile.in |
397 |
|
|
--- mailman-2.1.2/src/Makefile.in.orig 2003-03-31 14:27:14.000000000 -0500 |
398 |
|
|
+++ mailman-2.1.2/src/Makefile.in 2003-05-02 16:28:11.000000000 -0400 |
399 |
|
|
@@ -49,9 +49,9 @@ |
400 |
|
|
|
401 |
|
|
SHELL= /bin/sh |
402 |
|
|
|
403 |
|
|
-MAIL_FLAGS= -DMAIL_GROUP="\"$(MAIL_GROUP)\"" |
404 |
|
|
+MAIL_FLAGS= -DMAIL_GROUP='$(MAIL_GROUP)' |
405 |
|
|
|
406 |
|
|
-CGI_FLAGS= -DCGI_GROUP="\"$(CGI_GROUP)\"" |
407 |
|
|
+CGI_FLAGS= -DCGI_GROUP='$(CGI_GROUP)' |
408 |
|
|
|
409 |
|
|
HELPFUL= -DHELPFUL |
410 |
|
|
|