Subversion Repositories public

Rev

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
}