Make CEO non-SUID

It now binds to the directory as the invoking user.
pull/5/head
Michael Spang 15 years ago
parent 618bfa2d8d
commit 8c19dc63b6
  1. 32
      bin/ceo
  2. 16
      bin/csc-chfn
  3. 16
      bin/csc-chsh
  4. 62
      debian/postinst
  5. 45
      debian/postrm
  6. 12
      debian/rules
  7. 17
      etc/accounts.cf
  8. 176
      misc/setuid-prog.c
  9. 23
      pylib/csc/adm/members.py
  10. 12
      pylib/csc/apps/urwid/groups.py
  11. 21
      pylib/csc/apps/urwid/main.py
  12. 15
      pylib/csc/backends/ldapi.py
  13. 26
      src/config.c
  14. 5
      src/config.h

@ -1,33 +1,3 @@
#!/usr/bin/python2.4 --
"""CEO SUID Python Wrapper Script"""
import os, sys
safe_environment = ['LOGNAME', 'USERNAME', 'USER', 'HOME', 'TERM', 'LANG'
'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MESSAGES', 'LC_MONETARY',
'LC_NUMERIC', 'LC_TIME', 'UID', 'GID', 'SSH_CONNECTION', 'SSH_AUTH_SOCK',
'SSH_CLIENT']
for key in os.environ.keys():
if key not in safe_environment:
del os.environ[key]
os.environ['LESSSECURE'] = '1'
os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
for pathent in sys.path[:]:
if not pathent.find('/usr') == 0 and not pathent.find('/var') == 0:
sys.path.remove(pathent)
euid = os.geteuid()
egid = os.getegid()
uid = os.getuid()
gid = os.getgid()
try:
os.setreuid(euid, euid)
os.setregid(egid, egid)
except OSError, e:
print str(e)
sys.exit(1)
import csc.apps.urwid.main
csc.apps.urwid.main.start(uid, gid)
csc.apps.urwid.main.start()

@ -9,22 +9,6 @@ When run from an unprivileged account, authentication will be performed
before the account information is changed.
"""
import os, sys, pwd, getopt, PAM
safe_environment = ['LOGNAME', 'USERNAME', 'USER', 'HOME', 'TERM', 'LANG'
'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MESSAGES', 'LC_MONETARY',
'LC_NUMERIC', 'LC_TIME', 'UID', 'GID', 'SSH_CONNECTION', 'SSH_AUTH_SOCK',
'SSH_CLIENT']
for key in os.environ.keys():
if key not in safe_environment:
del os.environ[key]
os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
for pathent in sys.path[:]:
if not pathent.find('/usr') == 0:
sys.path.remove(pathent)
from csc.common.excep import InvalidArgument
from csc.adm import accounts

@ -9,22 +9,6 @@ When run from an unprivileged account, authentication will be performed
before the shell is changed, and the new shell must be listed in /etc/shells.
"""
import os, sys, pwd, getopt, PAM
safe_environment = ['LOGNAME', 'USERNAME', 'USER', 'HOME', 'TERM', 'LANG'
'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MESSAGES', 'LC_MONETARY',
'LC_NUMERIC', 'LC_TIME', 'UID', 'GID', 'SSH_CONNECTION', 'SSH_AUTH_SOCK',
'SSH_CLIENT']
for key in os.environ.keys():
if key not in safe_environment:
del os.environ[key]
os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
for pathent in sys.path[:]:
if not pathent.find('/usr') == 0:
sys.path.remove(pathent)
from csc.common.excep import InvalidArgument
from csc.adm import accounts

62
debian/postinst vendored

