• 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   char *line;
117   int lno, fd = xopenro(fname);
118   long plen = 0;
119 
120   for (lno = 1; (line = get_rawline(fd, &plen, '\n')); lno++,free(line)) {
121     char *name, *val, *tokens[5] = {0,}, *ptr = line;
122     int count = 0;
123 
124     if (line[plen - 1] == '\n') line[--plen] = '\0';
125     else {
126       snprintf(toybuf, sizeof(toybuf), "'%d': premature EOF\n", lno);
127       goto OUT;
128     }
129 
130     ptr = omitspace(ptr);
131     if (!*ptr || *ptr == '#' || *ptr == '@') continue;
132     while (count<5) {
133       int len = strcspn(ptr, " \t");
134 
135       if (ptr[len]) ptr[len++] = '\0';
136       tokens[count++] = ptr;
137       ptr += len;
138       ptr = omitspace(ptr);
139       if (!*ptr) break;
140     }
141     switch (count) {
142       case 1: // form SHELL=/bin/sh
143         name = tokens[0];
144         if ((val = strchr(name, '='))) *val++ = 0;
145         if (!val || !*val) {
146           snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
147           goto OUT;
148         }
149         break;
150       case 2: // form SHELL =/bin/sh or SHELL= /bin/sh
151         name = tokens[0];
152         if ((val = strchr(name, '='))) {
153           *val = 0;
154           val = tokens[1];
155         } else {
156           if (*(tokens[1]) != '=') {
157             snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
158             goto OUT;
159           }
160           val = tokens[1] + 1;
161         }
162         if (!*val) {
163           snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
164           goto OUT;
165         }
166         break;
167       case 3: // NAME = VAL
168         name = tokens[0];
169         val = tokens[2];
170         if (*(tokens[1]) != '=') {
171           snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
172           goto OUT;
173         }
174         break;
175       default:
176         if (validate_component(0, 60, tokens[0])) {
177           snprintf(toybuf, sizeof(toybuf), "'%d': bad minute\n", lno);
178           goto OUT;
179         }
180         if (validate_component(0, 24, tokens[1])) {
181           snprintf(toybuf, sizeof(toybuf), "'%d': bad hour\n", lno);
182           goto OUT;
183         }
184         if (validate_component(1, 31, tokens[2])) {
185           snprintf(toybuf, sizeof(toybuf), "'%d': bad day-of-month\n", lno);
186           goto OUT;
187         }
188         if (validate_component(1, 12, tokens[3])) {
189           snprintf(toybuf, sizeof(toybuf), "'%d': bad month\n", lno);
190           goto OUT;
191         }
192         if (validate_component(0, 7, tokens[4])) {
193           snprintf(toybuf, sizeof(toybuf), "'%d': bad day-of-week\n", lno);
194           goto OUT;
195         }
196         if (!*ptr) { // don't have any cmd to execute.
197           snprintf(toybuf, sizeof(toybuf), "'%d': bad command\n", lno);
198           goto OUT;
199         }
200         break;
201     }
202   }
203   xclose(fd);
204   return 0;
205 OUT:
206   free(line);
207   printf("Error at line no %s", toybuf);
208   xclose(fd);
209   return 1;
210 }
211 
do_list(char * name)212 static void do_list(char *name)
213 {
214   int fdin;
215 
216   snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, name);
217   fdin = xopenro(toybuf);
218   xsendfile(fdin, 1);
219   xclose(fdin);
220 }
221 
do_remove(char * name)222 static void do_remove(char *name)
223 {
224   snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, name);
225   if (unlink(toybuf))
226     error_exit("No crontab for '%s'", name);
227 }
228 
update_crontab(char * src,char * dest)229 static void update_crontab(char *src, char *dest)
230 {
231   int fdin, fdout;
232 
233   snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, dest);
234   fdout = xcreate(toybuf, O_WRONLY|O_CREAT|O_TRUNC, 0600);
235   fdin = xopenro(src);
236   xsendfile(fdin, fdout);
237   xclose(fdin);
238 
239   fchown(fdout, getuid(), geteuid());
240   xclose(fdout);
241 }
242 
do_replace(char * name)243 static void do_replace(char *name)
244 {
245   char *fname = *toys.optargs ? *toys.optargs : "-";
246   char tname[] = "/tmp/crontab.XXXXXX";
247 
248   if ((*fname == '-') && !*(fname+1)) {
249     int tfd = mkstemp(tname);
250 
251     if (tfd < 0) perror_exit("mkstemp");
252     xsendfile(0, tfd);
253     xclose(tfd);
254     fname = tname;
255   }
256 
257   if (parse_crontab(fname))
258     error_exit("errors in crontab file '%s', can't install.", fname);
259   update_crontab(fname, name);
260   unlink(tname);
261 }
262 
do_edit(struct passwd * pwd)263 static void do_edit(struct passwd *pwd)
264 {
265   struct stat sb;
266   time_t mtime = 0;
267   int srcfd, destfd, status;
268   pid_t pid, cpid;
269   char tname[] = "/tmp/crontab.XXXXXX";
270 
271   if ((destfd = mkstemp(tname)) < 0)
272     perror_exit("Can't open tmp file");
273 
274   fchmod(destfd, 0666);
275   snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, pwd->pw_name);
276 
277   if (!stat(toybuf, &sb)) { // file exists and have some content.
278     if (sb.st_size) {
279       srcfd = xopenro(toybuf);
280       xsendfile(srcfd, destfd);
281       xclose(srcfd);
282     }
283   } else printf("No crontab for '%s'- using an empty one\n", pwd->pw_name);
284   xclose(destfd);
285 
286   if (!stat(tname, &sb)) mtime = sb.st_mtime;
287 
288 RETRY:
289   if (!(pid = xfork())) {
290     char *prog = pwd->pw_shell;
291 
292     xsetuser(pwd);
293     if (pwd->pw_uid) {
294       if (setenv("USER", pwd->pw_name, 1)) _exit(1);
295       if (setenv("LOGNAME", pwd->pw_name, 1)) _exit(1);
296     }
297     if (setenv("HOME", pwd->pw_dir, 1)) _exit(1);
298     if (setenv("SHELL",((!prog || !*prog) ? "/bin/sh" : prog), 1)) _exit(1);
299 
300     if (!(prog = getenv("VISUAL"))) {
301       if (!(prog = getenv("EDITOR")))
302         prog = "vi";
303     }
304     execlp(prog, prog, tname, (char *) NULL);
305     perror_exit("can't execute '%s'", prog);
306   }
307 
308   // Parent Process.
309   do {
310     cpid = waitpid(pid, &status, 0);
311   } while ((cpid == -1) && (errno == EINTR));
312 
313   if (!stat(tname, &sb) && (mtime == sb.st_mtime)) {
314     printf("%s: no changes made to crontab\n", toys.which->name);
315     unlink(tname);
316     return;
317   }
318   printf("%s: installing new crontab\n", toys.which->name);
319   if (parse_crontab(tname)) {
320     fprintf(stderr, "errors in crontab file, can't install.\n"
321         "Do you want to retry the same edit? ");
322     if (!yesno(0)) {
323       error_msg("edits left in '%s'", tname);
324       return;
325     }
326     goto RETRY;
327   }
328   // parsing of crontab success; update the crontab.
329   update_crontab(tname, pwd->pw_name);
330   unlink(tname);
331 }
332 
crontab_main(void)333 void crontab_main(void)
334 {
335   struct passwd *pwd = NULL;
336   long FLAG_elr = toys.optflags & (FLAG_e|FLAG_l|FLAG_r);
337 
338   if (TT.cdir && (TT.cdir[strlen(TT.cdir)-1] != '/'))
339     TT.cdir = xmprintf("%s/", TT.cdir);
340   if (!TT.cdir) TT.cdir = xstrdup("/var/spool/cron/crontabs/");
341 
342   if (toys.optflags & FLAG_u) {
343     if (getuid()) error_exit("must be privileged to use -u");
344     pwd = xgetpwnam(TT.user);
345   } else pwd = xgetpwuid(getuid());
346 
347   if (!toys.optc) {
348     if (!FLAG_elr) {
349       if (toys.optflags & FLAG_u)
350         help_exit("file name must be specified for replace");
351       do_replace(pwd->pw_name);
352     }
353     else if (toys.optflags & FLAG_e) do_edit(pwd);
354     else if (toys.optflags & FLAG_l) do_list(pwd->pw_name);
355     else if (toys.optflags & FLAG_r) do_remove(pwd->pw_name);
356   } else {
357     if (FLAG_elr) help_exit("no arguments permitted after this option");
358     do_replace(pwd->pw_name);
359   }
360   if (!(toys.optflags & FLAG_c)) free(TT.cdir);
361 }
362