Magellan Linux

Diff of /trunk/mkinitrd-magellan/busybox/miscutils/crontab.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 815 by niro, Sat Sep 1 22:45:15 2007 UTC revision 816 by niro, Fri Apr 24 18:33:46 2009 UTC
# Line 10  Line 10 
10   * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.   * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
11   */   */
12    
13  #include "busybox.h"  #include "libbb.h"
14    
15  #ifndef CRONTABS  #ifndef CRONTABS
16  #define CRONTABS        "/var/spool/cron/crontabs"  #define CRONTABS        "/var/spool/cron/crontabs"
17  #endif  #endif
 #ifndef TMPDIR  
 #define TMPDIR          "/var/spool/cron"  
 #endif  
18  #ifndef CRONUPDATE  #ifndef CRONUPDATE
19  #define CRONUPDATE      "cron.update"  #define CRONUPDATE      "cron.update"
20  #endif  #endif
 #ifndef PATH_VI  
 #define PATH_VI         "/bin/vi"   /* location of vi */  
 #endif  
   
 static const char *CDir = CRONTABS;  
21    
22  static void EditFile(const char *user, const char *file);  static void change_user(const struct passwd *pas)
 static int GetReplaceStream(const char *user, const char *file);  
 static int ChangeUser(const char *user, short dochdir);  
   
 int crontab_main(int ac, char **av)  
23  {  {
24   enum { NONE, EDIT, LIST, REPLACE, DELETE } option = NONE;   xsetenv("USER", pas->pw_name);
25   const struct passwd *pas;   xsetenv("HOME", pas->pw_dir);
26   const char *repFile = NULL;   xsetenv("SHELL", DEFAULT_SHELL);
  int repFd = 0;  
  int i;  
  char caller[256];           /* user that ran program */  
  char buf[1024];  
  int UserId;  
   
  UserId = getuid();  
  pas = getpwuid(UserId);  
  if (pas == NULL)  
  bb_perror_msg_and_die("getpwuid");  
   
  safe_strncpy(caller, pas->pw_name, sizeof(caller));  
   
  i = 1;  
  if (ac > 1) {  
  if (LONE_DASH(av[1])) {  
  option = REPLACE;  
  ++i;  
  } else if (av[1][0] != '-') {  
  option = REPLACE;  
  ++i;  
  repFile = av[1];  
  }  
  }  
   
  for (; i < ac; ++i) {  
  char *ptr = av[i];  
27    
28   if (*ptr != '-')   /* initgroups, setgid, setuid */
29   break;   change_identity(pas);
  ptr += 2;  
30    
31   switch (ptr[-1]) {   if (chdir(pas->pw_dir) < 0) {
32   case 'l':   bb_perror_msg("chdir(%s) by %s failed",
33   if (ptr[-1] == 'l')   pas->pw_dir, pas->pw_name);
34   option = LIST;   xchdir("/tmp");
  /* fall through */  
  case 'e':  
  if (ptr[-1] == 'e')  
  option = EDIT;  
  /* fall through */  
  case 'd':  
  if (ptr[-1] == 'd')  
  option = DELETE;  
  /* fall through */  
  case 'u':  
  if (i + 1 < ac && av[i+1][0] != '-') {  
  ++i;  
  if (getuid() == geteuid()) {  
  pas = getpwnam(av[i]);  
  if (pas) {  
  UserId = pas->pw_uid;  
  } else {  
  bb_error_msg_and_die("user %s unknown", av[i]);  
  }  
  } else {  
  bb_error_msg_and_die("only the superuser may specify a user");  
  }  
  }  
  break;  
  case 'c':  
  if (getuid() == geteuid()) {  
  CDir = (*ptr) ? ptr : av[++i];  
  } else {  
  bb_error_msg_and_die("-c option: superuser only");  
  }  
  break;  
  default:  
  i = ac;  
  break;  
  }  
35   }   }
36   if (i != ac || option == NONE)  }
  bb_show_usage();  
   
  /*  
  * Get password entry  
  */  
   
  pas = getpwuid(UserId);  
  if (pas == NULL)  
  bb_perror_msg_and_die("getpwuid");  
37    
38   /*  static void edit_file(const struct passwd *pas, const char *file)
39   * If there is a replacement file, obtain a secure descriptor to it.  {
40   */   const char *ptr;
41     int pid = vfork();
42    
43   if (repFile) {   if (pid < 0) /* failure */
44   repFd = GetReplaceStream(caller, repFile);   bb_perror_msg_and_die("vfork");
45   if (repFd < 0)   if (pid) { /* parent */
46   bb_error_msg_and_die("cannot read replacement file");   wait4pid(pid);
47     return;
48     }
49    
50     /* CHILD - change user and run editor */
51     change_user(pas);
52     ptr = getenv("VISUAL");
53     if (!ptr) {
54     ptr = getenv("EDITOR");
55     if (!ptr)
56     ptr = "vi";
57   }   }
58    
59   /*   BB_EXECLP(ptr, ptr, file, NULL);
60   * Change directory to our crontab directory   bb_perror_msg_and_die("exec %s", ptr);
61   */  }
   
  xchdir(CDir);  
   
  /*  
  * Handle options as appropriate  
  */  
