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 7USE_CRONTAB(NEWTOY(crontab, "c:u:elr[!elr]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT)) 8 9config 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 30GLOBALS( 31 char *user; 32 char *cdir; 33) 34 35static 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 */ 45static 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 63static 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. 72static 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 114static 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; 205OUT: 206 free(line); 207 printf("Error at line no %s", toybuf); 208 xclose(fd); 209 return 1; 210} 211 212static 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 222static 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 229static 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 243static 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 263static 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 288RETRY: 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 333void 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