Rev 69 | Rev 75 | 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 | |
73 | luk | 109 | if (unlink(tp.c_str()) != 0) { |
110 | if (errno == ENOENT) { |
||
111 | fprintf(stderr, "table for user '%s' does not exist\n", rUser.c_str()); |
||
112 | return true; |
||
113 | } |
||
114 | else { |
||
115 | fprintf(stderr, "cannot remove table for user '%s': %s\n", rUser.c_str(), strerror(errno)); |
||
116 | return false; |
||
117 | } |
||
45 | luk | 118 | } |
73 | luk | 119 | |
120 | fprintf(stderr, "table for user '%s' successfully removed\n", rUser.c_str()); |
||
45 | luk | 121 | return true; |
122 | } |
||
123 | |||
49 | luk | 124 | /// Lists an user table. |
125 | /** |
||
69 | luk | 126 | * \param[in] rUser user name |
49 | luk | 127 | * \return true = success, false = failure |
128 | */ |
||
69 | luk | 129 | bool list_table(const std::string& rUser) |
45 | luk | 130 | { |
69 | luk | 131 | std::string tp(IncronTab::GetUserTablePath(rUser)); |
45 | luk | 132 | |
69 | luk | 133 | FILE* f = fopen(tp.c_str(), "r"); |
134 | if (f == NULL) { |
||
45 | luk | 135 | if (errno == ENOENT) { |
69 | luk | 136 | fprintf(stderr, "no table for %s\n", rUser.c_str()); |
45 | luk | 137 | return true; |
138 | } |
||
139 | else { |
||
69 | luk | 140 | fprintf(stderr, "cannot read table for '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 141 | return false; |
142 | } |
||
143 | } |
||
65 | luk | 144 | |
145 | char s[1024]; |
||
146 | while (fgets(s, 1024, f) != NULL) { |
||
147 | fputs(s, stdout); |
||
148 | } |
||
149 | |||
150 | fclose(f); |
||
151 | |||
152 | return true; |
||
45 | luk | 153 | } |
154 | |||
49 | luk | 155 | /// Allows to edit an user table. |
156 | /** |
||
69 | luk | 157 | * \param[in] rUser user name |
49 | luk | 158 | * \return true = success, false = failure |
159 | * |
||
160 | * \attention This function is very complex and may contain |
||
161 | * various bugs including security ones. Please keep |
||
162 | * it in mind.. |
||
163 | */ |
||
69 | luk | 164 | bool edit_table(const std::string& rUser) |
45 | luk | 165 | { |
69 | luk | 166 | std::string tp(IncronTab::GetUserTablePath(rUser)); |
45 | luk | 167 | |
69 | luk | 168 | struct passwd* ppwd = getpwnam(rUser.c_str()); |
45 | luk | 169 | if (ppwd == NULL) { |
69 | luk | 170 | fprintf(stderr, "cannot find user '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 171 | return false; |
172 | } |
||
61 | luk | 173 | |
45 | luk | 174 | uid_t uid = ppwd->pw_uid; |
61 | luk | 175 | uid_t gid = ppwd->pw_gid; |
45 | luk | 176 | |
177 | char s[NAME_MAX]; |
||
178 | strcpy(s, "/tmp/incron.table-XXXXXX"); |
||
179 | |||
180 | uid_t iu = geteuid(); |
||
61 | luk | 181 | uid_t ig = getegid(); |
55 | luk | 182 | |
63 | luk | 183 | if (setegid(gid) != 0 || seteuid(uid) != 0) { |
69 | luk | 184 | fprintf(stderr, "cannot change effective UID/GID for user '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 185 | return false; |
186 | } |
||
187 | |||
188 | int fd = mkstemp(s); |
||
189 | if (fd == -1) { |
||
190 | fprintf(stderr, "cannot create temporary file: %s\n", strerror(errno)); |
||
191 | return false; |
||
192 | } |
||
193 | |||
61 | luk | 194 | bool ok = false; |
195 | FILE* out = NULL; |
||
196 | FILE* in = NULL; |
||
197 | time_t mt = (time_t) 0; |
||
198 | const char* e = NULL; |
||
69 | luk | 199 | std::string ed; |
61 | luk | 200 | |
63 | luk | 201 | if (setegid(ig) != 0 || seteuid(iu) != 0) { |
61 | luk | 202 | fprintf(stderr, "cannot change effective UID/GID: %s\n", strerror(errno)); |
45 | luk | 203 | close(fd); |
61 | luk | 204 | goto end; |
45 | luk | 205 | } |
206 | |||
61 | luk | 207 | out = fdopen(fd, "w"); |
45 | luk | 208 | if (out == NULL) { |
209 | fprintf(stderr, "cannot write to temporary file: %s\n", strerror(errno)); |
||
210 | close(fd); |
||
61 | luk | 211 | goto end; |
45 | luk | 212 | } |
213 | |||
61 | luk | 214 | in = fopen(tp.c_str(), "r"); |
45 | luk | 215 | if (in == NULL) { |
216 | if (errno == ENOENT) { |
||
217 | in = fopen("/dev/null", "r"); |
||
218 | if (in == NULL) { |
||
69 | luk | 219 | fprintf(stderr, "cannot get empty table for '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 220 | fclose(out); |
61 | luk | 221 | goto end; |
45 | luk | 222 | } |
223 | } |
||
224 | else { |
||
69 | luk | 225 | fprintf(stderr, "cannot read old table for '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 226 | fclose(out); |
61 | luk | 227 | goto end; |
45 | luk | 228 | } |
229 | } |
||
230 | |||
231 | char buf[1024]; |
||
232 | while (fgets(buf, 1024, in) != NULL) { |
||
233 | fputs(buf, out); |
||
234 | } |
||
235 | fclose(in); |
||
236 | fclose(out); |
||
237 | |||
238 | struct stat st; |
||
239 | if (stat(s, &st) != 0) { |
||
240 | fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno)); |
||
61 | luk | 241 | goto end; |
45 | luk | 242 | } |
243 | |||
67 | luk | 244 | mt = st.st_mtime; // save modification time for detecting its change |
45 | luk | 245 | |
67 | luk | 246 | // Editor selecting algorithm: |
247 | // 1. Check EDITOR environment variable |
||
248 | // 2. Check VISUAL environment variable |
||
69 | luk | 249 | // 3. Try to get from configuration |
250 | // 4. Check presence of /etc/alternatives/editor |
||
251 | // 5. Use hard-wired editor |
||
252 | |||
61 | luk | 253 | e = getenv("EDITOR"); |
67 | luk | 254 | if (e == NULL) { |
255 | e = getenv("VISUAL"); |
||
256 | if (e == NULL) { |
||
69 | luk | 257 | |
258 | if (!IncronCfg::GetValue("editor", ed)) |
||
259 | throw InotifyException("configuration is corrupted", EINVAL); |
||
260 | |||
261 | if (!ed.empty()) { |
||
262 | e = ed.c_str(); |
||
263 | } |
||
264 | else { |
||
265 | if (access(INCRON_ALT_EDITOR, X_OK) == 0) |
||
266 | e = INCRON_ALT_EDITOR; |
||
267 | else |
||
268 | e = INCRON_DEFAULT_EDITOR; |
||
269 | } |
||
67 | luk | 270 | } |
271 | } |
||
45 | luk | 272 | |
67 | luk | 273 | // this block is explicite due to gotos' usage simplification |
61 | luk | 274 | { |
275 | pid_t pid = fork(); |
||
276 | if (pid == 0) { |
||
63 | luk | 277 | if (setgid(gid) != 0 || setuid(uid) != 0) { |
69 | luk | 278 | fprintf(stderr, "cannot set user '%s': %s\n", rUser.c_str(), strerror(errno)); |
61 | luk | 279 | goto end; |
280 | } |
||
281 | |||
282 | execlp(e, e, s, NULL); |
||
283 | _exit(1); |
||
45 | luk | 284 | } |
61 | luk | 285 | else if (pid > 0) { |
286 | int status; |
||
287 | if (wait(&status) != pid) { |
||
288 | perror("error while waiting for editor"); |
||
289 | goto end; |
||
290 | } |
||
291 | if (!(WIFEXITED(status)) || WEXITSTATUS(status) != 0) { |
||
292 | perror("editor finished with error"); |
||
293 | goto end; |
||
294 | } |
||
45 | luk | 295 | } |
61 | luk | 296 | else { |
297 | perror("cannot start editor"); |
||
298 | goto end; |
||
299 | } |
||
45 | luk | 300 | } |
301 | |||
302 | if (stat(s, &st) != 0) { |
||
303 | fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno)); |
||
61 | luk | 304 | goto end; |
45 | luk | 305 | } |
306 | |||
307 | if (st.st_mtime == mt) { |
||
308 | fprintf(stderr, "table unchanged\n"); |
||
61 | luk | 309 | ok = true; |
310 | goto end; |
||
45 | luk | 311 | } |
312 | |||
61 | luk | 313 | { |
69 | luk | 314 | IncronTab ict; |
63 | luk | 315 | if (ict.Load(s) && ict.Save(tp)) { |
316 | if (chmod(tp.c_str(), S_IRUSR | S_IWUSR) != 0) { |
||
317 | fprintf(stderr, "cannot change mode of temporary file: %s\n", strerror(errno)); |
||
318 | } |
||
319 | } |
||
320 | else { |
||
61 | luk | 321 | fprintf(stderr, "cannot move temporary table: %s\n", strerror(errno)); |
322 | goto end; |
||
323 | } |
||
63 | luk | 324 | |
45 | luk | 325 | } |
326 | |||
61 | luk | 327 | ok = true; |
45 | luk | 328 | fprintf(stderr, "table updated\n"); |
329 | |||
61 | luk | 330 | end: |
331 | |||
332 | unlink(s); |
||
333 | return ok; |
||
45 | luk | 334 | } |
335 | |||
336 | |||
67 | luk | 337 | /// Prints the list of all available inotify event types. |
338 | void list_types() |
||
339 | { |
||
340 | printf( "IN_ACCESS,IN_MODIFY,IN_ATTRIB,IN_CLOSE_WRITE,"\ |
||
341 | "IN_CLOSE_NOWRITE,IN_OPEN,IN_MOVED_FROM,IN_MOVED_TO,"\ |
||
342 | "IN_CREATE,IN_DELETE,IN_DELETE_SELF,IN_CLOSE,IN_MOVE,"\ |
||
343 | "IN_ONESHOT,IN_ALL_EVENTS"); |
||
344 | |||
345 | #ifdef IN_DONT_FOLLOW |
||
346 | printf(",IN_DONT_FOLLOW"); |
||
347 | #endif // IN_DONT_FOLLOW |
||
348 | |||
349 | #ifdef IN_ONLYDIR |
||
350 | printf(",IN_ONLYDIR"); |
||
351 | #endif // IN_ONLYDIR |
||
352 | |||
353 | #ifdef IN_MOVE_SELF |
||
354 | printf(",IN_MOVE_SELF"); |
||
355 | #endif // IN_MOVE_SELF |
||
356 | |||
357 | printf("\n"); |
||
358 | } |
||
359 | |||
69 | luk | 360 | /// Reloads an user table. |
361 | /** |
||
362 | * \param[in] rUser user name |
||
363 | * \return true = success, false = otherwise |
||
364 | */ |
||
365 | bool reload_table(const std::string& rUser) |
||
366 | { |
||
367 | fprintf(stderr, "requesting table reload for user '%s'...\n", rUser.c_str()); |
||
368 | |||
369 | std::string tp(IncronTab::GetUserTablePath(rUser)); |
||
370 | |||
371 | int fd = open(tp.c_str(), O_WRONLY | O_APPEND); |
||
372 | if (fd == -1) { |
||
373 | if (errno == ENOENT) { |
||
374 | fprintf(stderr, "no table for '%s'\n", rUser.c_str()); |
||
375 | return true; |
||
376 | } |
||
377 | else { |
||
378 | fprintf(stderr, "cannot access table for '%s': %s\n", rUser.c_str(), strerror(errno)); |
||
379 | return false; |
||
380 | } |
||
381 | } |
||
382 | |||
383 | close(fd); |
||
384 | |||
385 | fprintf(stderr, "request done\n"); |
||
386 | |||
387 | return true; |
||
388 | } |
||
67 | luk | 389 | |
45 | luk | 390 | int main(int argc, char** argv) |
391 | { |
||
69 | luk | 392 | AppArgs::Init(); |
393 | |||
394 | if (!( AppArgs::AddOption("about", '?', AAT_NO_VALUE, false) |
||
395 | && AppArgs::AddOption("help", 'h', AAT_NO_VALUE, false) |
||
396 | && AppArgs::AddOption("list", 'l', AAT_NO_VALUE, false) |
||
397 | && AppArgs::AddOption("remove", 'r', AAT_NO_VALUE, false) |
||
398 | && AppArgs::AddOption("edit", 'e', AAT_NO_VALUE, false) |
||
399 | && AppArgs::AddOption("types", 't', AAT_NO_VALUE, false) |
||
400 | && AppArgs::AddOption("reload", 'd', AAT_NO_VALUE, false) |
||
401 | && AppArgs::AddOption("user", 'u', AAT_MANDATORY_VALUE, false) |
||
402 | && AppArgs::AddOption("config", 'f', AAT_MANDATORY_VALUE, false)) |
||
403 | && AppArgs::AddOption("version", 'V', AAT_NO_VALUE, false)) |
||
404 | { |
||
405 | fprintf(stderr, "error while initializing application"); |
||
406 | return 1; |
||
407 | } |
||
45 | luk | 408 | |
69 | luk | 409 | AppArgs::Parse(argc, argv); |
45 | luk | 410 | |
69 | luk | 411 | if (AppArgs::ExistsOption("help")) { |
412 | fprintf(stderr, "%s\n", INCRONTAB_HELP); |
||
413 | return 0; |
||
414 | } |
||
45 | luk | 415 | |
69 | luk | 416 | if (AppArgs::ExistsOption("about")) { |
417 | fprintf(stderr, "%s\n", INCRONTAB_DESCRIPTION); |
||
418 | return 0; |
||
419 | } |
||
420 | |||
421 | if (AppArgs::ExistsOption("version")) { |
||
422 | fprintf(stderr, "%s\n", INCRONTAB_VERSION); |
||
423 | return 0; |
||
424 | } |
||
425 | |||
426 | bool oper = AppArgs::ExistsOption("list") |
||
427 | || AppArgs::ExistsOption("remove") |
||
428 | || AppArgs::ExistsOption("edit") |
||
429 | || AppArgs::ExistsOption("types") |
||
430 | || AppArgs::ExistsOption("reload"); |
||
431 | |||
432 | size_t vals = AppArgs::GetValueCount(); |
||
433 | |||
434 | if (!oper && vals == 0) { |
||
435 | fprintf(stderr, "invalid arguments - specify operation or source file\n"); |
||
45 | luk | 436 | return 1; |
437 | } |
||
69 | luk | 438 | |
439 | if (oper && vals > 0) { |
||
440 | fprintf(stderr, "invalid arguments - operation and source file cannot be combined\n"); |
||
45 | luk | 441 | return 1; |
442 | } |
||
443 | |||
444 | uid_t uid = getuid(); |
||
445 | |||
69 | luk | 446 | std::string user; |
447 | bool chuser = AppArgs::GetOption("user", user); |
||
448 | |||
449 | if (uid != 0 && chuser) { |
||
450 | fprintf(stderr, "cannot override user to '%s': insufficient privileges\n", user.c_str()); |
||
45 | luk | 451 | return 1; |
452 | } |
||
453 | |||
454 | struct passwd pwd; |
||
455 | |||
69 | luk | 456 | if (!chuser) { |
45 | luk | 457 | struct passwd* ppwd = getpwuid(uid); |
458 | if (ppwd == NULL) { |
||
459 | fprintf(stderr, "cannot determine current user\n"); |
||
460 | return 1; |
||
461 | } |
||
462 | memcpy(&pwd, ppwd, sizeof(pwd)); |
||
69 | luk | 463 | user = pwd.pw_name; |
45 | luk | 464 | } |
69 | luk | 465 | else if (getpwnam(user.c_str()) == NULL) { |
466 | fprintf(stderr, "user '%s' not found\n", user.c_str()); |
||
45 | luk | 467 | return 1; |
468 | } |
||
469 | |||
69 | luk | 470 | try { |
45 | luk | 471 | |
69 | luk | 472 | IncronCfg::Init(); |
473 | |||
474 | std::string cfg(INCRON_CONFIG); |
||
475 | if (AppArgs::GetOption("config", cfg)) { |
||
476 | if (uid != 0) { |
||
477 | fprintf(stderr, "insufficient privileges to use custom configuration (will use default)\n"); |
||
478 | } |
||
479 | else if (euidaccess(cfg.c_str(), R_OK) != 0) { |
||
480 | perror("cannot read configuration file (will use default)"); |
||
481 | } |
||
482 | } |
||
483 | |||
484 | IncronCfg::Load(cfg); |
||
485 | |||
486 | if (!IncronTab::CheckUser(user)) { |
||
487 | fprintf(stderr, "user '%s' is not allowed to use incron\n", user.c_str()); |
||
488 | return 1; |
||
489 | } |
||
490 | |||
491 | if (!oper) { |
||
492 | std::string file; |
||
493 | if (!AppArgs::GetValue(0, file) |
||
494 | || !copy_from_file(file, user)) |
||
495 | { |
||
45 | luk | 496 | return 1; |
69 | luk | 497 | } |
498 | } |
||
499 | else { |
||
500 | if (AppArgs::ExistsOption("list")) { |
||
501 | if (!list_table(user)) |
||
502 | return 1; |
||
503 | } |
||
504 | else if (AppArgs::ExistsOption("remove")) { |
||
505 | if (!remove_table(user)) |
||
506 | return 1; |
||
507 | } |
||
508 | else if (AppArgs::ExistsOption("edit")) { |
||
509 | if (!edit_table(user)) |
||
510 | return 1; |
||
511 | } |
||
512 | else if (AppArgs::ExistsOption("types")) { |
||
513 | list_types(); |
||
514 | } |
||
515 | else if (AppArgs::ExistsOption("reload")) { |
||
516 | if (!reload_table(user)) |
||
517 | return 1; |
||
518 | } |
||
519 | else { |
||
520 | fprintf(stderr, "invalid usage\n"); |
||
45 | luk | 521 | return 1; |
69 | luk | 522 | } |
523 | } |
||
524 | |||
525 | return 0; |
||
526 | |||
527 | } catch (InotifyException e) { |
||
528 | fprintf(stderr, "*** unhandled exception occurred ***\n"); |
||
529 | fprintf(stderr, "%s\n", e.GetMessage().c_str()); |
||
530 | fprintf(stderr, "error: (%i) %s\n", e.GetErrorNumber(), strerror(e.GetErrorNumber())); |
||
531 | |||
532 | return 1; |
||
45 | luk | 533 | } |
534 | } |