cron.c 11 KB

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