crond.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * crond -d[#] -c <crondir> -f -b
  4. *
  5. * run as root, but NOT setuid root
  6. *
  7. * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
  8. * (version 2.3.2)
  9. * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
  10. *
  11. * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
  12. */
  13. #include <sys/syslog.h>
  14. #include "libbb.h"
  15. #ifndef CRONTABS
  16. #define CRONTABS "/var/spool/cron/crontabs"
  17. #endif
  18. #ifndef TMPDIR
  19. #define TMPDIR "/var/spool/cron"
  20. #endif
  21. #ifndef SENDMAIL
  22. #define SENDMAIL "sendmail"
  23. #endif
  24. #ifndef SENDMAIL_ARGS
  25. #define SENDMAIL_ARGS "-ti", "oem"
  26. #endif
  27. #ifndef CRONUPDATE
  28. #define CRONUPDATE "cron.update"
  29. #endif
  30. #ifndef MAXLINES
  31. #define MAXLINES 256 /* max lines in non-root crontabs */
  32. #endif
  33. typedef struct CronFile {
  34. struct CronFile *cf_Next;
  35. struct CronLine *cf_LineBase;
  36. char *cf_User; /* username */
  37. int cf_Ready; /* bool: one or more jobs ready */
  38. int cf_Running; /* bool: one or more jobs running */
  39. int cf_Deleted; /* marked for deletion, ignore */
  40. } CronFile;
  41. typedef struct CronLine {
  42. struct CronLine *cl_Next;
  43. char *cl_Shell; /* shell command */
  44. pid_t cl_Pid; /* running pid, 0, or armed (-1) */
  45. int cl_MailFlag; /* running pid is for mail */
  46. int cl_MailPos; /* 'empty file' size */
  47. char cl_Mins[60]; /* 0-59 */
  48. char cl_Hrs[24]; /* 0-23 */
  49. char cl_Days[32]; /* 1-31 */
  50. char cl_Mons[12]; /* 0-11 */
  51. char cl_Dow[7]; /* 0-6, beginning sunday */
  52. } CronLine;
  53. #define RUN_RANOUT 1
  54. #define RUN_RUNNING 2
  55. #define RUN_FAILED 3
  56. #define DaemonUid 0
  57. #if ENABLE_DEBUG_CROND_OPTION
  58. static unsigned DebugOpt;
  59. #endif
  60. static unsigned LogLevel = 8;
  61. static const char *LogFile;
  62. static const char *CDir = CRONTABS;
  63. static void startlogger(void);
  64. static void CheckUpdates(void);
  65. static void SynchronizeDir(void);
  66. static int TestJobs(time_t t1, time_t t2);
  67. static void RunJobs(void);
  68. static int CheckJobs(void);
  69. static void RunJob(const char *user, CronLine * line);
  70. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  71. static void EndJob(const char *user, CronLine * line);
  72. #else
  73. #define EndJob(user, line) line->cl_Pid = 0
  74. #endif
  75. static void DeleteFile(const char *userName);
  76. static CronFile *FileBase;
  77. static void crondlog(const char *ctl, ...)
  78. {
  79. va_list va;
  80. const char *fmt;
  81. int level = (int) (ctl[0] & 0xf);
  82. int type = level == 20 ?
  83. LOG_ERR : ((ctl[0] & 0100) ? LOG_WARNING : LOG_NOTICE);
  84. va_start(va, ctl);
  85. fmt = ctl + 1;
  86. if (level >= LogLevel) {
  87. #if ENABLE_DEBUG_CROND_OPTION
  88. if (DebugOpt) {
  89. vfprintf(stderr, fmt, va);
  90. } else
  91. #endif
  92. if (LogFile == 0) {
  93. vsyslog(type, fmt, va);
  94. } else {
  95. #if !ENABLE_DEBUG_CROND_OPTION
  96. int logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600);
  97. #else
  98. int logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600);
  99. #endif
  100. if (logfd >= 0) {
  101. vdprintf(logfd, fmt, va);
  102. close(logfd);
  103. }
  104. }
  105. }
  106. va_end(va);
  107. if (ctl[0] & 0200) {
  108. exit(20);
  109. }
  110. }
  111. int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  112. int crond_main(int ac, char **av)
  113. {
  114. unsigned opt;
  115. char *lopt, *Lopt, *copt;
  116. USE_DEBUG_CROND_OPTION(char *dopt;)
  117. opt_complementary = "f-b:b-f:S-L:L-S" USE_DEBUG_CROND_OPTION(":d-l");
  118. opterr = 0; /* disable getopt 'errors' message. */
  119. opt = getopt32(av, "l:L:fbSc:" USE_DEBUG_CROND_OPTION("d:"),
  120. &lopt, &Lopt, &copt USE_DEBUG_CROND_OPTION(, &dopt));
  121. if (opt & 1) /* -l */
  122. LogLevel = xatou(lopt);
  123. if (opt & 2) /* -L */
  124. if (*Lopt)
  125. LogFile = Lopt;
  126. if (opt & 32) /* -c */
  127. if (*copt)
  128. CDir = copt;
  129. #if ENABLE_DEBUG_CROND_OPTION
  130. if (opt & 64) { /* -d */
  131. DebugOpt = xatou(dopt);
  132. LogLevel = 0;
  133. }
  134. #endif
  135. /* close stdin and stdout, stderr.
  136. * close unused descriptors - don't need.
  137. * optional detach from controlling terminal
  138. */
  139. if (!(opt & 4))
  140. bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, av);
  141. xchdir(CDir);
  142. signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */
  143. startlogger(); /* need if syslog mode selected */
  144. /*
  145. * main loop - synchronize to 1 second after the minute, minimum sleep
  146. * of 1 second.
  147. */
  148. crondlog("\011%s " BB_VER " started, log level %d\n",
  149. applet_name, LogLevel);
  150. SynchronizeDir();
  151. {
  152. time_t t1 = time(NULL);
  153. time_t t2;
  154. long dt;
  155. int rescan = 60;
  156. short sleep_time = 60;
  157. write_pidfile("/var/run/crond.pid");
  158. for (;;) {
  159. sleep((sleep_time + 1) - (short) (time(NULL) % sleep_time));
  160. t2 = time(NULL);
  161. dt = t2 - t1;
  162. /*
  163. * The file 'cron.update' is checked to determine new cron
  164. * jobs. The directory is rescanned once an hour to deal
  165. * with any screwups.
  166. *
  167. * check for disparity. Disparities over an hour either way
  168. * result in resynchronization. A reverse-indexed disparity
  169. * less then an hour causes us to effectively sleep until we
  170. * match the original time (i.e. no re-execution of jobs that
  171. * have just been run). A forward-indexed disparity less then
  172. * an hour causes intermediate jobs to be run, but only once
  173. * in the worst case.
  174. *
  175. * when running jobs, the inequality used is greater but not
  176. * equal to t1, and less then or equal to t2.
  177. */
  178. if (--rescan == 0) {
  179. rescan = 60;
  180. SynchronizeDir();
  181. }
  182. CheckUpdates();
  183. #if ENABLE_DEBUG_CROND_OPTION
  184. if (DebugOpt)
  185. crondlog("\005Wakeup dt=%d\n", dt);
  186. #endif
  187. if (dt < -60 * 60 || dt > 60 * 60) {
  188. t1 = t2;
  189. crondlog("\111time disparity of %d minutes detected\n", dt / 60);
  190. } else if (dt > 0) {
  191. TestJobs(t1, t2);
  192. RunJobs();
  193. sleep(5);
  194. if (CheckJobs() > 0) {
  195. sleep_time = 10;
  196. } else {
  197. sleep_time = 60;
  198. }
  199. t1 = t2;
  200. }
  201. }
  202. }
  203. return 0; /* not reached */
  204. }
  205. static int ChangeUser(const char *user)
  206. {
  207. struct passwd *pas;
  208. const char *err_msg;
  209. /*
  210. * Obtain password entry and change privileges
  211. */
  212. pas = getpwnam(user);
  213. if (pas == 0) {
  214. crondlog("\011failed to get uid for %s", user);
  215. return -1;
  216. }
  217. setenv("USER", pas->pw_name, 1);
  218. setenv("HOME", pas->pw_dir, 1);
  219. setenv("SHELL", DEFAULT_SHELL, 1);
  220. /*
  221. * Change running state to the user in question
  222. */
  223. err_msg = change_identity_e2str(pas);
  224. if (err_msg) {
  225. crondlog("\011%s for user %s", err_msg, user);
  226. return -1;
  227. }
  228. if (chdir(pas->pw_dir) < 0) {
  229. crondlog("\011chdir failed: %s: %m", pas->pw_dir);
  230. if (chdir(TMPDIR) < 0) {
  231. crondlog("\011chdir failed: %s: %m", TMPDIR);
  232. return -1;
  233. }
  234. }
  235. return pas->pw_uid;
  236. }
  237. static void startlogger(void)
  238. {
  239. if (LogFile == 0) {
  240. openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON);
  241. }
  242. #if ENABLE_DEBUG_CROND_OPTION
  243. else { /* test logfile */
  244. int logfd;
  245. logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600);
  246. if (logfd >= 0) {
  247. close(logfd);
  248. }
  249. }
  250. #endif
  251. }
  252. static const char DowAry[] ALIGN1 =
  253. "sun""mon""tue""wed""thu""fri""sat"
  254. /* "Sun""Mon""Tue""Wed""Thu""Fri""Sat" */
  255. ;
  256. static const char MonAry[] ALIGN1 =
  257. "jan""feb""mar""apr""may""jun""jul""aug""sep""oct""nov""dec"
  258. /* "Jan""Feb""Mar""Apr""May""Jun""Jul""Aug""Sep""Oct""Nov""Dec" */
  259. ;
  260. static char *ParseField(char *user, char *ary, int modvalue, int off,
  261. const char *names, char *ptr)
  262. /* 'names' is a pointer to a set of 3-char abbreviations */
  263. {
  264. char *base = ptr;
  265. int n1 = -1;
  266. int n2 = -1;
  267. if (base == NULL) {
  268. return NULL;
  269. }
  270. while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
  271. int skip = 0;
  272. /* Handle numeric digit or symbol or '*' */
  273. if (*ptr == '*') {
  274. n1 = 0; /* everything will be filled */
  275. n2 = modvalue - 1;
  276. skip = 1;
  277. ++ptr;
  278. } else if (*ptr >= '0' && *ptr <= '9') {
  279. if (n1 < 0) {
  280. n1 = strtol(ptr, &ptr, 10) + off;
  281. } else {
  282. n2 = strtol(ptr, &ptr, 10) + off;
  283. }
  284. skip = 1;
  285. } else if (names) {
  286. int i;
  287. for (i = 0; names[i]; i += 3) {
  288. /* was using strncmp before... */
  289. if (strncasecmp(ptr, &names[i], 3) == 0) {
  290. ptr += 3;
  291. if (n1 < 0) {
  292. n1 = i / 3;
  293. } else {
  294. n2 = i / 3;
  295. }
  296. skip = 1;
  297. break;
  298. }
  299. }
  300. }
  301. /* handle optional range '-' */
  302. if (skip == 0) {
  303. crondlog("\111failed user %s parsing %s\n", user, base);
  304. return NULL;
  305. }
  306. if (*ptr == '-' && n2 < 0) {
  307. ++ptr;
  308. continue;
  309. }
  310. /*
  311. * collapse single-value ranges, handle skipmark, and fill
  312. * in the character array appropriately.
  313. */
  314. if (n2 < 0) {
  315. n2 = n1;
  316. }
  317. if (*ptr == '/') {
  318. skip = strtol(ptr + 1, &ptr, 10);
  319. }
  320. /*
  321. * fill array, using a failsafe is the easiest way to prevent
  322. * an endless loop
  323. */
  324. {
  325. int s0 = 1;
  326. int failsafe = 1024;
  327. --n1;
  328. do {
  329. n1 = (n1 + 1) % modvalue;
  330. if (--s0 == 0) {
  331. ary[n1 % modvalue] = 1;
  332. s0 = skip;
  333. }
  334. }
  335. while (n1 != n2 && --failsafe);
  336. if (failsafe == 0) {
  337. crondlog("\111failed user %s parsing %s\n", user, base);
  338. return NULL;
  339. }
  340. }
  341. if (*ptr != ',') {
  342. break;
  343. }
  344. ++ptr;
  345. n1 = -1;
  346. n2 = -1;
  347. }
  348. if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
  349. crondlog("\111failed user %s parsing %s\n", user, base);
  350. return NULL;
  351. }
  352. while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') {
  353. ++ptr;
  354. }
  355. #if ENABLE_DEBUG_CROND_OPTION
  356. if (DebugOpt) {
  357. int i;
  358. for (i = 0; i < modvalue; ++i) {
  359. crondlog("\005%d", ary[i]);
  360. }
  361. crondlog("\005\n");
  362. }
  363. #endif
  364. return ptr;
  365. }
  366. static void FixDayDow(CronLine * line)
  367. {
  368. int i;
  369. int weekUsed = 0;
  370. int daysUsed = 0;
  371. for (i = 0; i < (int)(ARRAY_SIZE(line->cl_Dow)); ++i) {
  372. if (line->cl_Dow[i] == 0) {
  373. weekUsed = 1;
  374. break;
  375. }
  376. }
  377. for (i = 0; i < (int)(ARRAY_SIZE(line->cl_Days)); ++i) {
  378. if (line->cl_Days[i] == 0) {
  379. daysUsed = 1;
  380. break;
  381. }
  382. }
  383. if (weekUsed && !daysUsed) {
  384. memset(line->cl_Days, 0, sizeof(line->cl_Days));
  385. }
  386. if (daysUsed && !weekUsed) {
  387. memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
  388. }
  389. }
  390. static void SynchronizeFile(const char *fileName)
  391. {
  392. int maxEntries = MAXLINES;
  393. int maxLines;
  394. char buf[1024];
  395. if (strcmp(fileName, "root") == 0) {
  396. maxEntries = 65535;
  397. }
  398. maxLines = maxEntries * 10;
  399. if (fileName) {
  400. FILE *fi;
  401. DeleteFile(fileName);
  402. fi = fopen(fileName, "r");
  403. if (fi != NULL) {
  404. struct stat sbuf;
  405. if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
  406. CronFile *file = xzalloc(sizeof(CronFile));
  407. CronLine **pline;
  408. file->cf_User = strdup(fileName);
  409. pline = &file->cf_LineBase;
  410. while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
  411. CronLine line;
  412. char *ptr;
  413. trim(buf);
  414. if (buf[0] == 0 || buf[0] == '#') {
  415. continue;
  416. }
  417. if (--maxEntries == 0) {
  418. break;
  419. }
  420. memset(&line, 0, sizeof(line));
  421. #if ENABLE_DEBUG_CROND_OPTION
  422. if (DebugOpt) {
  423. crondlog("\111User %s Entry %s\n", fileName, buf);
  424. }
  425. #endif
  426. /* parse date ranges */
  427. ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf);
  428. ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr);
  429. ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr);
  430. ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr);
  431. ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr);
  432. /* check failure */
  433. if (ptr == NULL) {
  434. continue;
  435. }
  436. /*
  437. * fix days and dow - if one is not * and the other
  438. * is *, the other is set to 0, and vise-versa
  439. */
  440. FixDayDow(&line);
  441. *pline = xzalloc(sizeof(CronLine));
  442. **pline = line;
  443. /* copy command */
  444. (*pline)->cl_Shell = strdup(ptr);
  445. #if ENABLE_DEBUG_CROND_OPTION
  446. if (DebugOpt) {
  447. crondlog("\111 Command %s\n", ptr);
  448. }
  449. #endif
  450. pline = &((*pline)->cl_Next);
  451. }
  452. *pline = NULL;
  453. file->cf_Next = FileBase;
  454. FileBase = file;
  455. if (maxLines == 0 || maxEntries == 0) {
  456. crondlog("\111Maximum number of lines reached for user %s\n", fileName);
  457. }
  458. }
  459. fclose(fi);
  460. }
  461. }
  462. }
  463. static void CheckUpdates(void)
  464. {
  465. FILE *fi;
  466. char buf[256];
  467. fi = fopen(CRONUPDATE, "r");
  468. if (fi != NULL) {
  469. remove(CRONUPDATE);
  470. while (fgets(buf, sizeof(buf), fi) != NULL) {
  471. SynchronizeFile(strtok(buf, " \t\r\n"));
  472. }
  473. fclose(fi);
  474. }
  475. }
  476. static void SynchronizeDir(void)
  477. {
  478. /* Attempt to delete the database. */
  479. for (;;) {
  480. CronFile *file;
  481. for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next);
  482. if (file == NULL) {
  483. break;
  484. }
  485. DeleteFile(file->cf_User);
  486. }
  487. /*
  488. * Remove cron update file
  489. *
  490. * Re-chdir, in case directory was renamed & deleted, or otherwise
  491. * screwed up.
  492. *
  493. * scan directory and add associated users
  494. */
  495. remove(CRONUPDATE);
  496. if (chdir(CDir) < 0) {
  497. crondlog("\311cannot find %s\n", CDir);
  498. }
  499. {
  500. DIR *dir = opendir(".");
  501. struct dirent *den;
  502. if (dir) {
  503. while ((den = readdir(dir))) {
  504. if (strchr(den->d_name, '.') != NULL) {
  505. continue;
  506. }
  507. if (getpwnam(den->d_name)) {
  508. SynchronizeFile(den->d_name);
  509. } else {
  510. crondlog("\007ignoring %s\n", den->d_name);
  511. }
  512. }
  513. closedir(dir);
  514. } else {
  515. crondlog("\311cannot open current dir!\n");
  516. }
  517. }
  518. }
  519. /*
  520. * DeleteFile() - delete user database
  521. *
  522. * Note: multiple entries for same user may exist if we were unable to
  523. * completely delete a database due to running processes.
  524. */
  525. static void DeleteFile(const char *userName)
  526. {
  527. CronFile **pfile = &FileBase;
  528. CronFile *file;
  529. while ((file = *pfile) != NULL) {
  530. if (strcmp(userName, file->cf_User) == 0) {
  531. CronLine **pline = &file->cf_LineBase;
  532. CronLine *line;
  533. file->cf_Running = 0;
  534. file->cf_Deleted = 1;
  535. while ((line = *pline) != NULL) {
  536. if (line->cl_Pid > 0) {
  537. file->cf_Running = 1;
  538. pline = &line->cl_Next;
  539. } else {
  540. *pline = line->cl_Next;
  541. free(line->cl_Shell);
  542. free(line);
  543. }
  544. }
  545. if (file->cf_Running == 0) {
  546. *pfile = file->cf_Next;
  547. free(file->cf_User);
  548. free(file);
  549. } else {
  550. pfile = &file->cf_Next;
  551. }
  552. } else {
  553. pfile = &file->cf_Next;
  554. }
  555. }
  556. }
  557. /*
  558. * TestJobs()
  559. *
  560. * determine which jobs need to be run. Under normal conditions, the
  561. * period is about a minute (one scan). Worst case it will be one
  562. * hour (60 scans).
  563. */
  564. static int TestJobs(time_t t1, time_t t2)
  565. {
  566. int nJobs = 0;
  567. time_t t;
  568. /* Find jobs > t1 and <= t2 */
  569. for (t = t1 - t1 % 60; t <= t2; t += 60) {
  570. if (t > t1) {
  571. struct tm *tp = localtime(&t);
  572. CronFile *file;
  573. CronLine *line;
  574. for (file = FileBase; file; file = file->cf_Next) {
  575. #if ENABLE_DEBUG_CROND_OPTION
  576. if (DebugOpt)
  577. crondlog("\005FILE %s:\n", file->cf_User);
  578. #endif
  579. if (file->cf_Deleted)
  580. continue;
  581. for (line = file->cf_LineBase; line; line = line->cl_Next) {
  582. #if ENABLE_DEBUG_CROND_OPTION
  583. if (DebugOpt)
  584. crondlog("\005 LINE %s\n", line->cl_Shell);
  585. #endif
  586. if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour] &&
  587. (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday])
  588. && line->cl_Mons[tp->tm_mon]) {
  589. #if ENABLE_DEBUG_CROND_OPTION
  590. if (DebugOpt) {
  591. crondlog("\005 JobToDo: %d %s\n",
  592. line->cl_Pid, line->cl_Shell);
  593. }
  594. #endif
  595. if (line->cl_Pid > 0) {
  596. crondlog("\010 process already running: %s %s\n",
  597. file->cf_User, line->cl_Shell);
  598. } else if (line->cl_Pid == 0) {
  599. line->cl_Pid = -1;
  600. file->cf_Ready = 1;
  601. ++nJobs;
  602. }
  603. }
  604. }
  605. }
  606. }
  607. }
  608. return nJobs;
  609. }
  610. static void RunJobs(void)
  611. {
  612. CronFile *file;
  613. CronLine *line;
  614. for (file = FileBase; file; file = file->cf_Next) {
  615. if (file->cf_Ready) {
  616. file->cf_Ready = 0;
  617. for (line = file->cf_LineBase; line; line = line->cl_Next) {
  618. if (line->cl_Pid < 0) {
  619. RunJob(file->cf_User, line);
  620. crondlog("\010USER %s pid %3d cmd %s\n",
  621. file->cf_User, line->cl_Pid, line->cl_Shell);
  622. if (line->cl_Pid < 0) {
  623. file->cf_Ready = 1;
  624. }
  625. else if (line->cl_Pid > 0) {
  626. file->cf_Running = 1;
  627. }
  628. }
  629. }
  630. }
  631. }
  632. }
  633. /*
  634. * CheckJobs() - check for job completion
  635. *
  636. * Check for job completion, return number of jobs still running after
  637. * all done.
  638. */
  639. static int CheckJobs(void)
  640. {
  641. CronFile *file;
  642. CronLine *line;
  643. int nStillRunning = 0;
  644. for (file = FileBase; file; file = file->cf_Next) {
  645. if (file->cf_Running) {
  646. file->cf_Running = 0;
  647. for (line = file->cf_LineBase; line; line = line->cl_Next) {
  648. if (line->cl_Pid > 0) {
  649. int status;
  650. int r = wait4(line->cl_Pid, &status, WNOHANG, NULL);
  651. if (r < 0 || r == line->cl_Pid) {
  652. EndJob(file->cf_User, line);
  653. if (line->cl_Pid) {
  654. file->cf_Running = 1;
  655. }
  656. } else if (r == 0) {
  657. file->cf_Running = 1;
  658. }
  659. }
  660. }
  661. }
  662. nStillRunning += file->cf_Running;
  663. }
  664. return nStillRunning;
  665. }
  666. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  667. static void
  668. ForkJob(const char *user, CronLine * line, int mailFd,
  669. const char *prog, const char *cmd, const char *arg, const char *mailf)
  670. {
  671. /* Fork as the user in question and run program */
  672. pid_t pid = fork();
  673. line->cl_Pid = pid;
  674. if (pid == 0) {
  675. /* CHILD */
  676. /* Change running state to the user in question */
  677. if (ChangeUser(user) < 0) {
  678. exit(0);
  679. }
  680. #if ENABLE_DEBUG_CROND_OPTION
  681. if (DebugOpt) {
  682. crondlog("\005Child Running %s\n", prog);
  683. }
  684. #endif
  685. if (mailFd >= 0) {
  686. dup2(mailFd, mailf != NULL);
  687. dup2((mailf ? mailFd : 1), 2);
  688. close(mailFd);
  689. }
  690. execl(prog, prog, cmd, arg, NULL);
  691. crondlog("\024cannot exec, user %s cmd %s %s %s\n", user, prog, cmd, arg);
  692. if (mailf) {
  693. fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
  694. }
  695. exit(0);
  696. } else if (pid < 0) {
  697. /* FORK FAILED */
  698. crondlog("\024cannot fork, user %s\n", user);
  699. line->cl_Pid = 0;
  700. if (mailf) {
  701. remove(mailf);
  702. }
  703. } else if (mailf) {
  704. /* PARENT, FORK SUCCESS
  705. * rename mail-file based on pid of process
  706. */
  707. char mailFile2[128];
  708. snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d", user, pid);
  709. rename(mailf, mailFile2);
  710. }
  711. /*
  712. * Close the mail file descriptor.. we can't just leave it open in
  713. * a structure, closing it later, because we might run out of descriptors
  714. */
  715. if (mailFd >= 0) {
  716. close(mailFd);
  717. }
  718. }
  719. static void RunJob(const char *user, CronLine * line)
  720. {
  721. char mailFile[128];
  722. int mailFd;
  723. line->cl_Pid = 0;
  724. line->cl_MailFlag = 0;
  725. /* open mail file - owner root so nobody can screw with it. */
  726. snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, getpid());
  727. mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600);
  728. if (mailFd >= 0) {
  729. line->cl_MailFlag = 1;
  730. fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", user,
  731. line->cl_Shell);
  732. line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR);
  733. } else {
  734. crondlog("\024cannot create mail file user %s file %s, output to /dev/null\n", user, mailFile);
  735. }
  736. ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile);
  737. }
  738. /*
  739. * EndJob - called when job terminates and when mail terminates
  740. */
  741. static void EndJob(const char *user, CronLine * line)
  742. {
  743. int mailFd;
  744. char mailFile[128];
  745. struct stat sbuf;
  746. /* No job */
  747. if (line->cl_Pid <= 0) {
  748. line->cl_Pid = 0;
  749. return;
  750. }
  751. /*
  752. * End of job and no mail file
  753. * End of sendmail job
  754. */
  755. snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, line->cl_Pid);
  756. line->cl_Pid = 0;
  757. if (line->cl_MailFlag != 1) {
  758. return;
  759. }
  760. line->cl_MailFlag = 0;
  761. /*
  762. * End of primary job - check for mail file. If size has increased and
  763. * the file is still valid, we sendmail it.
  764. */
  765. mailFd = open(mailFile, O_RDONLY);
  766. remove(mailFile);
  767. if (mailFd < 0) {
  768. return;
  769. }
  770. if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid
  771. || sbuf.st_nlink != 0 || sbuf.st_size == line->cl_MailPos
  772. || !S_ISREG(sbuf.st_mode)
  773. ) {
  774. close(mailFd);
  775. return;
  776. }
  777. ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL);
  778. }
  779. #else
  780. /* crond without sendmail */
  781. static void RunJob(const char *user, CronLine * line)
  782. {
  783. /* Fork as the user in question and run program */
  784. pid_t pid = fork();
  785. if (pid == 0) {
  786. /* CHILD */
  787. /* Change running state to the user in question */
  788. if (ChangeUser(user) < 0) {
  789. exit(0);
  790. }
  791. #if ENABLE_DEBUG_CROND_OPTION
  792. if (DebugOpt) {
  793. crondlog("\005Child Running %s\n", DEFAULT_SHELL);
  794. }
  795. #endif
  796. execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL);
  797. crondlog("\024cannot exec, user %s cmd %s -c %s\n", user,
  798. DEFAULT_SHELL, line->cl_Shell);
  799. exit(0);
  800. } else if (pid < 0) {
  801. /* FORK FAILED */
  802. crondlog("\024cannot, user %s\n", user);
  803. pid = 0;
  804. }
  805. line->cl_Pid = pid;
  806. }
  807. #endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */