Contents of /tags/mkinitrd-6_1_3/busybox/miscutils/crontab.c
Parent Directory | Revision Log
Revision 846 -
(show annotations)
(download)
Mon May 4 18:51:23 2009 UTC (15 years, 4 months ago) by niro
File MIME type: text/plain
File size: 5723 byte(s)
Mon May 4 18:51:23 2009 UTC (15 years, 4 months ago) by niro
File MIME type: text/plain
File size: 5723 byte(s)
tagged 'mkinitrd-6_1_3'
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * CRONTAB |
4 | * |
5 | * usually setuid root, -c option only works if getuid() == geteuid() |
6 | * |
7 | * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com) |
8 | * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 |
9 | * |
10 | * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. |
11 | */ |
12 | |
13 | #include "libbb.h" |
14 | |
15 | #ifndef CRONTABS |
16 | #define CRONTABS "/var/spool/cron/crontabs" |
17 | #endif |
18 | #ifndef CRONUPDATE |
19 | #define CRONUPDATE "cron.update" |
20 | #endif |
21 | |
22 | static void change_user(const struct passwd *pas) |
23 | { |
24 | xsetenv("USER", pas->pw_name); |
25 | xsetenv("HOME", pas->pw_dir); |
26 | xsetenv("SHELL", DEFAULT_SHELL); |
27 | |
28 | /* initgroups, setgid, setuid */ |
29 | change_identity(pas); |
30 | |
31 | if (chdir(pas->pw_dir) < 0) { |
32 | bb_perror_msg("chdir(%s) by %s failed", |
33 | pas->pw_dir, pas->pw_name); |
34 | xchdir("/tmp"); |
35 | } |
36 | } |
37 | |
38 | static void edit_file(const struct passwd *pas, const char *file) |
39 | { |
40 | const char *ptr; |
41 | int pid = vfork(); |
42 | |
43 | if (pid < 0) /* failure */ |
44 | bb_perror_msg_and_die("vfork"); |
45 | if (pid) { /* parent */ |
46 | 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 | bb_perror_msg_and_die("exec %s", ptr); |
61 | } |
62 | |
63 | static int open_as_user(const struct passwd *pas, const char *file) |
64 | { |
65 | pid_t pid; |
66 | char c; |
67 | |
68 | pid = vfork(); |
69 | if (pid < 0) /* ERROR */ |
70 | bb_perror_msg_and_die("vfork"); |
71 | if (pid) { /* PARENT */ |
72 | if (wait4pid(pid) == 0) { |
73 | /* exitcode 0: child says it can read */ |
74 | return open(file, O_RDONLY); |
75 | } |
76 | return -1; |
77 | } |
78 | |
79 | /* CHILD */ |
80 | /* initgroups, setgid, setuid */ |
81 | 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 | _exit(safe_read(xopen(file, O_RDONLY), &c, 1) < 0); |
85 | } |
86 | |
87 | int crontab_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
88 | int crontab_main(int argc UNUSED_PARAM, char **argv) |
89 | { |
90 | const struct passwd *pas; |
91 | const char *crontab_dir = CRONTABS; |
92 | char *tmp_fname; |
93 | char *new_fname; |
94 | char *user_name; /* -u USER */ |
95 | int fd; |
96 | int src_fd; |
97 | int opt_ler; |
98 | |
99 | /* file [opts] Replace crontab from file |
100 | * - [opts] Replace crontab from stdin |
101 | * -u user User |
102 | * -c dir Crontab directory |
103 | * -l List crontab for user |
104 | * -e Edit crontab for user |
105 | * -r Delete crontab for user |
106 | * bbox also supports -d == -r, but most other crontab |
107 | * implementations do not. Deprecated. |
108 | */ |
109 | enum { |
110 | OPT_u = (1 << 0), |
111 | OPT_c = (1 << 1), |
112 | OPT_l = (1 << 2), |
113 | OPT_e = (1 << 3), |
114 | OPT_r = (1 << 4), |
115 | OPT_ler = OPT_l + OPT_e + OPT_r, |
116 | }; |
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 | } |
158 | } |
159 | |
160 | /* cd to our crontab directory */ |
161 | xchdir(crontab_dir); |
162 | |
163 | tmp_fname = NULL; |
164 | |
165 | /* Handle requested operation */ |
166 | switch (opt_ler) { |
167 | |
168 | default: /* case OPT_r: Delete */ |
169 | unlink(pas->pw_name); |
170 | break; |
171 | |
172 | case OPT_l: /* List */ |
173 | { |
174 | 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 | 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 | } /* switch */ |
213 | |
214 | /* Bump notification file. Handle window where crond picks file up |
215 | * before we can write our entry out. |
216 | */ |
217 | while ((fd = open(CRONUPDATE, O_WRONLY|O_CREAT|O_APPEND, 0600)) >= 0) { |
218 | struct stat st; |
219 | |
220 | fdprintf(fd, "%s\n", pas->pw_name); |
221 | if (fstat(fd, &st) != 0 || st.st_nlink != 0) { |
222 | /*close(fd);*/ |
223 | 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 0; |
235 | } |