@ -1,62 +0,0 @@
#!/bin/bash -e
case "$1" in
configure|upgrade)
if getent passwd ceo > /dev/null; then
CEO=ceo
SUID=4750
SUIDALL=4755
else
CEO=root
SUID=755
SUIDALL=755
fi
if getent group office > /dev/null; then
OFFICE=office
else
OFFICE=root
fi
if ! dpkg-statoverride --list /usr/bin/ceo > /dev/null; then
dpkg-statoverride --add --update $CEO $OFFICE $SUID /usr/bin/ceo
fi
if ! dpkg-statoverride --list /usr/bin/addmember > /dev/null; then
dpkg-statoverride --add --update root $OFFICE $SUID /usr/bin/addmember
fi
if ! dpkg-statoverride --list /usr/bin/addclub > /dev/null; then
dpkg-statoverride --add --update root $OFFICE $SUID /usr/bin/addclub
fi
if ! dpkg-statoverride --list /usr/bin/ceoquery > /dev/null; then
dpkg-statoverride --add --update root root 755 /usr/bin/ceoquery
fi
if ! dpkg-statoverride --list /usr/bin/csc-chsh > /dev/null; then
dpkg-statoverride --add --update $CEO root $SUIDALL /usr/bin/csc-chsh
fi
if ! dpkg-statoverride --list /usr/bin/csc-chfn > /dev/null; then
dpkg-statoverride --add --update $CEO root $SUIDALL /usr/bin/csc-chfn
fi
if [ -f /etc/csc/ldap.cf ] && ! dpkg-statoverride --list /etc/csc/ldap.cf > /dev/null; then
dpkg-statoverride --add --update $CEO staff 640 /etc/csc/ldap.cf
fi
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \"$1\"" >&2
exit 1
;;
esac
#DEBHELPER#
exit 0

45
debian/postrm vendored

@ -1,45 +0,0 @@
#!/bin/bash -e
case "$1" in
purge)
if dpkg-statoverride --list /usr/bin/ceo > /dev/null; then
dpkg-statoverride --remove /usr/bin/ceo || true
fi
if dpkg-statoverride --list /usr/bin/ceoquery > /dev/null; then
dpkg-statoverride --remove /usr/bin/ceoquery || true
fi
if dpkg-statoverride --list /usr/bin/csc-chsh > /dev/null; then
dpkg-statoverride --remove /usr/bin/csc-chsh || true
fi
if dpkg-statoverride --list /usr/bin/csc-chfn > /dev/null; then
dpkg-statoverride --remove /usr/bin/csc-chfn || true
fi
if dpkg-statoverride --list /etc/csc/ldap.cf > /dev/null; then
dpkg-statoverride --remove /etc/csc/ldap.cf || true
fi
if dpkg-statoverride --list /etc/csc/ceo.keytab > /dev/null; then
dpkg-statoverride --remove /etc/csc/ceo.keytab || true
fi
rmdir --ignore-fail-on-non-empty /etc/csc
;;
remove|failed-upgrade|upgrade)
;;
*)
echo "postrm called with invalid argument \"$1\"" >&2
exit 1
;;
esac
#DEBHELPER#
exit 0

12
debian/rules vendored

