cron.c 12 KB

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