Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
#include <u.h>
2
#include <libc.h>
3
#include <bio.h>
4
#include <libsec.h>
5
#include <auth.h>
6
#include "authcmdlib.h"
7
 
8
char CRONLOG[] = "cron";
9
 
10
enum {
11
	Minute = 60,
12
	Hour = 60 * Minute,
13
	Day = 24 * Hour,
14
};
15
 
16
typedef struct Job	Job;
17
typedef struct Time	Time;
18
typedef struct User	User;
19
 
20
struct Time{			/* bit masks for each valid time */
21
	uvlong	min;
22
	ulong	hour;
23
	ulong	mday;
24
	ulong	wday;
25
	ulong	mon;
26
};
27
 
28
struct Job{
29
	char	*host;		/* where ... */
30
	Time	time;			/* when ... */
31
	char	*cmd;			/* and what to execute */
32
	Job	*next;
33
};
34
 
35
struct User{
36
	Qid	lastqid;			/* of last read /cron/user/cron */
37
	char	*name;			/* who ... */
38
	Job	*jobs;			/* wants to execute these jobs */
39
};
40
 
41
User	*users;
42
int	nuser;
43
int	maxuser;
44
char	*savec;
45
char	*savetok;
46
int	tok;
47
int	debug;
48
ulong	lexval;
49
 
50
void	rexec(User*, Job*);
51
void	readalljobs(void);
52
Job	*readjobs(char*, User*);
53
int	getname(char**);
54
uvlong	gettime(int, int);
55
int	gettok(int, int);
56
void	initcap(void);
57
void	pushtok(void);
58
void	usage(void);
59
void	freejobs(Job*);
60
User	*newuser(char*);
61
void	*emalloc(ulong);
62
void	*erealloc(void*, ulong);
63
int	myauth(int, char*);
64
void	createuser(void);
65
int	mkcmd(char*, char*, int);
66
void	printjobs(void);
67
int	qidcmp(Qid, Qid);
68
int	becomeuser(char*);
69
 
70
ulong
71
minute(ulong tm)
72
{
73
	return tm - tm%Minute;		/* round down to the minute */
74
}
75
 
76
int
77
sleepuntil(ulong tm)
78
{
79
	ulong now = time(0);
80
 
81
	if (now < tm)
82
		return sleep((tm - now)*1000);
83
	else
84
		return 0;
85
}
86
 
87
#pragma varargck	argpos clog 1
88
#pragma varargck	argpos fatal 1
89
 
90
static void
91
clog(char *fmt, ...)
92
{
93
	char msg[256];
94
	va_list arg;
95
 
96
	va_start(arg, fmt);
97
	vseprint(msg, msg + sizeof msg, fmt, arg);
98
	va_end(arg);
99
	syslog(0, CRONLOG, msg);
100
}
101
 
102
static void
103
fatal(char *fmt, ...)
104
{
105
	char msg[256];
106
	va_list arg;
107
 
108
	va_start(arg, fmt);
109
	vseprint(msg, msg + sizeof msg, fmt, arg);
110
	va_end(arg);
111
	clog("%s", msg);
112
	error("%s", msg);
113
}
114
 
115
static int
116
openlock(char *file)
117
{
118
	return create(file, ORDWR, 0600);
119
}
120
 
121
static int
122
mklock(char *file)
123
{
124
	int fd, try;
125
	Dir *dir;
126
 
127
	fd = openlock(file);
128
	if (fd >= 0) {
129
		/* make it a lock file if it wasn't */
130
		dir = dirfstat(fd);
131
		if (dir == nil)
132
			error("%s vanished: %r", file);
133
		dir->mode |= DMEXCL;
134
		dir->qid.type |= QTEXCL;
135
		dirfwstat(fd, dir);
136
		free(dir);
137
 
138
		/* reopen in case it wasn't a lock file at last open */
139
		close(fd);
140
	}
141
	for (try = 0; try < 65 && (fd = openlock(file)) < 0; try++)
142
		sleep(10*1000);
143
	return fd;
144
}
145
 