@ -2,15 +2,8 @@
PYTHON := python2.4
build: build-stamp
build-stamp:
mkdir build
$(CC) -DFULL_PATH='"/usr/lib/csc/ceo"' -o build/ceo misc/setuid-prog.c
$(CC) -DFULL_PATH='"/usr/lib/csc/csc-chfn"' -o build/csc-chfn misc/setuid-prog.c
$(CC) -DFULL_PATH='"/usr/lib/csc/csc-chsh"' -o build/csc-chsh misc/setuid-prog.c
build:
cd src && make
touch build-stamp
clean:
dh_testdir
@ -30,8 +23,7 @@ install: build
dh_install pylib/* usr/lib/$(PYTHON)/site-packages/
dh_install etc/* etc/csc/
dh_install bin/ceo bin/csc-chsh bin/csc-chfn usr/lib/csc/
dh_install build/ceo bin/ceoquery build/csc-chsh build/csc-chfn src/addmember src/addclub usr/bin/
dh_install bin/ceo bin/ceoquery bin/csc-chsh bin/csc-chfn src/addmember src/addclub usr/bin/
dh_install misc/csc.schema etc/ldap/schema/
binary-arch: build install

@ -9,8 +9,6 @@ member_min_id = 20001
member_max_id = 29999
member_shell = "/bin/bash"
member_home = "/users"
member_desc = "CSC Member Account"
member_group = ""
### Club Account Options ###
@ -18,8 +16,6 @@ club_min_id = 30001
club_max_id = 39999
club_shell = "/bin/bash"
club_home = "/users"
club_desc = "CSC Club Account"
club_group = ""
### Administrative Account Options
@ -27,26 +23,17 @@ admin_min_id = 10001
admin_max_id = 19999
admin_shell = "/bin/bash"
admin_home = "/users"
admin_desc = "CSC Administrative Account"
admin_group = ""
### Account Group Options ###
group_min_id = 10001
group_max_id = 19999
group_desc = "CSC Group"
### Home Directory Options ###
skeleton_dir = "/etc/skel"
homedir_mode = 0755
homedir_min_uid = 10000
quota_prototype = "ctdalek"
### Validation Tuning ###
username_regex = "^[a-z][-a-z0-9]*$"
groupname_regex = "^[a-z][-a-z0-9]*$"
homedir_regex = "^/users/[^\.]+$"
min_password_length = 4
shells_file = "/etc/shells"
privileged_group = "staff"
notify_hook = "/etc/csc/thingie"

@ -1,176 +0,0 @@
/*
Template for a setuid program that calls a script.
The script should be in an unwritable directory and should itself
be unwritable. In fact all parent directories up to the root
should be unwritable. The script must not be setuid, that's what
this program is for.
This is a template program. You need to fill in the name of the
script that must be executed. This is done by changing the
definition of FULL_PATH below.
There are also some rules that should be adhered to when writing
the script itself.
The first and most important rule is to never, ever trust that the
user of the program will behave properly. Program defensively.
Check your arguments for reasonableness. If the user is allowed to
create files, check the names of the files. If the program depends
on argv[0] for the action it should perform, check it.
Assuming the script is a Bourne shell script, the first line of the
script should be
#!/bin/sh -
The - is important, don't omit it. If you're using esh, the first
line should be
#!/usr/local/bin/esh -f
and for ksh, the first line should be
#!/usr/local/bin/ksh -p
The script should then set the variable IFS to the string
consisting of <space>, <tab>, and <newline>. After this (*not*
before!), the PATH variable should be set to a reasonable value and
exported. Do not expect the PATH to have a reasonable value, so do
not trust the old value of PATH. You should then set the umask of
the program by calling
umask 077 # or 022 if you want the files to be readable
If you plan to change directories, you should either unset CDPATH
or set it to a good value. Setting CDPATH to just ``.'' (dot) is a
good idea.
If, for some reason, you want to use csh, the first line should be
#!/bin/csh -fb
You should then set the path variable to something reasonable,
without trusting the inherited path. Here too, you should set the
umask using the command
umask 077 # or 022 if you want the files to be readable
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
/* CONFIGURATION SECTION */
#ifndef FULL_PATH /* so that this can be specified from the Makefile */
/* Uncomment the following line:
#define FULL_PATH "/full/path/of/script"
* Then comment out the #error line. */
#error "You must define FULL_PATH somewhere"
#endif
#ifndef UMASK
#define UMASK 077
#endif
/* END OF CONFIGURATION SECTION */
#if defined(__STDC__) && defined(__sgi)
#define environ _environ
#endif
/* don't change def_IFS */
char def_IFS[] = "IFS= \t\n";
/* you may want to change def_PATH, but you should really change it in */
/* your script */
#ifdef __sgi
char def_PATH[] = "PATH=/usr/bsd:/usr/bin:/bin:/usr/local/bin:/usr/sbin";
#else
char def_PATH[] = "PATH=/usr/ucb:/usr/bin:/bin:/usr/local/bin";
#endif
/* don't change def_CDPATH */
char def_CDPATH[] = "CDPATH=.";
/* don't change def_ENV */
char def_ENV[] = "ENV=:";
/*
This function changes all environment variables that start with LD_
into variables that start with XD_. This is important since we
don't want the script that is executed to use any funny shared
libraries.
The other changes to the environment are, strictly speaking, not
needed here. They can safely be done in the script. They are done
here because we don't trust the script writer (just like the script
writer shouldn't trust the user of the script).
If IFS is set in the environment, set it to space,tab,newline.
If CDPATH is set in the environment, set it to ``.''.
Set PATH to a reasonable default.
*/
void
clean_environ(void)
{
char **p;
extern char **environ;
for (p = environ; *p; p++) {
if (strncmp(*p, "LD_", 3) == 0)
**p = 'X';
else if (strncmp(*p, "_RLD", 4) == 0)
**p = 'X';
else if (strncmp(*p, "PYTHON", 6) == 0)
**p = 'X';
else if (strncmp(*p, "IFS=", 4) == 0)
*p = def_IFS;
else if (strncmp(*p, "CDPATH=", 7) == 0)
*p = def_CDPATH;
else if (strncmp(*p, "ENV=", 4) == 0)
*p = def_ENV;
}
putenv(def_PATH);
}
int
main(int argc, char **argv)
{
struct stat statb;
gid_t egid = getegid();
uid_t euid = geteuid();
/*
Sanity check #1.
This check should be made compile-time, but that's not possible.
If you're sure that you specified a full path name for FULL_PATH,
you can omit this check.
*/
if (FULL_PATH[0] != '/') {
fprintf(stderr, "%s: %s is not a full path name\n", argv[0],
FULL_PATH);
fprintf(stderr, "You can only use this wrapper if you\n");
fprintf(stderr, "compile it with an absolute path.\n");
exit(1);
}
/*
Sanity check #2.
Check that the owner of the script is equal to either the
effective uid or the super user.
*/
if (stat(FULL_PATH, &statb) < 0) {
perror("stat");
exit(1);
}
if (statb.st_uid != 0 && statb.st_uid != euid) {
fprintf(stderr, "%s: %s has the wrong owner\n", argv[0],
FULL_PATH);
fprintf(stderr, "The script should be owned by root,\n");
fprintf(stderr, "and shouldn't be writeable by anyone.\n");
exit(1);
}
//if (setregid(egid, egid) < 0)
// perror("setregid");
//if (setreuid(euid, euid) < 0)
// perror("setreuid");
clean_environ();
umask(UMASK);
while (**argv == '-') /* don't let argv[0] start with '-' */
(*argv)++;
execv(FULL_PATH, argv);
fprintf(stderr, "%s: could not execute the script\n", argv[0]);
exit(1);
}

