Skip to content

Commit

Permalink
use setgid/crontab instead of setuid/root
Browse files Browse the repository at this point in the history
  • Loading branch information
a-detiste committed Dec 13, 2014
1 parent 7b93059 commit 85bb2df
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 42 deletions.
26 changes: 16 additions & 10 deletions Makefile.in
Expand Up @@ -7,7 +7,7 @@ version := $(shell cat VERSION)
schedules := @schedules@
schedules_not := @schedules_not@
enable_persistent := @enable_persistent@
enable_setuid := @enable_setuid@
enable_setgid := @enable_setgid@

prefix := @prefix@
bindir := @bindir@
Expand Down Expand Up @@ -37,7 +37,7 @@ out_units := $(out_services) $(out_timers) $(out_targets) $(builddir)/units/cro
$(builddir)/units/cron-failure@.service
out_manuals := $(patsubst $(srcdir)/man/%.in,$(builddir)/man/%,$(wildcard $(srcdir)/man/*))
out_programs := $(patsubst $(srcdir)/bin/%.py,$(builddir)/bin/%,$(wildcard $(srcdir)/bin/*.py)) \
$(builddir)/bin/crontab_setuid
$(builddir)/bin/crontab_setgid
outputs := $(out_units) $(out_manuals) $(out_programs)

define \n
Expand Down Expand Up @@ -86,18 +86,24 @@ test: all
$(foreach manpage,$(out_manuals),\
man --warnings --encoding=utf8 --local-file $(manpage) 2>&1 > /dev/null${\n})

setuid: $(builddir)/bin/crontab_setuid
sudo chown root:root out/build/bin/crontab_setuid
sudo chmod 4755 out/build/bin/crontab_setuid
setgid: $(builddir)/bin/crontab_setgid
getent group crontab > /dev/null 2>&1 || addgroup --system crontab
chown root:crontab out/build/bin/crontab_setgid
chmod 2755 out/build/bin/crontab_setgid
mkdir -p $(statedir)
chown root:crontab $(statedir)
chmod 0770 $(statedir)

install: all
install -m755 -D $(builddir)/bin/crontab $(DESTDIR)$(bindir)/crontab
install -m755 -D $(builddir)/bin/systemd-crontab-generator $(DESTDIR)$(libdir)/systemd/system-generators/systemd-crontab-generator
install -m755 -D $(builddir)/bin/remove_stale_stamps $(DESTDIR)$(stale_stamps)
install -m755 -D $(builddir)/bin/mail_on_failure $(DESTDIR)$(libdir)/$(package)/mail_on_failure
install -m755 -D $(builddir)/bin/boot_delay $(DESTDIR)$(libdir)/$(package)/boot_delay
ifneq ($(enable_setuid),no)
install -m4755 -D $(builddir)/bin/crontab_setuid $(DESTDIR)$(libdir)/$(package)/crontab_setuid
ifneq ($(enable_setgid),no)
install -m755 -D $(builddir)/bin/crontab_setgid $(DESTDIR)$(libdir)/$(package)/crontab_setgid
chgrp crontab $(DESTDIR)$(libdir)/$(package)/crontab_setgid || true
chmod 2755 $(DESTDIR)$(libdir)/$(package)/crontab_setgid || true
endif

install -m644 -D $(builddir)/man/systemd.cron.7 $(DESTDIR)$(mandir)/man7
Expand All @@ -124,7 +130,7 @@ uninstall:
rm -f $(DESTDIR)$(stale_stamps)
rm -f $(DESTDIR)$(libdir)/$(package)/mail_on_failure
rm -f $(DESTDIR)$(libdir)/$(package)/boot_delay
rm -f $(DESTDIR)$(libdir)/$(package)/crontab_setuid
rm -f $(DESTDIR)$(libdir)/$(package)/crontab_setgid

rm -f $(DESTDIR)$(mandir)/man7/systemd.cron.7
rm -f $(DESTDIR)$(mandir)/man1/crontab.1
Expand Down Expand Up @@ -190,8 +196,8 @@ $(builddir)/bin/%: $(srcdir)/bin/%.py
$(call in2out,$<,$@)
chmod +x $@

$(builddir)/bin/crontab_setuid: $(srcdir)/bin/crontab_setuid.c
ifneq ($(enable_setuid),no)
$(builddir)/bin/crontab_setgid: $(srcdir)/bin/crontab_setgid.c
ifneq ($(enable_setgid),no)
$(CC) $(CFLAGS) $(LDFLAGS) $< -DCRONTAB_DIR='"$(statedir)"' -o $@
endif

Expand Down
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -28,7 +28,7 @@ Dependencies
* [run-parts][3]
* python 3
* /usr/sbin/sendmail (optional, evaluated at runtime)
* gcc or clang (needed to build `crontab_setuid.c`; this helper is optional and evaluated at runtime)
* gcc or clang (needed to build `crontab_setgid.c`; this helper is optional and evaluated at runtime)

Installation
----------------
Expand Down Expand Up @@ -93,7 +93,7 @@ Other options include:
Default: `no`.
* `--enable-persistent[=yes|no]` Make timers [persistent][5]. Requires systemd ≥ 212.
Default: `no`.
* `--enable-setuid[=yes|no]` Compile setuid C helper for crontab. Needs GCC or Clang.
* `--enable-setgid[=yes|no]` Compile setgid C helper for crontab. Needs GCC or Clang.
Default: `no`.

A typical configuration for the latest systemd would be:
Expand Down
10 changes: 5 additions & 5 deletions configure
Expand Up @@ -10,7 +10,7 @@ mandir='$(datadir)/man'
docdir='$(datadir)/doc/$(package)'
unitdir='$(libdir)/systemd/system'
runparts='/usr/bin/run-parts'
enable_setuid=no
enable_setgid=no

# systemd ≥ 197
enable_boot=yes
Expand Down Expand Up @@ -53,7 +53,7 @@ enable-quarterly::,
enable-semi_annually::,
enable-yearly::,
enable-persistent::,
enable-setuid::,
enable-setgid::,
' -- "${@}")

if [ $? -ne 0 ]; then
Expand Down Expand Up @@ -159,8 +159,8 @@ do
set_enable_flag persistent ${2}
shift 2;;

'--enable-setuid')
set_enable_flag setuid ${2}
'--enable-setgid')
set_enable_flag setgid ${2}
shift 2;;

'--')
Expand Down Expand Up @@ -221,7 +221,7 @@ sed "
s|@schedules@|${schedules}|g
s|@schedules_not@|${schedules_not}|g
s|@enable_persistent@|${enable_persistent}|g
s|@enable_setuid@|${enable_setuid}|g
s|@enable_setgid@|${enable_setgid}|g
s|@prefix@|${prefix}|g
s|@bindir@|${bindir}|g
s|@confdir@|${confdir}|g
Expand Down
45 changes: 24 additions & 21 deletions src/bin/crontab.py
Expand Up @@ -7,6 +7,7 @@
import argparse
import getpass
import pwd
import grp
import subprocess
from subprocess import Popen, PIPE
import importlib.machinery
Expand All @@ -15,13 +16,13 @@
os.environ.get('VISUAL','/usr/bin/vim'))
CRONTAB_DIR = '@statedir@'

SETUID_HELPER = '@libdir@/@package@/crontab_setuid'
SETGID_HELPER = '@libdir@/@package@/crontab_setgid'

HAS_SETUID = os.geteuid() != 0 \
and os.path.isfile(SETUID_HELPER) \
and os.stat(SETUID_HELPER).st_uid == 0 \
and os.stat(SETUID_HELPER).st_mode & stat.S_ISUID \
and os.stat(SETUID_HELPER).st_mode & stat.S_IXUSR
HAS_SETGID = os.geteuid() != 0 \
and os.path.isfile(SETGID_HELPER) \
and os.stat(SETGID_HELPER).st_uid == 0 \
and os.stat(SETGID_HELPER).st_mode & stat.S_ISGID \
and os.stat(SETGID_HELPER).st_mode & stat.S_IXGRP

REBOOT_FILE = '/run/crond.reboot'

Expand Down Expand Up @@ -94,21 +95,22 @@ def list(cron_file, args):
with open(cron_file, 'r') as f:
sys.stdout.write(f.read())
check(cron_file)
except IOError as e:
except (IOError, PermissionError) as e:
if e.errno == os.errno.ENOENT:
sys.stderr.write('no crontab for %s\n' % args.user)
exit(1)
elif args.user != getpass.getuser():
sys.stderr.write("you can not display %s's crontab\n" % args.user)
exit(1)
elif HAS_SETUID:
elif HAS_SETGID:
try:
sys.stdout.write(subprocess.check_output([SETUID_HELPER,'r'], universal_newlines=True))
sys.stdout.write(subprocess.check_output([SETGID_HELPER,'r'], universal_newlines=True))
except subprocess.CalledProcessError as f:
if f.returncode == os.errno.ENOENT:
sys.stderr.write('no crontab for %s\n' % args.user)
exit(1)
else:
sys.stderr.write('%s\n' % f.output)
raise
pass
else:
Expand All @@ -125,8 +127,8 @@ def remove(cron_file, args):
elif args.user != getpass.getuser():
sys.stderr.write("you can not delete %s's crontab\n" % args.user)
exit(1)
elif HAS_SETUID:
subprocess.check_output([SETUID_HELPER,'d'], universal_newlines=True)
elif HAS_SETGID:
subprocess.check_output([SETGID_HELPER,'d'], universal_newlines=True)
pass
elif e.errno == os.errno.EACCES:
with open(cron_file, 'w') as out:
Expand All @@ -151,14 +153,15 @@ def edit(cron_file, args):
tmp.close()
os.unlink(tmp.name)
exit(1)
elif HAS_SETUID:
elif HAS_SETGID:
try:
tmp.file.write(subprocess.check_output([SETUID_HELPER,'r'], universal_newlines=True))
tmp.file.write(subprocess.check_output([SETGID_HELPER,'r'], universal_newlines=True))
except subprocess.CalledProcessError as f:
if f.returncode == os.errno.ENOENT:
tmp.file.write('# min hour dom month dow command')
pass
else:
sys.stderr.write('%s\n' % f.output)
tmp.close()
os.unlink(tmp.name)
raise
Expand Down Expand Up @@ -191,9 +194,9 @@ def edit(cron_file, args):
tmp.close()
os.unlink(tmp.name)
try:
os.chown(cron_file, pwd.getpwnam(args.user).pw_uid, 0)
os.chown(cron_file, pwd.getpwnam(args.user).pw_uid, grp.getgrnam("crontab").gr_gid)
os.chmod(cron_file, stat.S_IRUSR | stat.S_IWUSR)
except PermissionError:
except (PermissionError, KeyError):
pass
except (IOError, PermissionError) as e:
if e.errno == os.errno.ENOSPC:
Expand All @@ -205,8 +208,8 @@ def edit(cron_file, args):
sys.stderr.write("you can not edit %s's crontab, your edit is kept here:%s\n" % (args.user, tmp.name))
tmp.close()
exit(1)
elif HAS_SETUID:
p = Popen([SETUID_HELPER,'w'], stdin=PIPE)
elif HAS_SETGID:
p = Popen([SETGID_HELPER,'w'], stdin=PIPE)
p.communicate(bytes(tmp.file.read(), 'UTF-8'))
if p.returncode: sys.stderr.write("your edit is kept here:%s\n" % tmp.name)
exit(p.returncode)
Expand Down Expand Up @@ -250,9 +253,9 @@ def replace(cron_file, args):
new.close()
os.rename(new.name, cron_file)
try:
os.chown(cron_file, pwd.getpwnam(args.user).pw_uid, 0)
os.chown(cron_file, pwd.getpwnam(args.user).pw_uid, grp.getgrnam("crontab").gr_gid)
os.chmod(cron_file, stat.S_IRUSR | stat.S_IWUSR)
except PermissionError:
except (PermissionError, KeyError):
pass
except (IOError, PermissionError) as e:
if args.user != getpass.getuser():
Expand All @@ -262,8 +265,8 @@ def replace(cron_file, args):
sys.stderr.write("no space left on %s\n" % CRONTAB_DIR)
os.unlink(new.name)
exit(1)
elif HAS_SETUID:
p = Popen([SETUID_HELPER,'w'], stdin=PIPE)
elif HAS_SETGID:
p = Popen([SETGID_HELPER,'w'], stdin=PIPE)
p.communicate(bytes(crontab, 'UTF-8'))
exit(p.returncode)
else:
Expand Down
9 changes: 6 additions & 3 deletions src/bin/crontab_setuid.c → src/bin/crontab_setgid.c
Expand Up @@ -2,6 +2,7 @@
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
Expand All @@ -11,7 +12,7 @@
#define MAX_LINES 1000

void end(char * msg){
fprintf(stderr,"crontab_setuid: %s\n", msg);
fprintf(stderr,"crontab_setgid: %s\n", msg);
exit(1);
}

Expand Down Expand Up @@ -101,9 +102,11 @@ int main(int argc, char *argv[]) {
end("maximum lines reached");
}
}
fchown(fd,getuid(),0);
fchmod(fd,0600);
if (fclose(file)) {perror("fclose"); return 1;}
struct group *grp;
grp = getgrnam("crontab");
fchown(fd,getuid(),grp->gr_gid);
fchmod(fd,0600);
if (rename(temp,crontab)) {perror("rename"); return 1;}
break;
case 'd':
Expand Down
2 changes: 1 addition & 1 deletion src/man/crontab.1.in
Expand Up @@ -56,4 +56,4 @@ The \-s flag (SELinux) is not supported.
.SH AUTHOR
Konstantin Stepanov <me@kstep.me>
.br
Alexandre Detiste <alexandre@detiste.be> for this manpage & setuid helper
Alexandre Detiste <alexandre@detiste.be> for this manpage & setgid helper

0 comments on commit 85bb2df

Please sign in to comment.