146
void
147
main(int argc, char *argv[])
148
{
149
	Job *j;
150
	Tm tm;
151
	Time t;
152
	ulong now, last;		/* in seconds */
153
	int i, lock;
154
 
155
	debug = 0;
156
	ARGBEGIN{
157
	case 'c':
158
		createuser();
159
		exits(0);
160
	case 'd':
161
		debug = 1;
162
		break;
163
	default:
164
		usage();
165
	}ARGEND
166
 
167
	if(debug){
168
		readalljobs();
169
		printjobs();
170
		exits(0);
171
	}
172
 
173
	initcap();		/* do this early, before cpurc removes it */
174
 
175
	switch(fork()){
176
	case -1:
177
		fatal("can't fork: %r");
178
	case 0:
179
		break;
180
	default:
181
		exits(0);
182
	}
183
 
184
	/*
185
	 * it can take a few minutes before the file server notices that
186
	 * we've rebooted and gives up the lock.
187
	 */
188
	lock = mklock("/cron/lock");
189
	if (lock < 0)
190
		fatal("cron already running: %r");
191
 
192
	argv0 = "cron";
193
	srand(getpid()*time(0));
194
	last = time(0);
195
	for(;;){
196
		readalljobs();
197
		/*
198
		 * the system's notion of time may have jumped forward or
199
		 * backward an arbitrary amount since the last call to time().
200
		 */
201
		now = time(0);
202
		/*
203
		 * if time has jumped backward, just note it and adapt.
204
		 * if time has jumped forward more than a day,
205
		 * just execute one day's jobs.
206
		 */
207
		if (now < last) {
208
			clog("time went backward");
209
			last = now;
210
		} else if (now - last > Day) {
211
			clog("time advanced more than a day");
212
			last = now - Day;
213
		}
214
		now = minute(now);
215
		for(last = minute(last); last <= now; last += Minute){
216
			tm = *localtime(last);
217
			t.min = 1ULL << tm.min;
218
			t.hour = 1 << tm.hour;
219
			t.wday = 1 << tm.wday;
220
			t.mday = 1 << tm.mday;
221
			t.mon =  1 << (tm.mon + 1);
222
			for(i = 0; i < nuser; i++)
223
				for(j = users[i].jobs; j; j = j->next)
224
					if(j->time.min & t.min
225
					&& j->time.hour & t.hour
226
					&& j->time.wday & t.wday
227
					&& j->time.mday & t.mday
228
					&& j->time.mon & t.mon)
229
						rexec(&users[i], j);
230
		}
231
		seek(lock, 0, 0);
232
		write(lock, "x", 1);	/* keep the lock alive */
233
		/*
234
		 * if we're not at next minute yet, sleep until a second past
235
		 * (to allow for sleep intervals being approximate),
236
		 * which synchronises with minute roll-over as a side-effect.
237
		 */
238
		sleepuntil(now + Minute + 1);
239
	}
240
	/* not reached */
241
}
242
 
243
void
244
createuser(void)
245
{
246
	Dir d;
247
	char file[128], *user;
248
	int fd;
249
 
250
	user = getuser();
251
	snprint(file, sizeof file, "/cron/%s", user);
252
	fd = create(file, OREAD, 0755|DMDIR);
253
	if(fd < 0)
254
		fatal("couldn't create %s: %r", file);
255
	nulldir(&d);
256
	d.gid = user;
257
	dirfwstat(fd, &d);
258
	close(fd);
259
	snprint(file, sizeof file, "/cron/%s/cron", user);
260
	fd = create(file, OREAD, 0644);
261
	if(fd < 0)
262
		fatal("couldn't create %s: %r", file);
263
	nulldir(&d);
264
	d.gid = user;
265
	dirfwstat(fd, &d);
266
	close(fd);
267
}
268
 
269
void
270
readalljobs(void)
271
{
272
	User *u;
273
	Dir *d, *du;
274
	char file[128];
275
	int i, n, fd;
276
 
277
	fd = open("/cron", OREAD);
278
	if(fd < 0)
279
		fatal("can't open /cron: %r");
280
	while((n = dirread(fd, &d)) > 0){
281
		for(i = 0; i < n; i++){
282
			if(strcmp(d[i].name, "log") == 0 ||
283
			    !(d[i].qid.type & QTDIR))
284
				continue;
285
			if(strcmp(d[i].name, d[i].uid) != 0){
286
				syslog(1, CRONLOG, "cron for %s owned by %s",
287
					d[i].name, d[i].uid);
288
				continue;
289
			}
290
			u = newuser(d[i].name);
291
			snprint(file, sizeof file, "/cron/%s/cron", d[i].name);
292
			du = dirstat(file);
293
			if(du == nil || qidcmp(u->lastqid, du->qid) != 0){
294
				freejobs(u->jobs);
295
				u->jobs = readjobs(file, u);
296
			}
297
			free(du);
298
		}
299
		free(d);
300
	}
301
	close(fd);
302
}
303
 