@ -24,16 +24,11 @@ cfg = {}
def configure():
"""Load Members Configuration"""
string_fields = [ 'member_shell', 'member_home', 'member_desc',
'member_group', 'club_shell', 'club_home', 'club_desc',
'club_group', 'admin_shell', 'admin_home', 'admin_desc',
'admin_group', 'group_desc', 'username_regex', 'groupname_regex',
'shells_file', 'server_url', 'users_base', 'groups_base',
'sasl_mech', 'sasl_realm', 'admin_bind_keytab',
'admin_bind_userid', 'realm', 'admin_principal', 'admin_keytab' ]
numeric_fields = [ 'member_min_id', 'member_max_id', 'club_min_id',
'club_max_id', 'admin_min_id', 'admin_max_id', 'group_min_id',
'group_max_id', 'min_password_length' ]
string_fields = [ 'username_regex', 'shells_file', 'server_url',
'users_base', 'groups_base', 'sasl_mech', 'sasl_realm',
'admin_bind_keytab', 'admin_bind_userid', 'realm',
'admin_principal', 'admin_keytab' ]
numeric_fields = [ 'min_password_length' ]
# read configuration file
cfg_tmp = conf.read(CONFIG_FILE)
@ -88,10 +83,9 @@ def connect():
"""Connect to LDAP."""
configure()
ldap_connection.connect_sasl(cfg['server_url'], cfg['sasl_mech'],
cfg['sasl_realm'], cfg['admin_bind_userid'],
('keytab', cfg['admin_bind_keytab']), cfg['users_base'],
cfg['groups_base'])
cfg['sasl_realm'], cfg['users_base'], cfg['groups_base'])
def disconnect():
"""Disconnect from LDAP."""
@ -188,8 +182,7 @@ def list_name(name):
Parameters:
name - the name to match members against
Returns: a list of member dictionaries
Returns: a list of member dictionaries
Example: list_name('Spang'): -> {
'mspang': { 'cn': 'Michael Spang', ... },

@ -20,18 +20,6 @@ def list_group_members(data):
search.member_list( mlist )
def group_members(data):
data, uid = data
# only syscom may modify non-club groups
if data['group'] != 'office':
user = pwd.getpwuid(uid).pw_name
users = grp.getgrnam('syscom').gr_mem
if user not in users:
member = members.get(data['group'])
if member is None or 'objectClass' not in member \
or 'club' not in member['objectClass']:
return
add_data = data.copy()
add_data['action'] = 'Add'
remove_data = data.copy()

@ -1,5 +1,4 @@
import random
import urwid.curses_display
import random, ldap, urwid.curses_display
from csc.apps.urwid.widgets import *
from csc.apps.urwid.window import *
@ -73,10 +72,8 @@ def main_menu():
("Search", search_members, None),
("Manage Club or Group Members", manage_group, None),
("Manage Positions", manage_positions, None),
("Manage Office Staff", groups.group_members,
(office_data, ui.uid)),
("Manage Systems Committee", groups.group_members,
(syscom_data, ui.uid)),
("Manage Office Staff", groups.group_members, office_data),
("Manage Systems Committee", groups.group_members, syscom_data),
("Exit", raise_abort, None),
]
@ -153,11 +150,13 @@ def run():
push_window( main_menu(), program_name() )
event_loop( ui )
def start(uid, gid):
ui.uid = uid
ui.gid = gid
ui.run_wrapper( run )
def start():
try:
ui.run_wrapper( run )
except ldap.LOCAL_ERROR, e:
print e[0]['info']
print "Hint: You may need to run 'kinit'"
if __name__ == '__main__':
import os
start(os.getuid(), os.getgid())
start()

