Subversion Repositories public

Rev

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
}