cron.c 13 KB

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