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 void 71 main(int argc, char *argv[]) 72 { 73 Job *j; 74 Tm tm; 75 Time t; 76 ulong now, last, nowsecs, future; 77 int i; 78 79 debug = 0; 80 ARGBEGIN{ 81 case 'c': 82 createuser(); 83 exits(0); 84 case 'd': 85 debug = 1; 86 break; 87 default: 88 usage(); 89 }ARGEND 90 91 if(debug){ 92 readalljobs(); 93 printjobs(); 94 exits(0); 95 } 96 97 initcap(); 98 99 switch(fork()){ 100 case -1: 101 error("can't fork"); 102 case 0: 103 break; 104 default: 105 exits(0); 106 } 107 108 argv0 = "cron"; 109 srand(getpid()*time(0)); 110 last = time(0) / Minute; 111 for(;;){ 112 readalljobs(); 113 now = time(0) / Minute; 114 if (now-last > Day/Minute) /* don't go mad */ 115 last = now - Day/Minute; /* just execute 1 day's jobs */ 116 for(; last <= now; last++){ 117 tm = *localtime(last*Minute); 118 t.min = 1ULL << tm.min; 119 t.hour = 1 << tm.hour; 120 t.wday = 1 << tm.wday; 121 t.mday = 1 << tm.mday; 122 t.mon = 1 << (tm.mon + 1); 123 for(i = 0; i < nuser; i++) 124 for(j = users[i].jobs; j; j = j->next) 125 if(j->time.min & t.min 126 && j->time.hour & t.hour 127 && j->time.wday & t.wday 128 && j->time.mday & t.mday 129 && j->time.mon & t.mon) 130 rexec(&users[i], j); 131 } 132 /* 133 * if we're not there yet, sleep until (now+1)*Minute, 134 * which synchronises with minute roll-over as a side-effect. 135 */ 136 future = (now + 1) * Minute; 137 nowsecs = time(0); 138 if (nowsecs < future) 139 sleep((future - nowsecs)*1000); 140 } 141 /* not reached */ 142 } 143 144 void 145 createuser(void) 146 { 147 Dir d; 148 char file[128], *user; 149 int fd; 150 151 user = getuser(); 152 sprint(file, "/cron/%s", user); 153 fd = create(file, OREAD, 0755|DMDIR); 154 if(fd < 0) 155 sysfatal("couldn't create %s: %r", file); 156 nulldir(&d); 157 d.gid = user; 158 dirfwstat(fd, &d); 159 close(fd); 160 sprint(file, "/cron/%s/cron", user); 161 fd = create(file, OREAD, 0644); 162 if(fd < 0) 163 sysfatal("couldn't create %s: %r", file); 164 nulldir(&d); 165 d.gid = user; 166 dirfwstat(fd, &d); 167 close(fd); 168 } 169 170 void 171 readalljobs(void) 172 { 173 User *u; 174 Dir *d, *du; 175 char file[128]; 176 int i, n, fd; 177 178 fd = open("/cron", OREAD); 179 if(fd < 0) 180 error("can't open /cron\n"); 181 while((n = dirread(fd, &d)) > 0){ 182 for(i = 0; i < n; i++){ 183 if(strcmp(d[i].name, "log") == 0) 184 continue; 185 if(strcmp(d[i].name, d[i].uid) != 0){ 186 syslog(1, CRONLOG, "cron for %s owned by %s\n", 187 d[i].name, d[i].uid); 188 continue; 189 } 190 u = newuser(d[i].name); 191 sprint(file, "/cron/%s/cron", d[i].name); 192 du = dirstat(file); 193 if(du == nil || qidcmp(u->lastqid, du->qid) != 0){ 194 freejobs(u->jobs); 195 u->jobs = readjobs(file, u); 196 } 197 free(du); 198 } 199 free(d); 200 } 201 close(fd); 202 } 203 204 /* 205 * parse user's cron file 206 * other lines: minute hour monthday month weekday host command 207 */ 208 Job * 209 readjobs(char *file, User *user) 210 { 211 Biobuf *b; 212 Job *j, *jobs; 213 Dir *d; 214 int line; 215 216 d = dirstat(file); 217 if(!d) 218 return nil; 219 b = Bopen(file, OREAD); 220 if(!b){ 221 free(d); 222 return nil; 223 } 224 jobs = nil; 225 user->lastqid = d->qid; 226 free(d); 227 for(line = 1; savec = Brdline(b, '\n'); line++){ 228 savec[Blinelen(b) - 1] = '\0'; 229 while(*savec == ' ' || *savec == '\t') 230 savec++; 231 if(*savec == '#' || *savec == '\0') 232 continue; 233 if(strlen(savec) > 1024){ 234 syslog(0, CRONLOG, "%s: line %d: line too long", 235 user->name, line); 236 continue; 237 } 238 j = emalloc(sizeof *j); 239 j->time.min = gettime(0, 59); 240 if(j->time.min && (j->time.hour = gettime(0, 23)) 241 && (j->time.mday = gettime(1, 31)) 242 && (j->time.mon = gettime(1, 12)) 243 && (j->time.wday = gettime(0, 6)) 244 && getname(&j->host)){ 245 j->cmd = emalloc(strlen(savec) + 1); 246 strcpy(j->cmd, savec); 247 j->next = jobs; 248 jobs = j; 249 }else{ 250 syslog(0, CRONLOG, "%s: line %d: syntax error", 251 user->name, line); 252 free(j); 253 } 254 } 255 Bterm(b); 256 return jobs; 257 } 258 259 void 260 printjobs(void) 261 { 262 char buf[8*1024]; 263 Job *j; 264 int i; 265 266 for(i = 0; i < nuser; i++){ 267 print("user %s\n", users[i].name); 268 for(j = users[i].jobs; j; j = j->next) 269 if(!mkcmd(j->cmd, buf, sizeof buf)) 270 print("\tbad job %s on host %s\n", 271 j->cmd, j->host); 272 else 273 print("\tjob %s on host %s\n", buf, j->host); 274 } 275 } 276 277 User * 278 newuser(char *name) 279 { 280 int i; 281 282 for(i = 0; i < nuser; i++) 283 if(strcmp(users[i].name, name) == 0) 284 return &users[i]; 285 if(nuser == maxuser){ 286 maxuser += 32; 287 users = erealloc(users, maxuser * sizeof *users); 288 } 289 memset(&users[nuser], 0, sizeof(users[nuser])); 290 users[nuser].name = strdup(name); 291 users[nuser].jobs = 0; 292 users[nuser].lastqid.type = QTFILE; 293 users[nuser].lastqid.path = ~0LL; 294 users[nuser].lastqid.vers = ~0L; 295 return &users[nuser++]; 296 } 297 298 void 299 freejobs(Job *j) 300 { 301 Job *next; 302 303 for(; j; j = next){ 304 next = j->next; 305 free(j->cmd); 306 free(j->host); 307 free(j); 308 } 309 } 310 311 int 312 getname(char **namep) 313 { 314 int c; 315 char buf[64], *p; 316 317 if(!savec) 318 return 0; 319 while(*savec == ' ' || *savec == '\t') 320 savec++; 321 for(p = buf; (c = *savec) && c != ' ' && c != '\t'; p++){ 322 if(p >= buf+sizeof buf -1) 323 return 0; 324 *p = *savec++; 325 } 326 *p = '\0'; 327 *namep = strdup(buf); 328 if(*namep == 0){ 329 syslog(0, CRONLOG, "internal error: strdup failure"); 330 _exits(0); 331 } 332 while(*savec == ' ' || *savec == '\t') 333 savec++; 334 return p > buf; 335 } 336 337 /* 338 * return the next time range (as a bit vector) in the file: 339 * times: '*' 340 * | range 341 * range: number 342 * | number '-' number 343 * | range ',' range 344 * a return of zero means a syntax error was discovered 345 */ 346 uvlong 347 gettime(int min, int max) 348 { 349 uvlong n, m, e; 350 351 if(gettok(min, max) == '*') 352 return ~0ULL; 353 n = 0; 354 while(tok == '1'){ 355 m = 1ULL << lexval; 356 n |= m; 357 if(gettok(0, 0) == '-'){ 358 if(gettok(lexval, max) != '1') 359 return 0; 360 e = 1ULL << lexval; 361 for( ; m <= e; m <<= 1) 362 n |= m; 363 gettok(min, max); 364 } 365 if(tok != ',') 366 break; 367 if(gettok(min, max) != '1') 368 return 0; 369 } 370 pushtok(); 371 return n; 372 } 373 374 void 375 pushtok(void) 376 { 377 savec = savetok; 378 } 379 380 int 381 gettok(int min, int max) 382 { 383 char c; 384 385 savetok = savec; 386 if(!savec) 387 return tok = 0; 388 while((c = *savec) == ' ' || c == '\t') 389 savec++; 390 switch(c){ 391 case '0': case '1': case '2': case '3': case '4': 392 case '5': case '6': case '7': case '8': case '9': 393 lexval = strtoul(savec, &savec, 10); 394 if(lexval < min || lexval > max) 395 return tok = 0; 396 return tok = '1'; 397 case '*': case '-': case ',': 398 savec++; 399 return tok = c; 400 default: 401 return tok = 0; 402 } 403 } 404 405 int 406 call(char *host) 407 { 408 char *na, *p; 409 410 na = netmkaddr(host, 0, "rexexec"); 411 p = utfrune(na, L'!'); 412 if(!p) 413 return -1; 414 p = utfrune(p+1, L'!'); 415 if(!p) 416 return -1; 417 if(strcmp(p, "!rexexec") != 0) 418 return -2; 419 return dial(na, 0, 0, 0); 420 } 421 422 /* 423 * convert command to run properly on the remote machine 424 * need to escape the quotes wo they don't get stripped 425 */ 426 int 427 mkcmd(char *cmd, char *buf, int len) 428 { 429 char *p; 430 int n, m; 431 432 n = sizeof "exec rc -c '" -1; 433 if(n >= len) 434 return 0; 435 strcpy(buf, "exec rc -c '"); 436 while(p = utfrune(cmd, L'\'')){ 437 p++; 438 m = p - cmd; 439 if(n + m + 1 >= len) 440 return 0; 441 strncpy(&buf[n], cmd, m); 442 n += m; 443 buf[n++] = '\''; 444 cmd = p; 445 } 446 m = strlen(cmd); 447 if(n + m + sizeof "'</dev/null>/dev/null>[2=1]" >= len) 448 return 0; 449 strcpy(&buf[n], cmd); 450 strcpy(&buf[n+m], "'</dev/null>/dev/null>[2=1]"); 451 return 1; 452 } 453 454 void 455 rexec(User *user, Job *j) 456 { 457 char buf[8*1024]; 458 int n, fd; 459 AuthInfo *ai; 460 461 switch(rfork(RFPROC|RFNOWAIT|RFNAMEG|RFENVG|RFFDG)){ 462 case 0: 463 break; 464 case -1: 465 syslog(0, CRONLOG, "can't fork a job for %s: %r\n", user->name); 466 default: 467 return; 468 } 469 470 if(!mkcmd(j->cmd, buf, sizeof buf)){ 471 syslog(0, CRONLOG, "internal error: cmd buffer overflow"); 472 _exits(0); 473 } 474 475 /* 476 * remote call, auth, cmd with no i/o 477 * give it 2 min to complete 478 */ 479 if(strcmp(j->host, "local") == 0){ 480 if(becomeuser(user->name) < 0){ 481 syslog(0, CRONLOG, "%s: can't change uid for %s on %s: %r", user->name, j->cmd, j->host); 482 _exits(0); 483 } 484 syslog(0, CRONLOG, "%s: ran '%s' on %s", user->name, j->cmd, j->host); 485 execl("/bin/rc", "rc", "-c", buf, nil); 486 syslog(0, CRONLOG, "%s: exec failed for %s on %s: %r", 487 user->name, j->cmd, j->host); 488 _exits(0); 489 } 490 491 alarm(2*Minute*1000); 492 fd = call(j->host); 493 if(fd < 0){ 494 if(fd == -2) 495 syslog(0, CRONLOG, "%s: dangerous host %s", 496 user->name, j->host); 497 syslog(0, CRONLOG, "%s: can't call %s: %r", user->name, j->host); 498 _exits(0); 499 } 500 syslog(0, CRONLOG, "%s: called %s on %s", user->name, j->cmd, j->host); 501 if(becomeuser(user->name) < 0){ 502 syslog(0, CRONLOG, "%s: can't change uid for %s on %s: %r", 503 user->name, j->cmd, j->host); 504 _exits(0); 505 } 506 ai = auth_proxy(fd, nil, "proto=p9any role=client"); 507 if(ai == nil){ 508 syslog(0, CRONLOG, "%s: can't authenticate for %s on %s: %r", 509 user->name, j->cmd, j->host); 510 _exits(0); 511 } 512 syslog(0, CRONLOG, "%s: authenticated %s on %s", user->name, j->cmd, j->host); 513 write(fd, buf, strlen(buf)+1); 514 write(fd, buf, 0); 515 while((n = read(fd, buf, sizeof(buf)-1)) > 0){ 516 buf[n] = 0; 517 syslog(0, CRONLOG, "%s: %s\n", j->cmd, buf); 518 } 519 _exits(0); 520 } 521 522 void * 523 emalloc(ulong n) 524 { 525 void *p; 526 527 if(p = mallocz(n, 1)) 528 return p; 529 error("out of memory"); 530 return 0; 531 } 532 533 void * 534 erealloc(void *p, ulong n) 535 { 536 if(p = realloc(p, n)) 537 return p; 538 error("out of memory"); 539 return 0; 540 } 541 542 void 543 usage(void) 544 { 545 fprint(2, "usage: cron [-c]\n"); 546 exits("usage"); 547 } 548 549 int 550 qidcmp(Qid a, Qid b) 551 { 552 /* might be useful to know if a > b, but not for cron */ 553 return(a.path != b.path || a.vers != b.vers); 554 } 555 556 void 557 memrandom(void *p, int n) 558 { 559 uchar *cp; 560 561 for(cp = (uchar*)p; n > 0; n--) 562 *cp++ = fastrand(); 563 } 564 565 /* 566 * keep caphash fd open since opens of it could be disabled 567 */ 568 static int caphashfd; 569 570 void 571 initcap(void) 572 { 573 caphashfd = open("#¤/caphash", OWRITE); 574 if(caphashfd < 0) 575 fprint(2, "%s: opening #¤/caphash: %r", argv0); 576 } 577 578 /* 579 * create a change uid capability 580 */ 581 char* 582 mkcap(char *from, char *to) 583 { 584 uchar rand[20]; 585 char *cap; 586 char *key; 587 int nfrom, nto; 588 uchar hash[SHA1dlen]; 589 590 if(caphashfd < 0) 591 return nil; 592 593 /* create the capability */ 594 nto = strlen(to); 595 nfrom = strlen(from); 596 cap = emalloc(nfrom+1+nto+1+sizeof(rand)*3+1); 597 sprint(cap, "%s@%s", from, to); 598 memrandom(rand, sizeof(rand)); 599 key = cap+nfrom+1+nto+1; 600 enc64(key, sizeof(rand)*3, rand, sizeof(rand)); 601 602 /* hash the capability */ 603 hmac_sha1((uchar*)cap, strlen(cap), (uchar*)key, strlen(key), hash, nil); 604 605 /* give the kernel the hash */ 606 key[-1] = '@'; 607 if(write(caphashfd, hash, SHA1dlen) < 0){ 608 free(cap); 609 return nil; 610 } 611 612 return cap; 613 } 614 615 int 616 usecap(char *cap) 617 { 618 int fd, rv; 619 620 fd = open("#¤/capuse", OWRITE); 621 if(fd < 0) 622 return -1; 623 rv = write(fd, cap, strlen(cap)); 624 close(fd); 625 return rv; 626 } 627 628 int 629 becomeuser(char *new) 630 { 631 char *cap; 632 int rv; 633 cap = mkcap(getuser(), new); 634 if(cap == nil) 635 return -1; 636 rv = usecap(cap); 637 free(cap); 638 639 newns(new, nil); 640 return rv; 641 } 642