304
/*
305
 * parse user's cron file
306
 * other lines: minute hour monthday month weekday host command
307
 */
308
Job *
309
readjobs(char *file, User *user)
310
{
311
	Biobuf *b;
312
	Job *j, *jobs;
313
	Dir *d;
314
	int line;
315
 
316
	d = dirstat(file);
317
	if(!d)
318
		return nil;
319
	b = Bopen(file, OREAD);
320
	if(!b){
321
		free(d);
322
		return nil;
323
	}
324
	jobs = nil;
325
	user->lastqid = d->qid;
326
	free(d);
327
	for(line = 1; savec = Brdline(b, '\n'); line++){
328
		savec[Blinelen(b) - 1] = '\0';
329
		while(*savec == ' ' || *savec == '\t')
330
			savec++;
331
		if(*savec == '#' || *savec == '\0')
332
			continue;
333
		if(strlen(savec) > 1024){
334
			clog("%s: line %d: line too long", user->name, line);
335
			continue;
336
		}
337
		j = emalloc(sizeof *j);
338
		j->time.min = gettime(0, 59);
339
		if(j->time.min && (j->time.hour = gettime(0, 23))
340
		&& (j->time.mday = gettime(1, 31))
341
		&& (j->time.mon = gettime(1, 12))
342
		&& (j->time.wday = gettime(0, 6))
343
		&& getname(&j->host)){
344
			j->cmd = emalloc(strlen(savec) + 1);
345
			strcpy(j->cmd, savec);
346
			j->next = jobs;
347
			jobs = j;
348
		}else{
349
			clog("%s: line %d: syntax error", user->name, line);
350
			free(j);
351
		}
352
	}
353
	Bterm(b);
354
	return jobs;
355
}
356
 
357
void
358
printjobs(void)
359
{
360
	char buf[8*1024];
361
	Job *j;
362
	int i;
363
 
364
	for(i = 0; i < nuser; i++){
365
		print("user %s\n", users[i].name);
366
		for(j = users[i].jobs; j; j = j->next)
367
			if(!mkcmd(j->cmd, buf, sizeof buf))
368
				print("\tbad job %s on host %s\n",
369
					j->cmd, j->host);
370
			else
371
				print("\tjob %s on host %s\n", buf, j->host);
372
	}
373
}
374
 
375
User *
376
newuser(char *name)
377
{
378
	int i;
379
 
380
	for(i = 0; i < nuser; i++)
381
		if(strcmp(users[i].name, name) == 0)
382
			return &users[i];
383
	if(nuser == maxuser){
384
		maxuser += 32;
385
		users = erealloc(users, maxuser * sizeof *users);
386
	}
387
	memset(&users[nuser], 0, sizeof(users[nuser]));
388
	users[nuser].name = strdup(name);
389
	users[nuser].jobs = 0;
390
	users[nuser].lastqid.type = QTFILE;
391
	users[nuser].lastqid.path = ~0LL;
392
	users[nuser].lastqid.vers = ~0L;
393
	return &users[nuser++];
394
}
395
 
396
void
397
freejobs(Job *j)
398
{
399
	Job *next;
400
 
401
	for(; j; j = next){
402
		next = j->next;
403
		free(j->cmd);
404
		free(j->host);
405
		free(j);
406
	}
407
}
408
 
409
int
410
getname(char **namep)
411
{
412
	int c;
413
	char buf[64], *p;
414
 
415
	if(!savec)
416
		return 0;
417
	while(*savec == ' ' || *savec == '\t')
418
		savec++;
419
	for(p = buf; (c = *savec) && c != ' ' && c != '\t'; p++){
420
		if(p >= buf+sizeof buf -1)
421
			return 0;
422
		*p = *savec++;
423
	}
424
	*p = '\0';
425
	*namep = strdup(buf);
426
	if(*namep == 0){
427
		clog("internal error: strdup failure");
428
		_exits(0);
429
	}
430
	while(*savec == ' ' || *savec == '\t')
431
		savec++;
432
	return p > buf;
433
}
434
 
435
/*
436
 * return the next time range (as a bit vector) in the file:
437
 * times: '*'
438
 * 	| range
439
 * range: number
440
 *	| number '-' number
441
 *	| range ',' range
442
 * a return of zero means a syntax error was discovered
443
 */
