cron.c 13 KB

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