blob: c6bece75bcc87e43502216dd627aa376a3d00902
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 GPLv2 or later, see file LICENSE in this source tree. |
11 | */ |
12 | //config:config CRONTAB |
13 | //config: bool "crontab" |
14 | //config: default y |
15 | //config: help |
16 | //config: Crontab manipulates the crontab for a particular user. Only |
17 | //config: the superuser may specify a different user and/or crontab directory. |
18 | //config: Note that Busybox binary must be setuid root for this applet to |
19 | //config: work properly. |
20 | |
21 | /* Needs to be run by root or be suid root - needs to change /var/spool/cron* files: */ |
22 | //applet:IF_CRONTAB(APPLET(crontab, BB_DIR_USR_BIN, BB_SUID_REQUIRE)) |
23 | |
24 | //kbuild:lib-$(CONFIG_CRONTAB) += crontab.o |
25 | |
26 | //usage:#define crontab_trivial_usage |
27 | //usage: "[-c DIR] [-u USER] [-ler]|[FILE]" |
28 | //usage:#define crontab_full_usage "\n\n" |
29 | //usage: " -c Crontab directory" |
30 | //usage: "\n -u User" |
31 | //usage: "\n -l List crontab" |
32 | //usage: "\n -e Edit crontab" |
33 | //usage: "\n -r Delete crontab" |
34 | //usage: "\n FILE Replace crontab by FILE ('-': stdin)" |
35 | |
36 | #include "libbb.h" |
37 | |
38 | #define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs" |
39 | #ifndef CRONUPDATE |
40 | #define CRONUPDATE "cron.update" |
41 | #endif |
42 | |
43 | static void edit_file(const struct passwd *pas, const char *file) |
44 | { |
45 | const char *ptr; |
46 | pid_t pid; |
47 | |
48 | pid = xvfork(); |
49 | if (pid) { /* parent */ |
50 | wait4pid(pid); |
51 | return; |
52 | } |
53 | |
54 | /* CHILD - change user and run editor */ |
55 | /* initgroups, setgid, setuid */ |
56 | change_identity(pas); |
57 | setup_environment(pas->pw_shell, |
58 | SETUP_ENV_CHANGEENV | SETUP_ENV_TO_TMP, |
59 | pas); |
60 | ptr = getenv("VISUAL"); |
61 | if (!ptr) { |
62 | ptr = getenv("EDITOR"); |
63 | if (!ptr) |
64 | ptr = "vi"; |
65 | } |
66 | |
67 | BB_EXECLP(ptr, ptr, file, NULL); |
68 | bb_perror_msg_and_die("can't execute '%s'", ptr); |
69 | } |
70 | |
71 | int crontab_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
72 | int crontab_main(int argc UNUSED_PARAM, char **argv) |
73 | { |
74 | const struct passwd *pas; |
75 | const char *crontab_dir = CRONTABS; |
76 | char *tmp_fname; |
77 | char *new_fname; |
78 | char *user_name; /* -u USER */ |
79 | int fd; |
80 | int src_fd; |
81 | int opt_ler; |
82 | |
83 | /* file [opts] Replace crontab from file |
84 | * - [opts] Replace crontab from stdin |
85 | * -u user User |
86 | * -c dir Crontab directory |
87 | * -l List crontab for user |
88 | * -e Edit crontab for user |
89 | * -r Delete crontab for user |
90 | * bbox also supports -d == -r, but most other crontab |
91 | * implementations do not. Deprecated. |
92 | */ |
93 | enum { |
94 | OPT_u = (1 << 0), |
95 | OPT_c = (1 << 1), |
96 | OPT_l = (1 << 2), |
97 | OPT_e = (1 << 3), |
98 | OPT_r = (1 << 4), |
99 | OPT_ler = OPT_l + OPT_e + OPT_r, |
100 | }; |
101 | |
102 | opt_complementary = "?1:dr"; /* max one argument; -d implies -r */ |
103 | opt_ler = getopt32(argv, "u:c:lerd", &user_name, &crontab_dir); |
104 | argv += optind; |
105 | |
106 | if (sanitize_env_if_suid()) { /* Clears dangerous stuff, sets PATH */ |
107 | /* Run by non-root */ |
108 | if (opt_ler & (OPT_u|OPT_c)) |
109 | bb_error_msg_and_die("%s", bb_msg_you_must_be_root); |
110 | } |
111 | |
112 | if (opt_ler & OPT_u) { |
113 | pas = xgetpwnam(user_name); |
114 | } else { |
115 | pas = xgetpwuid(getuid()); |
116 | } |
117 | |
118 | #define user_name DONT_USE_ME_BEYOND_THIS_POINT |
119 | |
120 | /* From now on, keep only -l, -e, -r bits */ |
121 | opt_ler &= OPT_ler; |
122 | if ((opt_ler - 1) & opt_ler) /* more than one bit set? */ |
123 | bb_show_usage(); |
124 | |
125 | /* Read replacement file under user's UID/GID/group vector */ |
126 | src_fd = STDIN_FILENO; |
127 | if (!opt_ler) { /* Replace? */ |
128 | if (!argv[0]) |
129 | bb_show_usage(); |
130 | if (NOT_LONE_DASH(argv[0])) { |
131 | src_fd = xopen_as_uid_gid(argv[0], O_RDONLY, pas->pw_uid, pas->pw_gid); |
132 | } |
133 | } |
134 | |
135 | /* cd to our crontab directory */ |
136 | xchdir(crontab_dir); |
137 | |
138 | tmp_fname = NULL; |
139 | |
140 | /* Handle requested operation */ |
141 | switch (opt_ler) { |
142 | |
143 | default: /* case OPT_r: Delete */ |
144 | unlink(pas->pw_name); |
145 | break; |
146 | |
147 | case OPT_l: /* List */ |
148 | { |
149 | char *args[2] = { pas->pw_name, NULL }; |
150 | return bb_cat(args); |
151 | /* list exits, |
152 | * the rest go play with cron update file */ |
153 | } |
154 | |
155 | case OPT_e: /* Edit */ |
156 | tmp_fname = xasprintf("%s.%u", crontab_dir, (unsigned)getpid()); |
157 | /* No O_EXCL: we don't want to be stuck if earlier crontabs |
158 | * were killed, leaving stale temp file behind */ |
159 | src_fd = xopen3(tmp_fname, O_RDWR|O_CREAT|O_TRUNC, 0600); |
160 | fchown(src_fd, pas->pw_uid, pas->pw_gid); |
161 | fd = open(pas->pw_name, O_RDONLY); |
162 | if (fd >= 0) { |
163 | bb_copyfd_eof(fd, src_fd); |
164 | close(fd); |
165 | xlseek(src_fd, 0, SEEK_SET); |
166 | } |
167 | close_on_exec_on(src_fd); /* don't want editor to see this fd */ |
168 | edit_file(pas, tmp_fname); |
169 | /* fall through */ |
170 | |
171 | case 0: /* Replace (no -l, -e, or -r were given) */ |
172 | new_fname = xasprintf("%s.new", pas->pw_name); |
173 | fd = open(new_fname, O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0600); |
174 | if (fd >= 0) { |
175 | bb_copyfd_eof(src_fd, fd); |
176 | close(fd); |
177 | xrename(new_fname, pas->pw_name); |
178 | } else { |
179 | bb_error_msg("can't create %s/%s", |
180 | crontab_dir, new_fname); |
181 | } |
182 | if (tmp_fname) |
183 | unlink(tmp_fname); |
184 | /*free(tmp_fname);*/ |
185 | /*free(new_fname);*/ |
186 | } /* switch */ |
187 | |
188 | /* Bump notification file. Handle window where crond picks file up |
189 | * before we can write our entry out. |
190 | */ |
191 | while ((fd = open(CRONUPDATE, O_WRONLY|O_CREAT|O_APPEND, 0600)) >= 0) { |
192 | struct stat st; |
193 | |
194 | fdprintf(fd, "%s\n", pas->pw_name); |
195 | if (fstat(fd, &st) != 0 || st.st_nlink != 0) { |
196 | /*close(fd);*/ |
197 | break; |
198 | } |
199 | /* st.st_nlink == 0: |
200 | * file was deleted, maybe crond missed our notification */ |
201 | close(fd); |
202 | /* loop */ |
203 | } |
204 | if (fd < 0) { |
205 | bb_error_msg("can't append to %s/%s", |
206 | crontab_dir, CRONUPDATE); |
207 | } |
208 | return 0; |
209 | } |
210 |