444
uvlong
445
gettime(int min, int max)
446
{
447
	uvlong n, m, e;
448
 
449
	if(gettok(min, max) == '*')
450
		return ~0ULL;
451
	n = 0;
452
	while(tok == '1'){
453
		m = 1ULL << lexval;
454
		n |= m;
455
		if(gettok(0, 0) == '-'){
456
			if(gettok(lexval, max) != '1')
457
				return 0;
458
			e = 1ULL << lexval;
459
			for( ; m <= e; m <<= 1)
460
				n |= m;
461
			gettok(min, max);
462
		}
463
		if(tok != ',')
464
			break;
465
		if(gettok(min, max) != '1')
466
			return 0;
467
	}
468
	pushtok();
469
	return n;
470
}
471
 
472
void
473
pushtok(void)
474
{
475
	savec = savetok;
476
}
477
 
478
int
479
gettok(int min, int max)
480
{
481
	char c;
482
 
483
	savetok = savec;
484
	if(!savec)
485
		return tok = 0;
486
	while((c = *savec) == ' ' || c == '\t')
487
		savec++;
488
	switch(c){
489
	case '0': case '1': case '2': case '3': case '4':
490
	case '5': case '6': case '7': case '8': case '9':
491
		lexval = strtoul(savec, &savec, 10);
492
		if(lexval < min || lexval > max)
493
			return tok = 0;
494
		return tok = '1';
495
	case '*': case '-': case ',':
496
		savec++;
497
		return tok = c;
498
	default:
499
		return tok = 0;
500
	}
501
}
502
 
503
int
504
call(char *host)
505
{
506
	char *na, *p;
507
 
508
	na = netmkaddr(host, 0, "rexexec");
509
	p = utfrune(na, L'!');
510
	if(!p)
511
		return -1;
512
	p = utfrune(p+1, L'!');
513
	if(!p)
514
		return -1;
515
	if(strcmp(p, "!rexexec") != 0)
516
		return -2;
517
	return dial(na, 0, 0, 0);
518
}
519
 
520
/*
521
 * convert command to run properly on the remote machine
522
 * need to escape the quotes so they don't get stripped
523
 */
524
int
525
mkcmd(char *cmd, char *buf, int len)
526
{
527
	char *p;
528
	int n, m;
529
 
530
	n = sizeof "exec rc -c '" -1;
531
	if(n >= len)
532
		return 0;
533
	strcpy(buf, "exec rc -c '");
534
	while(p = utfrune(cmd, L'\'')){
535
		p++;
536
		m = p - cmd;
537
		if(n + m + 1 >= len)
538
			return 0;
539
		strncpy(&buf[n], cmd, m);
540
		n += m;
541
		buf[n++] = '\'';
542
		cmd = p;
543
	}
544
	m = strlen(cmd);
545
	if(n + m + sizeof "'</dev/null>/dev/null>[2=1]" >= len)
546
		return 0;
547
	strcpy(&buf[n], cmd);
548
	strcpy(&buf[n+m], "'</dev/null>/dev/null>[2=1]");
549
	return 1;
550
}
551
 
552
void
553
rexec(User *user, Job *j)
554
{
555
	char buf[8*1024];
556
	int n, fd;
557
	AuthInfo *ai;
558
 
559
	switch(rfork(RFPROC|RFNOWAIT|RFNAMEG|RFENVG|RFFDG)){
560
	case 0:
561
		break;
562
	case -1:
563
		clog("can't fork a job for %s: %r\n", user->name);
564
	default:
565
		return;
566
	}
567
 
568
	if(!mkcmd(j->cmd, buf, sizeof buf)){
569
		clog("internal error: cmd buffer overflow");
570
		_exits(0);
571
	}
572
 
573
	/*
574
	 * local call, auth, cmd with no i/o
575
	 */
576
	if(strcmp(j->host, "local") == 0){
577
		if(becomeuser(user->name) < 0){
578
			clog("%s: can't change uid for %s on %s: %r",
579
				user->name, j->cmd, j->host);
580
			_exits(0);
581
		}
582
		putenv("service", "rx");
583
		clog("%s: ran '%s' on %s", user->name, j->cmd, j->host);
584
		execl("/bin/rc", "rc", "-lc", buf, nil);
585
		clog("%s: exec failed for %s on %s: %r",
586
			user->name, j->cmd, j->host);
587
		_exits(0);
588
	}
589
 
590
	/*
591
	 * remote call, auth, cmd with no i/o
592
	 * give it 2 min to complete
593
	 */
594
	alarm(2*Minute*1000);
595
	fd = call(j->host);
596
	if(fd < 0){
597
		if(fd == -2)
598
			clog("%s: dangerous host %s", user->name, j->host);
599
		clog("%s: can't call %s: %r", user->name, j->host);
600
		_exits(0);
601
	}
602
	clog("%s: called %s on %s", user->name, j->cmd, j->host);
603
	if(becomeuser(user->name) < 0){
604
		clog("%s: can't change uid for %s on %s: %r",
605
			user->name, j->cmd, j->host);
606
		_exits(0);
607
	}
608
	ai = auth_proxy(fd, nil, "proto=p9any role=client");
609
	if(ai == nil){
610
		clog("%s: can't authenticate for %s on %s: %r",
611
			user->name, j->cmd, j->host);
612
		_exits(0);
613
	}
614
	clog("%s: authenticated %s on %s", user->name, j->cmd, j->host);
615
	write(fd, buf, strlen(buf)+1);
616
	write(fd, buf, 0);
617
	while((n = read(fd, buf, sizeof(buf)-1)) > 0){
618
		buf[n] = 0;
619
		clog("%s: %s\n", j->cmd, buf);
620
	}
621
	_exits(0);
622
}
623
 
