On Sat, 2004-10-16 at 04:30, Fil wrote: > I would like to know if this check can be expanded to list all GID that > mailman will accept? Or if I should just be more carful in setting up my > system? :)
We patch mailman to accept multiple GID's. The patch is attached. It includes changes to configure to accept a list of GID's so don't forget if you apply the patch you'll have to run autoconf to generate a new configure. BTW, the preferred solution as you allude to is to be rigorous with the identity and permissions of system components. This patch is more of a concession to the dilemma of distributing mailman in an environment where other packages and configurations may be installed which are beyond our control than a suggested approach. Ideally this would not be necessary and for what its worth I have some misgivings about it. Our distribution is moving to a more constrained set of packages and much more security driven. This patch which has been in our RPM for a while is on the table for removal because of these security concerns. -- John Dennis <[EMAIL PROTECTED]>
Mailman security is in part enforced by requiring it execute SGID. When the mail process or the web server attempts to execute a mailman script a C program is invoked to verify the group permission. Mailman as it is shipped only allows one group to be specified at build time. For users who build and install on their own machine this is not a limitation. However, when making a binary package to be installed on an arbitrary machine it is hard to predict the correct group to use for that installation. Therefore this patch allows us to specify at build time a list of groups that will be iterated over, if the mailman process is executing as any of one of the group in the set of groups then the permission check passes. Since the groups we build with are limited to a small number of safe groups this does not lower the security much while at the same time provides a much more friendly way to package a binary installation that will run in a wider range of installations. It was necessary to add the macro MM_FIND_GROUP_LIST to the configure.in file replacing the original use of MM_FIND_GROUP_NAME, the former operates on a list of group names while the later on a single name. MM_FIND_GROUP_LIST includes a filter parameter that was added with the notion of supporting the with-permcheck option. If filter is true then only group names that exist on the build machine are permitted in the list, otherwise all names are permitted. However, note that whenever MM_FIND_GROUP_LIST is invoked it is currently hardcoded to disable filtering and is not tied to with-permcheck, this was done because of the observation that if one is passing a list of groups it is likely one is doing so to support installations that have a group not present on the build machine, but one might still want to take advantage of the other with-permcheck functionality. diff -u mailman-2.1.2/configure.in.orig mailman-2.1.2/configure.in --- mailman-2.1.2/configure.in.orig 2003-04-21 23:34:51.000000000 -0400 +++ mailman-2.1.2/configure.in 2003-05-02 16:32:45.000000000 -0400 @@ -208,26 +208,101 @@ fi # new macro for finding group names -AC_DEFUN(MM_FIND_GROUP_NAME, [ +# returns a comma separated list of quoted group names +# the list is returned in the same order as specified with any duplicates removed +# the filter flag must be "yes" or "no", e.g. this is permcheck +# "no" ==> none existing groups are not filtered out +# "yes" ==> only those groups that are in the group database are included +# in the list +AC_DEFUN(MM_FIND_GROUP_LIST, [ # $1 == variable name -# $2 == user id to check for +# $2 == white space separated list of groups to check, +# list may contain mix of id's and names +# $3 == filter, if == 'yes' then remove any non-existing groups AC_SUBST($1) changequote(,) if test -z "$$1" then cat > conftest.py <<EOF import grp -gid = '' +group_names = [] +seen = {} +filter = "$3" + for group in "$2".split(): try: + gid = int(group) + try: + gname = grp.getgrgid(gid)[0] + except KeyError: + gname = '' + except ValueError: try: - gname = grp.getgrgid(int(group))[0] - break - except ValueError: gname = grp.getgrnam(group)[0] + except KeyError: + if filter == "yes": + gname = '' + else: + gname = group + if gname: + if gname not in seen: + seen[gname] = 1 + group_names.append(gname) + +if group_names: + val = '"' + '", "'.join(group_names) + '"' + #val = "'"+val+"'" +else: + val = '' + +fp = open("conftest.out", "w") +fp.write("%s\n" % val) +fp.close() +EOF + $PYTHON conftest.py + $1=`cat conftest.out` +fi +changequote([, ]) +rm -f conftest.out conftest.py]) + + +# new macro for finding group names +AC_DEFUN(MM_FIND_GROUP_NAME, [ +# Given a list of tokens, either a name or a number (gid) +# return the first one in the list that is found in the +# group database. The return value is always a name, possibly +# translated from a gid. If permcheck is "no" then the group +# database is not checked, instead the first token in the list +# which is a name is returned (e.g. the default value). If permcheck +# is no and only gid's are in the list then the null string is returned. +# $1 == variable name +# $2 == group id to check for +# $3 == permcheck, either "yes" or "no" +AC_SUBST($1) +changequote(,) +if test -z "$$1" +then + cat > conftest.py <<EOF +import grp +gname='' +if "$3" == "yes": + for group in "$2".split(): + try: + try: + gname = grp.getgrgid(int(group))[0] + break + except ValueError: + gname = grp.getgrnam(group)[0] + break + except KeyError: + gname = '' +else: + for group in "$2".split(): + try: + int(group) + except ValueError: + gname = group break - except KeyError: - gname = '' fp = open("conftest.out", "w") fp.write("%s\n" % gname) fp.close() @@ -241,25 +316,41 @@ # new macro for finding UIDs AC_DEFUN(MM_FIND_USER_NAME, [ +# Given a list of tokens, either a name or a number (uid) +# return the first one in the list that is found in the +# password database. The return value is always a name, possibly +# translated from a uid. If permcheck is "no" then the password +# database is not checked, instead the first token in the list +# which is a name is returned (e.g. the default value). If permcheck +# is no and only uid's are in the list then the null string is returned. # $1 == variable name # $2 == user id to check for +# $3 == permcheck, either "yes" or "no" AC_SUBST($1) changequote(,) if test -z "$$1" then cat > conftest.py <<EOF import pwd -uid = '' -for user in "$2".split(): - try: +uname='' +if "$3" == "yes": + for user in "$2".split(): try: - uname = pwd.getpwuid(int(user))[0] - break + try: + uname = pwd.getpwuid(int(user))[0] + break + except ValueError: + uname = pwd.getpwnam(user)[0] + break + except KeyError: + uname = '' +else: + for user in "$2".split(): + try: + int(user) except ValueError: - uname = pwd.getpwnam(user)[0] + uname = user break - except KeyError: - uname = '' fp = open("conftest.out", "w") fp.write("%s\n" % uname) fp.close() @@ -285,7 +376,7 @@ # User `mailman' must exist AC_SUBST(MAILMAN_USER) AC_MSG_CHECKING(for user name \"$USERNAME\") -MM_FIND_USER_NAME(MAILMAN_USER, $USERNAME) +MM_FIND_USER_NAME(MAILMAN_USER, $USERNAME, $with_permcheck) if test -z "$MAILMAN_USER" then if test "$with_permcheck" = "yes" @@ -316,7 +407,7 @@ # Target group must exist AC_SUBST(MAILMAN_GROUP) AC_MSG_CHECKING(for group name \"$GROUPNAME\") -MM_FIND_GROUP_NAME(MAILMAN_GROUP, $GROUPNAME) +MM_FIND_GROUP_NAME(MAILMAN_GROUP, $GROUPNAME, $with_permcheck) if test -z "$MAILMAN_GROUP" then if test "$with_permcheck" = "yes" @@ -339,11 +430,11 @@ prefix = "$prefixcheck" groupname = "$GROUPNAME" mailmangroup = "$MAILMAN_GROUP" -try: - mailmangid = grp.getgrnam(mailmangroup)[2] -except KeyError: - mailmangid = -1 problems = [] +try: mailmangid = grp.getgrnam(mailmangroup)[2] +except KeyError: + problems.append("group doesn't exist: " + mailmangroup) + mailmangid = 41 try: statdata = os.stat(prefix) except OSError: problems.append("Directory doesn't exist: " + prefix) @@ -393,7 +484,7 @@ then with_mail_gid="mailman other mail daemon" fi -MM_FIND_GROUP_NAME(MAIL_GROUP, $with_mail_gid) +MM_FIND_GROUP_LIST(MAIL_GROUP, $with_mail_gid, $with_permcheck) if test -z "$MAIL_GROUP" then if test "$with_permcheck" = "yes" @@ -420,7 +511,7 @@ with_cgi_gid="www www-data nobody" fi -MM_FIND_GROUP_NAME(CGI_GROUP, $with_cgi_gid) +MM_FIND_GROUP_LIST(CGI_GROUP, $with_cgi_gid, $with_permcheck) if test -z "$CGI_GROUP" then if test "$with_permcheck" = "yes" diff -u mailman-2.1.2/src/cgi-wrapper.c.orig mailman-2.1.2/src/cgi-wrapper.c --- mailman-2.1.2/src/cgi-wrapper.c.orig 2002-08-23 16:39:47.000000000 -0400 +++ mailman-2.1.2/src/cgi-wrapper.c 2003-05-02 16:28:11.000000000 -0400 @@ -28,11 +28,11 @@ /* Group name that CGI scripts run as. See your web server's documentation * for details. */ -#define LEGAL_PARENT_GROUP CGI_GROUP +#define LEGAL_PARENT_GROUPS CGI_GROUP const char* logident = LOG_IDENT; char* script = SCRIPTNAME; -const char* parentgroup = LEGAL_PARENT_GROUP; +const char* parentgroups[] = {LEGAL_PARENT_GROUPS}; int @@ -42,7 +42,7 @@ char* fake_argv[3]; running_as_cgi = 1; - check_caller(logident, parentgroup); + check_caller(logident, parentgroups, sizeof(parentgroups) / sizeof(parentgroups[0])); /* For these CGI programs, we can ignore argc and argv since they * don't contain anything useful. `script' will always be the driver diff -u mailman-2.1.2/src/common.c.orig mailman-2.1.2/src/common.c --- mailman-2.1.2/src/common.c.orig 2002-09-04 21:29:57.000000000 -0400 +++ mailman-2.1.2/src/common.c 2003-05-02 16:28:11.000000000 -0400 @@ -116,13 +116,14 @@ /* Is the parent process allowed to call us? */ void -check_caller(const char* ident, const char* parentgroup) +check_caller(const char* ident, const char** parentgroups, size_t numgroups) { GID_T mygid = getgid(); struct group *mygroup = getgrgid(mygid); char* option; char* server; char* wrapper; + int i; if (running_as_cgi) { option = "--with-cgi-gid"; @@ -136,22 +137,45 @@ } if (!mygroup) - fatal(ident, GROUP_NAME_NOT_FOUND, - "Failure to find group name %s. Try adding this group\n" - "to your system, or re-run configure, providing an\n" - "existing group name with the command line option %s.", - parentgroup, option); + fatal(ident, GROUP_ID_NOT_FOUND, + "Failure to lookup via getgrgid() the group info for group id %d that this Mailman %s wrapper is executing under.\n" + "This is probably due to an incorrectly configured system and is not a Mailman problem", + mygid, wrapper); + + for (i = 0; i < numgroups; i++) { + if (strcmp(parentgroups[i], mygroup->gr_name) == 0) break; + } + + if (i >= numgroups) { + char *groupset = NULL; + size_t size = 0; + + for (i = 0; i < numgroups; i++) { + size += strlen(parentgroups[i]) + 2; + } + + groupset = malloc(size); + + if (groupset) { + groupset[0] = 0; + for (i = 0; i < numgroups; i++) { + strcat(groupset, parentgroups[i]); + if (i < numgroups-1) strcat(groupset, ", "); + } + } - if (strcmp(parentgroup, mygroup->gr_name)) fatal(ident, GROUP_MISMATCH, - "Group mismatch error. Mailman expected the %s\n" - "wrapper script to be executed as group \"%s\", but\n" - "the system's %s server executed the %s script as\n" - "group \"%s\". Try tweaking the %s server to run the\n" - "script as group \"%s\", or re-run configure, \n" - "providing the command line option `%s=%s'.", - wrapper, parentgroup, server, wrapper, mygroup->gr_name, - server, parentgroup, option, mygroup->gr_name); + "Group mismatch error. Mailman expected the %s wrapper script to be\n" + "executed as one of the following groups:\n" + "[%s],\n" + "but the system's %s server executed the %s script as group: \"%s\".\n" + "Try tweaking the %s server to run the script as one of these groups:\n" + "[%s],\n" + "or re-run configure providing the command line option:\n" + "'%s=%s'.", + wrapper, groupset, server, wrapper, mygroup->gr_name, + server, groupset, option, mygroup->gr_name); + } } diff -u mailman-2.1.2/src/common.h.orig mailman-2.1.2/src/common.h --- mailman-2.1.2/src/common.h.orig 2002-10-21 14:48:03.000000000 -0400 +++ mailman-2.1.2/src/common.h 2003-05-02 16:28:11.000000000 -0400 @@ -33,7 +33,7 @@ #define GID_T GETGROUPS_T extern void fatal(const char*, int, char*, ...); -extern void check_caller(const char*, const char*); +extern void check_caller(const char* ident, const char**, size_t); extern int run_script(const char*, int, char**, char**); /* Global variable used as a flag. */ @@ -51,7 +51,7 @@ #define MAIL_USAGE_ERROR 5 #define MAIL_ILLEGAL_COMMAND 6 #define ADDALIAS_USAGE_ERROR 7 -#define GROUP_NAME_NOT_FOUND 8 +#define GROUP_ID_NOT_FOUND 8 /* diff -u mailman-2.1.2/src/mail-wrapper.c.orig mailman-2.1.2/src/mail-wrapper.c --- mailman-2.1.2/src/mail-wrapper.c.orig 2002-08-23 16:40:27.000000000 -0400 +++ mailman-2.1.2/src/mail-wrapper.c 2003-05-02 16:28:11.000000000 -0400 @@ -23,9 +23,9 @@ /* Group name that your mail programs run as. See your mail server's * documentation for details. */ -#define LEGAL_PARENT_GROUP MAIL_GROUP +#define LEGAL_PARENT_GROUPS MAIL_GROUP -const char* parentgroup = LEGAL_PARENT_GROUP; +const char* parentgroups[] = {LEGAL_PARENT_GROUPS}; const char* logident = "Mailman mail-wrapper"; @@ -74,7 +74,7 @@ fatal(logident, MAIL_ILLEGAL_COMMAND, "Illegal command: %s", argv[1]); - check_caller(logident, parentgroup); + check_caller(logident, parentgroups, sizeof(parentgroups) / sizeof(parentgroups[0])); /* If we got here, everything must be OK */ status = run_script(argv[1], argc, argv, env); diff -u mailman-2.1.2/src/Makefile.in.orig mailman-2.1.2/src/Makefile.in --- mailman-2.1.2/src/Makefile.in.orig 2003-03-31 14:27:14.000000000 -0500 +++ mailman-2.1.2/src/Makefile.in 2003-05-02 16:28:11.000000000 -0400 @@ -49,9 +49,9 @@ SHELL= /bin/sh -MAIL_FLAGS= -DMAIL_GROUP="\"$(MAIL_GROUP)\"" +MAIL_FLAGS= -DMAIL_GROUP='$(MAIL_GROUP)' -CGI_FLAGS= -DCGI_GROUP="\"$(CGI_GROUP)\"" +CGI_FLAGS= -DCGI_GROUP='$(CGI_GROUP)' HELPFUL= -DHELPFUL
_______________________________________________ Mailman-Developers mailing list [EMAIL PROTECTED] http://mail.python.org/mailman/listinfo/mailman-developers Unsubscribe: http://mail.python.org/mailman/options/mailman-developers/archive%40jab.org