Subversion Repositories public

Rev

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