Subversion Repositories public

Rev

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