62    
63   switch (option) {  static int open_as_user(const struct passwd *pas, const char *file)
64   case LIST:  {
65   {   pid_t pid;
66   FILE *fi;   char c;
67    
68   fi = fopen(pas->pw_name, "r");   pid = vfork();
69   if (fi) {   if (pid < 0) /* ERROR */
70   while (fgets(buf, sizeof(buf), fi) != NULL)   bb_perror_msg_and_die("vfork");
71   fputs(buf, stdout);   if (pid) { /* PARENT */
72   fclose(fi);   if (wait4pid(pid) == 0) {
73   } else {   /* exitcode 0: child says it can read */
74   bb_error_msg("no crontab for %s", pas->pw_name);   return open(file, O_RDONLY);
  }  
75   }   }
76   break;   return -1;
  case EDIT:  
  {  
 /* FIXME: messy code here! we have file copying helpers for this! */  
  FILE *fi;  
  int fd;  
  int n;  
  char tmp[128];  
   
  snprintf(tmp, sizeof(tmp), TMPDIR "/crontab.%d", getpid());  
  fd = xopen3(tmp, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600);  
 /* race, use fchown */  
  chown(tmp, getuid(), getgid());  
  fi = fopen(pas->pw_name, "r");  
  if (fi) {  
  while ((n = fread(buf, 1, sizeof(buf), fi)) > 0)  
  full_write(fd, buf, n);  
  }  
  EditFile(caller, tmp);  
  remove(tmp);  
  lseek(fd, 0L, SEEK_SET);  
  repFd = fd;  
  }  
  option = REPLACE;  
  /* fall through */  
  case REPLACE:  
  {  
 /* same here */  
  char path[1024];  
  int fd;  
  int n;  
   
  snprintf(path, sizeof(path), "%s.new", pas->pw_name);  
  fd = open(path, O_CREAT|O_TRUNC|O_APPEND|O_WRONLY, 0600);  
  if (fd >= 0) {  
  while ((n = read(repFd, buf, sizeof(buf))) > 0) {  
  full_write(fd, buf, n);  
  }  
  close(fd);  
  rename(path, pas->pw_name);  
  } else {  
  bb_error_msg("cannot create %s/%s", CDir, path);  
  }  
  close(repFd);  
  }  
  break;  
  case DELETE:  
  remove(pas->pw_name);  
  break;  
  case NONE:  
  default:  
  break;  
77   }   }
78    
79   /*   /* CHILD */
80   *  Bump notification file.  Handle window where crond picks file up   /* initgroups, setgid, setuid */
81   *  before we can write our entry out.   change_identity(pas);
82   */   /* We just try to read one byte. If it works, file is readable
83     * under this user. We signal that by exiting with 0. */
84   if (option == REPLACE || option == DELETE) {   _exit(safe_read(xopen(file, O_RDONLY), &c, 1) < 0);
  FILE *fo;  
  struct stat st;  
   
  while ((fo = fopen(CRONUPDATE, "a"))) {  
  fprintf(fo, "%s\n", pas->pw_name);  
  fflush(fo);  
  if (fstat(fileno(fo), &st) != 0 || st.st_nlink != 0) {  
  fclose(fo);  
  break;  
  }  
  fclose(fo);  
  /* loop */  
  }  
  if (fo == NULL) {  
  bb_error_msg("cannot append to %s/%s", CDir, CRONUPDATE);  
  }  
  }  
  return 0;  
85  }  }
86    
87  static int GetReplaceStream(const char *user, const char *file)  int crontab_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
88    int crontab_main(int argc UNUSED_PARAM, char **argv)
89  {  {
90   int filedes[2];   const struct passwd *pas;
91   int pid;   const char *crontab_dir = CRONTABS;
92     char *tmp_fname;
93     char *new_fname;
94     char *user_name;  /* -u USER */
95   int fd;   int fd;
96   int n;   int src_fd;
97   char buf[1024];   int opt_ler;
98    
99   if (pipe(filedes) < 0) {   /* file [opts]     Replace crontab from file
100   perror("pipe");   * - [opts]        Replace crontab from stdin
101   return -1;   * -u user         User
102   }   * -c dir          Crontab directory
103   pid = fork();   * -l              List crontab for user
104   if (pid < 0) {   * -e              Edit crontab for user
105   perror("fork");   * -r              Delete crontab for user
106   return -1;   * bbox also supports -d == -r, but most other crontab
107   }   * implementations do not. Deprecated.
108   if (pid > 0) {   */
109   /*   enum {
110   * PARENT   OPT_u = (1 << 0),
111   */   OPT_c = (1 << 1),
112     OPT_l = (1 << 2),
113   close(filedes[1]);   OPT_e = (1 << 3),
114   if (read(filedes[0], buf, 1) != 1) {   OPT_r = (1 << 4),
115   close(filedes[0]);   OPT_ler = OPT_l + OPT_e + OPT_r,
116   filedes[0] = -1;   };
117    
118     opt_complementary = "?1:dr"; /* max one argument; -d implies -r */
119     opt_ler = getopt32(argv, "u:c:lerd", &user_name, &crontab_dir);
120     argv += optind;
121    
122     if (sanitize_env_if_suid()) { /* Clears dangerous stuff, sets PATH */
123     /* run by non-root? */
124     if (opt_ler & (OPT_u|OPT_c))
125     bb_error_msg_and_die("only root can use -c or -u");
126     }
127    
128     if (opt_ler & OPT_u) {
129     pas = getpwnam(user_name);
130     if (!pas)
131     bb_error_msg_and_die("user %s is not known", user_name);
132     } else {
133    /* XXX: xgetpwuid */
134     uid_t my_uid = getuid();
135     pas = getpwuid(my_uid);
136     if (!pas)
137     bb_perror_msg_and_die("unknown uid %d", (int)my_uid);
138     }
139    
140    #define user_name DONT_USE_ME_BEYOND_THIS_POINT
141    
142     /* From now on, keep only -l, -e, -r bits */
143     opt_ler &= OPT_ler;
144     if ((opt_ler - 1) & opt_ler) /* more than one bit set? */
145     bb_show_usage();
146    
147     /* Read replacement file under user's UID/GID/group vector */
148     src_fd = STDIN_FILENO;
149     if (!opt_ler) { /* Replace? */
150     if (!argv[0])
151     bb_show_usage();
152     if (NOT_LONE_DASH(argv[0])) {
153     src_fd = open_as_user(pas, argv[0]);
154     if (src_fd < 0)
155     bb_error_msg_and_die("user %s cannot read %s",
156     pas->pw_name, argv[0]);
157   }   }
  return filedes[0];  
158   }   }
159    
160   /*   /* cd to our crontab directory */
161   * CHILD   xchdir(crontab_dir);
  */  
162    
163   close(filedes[0]);   tmp_fname = NULL;
164    
165   if (ChangeUser(user, 0) < 0)   /* Handle requested operation */
166   exit(0);   switch (opt_ler) {
167    
168   xfunc_error_retval = 0;   default: /* case OPT_r: Delete */
169   fd = xopen(file, O_RDONLY);   unlink(pas->pw_name);
170   buf[0] = 0;   break;
  write(filedes[1], buf, 1);  
  while ((n = read(fd, buf, sizeof(buf))) > 0) {  
  write(filedes[1], buf, n);  
  }  
  exit(0);  
 }  
   
 static void EditFile(const char *user, const char *file)  
 {  
  int pid = fork();  
   
  if (pid == 0) {  
  /*  
  * CHILD - change user and run editor  
  */  
  char *ptr;  
  char visual[1024];  
   
  if (ChangeUser(user, 1) < 0)  
  exit(0);  
  ptr = getenv("VISUAL");  
  if (ptr == NULL || strlen(ptr) > 256)  
  ptr = PATH_VI;  
   
  snprintf(visual, sizeof(visual), "%s %s", ptr, file);  
  execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", visual, NULL);  
  perror("exec");  
  exit(0);  
  }  
  if (pid < 0) {  
  /*  
  * PARENT - failure  
  */  
  bb_perror_msg_and_die("fork");  
  }  
  wait4(pid, NULL, 0, NULL);  
 }  
171    
172  static int ChangeUser(const char *user, short dochdir)   case OPT_l: /* List */
173  {   {
174   struct passwd *pas;   char *args[2] = { pas->pw_name, NULL };
175     return bb_cat(args);
176     /* list exits,
177     * the rest go play with cron update file */
178     }
179    
180     case OPT_e: /* Edit */
181     tmp_fname = xasprintf("%s.%u", crontab_dir, (unsigned)getpid());
182     /* No O_EXCL: we don't want to be stuck if earlier crontabs
183     * were killed, leaving stale temp file behind */
184     src_fd = xopen3(tmp_fname, O_RDWR|O_CREAT|O_TRUNC, 0600);
185     fchown(src_fd, pas->pw_uid, pas->pw_gid);
186     fd = open(pas->pw_name, O_RDONLY);
187     if (fd >= 0) {
188     bb_copyfd_eof(fd, src_fd);
189     close(fd);
190     xlseek(src_fd, 0, SEEK_SET);
191     }
192     close_on_exec_on(src_fd); /* don't want editor to see this fd */
193     edit_file(pas, tmp_fname);
194     /* fall through */
195    
196   /*   case 0: /* Replace (no -l, -e, or -r were given) */
197   * Obtain password entry and change privileges   new_fname = xasprintf("%s.new", pas->pw_name);
198   */   fd = open(new_fname, O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0600);
199     if (fd >= 0) {
200     bb_copyfd_eof(src_fd, fd);
201     close(fd);
202     xrename(new_fname, pas->pw_name);
203     } else {
204     bb_error_msg("cannot create %s/%s",
205     crontab_dir, new_fname);
206     }
207     if (tmp_fname)
208     unlink(tmp_fname);
209     /*free(tmp_fname);*/
210     /*free(new_fname);*/
211    
212   pas = getpwnam(user);   } /* switch */
  if (pas == NULL) {  
  bb_perror_msg_and_die("failed to get uid for %s", user);  
  }  
  setenv("USER", pas->pw_name, 1);  
  setenv("HOME", pas->pw_dir, 1);  
  setenv("SHELL", DEFAULT_SHELL, 1);  
213    
214   /*   /* Bump notification file.  Handle window where crond picks file up
215   * Change running state to the user in question   * before we can write our entry out.
216   */   */
217   change_identity(pas);   while ((fd = open(CRONUPDATE, O_WRONLY|O_CREAT|O_APPEND, 0600)) >= 0) {
218     struct stat st;
219    
220   if (dochdir) {   fdprintf(fd, "%s\n", pas->pw_name);
221   if (chdir(pas->pw_dir) < 0) {   if (fstat(fd, &st) != 0 || st.st_nlink != 0) {
222   bb_perror_msg("chdir(%s) by %s failed", pas->pw_dir, user);   /*close(fd);*/
223   xchdir(TMPDIR);   break;
224   }   }
225     /* st.st_nlink == 0:
226     * file was deleted, maybe crond missed our notification */
227     close(fd);
228     /* loop */
229     }
230     if (fd < 0) {
231     bb_error_msg("cannot append to %s/%s",
232     crontab_dir, CRONUPDATE);
233   }   }
234   return pas->pw_uid;   return 0;
235  }  }

Legend:
Removed from v.815  
changed lines
  Added in v.816