Hallo David, hallo Leute, Am Montag, 11. Juli 2005 00:22 schrieb David Haller:
Am Sun, 10 Jul 2005, Christian Boltz schrieb:
Am Sonntag, 10. Juli 2005 03:29 schrieb David Haller:
Am Sun, 10 Jul 2005, Christian Boltz schrieb: [...] Bzw. mit dem Test mit cron.allow:
==== PASSWD='/etc/passwd' ALLOW='/etc/cron.allow' # [..] if test -f "$ALLOW" && test -r "$ALLOW"; then
Useless use of second test ;-) test -f "$ALLOW" -a -r "$ALLOW"
listusers='cat "$ALLOW"' else # listusers='cat "$PASSWD"' listusers='getent passwd' fi
BTW: Ich bin Experte im Vermeiden von if-Blöcken in Bash-Scripten ;-) listusers='getent passwd' test -f -a -r "$ALLOW" && listusers='cat "$ALLOW"'
$listusers | cut -d ':' -f 1 | while read -r user; do
Der "cut ..." gehört wohl noch zur Variable $listusers - /var/spool/cron/allow enthält ja _nur_ Usernamen. (nach einem kurzen Test) Soviel zur Theorie. In der Praxis bekomme ich eine "nette" Fehlermeldung - dann also doch hinter den $listusers-Aufruf :-/
====
Schöne Lösung. Darf ich die übernehmen?
Was meinst du, warum ich die geschrieben habe? Klar darfst du!
Eine Nachfrage kann nie schaden - weiß ich, ob Deine Mail unter GPL steht? ;-)
# deny cronjobs for users in /etc/cron.deny grep "^[ ]*$user[ ]*$" /etc/cron.deny &>/dev/null && continue # ^^^ und ^^^ = Space Tab
grep -q "^[[:space:]]*$user[[:space:]]*$" /etc/cron.deny && continue
Ist zumindest fürs Zitieren in Mails besser ;-)
Jep :) Achso: das ein grep die Zeichenklassen kennt ist zwar POSIX und GNU (und BSD AFAIK) aber sonst (v.a. auf kommerziellen Unices) gibt's haeufiger noch greps die das nicht koennen. Das nur nebenbei, aber wir haben uns ja wohl eh schon auf bash und GNU grep und so festgelegt ;)
Eben.
# get homedir for $user homedir="`grep \"^$user:\" /etc/passwd | cut -d: -f6`";
Auch hier kann man 'getent' verwenden. "Einfacher" (naja, eher "effektiver" ;) ist aber:
homedir="`awk -F ':' -v u=\"$user\" '$1 == u { print $6; }' /etc/passwd`";
/etc/passwort schließt aber nicht-/etc/passwd-User (z. B. LDAP, ...) aus, und das haben wir ja oben mit getent vermieden. Deshalb:
bzw.:
homedir="`getent passwd | awk -F ':' -v u=\"$user\" '$1 == u { print $6; }'`";
... ist das wohl geschickter. Hmm, ob awk so viel schneller ist als grep | cut? Mal sehen... # time for i in `seq 1 1000` ; do echo "a:b:c d:e:f" | grep "^d:" | cut -d':' -f1; done >/dev/null real 0m2.879s user 0m1.087s sys 0m1.761s # time for i in `seq 1 1000` ; do echo "a:b:c d:e:f" | awk -F ':' '$1 == "d" { print $1; }' ; done >/dev/null real 0m1.674s user 0m0.609s sys 0m1.034s Tatsächlich - auch wenn der Unterschied in der Praxis mangels entsprechender Userzahl eher irrelevant sein dürfte ;-) Das Cachen von `getent passwd` in einer Variable müsste auch etwas bringen - echo "$cache" dürfte deutlich schneller sein als getent ;-) # time { for i in `seq 1 1000` ; do getent passwd ; done >/dev/null ; } real 0m1.474s user 0m0.612s sys 0m0.783s # time { cache=`getent passwd` ; for i in `seq 1 1000` ; do echo "$cache" ; done >/dev/null ; } real 0m0.193s user 0m0.151s sys 0m0.003s Bei NIS/LDAP/sonstwas-Usern dürfte der Unterschied nochmal deutlicher sein, also: Rein mit dem Cache ;-)
Achso: da das script unter cron laufen soll, sollte wohl noch
a) PATH zum script-Anfang ergaenzen / explizit setzen, oder b) alle Programme explizit mit Pfad aufrufen.
Eher a) ;-)
*g*
Da ich /bin und /usr/bin sogar im cron-Pfad vermute, habe ich erstmal auf ein explizites Setzen von $PATH verzichtet. Und eins meiner Scripte in ~/.cron.daily müsste mir demnächst `env` zwecks Kontrolle zumailen ;-)
[..]
test -x "$homedir/$dirname/$file" && {
Ich verwende, wenn ich Variablen zusammenfuege meistens ${} um auf der sicheren Seite zu sein, also 'test -x "${homedir}/${dirname}/${file}"' Sollte aber natuerlich auch so funktionieren ;)
Durch den / als einziges Zwischenzeichen habe ich keine Bedenken. Ansonsten ist Dein Einwand korrekt und bei mir auch nicht unüblich.
Ich habe mir eben angewoehnt *grundsaetzlich* so zu scripten; so wie ich auch Variablen immer quote,
Ich bin eben manchmal etwas tippfaul - aber nur, wenn ich genau weiß, was ich tue. [...]
Hat eben den Vorteil, dass ich nur dann nachdenken muss, wenn's mal nicht mit ${} oder '' oder "" klappt.
;-)
Achtung: Obiges Script habe ich eben in Rekordzeit getippt - es ist also nur minimal getestet. Die Behandlung von cron.deny und cron.allow [1] habe ich eingebaut, aber nicht getestet.
Sieht ok aus, wenn in den beiden Datein jeweils einfach eine (zeilenweise) Liste von Usernamen steht.
Hatte ich auch vermutet, aber mangels schriftlichem Beweis...
Hm. Laut man 1 crontab sind das /var/spool/cron/{allow,deny} und ich habe eine /var/cron/deny in meiner SuSE 6.2 und eine /var/spool/cron/deny in der 9.1.
==== SUSE 9.1: man 1p crontab ==== Users shall be permitted to use crontab if their names appear in the file /usr/lib/cron/cron.allow. If that file does not exist, the file /usr/lib/cron/cron.deny shall be checked to determine whether the user shall be denied access to crontab.
Das habe ich ja bereits so implementiert.
If neither file exists, only a process with appropriate privileges shall be allowed to submit a job.
Schöne Spezialität - naja, der Vollständigkeit halber baue ich das auch noch ein.
If only cron.deny exists and is empty, global usage shall be permitted.
Das regelt bereits grep.
The cron.allow and cron.deny files shall consist of one user name per line.
Prima, das Format ist also auch klar.
====
# rpm -qf `man -w 1p crontab` man-pages-1.66-38 # man -k crontab [gekuerzt] crontab (1p) - schedule periodic background work crontab (1) - maintain crontab files for individual users (V3)
Sowas fieses. man 1 crontab hatte ich ja schon gefunden, aber die 1p ist mir entwischt. crontab(1) und crontab(5) enthalten auch keinen Hinweis darauf. Bei mir (SuSE 9.3) heißen die Dateien übrigens /var/spool/cron/deny (bzw. .../allow) - zumindest deny habe ich "erfolglos" getestet.
Eine noch offene Baustelle ist der Mailversand im Fehlerfall - die Mail geht nämlich grundsätzlich an root, nicht an den jeweiligen User. Falls jemand eine gute Idee hat, wie man dieses Problem elegant löst - immer her damit ;-) [2]
Einfachst Variante, nur mit stderr als Mail:
su "$user" -c "$homedir/$dirname/$file" 2>&1 >/dev/null | mail "$user"
Man achte auf die Reihenfolge von '2>&1' und '>/dev/null'...
Das Problem dabei: Wenn es keine Ausgabe gibt, erhält der User eine leere Mail - das will ich eigentlich vermeiden.
Oder willst du evtl. Fehlermeldungen des scripts selber auch dem User zukommen lassen?
Nö, das ist wieder eine Sache für root ;-)
Achso, dann schreib selber logs und maile dem User ggfs. nur eine Meldung:
==== #!/bin/bash
exec 1>/var/log/usercron.log exec 2>/var/log/usercron.err
Damit hätte ich die Ausgabe für alle User in einem gemeinsamen Log :-(
errexit() { err="$1"; shift; user="$1"; shift;
if test -z "$err" || test -z "$user"; then echo "Invalid params for 'errexit()'" >&2 exit 22 ## EINVAL fi
getent passwd "$user" >/dev/null || { echo "Unknown user for 'errexit()'" >&2 exit 2 ## das gibt auch getent zurueck }
Zumindest dieser Test dürfte eher überflüssig sein - außer durch einen flaschen Eintrag in cron.allow haben wir ja - siehe getent - garantiert gültige Usernamen. Auch wenn sich mal ein ungültiger einschleichen sollte, wäre das beim Mailversand nicht so schlimm - es geht eben ein Bounce an root - vielleicht merkt er dadurch ja den Fehler in cron.allow. Moment - nicht existente User bzw. leeres homedir sollte ich nach homedir=... abfangen, ansonsten wird nach /.cron.daily gesucht... [...]
Das nur so als Anregung ;)
Danke - aber ich hatte eher an ein paar Backticks rund um die "ls"-Schleife gedacht (gefolgt von test -n und so). Das würde dafür sorgen, dass jeder User die für ihn gedachten Meldungen sieht. Heute nicht mehr. So, hier noch der aktuelle Stand des Scripts: ----------------------------------------------------------------------- #!/bin/bash # usercron.daily # # Copyright (C)2005 by Christian Boltz - www.cboltz.de # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # check man 1p crontab for the correct ALLOW / DENY filenames ALLOW='/var/spool/cron/allow' DENY='/var/spool/cron/deny' PASSWD='/etc/passwd' me="${0//.*\/}" case "$me" in usercron.monthly) dirname=.cron.monthly ;; usercron.weekly) dirname=.cron.weekly ;; usercron.daily) dirname=.cron.daily ;; usercron.hourly) dirname=.cron.hourly ;; *) echo "unknown basename. Please name this script (or its symlink)" echo "usercron.monthly, usercron.weekly, usercron.daily or" echo "usercron.hourly." exit 1 esac # get list of users listusers='getent passwd' # default command # if /etc/cron.allow exists, allow only users in this file test -f "$ALLOW" -a -r "$ALLOW" && listusers='cat "$ALLOW"' # see man 1p crontab for the following behaviour again ;-) test -f "$ALLOW" -o -f "$DENY" && { logger -t "$me" "neither $ALLOW or $DENY exist - exiting" exit 0 } usercache="`getent passwd`" $listusers | cut -d ':' -f 1 | while read -r user ; do # deny cronjobs for users in /etc/cron.deny grep "^[[:space:]]*$user[[:space:]]*$" "$DENY" &>/dev/null && continue # get homedir for $user homedir="`echo \"$usercache\" | awk -F ':' -v u=\"$user\" '$1 == u { print $6; }'`"; test -n "$homedir" || { # no homedir? bad. logger -t "$me" "ERROR: user $user has no homedir" continue } # test for .cron.* in homedir test -d "$homedir/$dirname" || continue # search and execute scripts in ~/cron.* ls -1 "$homedir/$dirname/" | while read file ; do test -x "$homedir/$dirname/$file" && { # log and execute logger -t "$me" "($user) CMD ($homedir/$dirname/$file)" su "$user" -c "$homedir/$dirname/$file" } done done ----------------------------------------------------------------------- Demnächst[tm] auch zu finden auf www.cboltz.de ;-) Gruß Christian Boltz --
Aber sorry, habe die Schnauze voll mit Linux.... Da gehört's eindeutig nicht hin. Nimm's lieber wieder raus. [> Juergen Jaeckel und Bernd Glueckert in suse-linux]