cron.c 12 KB

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