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