Rev 67 | Rev 73 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
45 | luk | 1 | |
2 | /// inotify cron table manipulator main file |
||
3 | /** |
||
4 | * \file ict-main.cpp |
||
5 | * |
||
6 | * inotify cron system |
||
7 | * |
||
63 | luk | 8 | * Copyright (C) 2006, 2007 Lukas Jelinek, <lukas@aiken.cz> |
45 | luk | 9 | * |
10 | * This program is free software; you can use it, redistribute |
||
11 | * it and/or modify it under the terms of the GNU General Public |
||
12 | * License, version 2 (see LICENSE-GPL). |
||
13 | * |
||
14 | */ |
||
15 | |||
16 | |||
17 | #include <argp.h> |
||
18 | #include <pwd.h> |
||
19 | #include <string> |
||
20 | #include <stdio.h> |
||
21 | #include <unistd.h> |
||
22 | #include <sys/stat.h> |
||
23 | #include <sys/wait.h> |
||
67 | luk | 24 | #include <sys/inotify.h> |
69 | luk | 25 | #include <fcntl.h> |
45 | luk | 26 | |
69 | luk | 27 | #include "inotify-cxx.h" |
28 | #include "appargs.h" |
||
29 | |||
55 | luk | 30 | #include "incron.h" |
45 | luk | 31 | #include "incrontab.h" |
69 | luk | 32 | #include "incroncfg.h" |
45 | luk | 33 | |
69 | luk | 34 | |
35 | /// Alternative editor |
||
67 | luk | 36 | #define INCRON_ALT_EDITOR "/etc/alternatives/editor" |
45 | luk | 37 | |
69 | luk | 38 | /// Default (hard-wired) editor |
45 | luk | 39 | #define INCRON_DEFAULT_EDITOR "vim" |
40 | |||
69 | luk | 41 | /// incrontab version string |
42 | #define INCRONTAB_VERSION INCRONTAB_NAME " " INCRON_VERSION |
||
45 | luk | 43 | |
69 | luk | 44 | /// incrontab description string |
45 | #define INCRONTAB_DESCRIPTION "incrontab - inotify cron table manipulator\n" \ |
||
46 | "(c) Lukas Jelinek, 2006, 2007" |
||
45 | luk | 47 | |
69 | luk | 48 | /// incrontab help string |
49 | #define INCRONTAB_HELP INCRONTAB_DESCRIPTION "\n\n" \ |
||
50 | "usage: incrontab [<options>] <operation>\n" \ |
||
51 | " incrontab [<options>] <FILE-TO-IMPORT>\n\n" \ |
||
52 | "<operation> may be one of the following:\n" \ |
||
53 | " -?, --about gives short information about program\n" \ |
||
54 | " -h, --help prints this help text\n" \ |
||
55 | " -l, --list lists user table\n" \ |
||
56 | " -r, --remove removes user table\n" \ |
||
57 | " -e, --edit provides editting user table\n" \ |
||
58 | " -t, --types list supported event types\n" \ |
||
59 | " -d, --reload request incrond to reload user table\n" \ |
||
60 | " -V, --version prints program version\n\n" \ |
||
61 | "\n" \ |
||
62 | "These options may be used:\n" \ |
||
63 | " -u <USER>, --user=<USER> overrides current user (requires root privileges)\n" \ |
||
64 | " -f <FILE>, --config=<FILE> overrides default configuration file (requires root privileges)\n\n" \ |
||
65 | "For reporting bugs please use http:://bts.aiken.cz\n" |
||
66 | |||
45 | luk | 67 | |
68 | |||
69 | |||
49 | luk | 70 | /// Copies a file to an user table. |
71 | /** |
||
69 | luk | 72 | * \param[in] rPath path to file |
73 | * \param[in] rUser user name |
||
49 | luk | 74 | * \return true = success, false = failure |
75 | */ |
||
69 | luk | 76 | bool copy_from_file(const std::string& rPath, const std::string& rUser) |
45 | luk | 77 | { |
69 | luk | 78 | fprintf(stderr, "copying table from file '%s'\n", rPath.c_str()); |
79 | |||
80 | IncronTab tab; |
||
81 | std::string s(rPath); |
||
45 | luk | 82 | if (s == "-") |
83 | s = "/dev/stdin"; |
||
84 | if (!tab.Load(s)) { |
||
69 | luk | 85 | fprintf(stderr, "cannot load table from file '%s'\n", rPath.c_str()); |
45 | luk | 86 | return false; |
87 | } |
||
88 | |||
69 | luk | 89 | std::string out(IncronTab::GetUserTablePath(rUser)); |
45 | luk | 90 | if (!tab.Save(out)) { |
69 | luk | 91 | fprintf(stderr, "cannot create table for user '%s'\n", rUser.c_str()); |
45 | luk | 92 | return false; |
93 | } |
||
94 | |||
95 | return true; |
||
96 | } |
||
97 | |||
49 | luk | 98 | /// Removes an user table. |
99 | /** |
||
69 | luk | 100 | * \param[in] rUser user name |
49 | luk | 101 | * \return true = success, false = failure |
102 | */ |
||
69 | luk | 103 | bool remove_table(const std::string& rUser) |
45 | luk | 104 | { |
69 | luk | 105 | fprintf(stderr, "removing table for user '%s'\n", rUser.c_str()); |
106 | |||
107 | std::string tp(IncronTab::GetUserTablePath(rUser)); |
||
45 | luk | 108 | |
109 | if (unlink(tp.c_str()) != 0 && errno != ENOENT) { |
||
69 | luk | 110 | fprintf(stderr, "cannot remove table for user '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 111 | return false; |
112 | } |
||
113 | |||
114 | return true; |
||
115 | } |
||
116 | |||
49 | luk | 117 | /// Lists an user table. |
118 | /** |
||
69 | luk | 119 | * \param[in] rUser user name |
49 | luk | 120 | * \return true = success, false = failure |
121 | */ |
||
69 | luk | 122 | bool list_table(const std::string& rUser) |
45 | luk | 123 | { |
69 | luk | 124 | std::string tp(IncronTab::GetUserTablePath(rUser)); |
45 | luk | 125 | |
69 | luk | 126 | FILE* f = fopen(tp.c_str(), "r"); |
127 | if (f == NULL) { |
||
45 | luk | 128 | if (errno == ENOENT) { |
69 | luk | 129 | fprintf(stderr, "no table for %s\n", rUser.c_str()); |
45 | luk | 130 | return true; |
131 | } |
||
132 | else { |
||
69 | luk | 133 | fprintf(stderr, "cannot read table for '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 134 | return false; |
135 | } |
||
136 | } |
||
65 | luk | 137 | |
138 | char s[1024]; |
||
139 | while (fgets(s, 1024, f) != NULL) { |
||
140 | fputs(s, stdout); |
||
141 | } |
||
142 | |||
143 | fclose(f); |
||
144 | |||
145 | return true; |
||
45 | luk | 146 | } |
147 | |||
49 | luk | 148 | /// Allows to edit an user table. |
149 | /** |
||
69 | luk | 150 | * \param[in] rUser user name |
49 | luk | 151 | * \return true = success, false = failure |
152 | * |
||
153 | * \attention This function is very complex and may contain |
||
154 | * various bugs including security ones. Please keep |
||
155 | * it in mind.. |
||
156 | */ |
||
69 | luk | 157 | bool edit_table(const std::string& rUser) |
45 | luk | 158 | { |
69 | luk | 159 | std::string tp(IncronTab::GetUserTablePath(rUser)); |
45 | luk | 160 | |
69 | luk | 161 | struct passwd* ppwd = getpwnam(rUser.c_str()); |
45 | luk | 162 | if (ppwd == NULL) { |
69 | luk | 163 | fprintf(stderr, "cannot find user '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 164 | return false; |
165 | } |
||
61 | luk | 166 | |
45 | luk | 167 | uid_t uid = ppwd->pw_uid; |
61 | luk | 168 | uid_t gid = ppwd->pw_gid; |
45 | luk | 169 | |
170 | char s[NAME_MAX]; |
||
171 | strcpy(s, "/tmp/incron.table-XXXXXX"); |
||
172 | |||
173 | uid_t iu = geteuid(); |
||
61 | luk | 174 | uid_t ig = getegid(); |
55 | luk | 175 | |
63 | luk | 176 | if (setegid(gid) != 0 || seteuid(uid) != 0) { |
69 | luk | 177 | fprintf(stderr, "cannot change effective UID/GID for user '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 178 | return false; |
179 | } |
||
180 | |||
181 | int fd = mkstemp(s); |
||
182 | if (fd == -1) { |
||
183 | fprintf(stderr, "cannot create temporary file: %s\n", strerror(errno)); |
||
184 | return false; |
||
185 | } |
||
186 | |||
61 | luk | 187 | bool ok = false; |
188 | FILE* out = NULL; |
||
189 | FILE* in = NULL; |
||
190 | time_t mt = (time_t) 0; |
||
191 | const char* e = NULL; |
||
69 | luk | 192 | std::string ed; |
61 | luk | 193 | |
63 | luk | 194 | if (setegid(ig) != 0 || seteuid(iu) != 0) { |
61 | luk | 195 | fprintf(stderr, "cannot change effective UID/GID: %s\n", strerror(errno)); |
45 | luk | 196 | close(fd); |
61 | luk | 197 | goto end; |
45 | luk | 198 | } |
199 | |||
61 | luk | 200 | out = fdopen(fd, "w"); |
45 | luk | 201 | if (out == NULL) { |
202 | fprintf(stderr, "cannot write to temporary file: %s\n", strerror(errno)); |
||
203 | close(fd); |
||
61 | luk | 204 | goto end; |
45 | luk | 205 | } |
206 | |||
61 | luk | 207 | in = fopen(tp.c_str(), "r"); |
45 | luk | 208 | if (in == NULL) { |
209 | if (errno == ENOENT) { |
||
210 | in = fopen("/dev/null", "r"); |
||
211 | if (in == NULL) { |
||
69 | luk | 212 | fprintf(stderr, "cannot get empty table for '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 213 | fclose(out); |
61 | luk | 214 | goto end; |
45 | luk | 215 | } |
216 | } |
||
217 | else { |
||
69 | luk | 218 | fprintf(stderr, "cannot read old table for '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 219 | fclose(out); |
61 | luk | 220 | goto end; |
45 | luk | 221 | } |
222 | } |
||
223 | |||
224 | char buf[1024]; |
||
225 | while (fgets(buf, 1024, in) != NULL) { |
||
226 | fputs(buf, out); |
||
227 | } |
||
228 | fclose(in); |
||
229 | fclose(out); |
||
230 | |||
231 | struct stat st; |
||
232 | if (stat(s, &st) != 0) { |
||
233 | fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno)); |
||
61 | luk | 234 | goto end; |
45 | luk | 235 | } |
236 | |||
67 | luk | 237 | mt = st.st_mtime; // save modification time for detecting its change |
45 | luk | 238 | |
67 | luk | 239 | // Editor selecting algorithm: |
240 | // 1. Check EDITOR environment variable |
||
241 | // 2. Check VISUAL environment variable |
||
69 | luk | 242 | // 3. Try to get from configuration |
243 | // 4. Check presence of /etc/alternatives/editor |
||
244 | // 5. Use hard-wired editor |
||
245 | |||
61 | luk | 246 | e = getenv("EDITOR"); |
67 | luk | 247 | if (e == NULL) { |
248 | e = getenv("VISUAL"); |
||
249 | if (e == NULL) { |
||
69 | luk | 250 | |
251 | if (!IncronCfg::GetValue("editor", ed)) |
||
252 | throw InotifyException("configuration is corrupted", EINVAL); |
||
253 | |||
254 | if (!ed.empty()) { |
||
255 | e = ed.c_str(); |
||
256 | } |
||
257 | else { |
||
258 | if (access(INCRON_ALT_EDITOR, X_OK) == 0) |
||
259 | e = INCRON_ALT_EDITOR; |
||
260 | else |
||
261 | e = INCRON_DEFAULT_EDITOR; |
||
262 | } |
||
67 | luk | 263 | } |
264 | } |
||
45 | luk | 265 | |
67 | luk | 266 | // this block is explicite due to gotos' usage simplification |
61 | luk | 267 | { |
268 | pid_t pid = fork(); |
||
269 | if (pid == 0) { |
||
63 | luk | 270 | if (setgid(gid) != 0 || setuid(uid) != 0) { |
69 | luk | 271 | fprintf(stderr, "cannot set user '%s': %s\n", rUser.c_str(), strerror(errno)); |
61 | luk | 272 | goto end; |
273 | } |
||
274 | |||
275 | execlp(e, e, s, NULL); |
||
276 | _exit(1); |
||
45 | luk | 277 | } |
61 | luk | 278 | else if (pid > 0) { |
279 | int status; |
||
280 | if (wait(&status) != pid) { |
||
281 | perror("error while waiting for editor"); |
||
282 | goto end; |
||
283 | } |
||
284 | if (!(WIFEXITED(status)) || WEXITSTATUS(status) != 0) { |
||
285 | perror("editor finished with error"); |
||
286 | goto end; |
||
287 | } |
||
45 | luk | 288 | } |
61 | luk | 289 | else { |
290 | perror("cannot start editor"); |
||
291 | goto end; |
||
292 | } |
||
45 | luk | 293 | } |
294 | |||
295 | if (stat(s, &st) != 0) { |
||
296 | fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno)); |
||
61 | luk | 297 | goto end; |
45 | luk | 298 | } |
299 | |||
300 | if (st.st_mtime == mt) { |
||
301 | fprintf(stderr, "table unchanged\n"); |
||
61 | luk | 302 | ok = true; |
303 | goto end; |
||
45 | luk | 304 | } |
305 | |||
61 | luk | 306 | { |
69 | luk | 307 | IncronTab ict; |
63 | luk | 308 | if (ict.Load(s) && ict.Save(tp)) { |
309 | if (chmod(tp.c_str(), S_IRUSR | S_IWUSR) != 0) { |
||
310 | fprintf(stderr, "cannot change mode of temporary file: %s\n", strerror(errno)); |
||
311 | } |
||
312 | } |
||
313 | else { |
||
61 | luk | 314 | fprintf(stderr, "cannot move temporary table: %s\n", strerror(errno)); |
315 | goto end; |
||
316 | } |
||
63 | luk | 317 | |
45 | luk | 318 | } |
319 | |||
61 | luk | 320 | ok = true; |
45 | luk | 321 | fprintf(stderr, "table updated\n"); |
322 | |||
61 | luk | 323 | end: |
324 | |||
325 | unlink(s); |
||
326 | return ok; |
||
45 | luk | 327 | } |
328 | |||
329 | |||
67 | luk | 330 | /// Prints the list of all available inotify event types. |
331 | void list_types() |
||
332 | { |
||
333 | printf( "IN_ACCESS,IN_MODIFY,IN_ATTRIB,IN_CLOSE_WRITE,"\ |
||
334 | "IN_CLOSE_NOWRITE,IN_OPEN,IN_MOVED_FROM,IN_MOVED_TO,"\ |
||
335 | "IN_CREATE,IN_DELETE,IN_DELETE_SELF,IN_CLOSE,IN_MOVE,"\ |
||
336 | "IN_ONESHOT,IN_ALL_EVENTS"); |
||
337 | |||
338 | #ifdef IN_DONT_FOLLOW |
||
339 | printf(",IN_DONT_FOLLOW"); |
||
340 | #endif // IN_DONT_FOLLOW |
||
341 | |||
342 | #ifdef IN_ONLYDIR |
||
343 | printf(",IN_ONLYDIR"); |
||
344 | #endif // IN_ONLYDIR |
||
345 | |||
346 | #ifdef IN_MOVE_SELF |
||
347 | printf(",IN_MOVE_SELF"); |
||
348 | #endif // IN_MOVE_SELF |
||
349 | |||
350 | printf("\n"); |
||
351 | } |
||
352 | |||
69 | luk | 353 | /// Reloads an user table. |
354 | /** |
||
355 | * \param[in] rUser user name |
||
356 | * \return true = success, false = otherwise |
||
357 | */ |
||
358 | bool reload_table(const std::string& rUser) |
||
359 | { |
||
360 | fprintf(stderr, "requesting table reload for user '%s'...\n", rUser.c_str()); |
||
361 | |||
362 | std::string tp(IncronTab::GetUserTablePath(rUser)); |
||
363 | |||
364 | int fd = open(tp.c_str(), O_WRONLY | O_APPEND); |
||
365 | if (fd == -1) { |
||
366 | if (errno == ENOENT) { |
||
367 | fprintf(stderr, "no table for '%s'\n", rUser.c_str()); |
||
368 | return true; |
||
369 | } |
||
370 | else { |
||
371 | fprintf(stderr, "cannot access table for '%s': %s\n", rUser.c_str(), strerror(errno)); |
||
372 | return false; |
||
373 | } |
||
374 | } |
||
375 | |||
376 | close(fd); |
||
377 | |||
378 | fprintf(stderr, "request done\n"); |
||
379 | |||
380 | return true; |
||
381 | } |
||
67 | luk | 382 | |
45 | luk | 383 | int main(int argc, char** argv) |
384 | { |
||
69 | luk | 385 | AppArgs::Init(); |
386 | |||
387 | if (!( AppArgs::AddOption("about", '?', AAT_NO_VALUE, false) |
||
388 | && AppArgs::AddOption("help", 'h', AAT_NO_VALUE, false) |
||
389 | && AppArgs::AddOption("list", 'l', AAT_NO_VALUE, false) |
||
390 | && AppArgs::AddOption("remove", 'r', AAT_NO_VALUE, false) |
||
391 | && AppArgs::AddOption("edit", 'e', AAT_NO_VALUE, false) |
||
392 | && AppArgs::AddOption("types", 't', AAT_NO_VALUE, false) |
||
393 | && AppArgs::AddOption("reload", 'd', AAT_NO_VALUE, false) |
||
394 | && AppArgs::AddOption("user", 'u', AAT_MANDATORY_VALUE, false) |
||
395 | && AppArgs::AddOption("config", 'f', AAT_MANDATORY_VALUE, false)) |
||
396 | && AppArgs::AddOption("version", 'V', AAT_NO_VALUE, false)) |
||
397 | { |
||
398 | fprintf(stderr, "error while initializing application"); |
||
399 | return 1; |
||
400 | } |
||
45 | luk | 401 | |
69 | luk | 402 | AppArgs::Parse(argc, argv); |
45 | luk | 403 | |
69 | luk | 404 | if (AppArgs::ExistsOption("help")) { |
405 | fprintf(stderr, "%s\n", INCRONTAB_HELP); |
||
406 | return 0; |
||
407 | } |
||
45 | luk | 408 | |
69 | luk | 409 | if (AppArgs::ExistsOption("about")) { |
410 | fprintf(stderr, "%s\n", INCRONTAB_DESCRIPTION); |
||
411 | return 0; |
||
412 | } |
||
413 | |||
414 | if (AppArgs::ExistsOption("version")) { |
||
415 | fprintf(stderr, "%s\n", INCRONTAB_VERSION); |
||
416 | return 0; |
||
417 | } |
||
418 | |||
419 | bool oper = AppArgs::ExistsOption("list") |
||
420 | || AppArgs::ExistsOption("remove") |
||
421 | || AppArgs::ExistsOption("edit") |
||
422 | || AppArgs::ExistsOption("types") |
||
423 | || AppArgs::ExistsOption("reload"); |
||
424 | |||
425 | size_t vals = AppArgs::GetValueCount(); |
||
426 | |||
427 | if (!oper && vals == 0) { |
||
428 | fprintf(stderr, "invalid arguments - specify operation or source file\n"); |
||
45 | luk | 429 | return 1; |
430 | } |
||
69 | luk | 431 | |
432 | if (oper && vals > 0) { |
||
433 | fprintf(stderr, "invalid arguments - operation and source file cannot be combined\n"); |
||
45 | luk | 434 | return 1; |
435 | } |
||
436 | |||
437 | uid_t uid = getuid(); |
||
438 | |||
69 | luk | 439 | std::string user; |
440 | bool chuser = AppArgs::GetOption("user", user); |
||
441 | |||
442 | if (uid != 0 && chuser) { |
||
443 | fprintf(stderr, "cannot override user to '%s': insufficient privileges\n", user.c_str()); |
||
45 | luk | 444 | return 1; |
445 | } |
||
446 | |||
447 | struct passwd pwd; |
||
448 | |||
69 | luk | 449 | if (!chuser) { |
45 | luk | 450 | struct passwd* ppwd = getpwuid(uid); |
451 | if (ppwd == NULL) { |
||
452 | fprintf(stderr, "cannot determine current user\n"); |
||
453 | return 1; |
||
454 | } |
||
455 | memcpy(&pwd, ppwd, sizeof(pwd)); |
||
69 | luk | 456 | user = pwd.pw_name; |
45 | luk | 457 | } |
69 | luk | 458 | else if (getpwnam(user.c_str()) == NULL) { |
459 | fprintf(stderr, "user '%s' not found\n", user.c_str()); |
||
45 | luk | 460 | return 1; |
461 | } |
||
462 | |||
69 | luk | 463 | try { |
45 | luk | 464 | |
69 | luk | 465 | IncronCfg::Init(); |
466 | |||
467 | std::string cfg(INCRON_CONFIG); |
||
468 | if (AppArgs::GetOption("config", cfg)) { |
||
469 | if (uid != 0) { |
||
470 | fprintf(stderr, "insufficient privileges to use custom configuration (will use default)\n"); |
||
471 | } |
||
472 | else if (euidaccess(cfg.c_str(), R_OK) != 0) { |
||
473 | perror("cannot read configuration file (will use default)"); |
||
474 | } |
||
475 | } |
||
476 | |||
477 | IncronCfg::Load(cfg); |
||
478 | |||
479 | if (!IncronTab::CheckUser(user)) { |
||
480 | fprintf(stderr, "user '%s' is not allowed to use incron\n", user.c_str()); |
||
481 | return 1; |
||
482 | } |
||
483 | |||
484 | if (!oper) { |
||
485 | std::string file; |
||
486 | if (!AppArgs::GetValue(0, file) |
||
487 | || !copy_from_file(file, user)) |
||
488 | { |
||
45 | luk | 489 | return 1; |
69 | luk | 490 | } |
491 | } |
||
492 | else { |
||
493 | if (AppArgs::ExistsOption("list")) { |
||
494 | if (!list_table(user)) |
||
495 | return 1; |
||
496 | } |
||
497 | else if (AppArgs::ExistsOption("remove")) { |
||
498 | if (!remove_table(user)) |
||
499 | return 1; |
||
500 | } |
||
501 | else if (AppArgs::ExistsOption("edit")) { |
||
502 | if (!edit_table(user)) |
||
503 | return 1; |
||
504 | } |
||
505 | else if (AppArgs::ExistsOption("types")) { |
||
506 | list_types(); |
||
507 | } |
||
508 | else if (AppArgs::ExistsOption("reload")) { |
||
509 | if (!reload_table(user)) |
||
510 | return 1; |
||
511 | } |
||
512 | else { |
||
513 | fprintf(stderr, "invalid usage\n"); |
||
45 | luk | 514 | return 1; |
69 | luk | 515 | } |
516 | } |
||
517 | |||
518 | return 0; |
||
519 | |||
520 | } catch (InotifyException e) { |
||
521 | fprintf(stderr, "*** unhandled exception occurred ***\n"); |
||
522 | fprintf(stderr, "%s\n", e.GetMessage().c_str()); |
||
523 | fprintf(stderr, "error: (%i) %s\n", e.GetErrorNumber(), strerror(e.GetErrorNumber())); |
||
524 | |||
525 | return 1; |
||
45 | luk | 526 | } |
527 | } |