• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* crontab.c - files used to schedule the execution of programs.
2  *
3  * Copyright 2014 Ranjan Kumar <ranjankumar.bth@gmail.com>
4  *
5  * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html
6 
7 USE_CRONTAB(NEWTOY(crontab, "c:u:elr[!elr]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
8 
9 config CRONTAB
10   bool "crontab"
11   default n
12   depends on TOYBOX_FORK
13   help
14     usage: crontab [-u user] FILE
15                    [-u user] [-e | -l | -r]
16                    [-c dir]
17 
18     Files used to schedule the execution of programs.
19 
20     -c crontab dir
21     -e edit user's crontab
22     -l list user's crontab
23     -r delete user's crontab
24     -u user
25     FILE Replace crontab by FILE ('-': stdin)
26 */
27 #define FOR_crontab
28 #include "toys.h"
29 
GLOBALS(char * user;char * cdir;)30 GLOBALS(
31   char *user;
32   char *cdir;
33 )
34 
35 static char *omitspace(char *line)
36 {
37   while (*line == ' ' || *line == '\t') line++;
38   return line;
39 }
40 
41 /*
42  * Names can also be used for the 'month' and 'day of week' fields
43  * (First three letters of the particular day or month).
44  */
getindex(char * src,int size)45 static int getindex(char *src, int size)
46 {
47   int i;
48   char days[]={"sun""mon""tue""wed""thu""fri""sat"};
49   char months[]={"jan""feb""mar""apr""may""jun""jul"
50     "aug""sep""oct""nov""dec"};
51   char *field = (size == 12) ? months : days;
52 
53   // strings are not allowed for min, hour and dom fields.
54   if (!(size == 7 || size == 12)) return -1;
55 
56   for (i = 0; field[i]; i += 3) {
57     if (!strncasecmp(src, &field[i], 3))
58       return (i/3);
59   }
60   return -1;
61 }
62 
getval(char * num,long low,long high)63 static long getval(char *num, long low, long high)
64 {
65   long val = strtol(num, &num, 10);
66 
67   if (*num || (val < low) || (val > high)) return -1;
68   return val;
69 }
70 
71 // Validate minute, hour, day of month, month and day of week fields.
validate_component(int min,int max,char * src)72 static int validate_component(int min, int max, char *src)
73 {
74   int skip = 0;
75   char *ptr;
76 
77   if (!src) return 1;
78   if ((ptr = strchr(src, '/'))) {
79     *ptr++ = 0;
80     if ((skip = getval(ptr, min, (min ? max: max-1))) < 0) return 1;
81   }
82 
83   if (*src == '-' || *src == ',') return 1;
84   if (*src == '*') {
85     if (*(src+1)) return 1;
86   }
87   else {
88     for (;;) {
89       char *ctoken = strsep(&src, ","), *dtoken;
90 
91       if (!ctoken) break;
92       if (!*ctoken) return 1;
93 
94       // validate start position.
95       dtoken = strsep(&ctoken, "-");
96       if (isdigit(*dtoken)) {
97         if (getval(dtoken, min, (min ? max : max-1)) < 0) return 1;
98       } else if (getindex(dtoken, max) < 0) return 1;
99 
100       // validate end position.
101       if (!ctoken) {
102         if (skip) return 1; // case 10/20 or 1,2,4/3
103       }
104       else if (*ctoken) {// e.g. N-M
105         if (isdigit(*ctoken)) {
106           if (getval(ctoken, min, (min ? max : max-1)) < 0) return 1;
107         } else if (getindex(ctoken, max) < 0) return 1;
108       } else return 1; // error condition 'N-'
109     }
110   }
111   return 0;
112 }
113 
parse_crontab(char * fname)114 static int parse_crontab(char *fname)
115 {
116   FILE *fp = xfopen(fname, "r");
117   long len = 0;
118   char *line = NULL;
119   size_t allocated_length;
120   int lno;
121 
122   for (lno = 1; (len = getline(&line, &allocated_length, fp)) > 0; lno++) {
123     char *name, *val, *tokens[5] = {0,}, *ptr = line;
124     int count = 0;
125 
126     if (line[len - 1] == '\n') line[--len] = '\0';
127     else {
128       snprintf(toybuf, sizeof(toybuf), "'%d': premature EOF\n", lno);
129       goto OUT;
130     }
131 
132     ptr = omitspace(ptr);
133     if (!*ptr || *ptr == '#' || *ptr == '@') continue;
134     while (count<5) {
135       int len = strcspn(ptr, " \t");
136 
137       if (ptr[len]) ptr[len++] = '\0';
138       tokens[count++] = ptr;
139       ptr += len;
140       ptr = omitspace(ptr);
141       if (!*ptr) break;
142     }
143     switch (count) {
144       case 1: // form SHELL=/bin/sh
145         name = tokens[0];
146         if ((val = strchr(name, '='))) *val++ = 0;
147         if (!val || !*val) {
148           snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
149           goto OUT;
150         }
151         break;
152       case 2: // form SHELL =/bin/sh or SHELL= /bin/sh
153         name = tokens[0];
154         if ((val = strchr(name, '='))) {
155           *val = 0;
156           val = tokens[1];
157         } else {
158           if (*(tokens[1]) != '=') {
159             snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
160             goto OUT;
161           }
162           val = tokens[1] + 1;
163         }
164         if (!*val) {
165           snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
166           goto OUT;
167         }
168         break;
169       case 3: // NAME = VAL
170         name = tokens[0];
171         val = tokens[2];
172         if (*(tokens[1]) != '=') {
173           snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
174           goto OUT;
175         }
176         break;
177       default:
178         if (validate_component(0, 60, tokens[0])) {
179           snprintf(toybuf, sizeof(toybuf), "'%d': bad minute\n", lno);
180           goto OUT;
181         }
182         if (validate_component(0, 24, tokens[1])) {
183           snprintf(toybuf, sizeof(toybuf), "'%d': bad hour\n", lno);
184           goto OUT;
185         }
186         if (validate_component(1, 31, tokens[2])) {
187           snprintf(toybuf, sizeof(toybuf), "'%d': bad day-of-month\n", lno);
188           goto OUT;
189         }
190         if (validate_component(1, 12, tokens[3])) {
191           snprintf(toybuf, sizeof(toybuf), "'%d': bad month\n", lno);
192           goto OUT;
193         }
194         if (validate_component(0, 7, tokens[4])) {
195           snprintf(toybuf, sizeof(toybuf), "'%d': bad day-of-week\n", lno);
196           goto OUT;
197         }
198         if (!*ptr) { // don't have any cmd to execute.
199           snprintf(toybuf, sizeof(toybuf), "'%d': bad command\n", lno);
200           goto OUT;
201         }
202         break;
203     }
204   }
205   free(line);
206   fclose(fp);
207   return 0;
208 OUT:
209   free(line);
210   printf("Error at line no %s", toybuf);
211   fclose(fp);
212   return 1;
213 }
214 
do_list(char * name)215 static void do_list(char *name)
216 {
217   int fdin;
218 
219   snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, name);
220   fdin = xopenro(toybuf);
221   xsendfile(fdin, 1);
222   xclose(fdin);
223 }
224 
do_remove(char * name)225 static void do_remove(char *name)
226 {
227   snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, name);
228   if (unlink(toybuf))
229     error_exit("No crontab for '%s'", name);
230 }
231 
update_crontab(char * src,char * dest)232 static void update_crontab(char *src, char *dest)
233 {
234   int fdin, fdout;
235 
236   snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, dest);
237   fdout = xcreate(toybuf, O_WRONLY|O_CREAT|O_TRUNC, 0600);
238   fdin = xopenro(src);
239   xsendfile(fdin, fdout);
240   xclose(fdin);
241 
242   fchown(fdout, getuid(), geteuid());
243   xclose(fdout);
244 }
245 
do_replace(char * name)246 static void do_replace(char *name)
247 {
248   char *fname = *toys.optargs ? *toys.optargs : "-";
249   char tname[] = "/tmp/crontab.XXXXXX";
250 
251   if ((*fname == '-') && !*(fname+1)) {
252     int tfd = mkstemp(tname);
253 
254     if (tfd < 0) perror_exit("mkstemp");
255     xsendfile(0, tfd);
256     xclose(tfd);
257     fname = tname;
258   }
259 
260   if (parse_crontab(fname))
261     error_exit("errors in crontab file '%s', can't install.", fname);
262   update_crontab(fname, name);
263   unlink(tname);
264 }
265 
do_edit(struct passwd * pwd)266 static void do_edit(struct passwd *pwd)
267 {
268   struct stat sb;
269   time_t mtime = 0;
270   int srcfd, destfd, status;
271   pid_t pid, cpid;
272   char tname[] = "/tmp/crontab.XXXXXX";
273 
274   if ((destfd = mkstemp(tname)) < 0)
275     perror_exit("Can't open tmp file");
276 
277   fchmod(destfd, 0666);
278   snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, pwd->pw_name);
279 
280   if (!stat(toybuf, &sb)) { // file exists and have some content.
281     if (sb.st_size) {
282       srcfd = xopenro(toybuf);
283       xsendfile(srcfd, destfd);
284       xclose(srcfd);
285     }
286   } else printf("No crontab for '%s'- using an empty one\n", pwd->pw_name);
287   xclose(destfd);
288 
289   if (!stat(tname, &sb)) mtime = sb.st_mtime;
290 
291 RETRY:
292   if (!(pid = xfork())) {
293     char *prog = pwd->pw_shell;
294 
295     xsetuser(pwd);
296     if (pwd->pw_uid) {
297       if (setenv("USER", pwd->pw_name, 1)) _exit(1);
298       if (setenv("LOGNAME", pwd->pw_name, 1)) _exit(1);
299     }
300     if (setenv("HOME", pwd->pw_dir, 1)) _exit(1);
301     if (setenv("SHELL",((!prog || !*prog) ? "/bin/sh" : prog), 1)) _exit(1);
302 
303     if (!(prog = getenv("VISUAL"))) {
304       if (!(prog = getenv("EDITOR")))
305         prog = "vi";
306     }
307     execlp(prog, prog, tname, (char *) NULL);
308     perror_exit("can't execute '%s'", prog);
309   }
310 
311   // Parent Process.
312   do {
313     cpid = waitpid(pid, &status, 0);
314   } while ((cpid == -1) && (errno == EINTR));
315 
316   if (!stat(tname, &sb) && (mtime == sb.st_mtime)) {
317     printf("%s: no changes made to crontab\n", toys.which->name);
318     unlink(tname);
319     return;
320   }
321   printf("%s: installing new crontab\n", toys.which->name);
322   if (parse_crontab(tname)) {
323     fprintf(stderr, "errors in crontab file, can't install.\n"
324         "Do you want to retry the same edit? ");
325     if (!yesno(0)) {
326       error_msg("edits left in '%s'", tname);
327       return;
328     }
329     goto RETRY;
330   }
331   // parsing of crontab success; update the crontab.
332   update_crontab(tname, pwd->pw_name);
333   unlink(tname);
334 }
335 
crontab_main(void)336 void crontab_main(void)
337 {
338   struct passwd *pwd = NULL;
339   long FLAG_elr = toys.optflags & (FLAG_e|FLAG_l|FLAG_r);
340 
341   if (TT.cdir && (TT.cdir[strlen(TT.cdir)-1] != '/'))
342     TT.cdir = xmprintf("%s/", TT.cdir);
343   if (!TT.cdir) TT.cdir = xstrdup("/var/spool/cron/crontabs/");
344 
345   if (toys.optflags & FLAG_u) {
346     if (getuid()) error_exit("must be privileged to use -u");
347     pwd = xgetpwnam(TT.user);
348   } else pwd = xgetpwuid(getuid());
349 
350   if (!toys.optc) {
351     if (!FLAG_elr) {
352       if (toys.optflags & FLAG_u)
353         help_exit("file name must be specified for replace");
354       do_replace(pwd->pw_name);
355     }
356     else if (toys.optflags & FLAG_e) do_edit(pwd);
357     else if (toys.optflags & FLAG_l) do_list(pwd->pw_name);
358     else if (toys.optflags & FLAG_r) do_remove(pwd->pw_name);
359   } else {
360     if (FLAG_elr) help_exit("no arguments permitted after this option");
361     do_replace(pwd->pw_name);
362   }
363   if (!(toys.optflags & FLAG_c)) free(TT.cdir);
364 }
365