Subversion Repositories public

Rev

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