xref: /plan9-contrib/sys/src/cmd/auth/cron.c (revision 219b2ee8daee37f4aad58d63f21287faa8e4ffdc)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <auth.h>
5 #include "authsrv.h"
6 
7 char CRONLOG[] = "cron";
8 
9 typedef struct Job	Job;
10 typedef struct Time	Time;
11 typedef struct User	User;
12 
13 struct Time{			/* bit masks for each valid time */
14 	ulong	min;			/* actually 1 bit for every 2 min */
15 	ulong	hour;
16 	ulong	mday;
17 	ulong	wday;
18 	ulong	mon;
19 };
20 
21 struct Job{
22 	char	host[NAMELEN];		/* where ... */
23 	Time	time;			/* when ... */
24 	char	*cmd;			/* and what to execute */
25 	Job	*next;
26 };
27 
28 struct User{
29 	char	name[NAMELEN];		/* who ... */
30 	Job	*jobs;			/* wants to execute these jobs */
31 };
32 
33 User	*users;
34 int	nuser;
35 int	maxuser;
36 char	*savec;
37 char	*savetok;
38 int	tok;
39 int	debug;
40 ulong	lexval;
41 
42 void	rexec(User*, Job*);
43 void	readalljobs(void);
44 Job	*readjobs(char*, User*);
45 int	getname(char*);
46 ulong	gettime(int, int);
47 int	gettok(int, int);
48 void	pushtok(void);
49 void	usage(void);
50 void	freejobs(Job*);
51 User	*newuser(char*);
52 void	*emalloc(ulong);
53 void	*erealloc(void*, ulong);
54 int	myauth(int, char*);
55 void	createuser(void);
56 int	mkcmd(char*, char*, int);
57 void	printjobs(void);
58 
59 void
60 main(int argc, char *argv[])
61 {
62 	Job *j;
63 	Tm tm;
64 	Time t;
65 	ulong now, last, x;
66 	int i;
67 
68 	debug = 0;
69 	ARGBEGIN{
70 	case 'c':
71 		createuser();
72 		exits(0);
73 	case 'd':
74 		debug = 1;
75 		break;
76 	default:
77 		usage();
78 	}ARGEND
79 	USED(argc, argv);
80 
81 	if(debug){
82 		readalljobs();
83 		printjobs();
84 		exits(0);
85 	}
86 
87 	switch(fork()){
88 	case -1:
89 		error("can't fork");
90 	case 0:
91 		break;
92 	default:
93 		exits(0);
94 	}
95 
96 	argv0 = "cron";
97 	srand(getpid()*time(0));
98 	last = time(0) / 60;
99 	for(;;){
100 		readalljobs();
101 		now = time(0) / 60;
102 		for(; last <= now; last += 2){
103 			tm = *localtime(last*60);
104 			t.min = 1 << tm.min/2;
105 			t.hour = 1 << tm.hour;
106 			t.wday = 1 << tm.wday;
107 			t.mday = 1 << tm.mday;
108 			t.mon = 1 << (tm.mon + 1);
109 			for(i = 0; i < nuser; i++)
110 				for(j = users[i].jobs; j; j = j->next)
111 					if(j->time.min & t.min && j->time.hour & t.hour
112 					&& j->time.wday & t.wday
113 					&& j->time.mday & t.mday
114 					&& j->time.mon & t.mon)
115 						rexec(&users[i], j);
116 		}
117 		x = time(0) / 60;
118 		if(x - now < 2)
119 			sleep((2 - (x - now))*60*1000);
120 	}
121 	exits(0);
122 }
123 
124 void
125 createuser(void)
126 {
127 	Dir d;
128 	char file[3*NAMELEN], *user;
129 	int fd;
130 
131 	user = getuser();
132 	sprint(file, "/cron/%s", user);
133 	fd = create(file, OREAD, 0755|CHDIR);
134 	if(fd < 0){
135 		fprint(2, "couldn't create %s: %r\n", file);
136 		exits("create");
137 	}
138 	dirfstat(fd, &d);
139 	strncpy(d.gid, user, NAMELEN);
140 	dirfwstat(fd, &d);
141 	close(fd);
142 	sprint(file, "/cron/%s/cron", user);
143 	fd = create(file, OREAD, 0644);
144 	if(fd < 0){
145 		fprint(2, "couldn't create %s: %r\n", file);
146 		exits("create");
147 	}
148 	dirfstat(fd, &d);
149 	strncpy(d.gid, user, NAMELEN);
150 	dirfwstat(fd, &d);
151 	close(fd);
152 }
153 
154 void
155 readalljobs(void)
156 {
157 	User *u;
158 	Dir d[64], db;
159 	char file[3*NAMELEN];
160 	int i, n, fd;
161 	ulong now;
162 	static ulong lasttime;
163 
164 	now = time(0);
165 	fd = open("/cron", OREAD);
166 	if(fd < 0)
167 		error("can't open /cron\n");
168 	while((n = dirread(fd, d, sizeof d)) > 0){
169 		n /= sizeof d[0];
170 		for(i = 0; i < n; i++){
171 			if(strcmp(d[i].name, "log") == 0)
172 				continue;
173 			if(strcmp(d[i].name, d[i].uid) != 0){
174 				syslog(1, CRONLOG, "cron for %s owned by %s\n", d[i].name, d[i].uid);
175 				continue;
176 			}
177 			u = newuser(d[i].name);
178 			sprint(file, "/cron/%s/cron", d[i].name);
179 			if(dirstat(file, &db) < 0)
180 				continue;
181 			if(lasttime < db.mtime){
182 				freejobs(u->jobs);
183 				u->jobs = readjobs(file, u);
184 			}
185 		}
186 	}
187 	lasttime = now;
188 	close(fd);
189 }
190 
191 /*
192  * parse user's cron file
193  * other lines: minute hour monthday month weekday host command
194  */
195 Job *
196 readjobs(char *file, User *user)
197 {
198 	Biobuf *b;
199 	Job *j, *jobs;
200 	int line;
201 
202 	b = Bopen(file, OREAD);
203 	if(!b)
204 		return 0;
205 	jobs = 0;
206 	for(line = 1; savec = Brdline(b, '\n'); line++){
207 		savec[Blinelen(b) - 1] = '\0';
208 		while(*savec == ' ' || *savec == '\t')
209 			savec++;
210 		if(*savec == '#' || *savec == '\0')
211 			continue;
212 		if(strlen(savec) > 1024){
213 			syslog(0, CRONLOG, "%s: line %d: line too long", user->name, line);
214 			continue;
215 		}
216 		j = emalloc(sizeof *j);
217 		if((j->time.min = gettime(0, 59))
218 		&& (j->time.hour = gettime(0, 23))
219 		&& (j->time.mday = gettime(1, 31))
220 		&& (j->time.mon = gettime(1, 12))
221 		&& (j->time.wday = gettime(0, 6))
222 		&& getname(j->host)){
223 			j->cmd = emalloc(strlen(savec) + 1);
224 			strcpy(j->cmd, savec);
225 			j->next = jobs;
226 			jobs = j;
227 		}else{
228 			syslog(0, CRONLOG, "%s: line %d: syntax error", user->name, line);
229 			free(j);
230 		}
231 	}
232 	Bterm(b);
233 	return jobs;
234 }
235 
236 void
237 printjobs(void)
238 {
239 	char buf[8*1024];
240 	Job *j;
241 	int i;
242 
243 	for(i = 0; i < nuser; i++){
244 		print("user %s\n", users[i].name);
245 		for(j = users[i].jobs; j; j = j->next){
246 			if(!mkcmd(j->cmd, buf, sizeof buf))
247 				print("\tbad job %s on host %s\n", j->cmd, j->host);
248 			else
249 				print("\tjob %s on host %s\n", buf, j->host);
250 		}
251 	}
252 }
253 
254 User *
255 newuser(char *name)
256 {
257 	int i;
258 
259 	for(i = 0; i < nuser; i++)
260 		if(strcmp(users[i].name, name) == 0)
261 			return &users[i];
262 	if(nuser == maxuser){
263 		maxuser += 32;
264 		users = erealloc(users, maxuser * sizeof *users);
265 	}
266 	strcpy(users[nuser].name, name);
267 	users[nuser].jobs = 0;
268 	return &users[nuser++];
269 }
270 
271 void
272 freejobs(Job *j)
273 {
274 	Job *fj;
275 
276 	for(fj = j; fj; fj = j){
277 		j = j->next;
278 		free(fj);
279 	}
280 }
281 
282 int
283 getname(char *name)
284 {
285 	int c;
286 	int i;
287 
288 	if(!savec)
289 		return 0;
290 	while(*savec == ' ' || *savec == '\t')
291 		savec++;
292 	for(i = 0; (c = *savec) && c != ' ' && c != '\t'; i++){
293 		if(i == NAMELEN - 1)
294 			return 0;
295 		name[i] = *savec++;
296 	}
297 	name[i] = '\0';
298 	while(*savec == ' ' || *savec == '\t')
299 		savec++;
300 	return i;
301 }
302 
303 /*
304  * return the next time range in the file:
305  * times: '*'
306  * 	| range
307  * range: number
308  *	| number '-' number
309  *	| range ',' range
310  * a return of zero means a syntax error was discovered
311  */
312 ulong
313 gettime(int min, int max)
314 {
315 	ulong n, m, e;
316 
317 	if(gettok(min, max) == '*')
318 		return ~0;
319 	n = 0;
320 	while(tok == '1'){
321 		m = 1 << lexval;
322 		n |= m;
323 		if(gettok(0, 0) == '-'){
324 			if(gettok(lexval, max) != '1')
325 				return 0;
326 			e = 1 << lexval;
327 			for( ; m <= e; m <<= 1)
328 				n |= m;
329 			gettok(min, max);
330 		}
331 		if(tok != ',')
332 			break;
333 		if(gettok(min, max) != '1')
334 			return 0;
335 	}
336 	pushtok();
337 	return n;
338 }
339 
340 void
341 pushtok(void)
342 {
343 	savec = savetok;
344 }
345 
346 int
347 gettok(int min, int max)
348 {
349 	char c;
350 
351 	savetok = savec;
352 	if(!savec)
353 		return tok = 0;
354 	while((c = *savec) == ' ' || c == '\t')
355 		savec++;
356 	switch(c){
357 	case '0': case '1': case '2': case '3': case '4':
358 	case '5': case '6': case '7': case '8': case '9':
359 		lexval = strtoul(savec, &savec, 10);
360 		if(lexval < min || lexval > max)
361 			return tok = 0;
362 		if(max > 32)
363 			lexval /= 2;			/* yuk: correct min by / 2 */
364 		return tok = '1';
365 	case '*': case '-': case ',':
366 		savec++;
367 		return tok = c;
368 	default:
369 		return tok = 0;
370 	}
371 }
372 
373 int
374 call(char *host)
375 {
376 	char *na, *p;
377 
378 	na = netmkaddr(host, 0, "rexexec");
379 	p = utfrune(na, L'!');
380 	if(!p)
381 		return -1;
382 	p = utfrune(p+1, L'!');
383 	if(!p)
384 		return -1;
385 	if(strcmp(p, "!rexexec") != 0)
386 		return -2;
387 	return dial(na, 0, 0, 0);
388 }
389 
390 /*
391  * convert command to run properly on the remote machine
392  * need to escape the quotes wo they don't get stripped
393  */
394 int
395 mkcmd(char *cmd, char *buf, int len)
396 {
397 	char *p;
398 	int n, m;
399 
400 	n = sizeof "exec rc -c '" -1;
401 	if(n >= len)
402 		return 0;
403 	strcpy(buf, "exec rc -c '");
404 	while(p = utfrune(cmd, L'\'')){
405 		p++;
406 		m = p - cmd;
407 		if(n + m + 1 >= len)
408 			return 0;
409 		strncpy(&buf[n], cmd, m);
410 		n += m;
411 		buf[n++] = '\'';
412 		cmd = p;
413 	}
414 	m = strlen(cmd);
415 	if(n + m + sizeof "'</dev/null>/dev/null>[2=1]" >= len)
416 		return 0;
417 	strcpy(&buf[n], cmd);
418 	strcpy(&buf[n+m], "'</dev/null>/dev/null>[2=1]");
419 	return 1;
420 }
421 
422 void
423 rexec(User *user, Job *j)
424 {
425 	char buf[8*1024], key[DESKEYLEN], err[ERRLEN];
426 	int fd;
427 
428 	switch(rfork(RFPROC|RFNOWAIT|RFNAMEG|RFENVG|RFFDG)){
429 	case 0:
430 		break;
431 	case -1:
432 		syslog(0, CRONLOG, "can't fork a job for %s: %r\n", user->name);
433 	default:
434 		return;
435 	}
436 	if(findkey(KEYDB, user->name, key) == 0){
437 		syslog(0, CRONLOG, "%s: key not found", user->name);
438 		_exits(0);
439 	}
440 
441 	if(!mkcmd(j->cmd, buf, sizeof buf)){
442 		syslog(0, CRONLOG, "internal error: cmd buffer overflow");
443 		_exits(0);
444 	}
445 
446 	/*
447 	 * remote call, auth, cmd with no i/o
448 	 * give it 2 min to complete
449 	 */
450 	alarm(2*60*1000);
451 	fd = call(j->host);
452 	if(fd < 0){
453 		if(fd == -2){
454 			syslog(0, AUTHLOG, "%s: dangerous host %s", user->name, j->host);
455 			syslog(0, CRONLOG, "%s: dangerous host %s", user->name, j->host);
456 		}
457 		syslog(0, CRONLOG, "%s: can't call '%s'", user->name, j->host);
458 		_exits(0);
459 	}
460 	if(myauth(fd, user->name) < 0){
461 		errstr(err);
462 		syslog(0, CRONLOG, "%s: can't auth %s on %s: %s", user->name, j->cmd, j->host, err);
463 		_exits(0);
464 	}
465 	write(fd, buf, strlen(buf)+1);
466 	write(fd, buf, 0);
467 	while(read(fd, buf, sizeof buf) > 0)
468 		;
469 	_exits(0);
470 }
471 
472 void *
473 emalloc(ulong n)
474 {
475 	void *p;
476 
477 	if(p = malloc(n))
478 		return p;
479 	error("out of memory");
480 	return 0;
481 }
482 
483 void *
484 erealloc(void *p, ulong n)
485 {
486 	if(p = realloc(p, n))
487 		return p;
488 	error("out of memory");
489 	return 0;
490 }
491 
492 void
493 usage(void)
494 {
495 	fprint(2, "usage: cron [-c]\n");
496 	exits("usage");
497 }
498 
499 int
500 myauth(int fd, char *user)
501 {
502 	int i;
503 	char hkey[DESKEYLEN];
504 	char buf[512];
505 	Ticketreq tr;
506 	Ticket t;
507 	Authenticator a;
508 
509 	/* get ticket request from remote machine */
510 	if(readn(fd, buf, TICKREQLEN) < 0){
511 		werrstr("bad request");
512 		return -1;
513 	}
514 	convM2TR(buf, &tr);
515 	if(tr.type != AuthTreq){
516 		werrstr("bad request");
517 		return -1;
518 	}
519 	if(findkey(KEYDB, tr.authid, hkey) == 0){
520 		werrstr("no key for authid %s", tr.authid);
521 		return -1;
522 	}
523 
524 	/* create ticket+authenticator and send to destination */
525 	memset(&t, 0, sizeof t);
526 	memmove(t.chal, tr.chal, CHALLEN);
527 	strcpy(t.cuid, user);
528 	strcpy(t.suid, user);
529 	srand(time(0));
530 	for(i = 0; i < DESKEYLEN; i++)
531 		t.key[i] = nrand(256);
532 	t.num = AuthTs;
533 	convT2M(&t, buf, hkey);
534 	memmove(a.chal, tr.chal, CHALLEN);
535 	a.id = 0;
536 	a.num = AuthAc;
537 	convA2M(&a, buf+TICKETLEN, t.key);
538 	if(write(fd, buf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN){
539 		werrstr("connection dropped: %r");
540 		return -1;
541 	}
542 
543 	/* get authenticator from server and check */
544 	if(readn(fd, buf, AUTHENTLEN) < 0){
545 		werrstr("connection dropped: %r");
546 		return -1;
547 	}
548 	convM2A(buf, &a, t.key);
549 	if(a.num != AuthAs){
550 		werrstr("bad reply authenticator");
551 		return -1;
552 	}
553 	return 0;
554 }
555