@ -68,13 +68,13 @@ class LDAPConnection(object):
self.group_base = group_base
def connect_sasl(self, uri, mech, realm, userid, password, user_base, group_base):
def connect_sasl(self, uri, mech, realm, user_base, group_base):
# open the connection
self.ldap = ldap.initialize(uri)
# authenticate
sasl = Sasl(mech, realm, userid, password)
sasl = Sasl(mech, realm)
self.ldap.sasl_interactive_bind_s('', sasl)
self.user_base = user_base
@ -364,16 +364,9 @@ class LDAPConnection(object):
class Sasl:
def __init__(self, mech, realm, userid, password):
def __init__(self, mech, realm):
self.mech = mech
if mech == 'GSSAPI':
credtype, cred = password
kinit_args = [ '/usr/bin/kinit', '%s@%s' % (userid, realm) ]
if credtype == 'keytab':
kinit_args += [ '-kt', cred ]
kinit = Popen(kinit_args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
kinit.wait()
self.realm = realm
def callback(self, id, challenge, prompt, defresult):
return ''

@ -15,7 +15,6 @@ char *groups_base = DEF_STR;
char *skeleton_dir = DEF_STR;
char *quota_prototype = DEF_STR;
char *homedir_prefix = DEF_STR;
char *member_home = DEF_STR;
char *member_shell = DEF_STR;
@ -29,6 +28,8 @@ long club_max_id = DEF_LONG;
char *notify_hook = DEF_STR;
long homedir_mode = DEF_LONG;
char *realm = DEF_STR;
char *admin_principal = DEF_STR;
@ -42,24 +43,21 @@ char *sasl_mech = DEF_STR;
char *privileged_group = DEF_STR;
long homedir_mode = DEF_LONG;
long homedir_min_uid = DEF_LONG;
static char *strvarnames[] = { "server_url", "users_base", "admin_principal",
"admin_keytab", "skeleton_dir", "quota_prototype", "homedir_prefix",
"member_home", "member_shell", "club_home", "club_shell", "realm",
"admin_bind_userid", "admin_bind_keytab", "groups_base",
"privileged_group", "notify_hook", "sasl_realm", "sasl_mech" };
"admin_keytab", "skeleton_dir", "quota_prototype", "member_home",
"member_shell", "club_home", "club_shell", "realm", "admin_bind_userid",
"admin_bind_keytab", "groups_base", "privileged_group", "notify_hook",
"sasl_realm", "sasl_mech" };
static char **strvars[] = { &server_url, &users_base, &admin_principal,
&admin_keytab, &skeleton_dir, &quota_prototype, &homedir_prefix,
&member_home, &member_shell, &club_home, &club_shell, &realm,
&admin_bind_userid, &admin_bind_keytab, &groups_base,
&privileged_group, &notify_hook, &sasl_realm, &sasl_mech };
&admin_keytab, &skeleton_dir, &quota_prototype, &member_home,
&member_shell, &club_home, &club_shell, &realm, &admin_bind_userid,
&admin_bind_keytab, &groups_base, &privileged_group, &notify_hook,
&sasl_realm, &sasl_mech };
static char *longvarnames[] = { "member_min_id", "member_max_id",
"homedir_mode", "homedir_min_uid", "club_min_id", "club_max_id" };
"homedir_mode", "club_min_id", "club_max_id" };
static long *longvars[] = { &member_min_id, &member_max_id, &homedir_mode,
&homedir_min_uid, &club_min_id, &club_max_id };
&club_min_id, &club_max_id };
void config_var(char *var, char *val) {
int i;

@ -4,12 +4,8 @@ extern char *server_url;
extern char *users_base;
extern char *groups_base;
extern char *admin_bind_dn;
extern char *admin_bind_pw;
extern char *skeleton_dir;
extern char *quota_prototype;
extern char *homedir_prefix;
extern char *member_home;
extern char *member_shell;
@ -24,7 +20,6 @@ extern long club_max_id;
extern char *notify_hook;
extern long homedir_mode;
extern long homedir_min_uid;
extern char *realm;

Loading…
Cancel
Save