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