1 /* crontab.c - files used to schedule the execution of programs. 2 * 3 * Copyright 2014 Ranjan Kumar <ranjankumar.bth (at) 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 help 13 usage: crontab [-u user] FILE 14 [-u user] [-e | -l | -r] 15 [-c dir] 16 17 Files used to schedule the execution of programs. 18 19 -c crontab dir 20 -e edit user's crontab 21 -l list user's crontab 22 -r delete user's crontab 23 -u user 24 FILE Replace crontab by FILE ('-': stdin) 25 */ 26 #define FOR_crontab 27 #include "toys.h" 28 29 GLOBALS( 30 char *user; 31 char *cdir; 32 ) 33 34 static char *omitspace(char *line) 35 { 36 while (*line == ' ' || *line == '\t') line++; 37 return line; 38 } 39 40 /* 41 * Names can also be used for the 'month' and 'day of week' fields 42 * (First three letters of the particular day or month). 43 */ 44 static int getindex(char *src, int size) 45 { 46 int i; 47 char days[]={"sun""mon""tue""wed""thu""fri""sat"}; 48 char months[]={"jan""feb""mar""apr""may""jun""jul" 49 "aug""sep""oct""nov""dec"}; 50 char *field = (size == 12) ? months : days; 51 52 // strings are not allowed for min, hour and dom fields. 53 if (!(size == 7 || size == 12)) return -1; 54 55 for (i = 0; field[i]; i += 3) { 56 if (!strncasecmp(src, &field[i], 3)) 57 return (i/3); 58 } 59 return -1; 60 } 61 62 static long getval(char *num, long low, long high) 63 { 64 long val = strtol(num, &num, 10); 65 66 if (*num || (val < low) || (val > high)) return -1; 67 return val; 68 } 69 70 // Validate minute, hour, day of month, month and day of week fields. 71 static int validate_component(int min, int max, char *src) 72 { 73 int skip = 0; 74 char *ptr; 75 76 if (!src) return 1; 77 if ((ptr = strchr(src, '/'))) { 78 *ptr++ = 0; 79 if ((skip = getval(ptr, min, (min ? max: max-1))) < 0) return 1; 80 } 81 82 if (*src == '-' || *src == ',') return 1; 83 if (*src == '*') { 84 if (*(src+1)) return 1; 85 } 86 else { 87 for (;;) { 88 char *ctoken = strsep(&src, ","), *dtoken; 89 90 if (!ctoken) break; 91 if (!*ctoken) return 1; 92 93 // validate start position. 94 dtoken = strsep(&ctoken, "-"); 95 if (isdigit(*dtoken)) { 96 if (getval(dtoken, min, (min ? max : max-1)) < 0) return 1; 97 } else if (getindex(dtoken, max) < 0) return 1; 98 99 // validate end position. 100 if (!ctoken) { 101 if (skip) return 1; // case 10/20 or 1,2,4/3 102 } 103 else if (*ctoken) {// e.g. N-M 104 if (isdigit(*ctoken)) { 105 if (getval(ctoken, min, (min ? max : max-1)) < 0) return 1; 106 } else if (getindex(ctoken, max) < 0) return 1; 107 } else return 1; // error condition 'N-' 108 } 109 } 110 return 0; 111 } 112 113 static int parse_crontab(char *fname) 114 { 115 char *line; 116 int lno, fd = xopen(fname, O_RDONLY); 117 long plen = 0; 118 119 for (lno = 1; (line = get_rawline(fd, &plen, '\n')); lno++,free(line)) { 120 char *name, *val, *tokens[5] = {0,}, *ptr = line; 121 int count = 0; 122 123 if (line[plen - 1] == '\n') line[--plen] = '\0'; 124 else { 125 snprintf(toybuf, sizeof(toybuf), "'%d': premature EOF\n", lno); 126 goto OUT; 127 } 128 129 ptr = omitspace(ptr); 130 if (!*ptr || *ptr == '#' || *ptr == '@') continue; 131 while (count<5) { 132 int len = strcspn(ptr, " \t"); 133 134 if (ptr[len]) ptr[len++] = '\0'; 135 tokens[count++] = ptr; 136 ptr += len; 137 ptr = omitspace(ptr); 138 if (!*ptr) break; 139 } 140 switch (count) { 141 case 1: // form SHELL=/bin/sh 142 name = tokens[0]; 143 if ((val = strchr(name, '='))) *val++ = 0; 144 if (!val || !*val) { 145 snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line); 146 goto OUT; 147 } 148 break; 149 case 2: // form SHELL =/bin/sh or SHELL= /bin/sh 150 name = tokens[0]; 151 if ((val = strchr(name, '='))) { 152 *val = 0; 153 val = tokens[1]; 154 } else { 155 if (*(tokens[1]) != '=') { 156 snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line); 157 goto OUT; 158 } 159 val = tokens[1] + 1; 160 } 161 if (!*val) { 162 snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line); 163 goto OUT; 164 } 165 break; 166 case 3: // NAME = VAL 167 name = tokens[0]; 168 val = tokens[2]; 169 if (*(tokens[1]) != '=') { 170 snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line); 171 goto OUT; 172 } 173 break; 174 default: 175 if (validate_component(0, 60, tokens[0])) { 176 snprintf(toybuf, sizeof(toybuf), "'%d': bad minute\n", lno); 177 goto OUT; 178 } 179 if (validate_component(0, 24, tokens[1])) { 180 snprintf(toybuf, sizeof(toybuf), "'%d': bad hour\n", lno); 181 goto OUT; 182 } 183 if (validate_component(1, 31, tokens[2])) { 184 snprintf(toybuf, sizeof(toybuf), "'%d': bad day-of-month\n", lno); 185 goto OUT; 186 } 187 if (validate_component(1, 12, tokens[3])) { 188 snprintf(toybuf, sizeof(toybuf), "'%d': bad month\n", lno); 189 goto OUT; 190 } 191 if (validate_component(0, 7, tokens[4])) { 192 snprintf(toybuf, sizeof(toybuf), "'%d': bad day-of-week\n", lno); 193 goto OUT; 194 } 195 if (!*ptr) { // don't have any cmd to execute. 196 snprintf(toybuf, sizeof(toybuf), "'%d': bad command\n", lno); 197 goto OUT; 198 } 199 break; 200 } 201 } 202 xclose(fd); 203 return 0; 204 OUT: 205 free(line); 206 printf("Error at line no %s", toybuf); 207 xclose(fd); 208 return 1; 209 } 210 211 static void do_list(char *name) 212 { 213 int fdin; 214 215 snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, name); 216 if ((fdin = open(toybuf, O_RDONLY)) == -1) 217 error_exit("No crontab for '%s'", name); 218 xsendfile(fdin, 1); 219 xclose(fdin); 220 } 221 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 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 = xopen(src, O_RDONLY); 236 xsendfile(fdin, fdout); 237 xclose(fdin); 238 239 fchown(fdout, getuid(), geteuid()); 240 xclose(fdout); 241 } 242 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 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 = xopen(toybuf, O_RDONLY); 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 snprintf(toybuf, sizeof(toybuf), "errors in crontab file, can't install.\n" 321 "Do you want to retry the same edit? "); 322 if (!yesno(toybuf, 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 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 toys.exithelp++; 351 error_exit("file name must be specified for replace"); 352 } 353 do_replace(pwd->pw_name); 354 } 355 else if (toys.optflags & FLAG_e) do_edit(pwd); 356 else if (toys.optflags & FLAG_l) do_list(pwd->pw_name); 357 else if (toys.optflags & FLAG_r) do_remove(pwd->pw_name); 358 } else { 359 if (FLAG_elr) { 360 toys.exithelp++; 361 error_exit("no arguments permitted after this option"); 362 } 363 do_replace(pwd->pw_name); 364 } 365 if (!(toys.optflags & FLAG_c)) free(TT.cdir); 366 } 367