Subversion Repositories public

Rev

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