624
void *
625
emalloc(ulong n)
626
{
627
	void *p;
628
 
629
	if(p = mallocz(n, 1))
630
		return p;
631
	fatal("out of memory");
632
	return 0;
633
}
634
 
635
void *
636
erealloc(void *p, ulong n)
637
{
638
	if(p = realloc(p, n))
639
		return p;
640
	fatal("out of memory");
641
	return 0;
642
}
643
 
644
void
645
usage(void)
646
{
647
	fprint(2, "usage: cron [-c]\n");
648
	exits("usage");
649
}
650
 
651
int
652
qidcmp(Qid a, Qid b)
653
{
654
	/* might be useful to know if a > b, but not for cron */
655
	return(a.path != b.path || a.vers != b.vers);
656
}
657
 
658
void
659
memrandom(void *p, int n)
660
{
661
	uchar *cp;
662
 
663
	for(cp = (uchar*)p; n > 0; n--)
664
		*cp++ = fastrand();
665
}
666
 
667
/*
668
 *  keep caphash fd open since opens of it could be disabled
669
 */
670
static int caphashfd;
671
 
672
void
673
initcap(void)
674
{
675
	caphashfd = open("#¤/caphash", OCEXEC|OWRITE);
676
	if(caphashfd < 0)
677
		fprint(2, "%s: opening #¤/caphash: %r\n", argv0);
678
}
679
 
680
/*
681
 *  create a change uid capability 
682
 */
683
char*
684
mkcap(char *from, char *to)
685
{
686
	uchar rand[20];
687
	char *cap;
688
	char *key;
689
	int nfrom, nto, ncap;
690
	uchar hash[SHA1dlen];
691
 
692
	if(caphashfd < 0)
693
		return nil;
694
 
695
	/* create the capability */
696
	nto = strlen(to);
697
	nfrom = strlen(from);
698
	ncap = nfrom + 1 + nto + 1 + sizeof(rand)*3 + 1;
699
	cap = emalloc(ncap);
700
	snprint(cap, ncap, "%s@%s", from, to);
701
	memrandom(rand, sizeof(rand));
702
	key = cap+nfrom+1+nto+1;
703
	enc64(key, sizeof(rand)*3, rand, sizeof(rand));
704
 
705
	/* hash the capability */
706
	hmac_sha1((uchar*)cap, strlen(cap), (uchar*)key, strlen(key), hash, nil);
707
 
708
	/* give the kernel the hash */
709
	key[-1] = '@';
710
	if(write(caphashfd, hash, SHA1dlen) < 0){
711
		free(cap);
712
		return nil;
713
	}
714
 
715
	return cap;
716
}
717
 
718
int
719
usecap(char *cap)
720
{
721
	int fd, rv;
722
 
723
	fd = open("#¤/capuse", OWRITE);
724
	if(fd < 0)
725
		return -1;
726
	rv = write(fd, cap, strlen(cap));
727
	close(fd);
728
	return rv;
729
}
730
 
731
int
732
becomeuser(char *new)
733
{
734
	char *cap;
735
	int rv;
736
 
737
	cap = mkcap(getuser(), new);
738
	if(cap == nil)
739
		return -1;
740
	rv = usecap(cap);
741
	free(cap);
742
 
743
	newns(new, nil);
744
	return rv;
745
}