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 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 */ 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 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. 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 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 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 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 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 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 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 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