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