cron.c 13 KB

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