cron.c 14 KB

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