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