123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744 |
- #include <u.h>
- #include <libc.h>
- #include <bio.h>
- #include <libsec.h>
- #include <auth.h>
- #include "authcmdlib.h"
- char CRONLOG[] = "cron";
- enum {
- Minute = 60,
- Hour = 60 * Minute,
- Day = 24 * Hour,
- };
- typedef struct Job Job;
- typedef struct Time Time;
- typedef struct User User;
- struct Time{ /* bit masks for each valid time */
- uvlong min;
- ulong hour;
- ulong mday;
- ulong wday;
- ulong mon;
- };
- struct Job{
- char *host; /* where ... */
- Time time; /* when ... */
- char *cmd; /* and what to execute */
- Job *next;
- };
- struct User{
- Qid lastqid; /* of last read /cron/user/cron */
- char *name; /* who ... */
- Job *jobs; /* wants to execute these jobs */
- };
- User *users;
- int nuser;
- int maxuser;
- char *savec;
- char *savetok;
- int tok;
- int debug;
- ulong lexval;
- void rexec(User*, Job*);
- void readalljobs(void);
- Job *readjobs(char*, User*);
- int getname(char**);
- uvlong gettime(int, int);
- int gettok(int, int);
- void initcap(void);
- void pushtok(void);
- void usage(void);
- void freejobs(Job*);
- User *newuser(char*);
- void *emalloc(ulong);
- void *erealloc(void*, ulong);
- int myauth(int, char*);
- void createuser(void);
- int mkcmd(char*, char*, int);
- void printjobs(void);
- int qidcmp(Qid, Qid);
- int becomeuser(char*);
- ulong
- minute(ulong tm)
- {
- return tm - tm%Minute; /* round down to the minute */
- }
- int
- sleepuntil(ulong tm)
- {
- ulong now = time(0);
-
- if (now < tm)
- return sleep((tm - now)*1000);
- else
- return 0;
- }
- #pragma varargck argpos clog 1
- #pragma varargck argpos fatal 1
- static void
- clog(char *fmt, ...)
- {
- char msg[256];
- va_list arg;
- va_start(arg, fmt);
- vseprint(msg, msg + sizeof msg, fmt, arg);
- va_end(arg);
- syslog(0, CRONLOG, msg);
- }
- static void
- fatal(char *fmt, ...)
- {
- char msg[256];
- va_list arg;
- va_start(arg, fmt);
- vseprint(msg, msg + sizeof msg, fmt, arg);
- va_end(arg);
- clog("%s", msg);
- error("%s", msg);
- }
- static int
- openlock(char *file)
- {
- return create(file, ORDWR, 0600);
- }
- static int
- mklock(char *file)
- {
- int fd, try;
- Dir *dir;
- fd = openlock(file);
- if (fd >= 0) {
- /* make it a lock file if it wasn't */
- dir = dirfstat(fd);
- if (dir == nil)
- error("%s vanished: %r", file);
- dir->mode |= DMEXCL;
- dir->qid.type |= QTEXCL;
- dirfwstat(fd, dir);
- free(dir);
- /* reopen in case it wasn't a lock file at last open */
- close(fd);
- }
- for (try = 0; try < 65 && (fd = openlock(file)) < 0; try++)
- sleep(10*1000);
- return fd;
- }
- void
- main(int argc, char *argv[])
- {
- Job *j;
- Tm tm;
- Time t;
- ulong now, last; /* in seconds */
- int i, lock;
- debug = 0;
- ARGBEGIN{
- case 'c':
- createuser();
- exits(0);
- case 'd':
- debug = 1;
- break;
- default:
- usage();
- }ARGEND
- if(debug){
- readalljobs();
- printjobs();
- exits(0);
- }
- initcap(); /* do this early, before cpurc removes it */
- switch(fork()){
- case -1:
- fatal("can't fork");
- case 0:
- break;
- default:
- exits(0);
- }
- /*
- * it can take a few minutes before the file server notices that
- * we've rebooted and gives up the lock.
- */
- lock = mklock("/cron/lock");
- if (lock < 0)
- fatal("cron already running: %r");
- argv0 = "cron";
- srand(getpid()*time(0));
- last = time(0);
- for(;;){
- readalljobs();
- /*
- * the system's notion of time may have jumped forward or
- * backward an arbitrary amount since the last call to time().
- */
- now = time(0);
- /*
- * if time has jumped backward, just note it and adapt.
- * if time has jumped forward more than a day,
- * just execute one day's jobs.
- */
- if (now < last) {
- clog("time went backward");
- last = now;
- } else if (now - last > Day) {
- clog("time advanced more than a day");
- last = now - Day;
- }
- now = minute(now);
- for(last = minute(last); last <= now; last += Minute){
- tm = *localtime(last);
- t.min = 1ULL << tm.min;
- t.hour = 1 << tm.hour;
- t.wday = 1 << tm.wday;
- t.mday = 1 << tm.mday;
- t.mon = 1 << (tm.mon + 1);
- for(i = 0; i < nuser; i++)
- for(j = users[i].jobs; j; j = j->next)
- if(j->time.min & t.min
- && j->time.hour & t.hour
- && j->time.wday & t.wday
- && j->time.mday & t.mday
- && j->time.mon & t.mon)
- rexec(&users[i], j);
- }
- seek(lock, 0, 0);
- write(lock, "x", 1); /* keep the lock alive */
- /*
- * if we're not at next minute yet, sleep until a second past
- * (to allow for sleep intervals being approximate),
- * which synchronises with minute roll-over as a side-effect.
- */
- sleepuntil(now + Minute + 1);
- }
- /* not reached */
- }
- void
- createuser(void)
- {
- Dir d;
- char file[128], *user;
- int fd;
- user = getuser();
- sprint(file, "/cron/%s", user);
- fd = create(file, OREAD, 0755|DMDIR);
- if(fd < 0)
- sysfatal("couldn't create %s: %r", file);
- nulldir(&d);
- d.gid = user;
- dirfwstat(fd, &d);
- close(fd);
- sprint(file, "/cron/%s/cron", user);
- fd = create(file, OREAD, 0644);
- if(fd < 0)
- sysfatal("couldn't create %s: %r", file);
- nulldir(&d);
- d.gid = user;
- dirfwstat(fd, &d);
- close(fd);
- }
- void
- readalljobs(void)
- {
- User *u;
- Dir *d, *du;
- char file[128];
- int i, n, fd;
- fd = open("/cron", OREAD);
- if(fd < 0)
- fatal("can't open /cron\n");
- while((n = dirread(fd, &d)) > 0){
- for(i = 0; i < n; i++){
- if(strcmp(d[i].name, "log") == 0 ||
- !(d[i].qid.type & QTDIR))
- continue;
- if(strcmp(d[i].name, d[i].uid) != 0){
- syslog(1, CRONLOG, "cron for %s owned by %s",
- d[i].name, d[i].uid);
- continue;
- }
- u = newuser(d[i].name);
- sprint(file, "/cron/%s/cron", d[i].name);
- du = dirstat(file);
- if(du == nil || qidcmp(u->lastqid, du->qid) != 0){
- freejobs(u->jobs);
- u->jobs = readjobs(file, u);
- }
- free(du);
- }
- free(d);
- }
- close(fd);
- }
- /*
- * parse user's cron file
- * other lines: minute hour monthday month weekday host command
- */
- Job *
- readjobs(char *file, User *user)
- {
- Biobuf *b;
- Job *j, *jobs;
- Dir *d;
- int line;
- d = dirstat(file);
- if(!d)
- return nil;
- b = Bopen(file, OREAD);
- if(!b){
- free(d);
- return nil;
- }
- jobs = nil;
- user->lastqid = d->qid;
- free(d);
- for(line = 1; savec = Brdline(b, '\n'); line++){
- savec[Blinelen(b) - 1] = '\0';
- while(*savec == ' ' || *savec == '\t')
- savec++;
- if(*savec == '#' || *savec == '\0')
- continue;
- if(strlen(savec) > 1024){
- clog("%s: line %d: line too long", user->name, line);
- continue;
- }
- j = emalloc(sizeof *j);
- j->time.min = gettime(0, 59);
- if(j->time.min && (j->time.hour = gettime(0, 23))
- && (j->time.mday = gettime(1, 31))
- && (j->time.mon = gettime(1, 12))
- && (j->time.wday = gettime(0, 6))
- && getname(&j->host)){
- j->cmd = emalloc(strlen(savec) + 1);
- strcpy(j->cmd, savec);
- j->next = jobs;
- jobs = j;
- }else{
- clog("%s: line %d: syntax error", user->name, line);
- free(j);
- }
- }
- Bterm(b);
- return jobs;
- }
- void
- printjobs(void)
- {
- char buf[8*1024];
- Job *j;
- int i;
- for(i = 0; i < nuser; i++){
- print("user %s\n", users[i].name);
- for(j = users[i].jobs; j; j = j->next)
- if(!mkcmd(j->cmd, buf, sizeof buf))
- print("\tbad job %s on host %s\n",
- j->cmd, j->host);
- else
- print("\tjob %s on host %s\n", buf, j->host);
- }
- }
- User *
- newuser(char *name)
- {
- int i;
- for(i = 0; i < nuser; i++)
- if(strcmp(users[i].name, name) == 0)
- return &users[i];
- if(nuser == maxuser){
- maxuser += 32;
- users = erealloc(users, maxuser * sizeof *users);
- }
- memset(&users[nuser], 0, sizeof(users[nuser]));
- users[nuser].name = strdup(name);
- users[nuser].jobs = 0;
- users[nuser].lastqid.type = QTFILE;
- users[nuser].lastqid.path = ~0LL;
- users[nuser].lastqid.vers = ~0L;
- return &users[nuser++];
- }
- void
- freejobs(Job *j)
- {
- Job *next;
- for(; j; j = next){
- next = j->next;
- free(j->cmd);
- free(j->host);
- free(j);
- }
- }
- int
- getname(char **namep)
- {
- int c;
- char buf[64], *p;
- if(!savec)
- return 0;
- while(*savec == ' ' || *savec == '\t')
- savec++;
- for(p = buf; (c = *savec) && c != ' ' && c != '\t'; p++){
- if(p >= buf+sizeof buf -1)
- return 0;
- *p = *savec++;
- }
- *p = '\0';
- *namep = strdup(buf);
- if(*namep == 0){
- clog("internal error: strdup failure");
- _exits(0);
- }
- while(*savec == ' ' || *savec == '\t')
- savec++;
- return p > buf;
- }
- /*
- * return the next time range (as a bit vector) in the file:
- * times: '*'
- * | range
- * range: number
- * | number '-' number
- * | range ',' range
- * a return of zero means a syntax error was discovered
- */
- uvlong
- gettime(int min, int max)
- {
- uvlong n, m, e;
- if(gettok(min, max) == '*')
- return ~0ULL;
- n = 0;
- while(tok == '1'){
- m = 1ULL << lexval;
- n |= m;
- if(gettok(0, 0) == '-'){
- if(gettok(lexval, max) != '1')
- return 0;
- e = 1ULL << lexval;
- for( ; m <= e; m <<= 1)
- n |= m;
- gettok(min, max);
- }
- if(tok != ',')
- break;
- if(gettok(min, max) != '1')
- return 0;
- }
- pushtok();
- return n;
- }
- void
- pushtok(void)
- {
- savec = savetok;
- }
- int
- gettok(int min, int max)
- {
- char c;
- savetok = savec;
- if(!savec)
- return tok = 0;
- while((c = *savec) == ' ' || c == '\t')
- savec++;
- switch(c){
- case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- lexval = strtoul(savec, &savec, 10);
- if(lexval < min || lexval > max)
- return tok = 0;
- return tok = '1';
- case '*': case '-': case ',':
- savec++;
- return tok = c;
- default:
- return tok = 0;
- }
- }
- int
- call(char *host)
- {
- char *na, *p;
- na = netmkaddr(host, 0, "rexexec");
- p = utfrune(na, L'!');
- if(!p)
- return -1;
- p = utfrune(p+1, L'!');
- if(!p)
- return -1;
- if(strcmp(p, "!rexexec") != 0)
- return -2;
- return dial(na, 0, 0, 0);
- }
- /*
- * convert command to run properly on the remote machine
- * need to escape the quotes so they don't get stripped
- */
- int
- mkcmd(char *cmd, char *buf, int len)
- {
- char *p;
- int n, m;
- n = sizeof "exec rc -c '" -1;
- if(n >= len)
- return 0;
- strcpy(buf, "exec rc -c '");
- while(p = utfrune(cmd, L'\'')){
- p++;
- m = p - cmd;
- if(n + m + 1 >= len)
- return 0;
- strncpy(&buf[n], cmd, m);
- n += m;
- buf[n++] = '\'';
- cmd = p;
- }
- m = strlen(cmd);
- if(n + m + sizeof "'</dev/null>/dev/null>[2=1]" >= len)
- return 0;
- strcpy(&buf[n], cmd);
- strcpy(&buf[n+m], "'</dev/null>/dev/null>[2=1]");
- return 1;
- }
- void
- rexec(User *user, Job *j)
- {
- char buf[8*1024];
- int n, fd;
- AuthInfo *ai;
- switch(rfork(RFPROC|RFNOWAIT|RFNAMEG|RFENVG|RFFDG)){
- case 0:
- break;
- case -1:
- clog("can't fork a job for %s: %r\n", user->name);
- default:
- return;
- }
- if(!mkcmd(j->cmd, buf, sizeof buf)){
- clog("internal error: cmd buffer overflow");
- _exits(0);
- }
- /*
- * local call, auth, cmd with no i/o
- */
- if(strcmp(j->host, "local") == 0){
- if(becomeuser(user->name) < 0){
- clog("%s: can't change uid for %s on %s: %r",
- user->name, j->cmd, j->host);
- _exits(0);
- }
- putenv("service", "rx");
- clog("%s: ran '%s' on %s", user->name, j->cmd, j->host);
- execl("/bin/rc", "rc", "-lc", buf, nil);
- clog("%s: exec failed for %s on %s: %r",
- user->name, j->cmd, j->host);
- _exits(0);
- }
- /*
- * remote call, auth, cmd with no i/o
- * give it 2 min to complete
- */
- alarm(2*Minute*1000);
- fd = call(j->host);
- if(fd < 0){
- if(fd == -2)
- clog("%s: dangerous host %s", user->name, j->host);
- clog("%s: can't call %s: %r", user->name, j->host);
- _exits(0);
- }
- clog("%s: called %s on %s", user->name, j->cmd, j->host);
- if(becomeuser(user->name) < 0){
- clog("%s: can't change uid for %s on %s: %r",
- user->name, j->cmd, j->host);
- _exits(0);
- }
- ai = auth_proxy(fd, nil, "proto=p9any role=client");
- if(ai == nil){
- clog("%s: can't authenticate for %s on %s: %r",
- user->name, j->cmd, j->host);
- _exits(0);
- }
- clog("%s: authenticated %s on %s", user->name, j->cmd, j->host);
- write(fd, buf, strlen(buf)+1);
- write(fd, buf, 0);
- while((n = read(fd, buf, sizeof(buf)-1)) > 0){
- buf[n] = 0;
- clog("%s: %s\n", j->cmd, buf);
- }
- _exits(0);
- }
- void *
- emalloc(ulong n)
- {
- void *p;
- if(p = mallocz(n, 1))
- return p;
- fatal("out of memory");
- return 0;
- }
- void *
- erealloc(void *p, ulong n)
- {
- if(p = realloc(p, n))
- return p;
- fatal("out of memory");
- return 0;
- }
- void
- usage(void)
- {
- fprint(2, "usage: cron [-c]\n");
- exits("usage");
- }
- int
- qidcmp(Qid a, Qid b)
- {
- /* might be useful to know if a > b, but not for cron */
- return(a.path != b.path || a.vers != b.vers);
- }
- void
- memrandom(void *p, int n)
- {
- uchar *cp;
- for(cp = (uchar*)p; n > 0; n--)
- *cp++ = fastrand();
- }
- /*
- * keep caphash fd open since opens of it could be disabled
- */
- static int caphashfd;
- void
- initcap(void)
- {
- caphashfd = open("#¤/caphash", OCEXEC|OWRITE);
- if(caphashfd < 0)
- fprint(2, "%s: opening #¤/caphash: %r\n", argv0);
- }
- /*
- * create a change uid capability
- */
- char*
- mkcap(char *from, char *to)
- {
- uchar rand[20];
- char *cap;
- char *key;
- int nfrom, nto;
- uchar hash[SHA1dlen];
- if(caphashfd < 0)
- return nil;
- /* create the capability */
- nto = strlen(to);
- nfrom = strlen(from);
- cap = emalloc(nfrom+1+nto+1+sizeof(rand)*3+1);
- sprint(cap, "%s@%s", from, to);
- memrandom(rand, sizeof(rand));
- key = cap+nfrom+1+nto+1;
- enc64(key, sizeof(rand)*3, rand, sizeof(rand));
- /* hash the capability */
- hmac_sha1((uchar*)cap, strlen(cap), (uchar*)key, strlen(key), hash, nil);
- /* give the kernel the hash */
- key[-1] = '@';
- if(write(caphashfd, hash, SHA1dlen) < 0){
- free(cap);
- return nil;
- }
- return cap;
- }
- int
- usecap(char *cap)
- {
- int fd, rv;
- fd = open("#¤/capuse", OWRITE);
- if(fd < 0)
- return -1;
- rv = write(fd, cap, strlen(cap));
- close(fd);
- return rv;
- }
- int
- becomeuser(char *new)
- {
- char *cap;
- int rv;
- cap = mkcap(getuser(), new);
- if(cap == nil)
- return -1;
- rv = usecap(cap);
- free(cap);
- newns(new, nil);
- return rv;
- }
|