Subversion Repositories public

Rev

Rev 65 | Rev 69 | 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>
45 luk 25
 
55 luk 26
#include "incron.h"
45 luk 27
#include "incrontab.h"
28
 
67 luk 29
// Alternative editor
30
#define INCRON_ALT_EDITOR "/etc/alternatives/editor"
45 luk 31
 
32
// #define INCRON_DEFAULT_EDITOR "nano" // for vim haters like me ;-)
33
#define INCRON_DEFAULT_EDITOR "vim"
34
 
35
 
55 luk 36
const char* argp_program_version = INCRON_TAB_NAME " " INCRON_VERSION;
37
const char* argp_program_bug_address = INCRON_BUG_ADDRESS;
45 luk 38
 
63 luk 39
static char doc[] = "incrontab - incron table manipulator\n(c) Lukas Jelinek, 2006, 2007";
45 luk 40
 
41
static char args_doc[] = "FILE";
42
 
43
static struct argp_option options[] = {
55 luk 44
  {"list",    'l', 0,      0,  "List the current table" },
45
  {"remove",  'r', 0,      0,  "Remove the table completely" },
46
  {"edit",    'e', 0,      0,  "Edit the table" },
67 luk 47
  {"types",   't', 0,      0,  "List all supported event types" },
55 luk 48
  {"user",    'u', "USER", 0,  "Override the current user" },
45 luk 49
  { 0 }
50
};
51
 
49 luk 52
/// incrontab operations
53
typedef enum
45 luk 54
{
49 luk 55
  OPER_NONE,    /// nothing
56
  OPER_LIST,    /// list table
57
  OPER_REMOVE,  /// remove table
67 luk 58
  OPER_EDIT,    /// edit table
59
  OPER_TYPES    /// list event types
49 luk 60
} InCronTab_Operation_t;
45 luk 61
 
49 luk 62
/// incrontab arguments
45 luk 63
struct arguments
64
{
49 luk 65
  char *user;     /// user name
66
  int oper;       /// operation code
67
  char *file;     /// file to import
45 luk 68
};
69
 
49 luk 70
/// Parses the program options (arguments).
71
/**
72
 * \param[in] key argument key (name)
73
 * \param[in] arg argument value
74
 * \param[out] state options setting
75
 * \return 0 on success, ARGP_ERR_UNKNOWN on unknown argument(s)
76
 */
77
static error_t parse_opt(int key, char *arg, struct argp_state *state)
45 luk 78
{
79
  struct arguments* arguments = (struct arguments*) state->input;
80
 
81
  switch (key) {
82
    case 'l':
83
      arguments->oper = OPER_LIST;
84
      break;
85
    case 'r':
86
      arguments->oper = OPER_REMOVE;
87
      break;
88
    case 'e':
89
      arguments->oper = OPER_EDIT;
90
      break;
67 luk 91
    case 't':
92
      arguments->oper = OPER_TYPES;
93
      break;
45 luk 94
    case 'u':
95
      arguments->user = arg;
96
      break;
97
    case ARGP_KEY_ARG:
98
      if (state->arg_num >= 1)
99
        argp_usage(state);
100
      arguments->file = arg;
101
      break;
102
    case ARGP_KEY_END:
103
      break;
104
    default:
105
      return ARGP_ERR_UNKNOWN;
106
  }
107
 
108
  return 0;
109
}
110
 
49 luk 111
/// Program arguments
45 luk 112
static struct argp argp = { options, parse_opt, args_doc, doc };
113
 
114
 
61 luk 115
 
49 luk 116
/// Copies a file to an user table.
117
/**
118
 * \param[in] path path to file
119
 * \param[in] user user name
120
 * \return true = success, false = failure
121
 */
45 luk 122
bool copy_from_file(const char* path, const char* user)
123
{
124
  InCronTab tab;
125
  std::string s(path);
126
  if (s == "-")
127
    s = "/dev/stdin";
128
  if (!tab.Load(s)) {
129
    fprintf(stderr, "cannot load table from file: %s\n", path);
130
    return false;
131
  }
132
 
133
  std::string out(InCronTab::GetUserTablePath(user));
134
  if (!tab.Save(out)) {
135
    fprintf(stderr, "cannot create table for user: %s\n", user);
136
    return false;
137
  }
138
 
139
  return true;
140
}
141
 
49 luk 142
/// Removes an user table.
143
/**
144
 * \param[in] user user name
145
 * \return true = success, false = failure
146
 */
45 luk 147
bool remove_table(const char* user)
148
{
149
  std::string tp(InCronTab::GetUserTablePath(user));
150
 
151
  if (unlink(tp.c_str()) != 0 && errno != ENOENT) {
152
    fprintf(stderr, "cannot remove table for user: %s\n", user);
153
    return false;
154
  }
155
 
156
  return true;
157
}
158
 
49 luk 159
/// Lists an user table.
160
/**
161
 * \param[in] user user name
162
 * \return true = success, false = failure
163
 */
45 luk 164
bool list_table(const char* user)
165
{
166
  std::string tp(InCronTab::GetUserTablePath(user));
167
 
67 luk 168
  if (euidaccess(tp.c_str(), R_OK) != 0) {
45 luk 169
    if (errno == ENOENT) {
170
      fprintf(stderr, "no table for %s\n", user);
171
      return true;
172
    }
173
    else {
174
      fprintf(stderr, "cannot read table for %s: %s\n", user, strerror(errno));
175
      return false;
176
    }
177
  }
178
 
65 luk 179
  FILE* f = fopen(tp.c_str(), "r");
180
  if (f == NULL)
181
    return false;
182
 
183
  char s[1024];
184
  while (fgets(s, 1024, f) != NULL) {
185
    fputs(s, stdout);
186
  }
187
 
188
  fclose(f);
189
 
190
  return true;
45 luk 191
}
192
 
49 luk 193
/// Allows to edit an user table.
194
/**
195
 * \param[in] user user name
196
 * \return true = success, false = failure
197
 *
198
 * \attention This function is very complex and may contain
199
 *            various bugs including security ones. Please keep
200
 *            it in mind..
201
 */
45 luk 202
bool edit_table(const char* user)
203
{
204
  std::string tp(InCronTab::GetUserTablePath(user));
205
 
206
  struct passwd* ppwd = getpwnam(user);
207
  if (ppwd == NULL) {
208
    fprintf(stderr, "cannot find user %s: %s\n", user, strerror(errno));
209
    return false;
210
  }
61 luk 211
 
45 luk 212
  uid_t uid = ppwd->pw_uid;
61 luk 213
  uid_t gid = ppwd->pw_gid;
45 luk 214
 
215
  char s[NAME_MAX];
216
  strcpy(s, "/tmp/incron.table-XXXXXX");
217
 
218
  uid_t iu = geteuid();
61 luk 219
  uid_t ig = getegid();
55 luk 220
 
63 luk 221
  if (setegid(gid) != 0 || seteuid(uid) != 0) {
61 luk 222
    fprintf(stderr, "cannot change effective UID/GID for user %s: %s\n", user, strerror(errno));
45 luk 223
    return false;
224
  }
225
 
226
  int fd = mkstemp(s);
227
  if (fd == -1) {
228
    fprintf(stderr, "cannot create temporary file: %s\n", strerror(errno));
229
    return false;
230
  }
231
 
61 luk 232
  bool ok = false;
233
  FILE* out = NULL;
234
  FILE* in = NULL;
235
  time_t mt = (time_t) 0;
236
  const char* e = NULL;
237
 
63 luk 238
  if (setegid(ig) != 0 || seteuid(iu) != 0) {
61 luk 239
    fprintf(stderr, "cannot change effective UID/GID: %s\n", strerror(errno));
45 luk 240
    close(fd);
61 luk 241
    goto end;
45 luk 242
  }
243
 
61 luk 244
  out = fdopen(fd, "w");
45 luk 245
  if (out == NULL) {
246
    fprintf(stderr, "cannot write to temporary file: %s\n", strerror(errno));
247
    close(fd);
61 luk 248
    goto end;
45 luk 249
  }
250
 
61 luk 251
  in = fopen(tp.c_str(), "r");
45 luk 252
  if (in == NULL) {
253
    if (errno == ENOENT) {
254
      in = fopen("/dev/null", "r");
255
      if (in == NULL) {
256
        fprintf(stderr, "cannot get empty table for %s: %s\n", user, strerror(errno));
257
        fclose(out);
61 luk 258
        goto end;
45 luk 259
      }
260
    }
261
    else {
262
      fprintf(stderr, "cannot read old table for %s: %s\n", user, strerror(errno));
263
      fclose(out);
61 luk 264
      goto end;
45 luk 265
    }
266
  }
267
 
268
  char buf[1024];
269
  while (fgets(buf, 1024, in) != NULL) {
270
    fputs(buf, out);
271
  }
272
  fclose(in);
273
  fclose(out);
274
 
275
  struct stat st;
276
  if (stat(s, &st) != 0) {
277
    fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno));
61 luk 278
    goto end;
45 luk 279
  }
280
 
67 luk 281
  mt = st.st_mtime; // save modification time for detecting its change
45 luk 282
 
67 luk 283
  // Editor selecting algorithm:
284
  // 1. Check EDITOR environment variable
285
  // 2. Check VISUAL environment variable
286
  // 3. Check presence of /etc/alternatives/editor
287
  // 4. Use hard-wired editor
61 luk 288
  e = getenv("EDITOR");
67 luk 289
  if (e == NULL) {
290
    e = getenv("VISUAL");
291
    if (e == NULL) {
292
      if (access(INCRON_ALT_EDITOR, X_OK) == 0)
293
        e = INCRON_ALT_EDITOR;
294
      else
295
        e = INCRON_DEFAULT_EDITOR;
296
    }
297
  }
45 luk 298
 
67 luk 299
  // this block is explicite due to gotos' usage simplification
61 luk 300
  {
301
    pid_t pid = fork();
302
    if (pid == 0) {
63 luk 303
      if (setgid(gid) != 0 || setuid(uid) != 0) {
61 luk 304
        fprintf(stderr, "cannot set user %s: %s\n", user, strerror(errno));
305
        goto end;
306
      }    
307
 
308
      execlp(e, e, s, NULL);
309
      _exit(1);
45 luk 310
    }
61 luk 311
    else if (pid > 0) {
312
      int status;
313
      if (wait(&status) != pid) {
314
        perror("error while waiting for editor");
315
        goto end;
316
      }
317
      if (!(WIFEXITED(status)) || WEXITSTATUS(status) != 0) {
318
        perror("editor finished with error");
319
        goto end;
320
      }
45 luk 321
    }
61 luk 322
    else {
323
      perror("cannot start editor");
324
      goto end;
325
    }
45 luk 326
  }
327
 
328
  if (stat(s, &st) != 0) {
329
    fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno));
61 luk 330
    goto end;
45 luk 331
  }
332
 
333
  if (st.st_mtime == mt) {
334
    fprintf(stderr, "table unchanged\n");
61 luk 335
    ok = true;
336
    goto end;
45 luk 337
  }
338
 
61 luk 339
  {
340
    InCronTab ict;
63 luk 341
    if (ict.Load(s) && ict.Save(tp)) {
342
      if (chmod(tp.c_str(), S_IRUSR | S_IWUSR) != 0) {
343
        fprintf(stderr, "cannot change mode of temporary file: %s\n", strerror(errno));
344
      }
345
    }
346
    else {
61 luk 347
      fprintf(stderr, "cannot move temporary table: %s\n", strerror(errno));
348
      goto end;
349
    }
63 luk 350
 
45 luk 351
  }
352
 
61 luk 353
  ok = true;
45 luk 354
  fprintf(stderr, "table updated\n");
355
 
61 luk 356
end:  
357
 
358
  unlink(s);
359
  return ok;
45 luk 360
}
361
 
362
 
67 luk 363
/// Prints the list of all available inotify event types.
364
void list_types()
365
{
366
  printf( "IN_ACCESS,IN_MODIFY,IN_ATTRIB,IN_CLOSE_WRITE,"\
367
          "IN_CLOSE_NOWRITE,IN_OPEN,IN_MOVED_FROM,IN_MOVED_TO,"\
368
          "IN_CREATE,IN_DELETE,IN_DELETE_SELF,IN_CLOSE,IN_MOVE,"\
369
          "IN_ONESHOT,IN_ALL_EVENTS");
370
 
371
#ifdef IN_DONT_FOLLOW
372
  printf(",IN_DONT_FOLLOW");
373
#endif // IN_DONT_FOLLOW
374
 
375
#ifdef IN_ONLYDIR
376
  printf(",IN_ONLYDIR");
377
#endif // IN_ONLYDIR
378
 
379
#ifdef IN_MOVE_SELF
380
  printf(",IN_MOVE_SELF");
381
#endif // IN_MOVE_SELF
382
 
383
  printf("\n");
384
}
385
 
386
 
45 luk 387
int main(int argc, char** argv)
388
{
389
  struct arguments arguments;
390
 
391
  arguments.user = NULL;
392
  arguments.oper = OPER_NONE;
393
  arguments.file = NULL;
394
 
395
  argp_parse (&argp, argc, argv, 0, 0, &arguments);
396
 
397
  if (arguments.file != NULL && arguments.oper != OPER_NONE) {
398
    fprintf(stderr, "invalid arguments - specify source file or operation\n");
399
    return 1;
400
  }
401
  if (arguments.file == NULL && arguments.oper == OPER_NONE) {
402
    fprintf(stderr, "invalid arguments - specify source file or operation\n");
403
    return 1;
404
  }
405
 
406
  uid_t uid = getuid();
407
 
408
  if (uid != 0 && arguments.user != NULL) {
409
    fprintf(stderr, "cannot access table for user %s: permission denied\n", arguments.user);
410
    return 1;
411
  }
412
 
413
  struct passwd pwd;
414
 
415
  if (arguments.user == NULL) {
416
    struct passwd* ppwd = getpwuid(uid);
417
    if (ppwd == NULL) {
418
      fprintf(stderr, "cannot determine current user\n");
419
      return 1;
420
    }
421
    memcpy(&pwd, ppwd, sizeof(pwd));
422
    arguments.user = pwd.pw_name;
423
  }
424
  else if (getpwnam(arguments.user) == NULL) {
425
    fprintf(stderr, "user %s not found\n", arguments.user);
426
    return 1;
427
  }
428
 
429
  if (!InCronTab::CheckUser(arguments.user)) {
430
    fprintf(stderr, "user %s is not allowed to use incron\n", arguments.user);
431
    return 1;
432
  }
433
 
434
  switch (arguments.oper) {
435
    case OPER_NONE:
436
      fprintf(stderr, "copying table from file: %s\n", arguments.file);
437
      if (!copy_from_file(arguments.file, arguments.user))
438
        return 1;
439
      break;
440
    case OPER_LIST:
441
      if (!list_table(arguments.user))
442
        return 1;
443
      break;
444
    case OPER_REMOVE:
445
      fprintf(stderr, "removing table for user %s\n", arguments.user);
446
      if (!remove_table(arguments.user))
447
        return 1;
448
      break;
449
    case OPER_EDIT:
450
      if (!edit_table(arguments.user))
451
        return 1;
452
      break;
67 luk 453
    case OPER_TYPES:
454
      list_types();
455
      break;
45 luk 456
    default:
457
      fprintf(stderr, "invalid usage\n");
458
      return 1;
459
  }
460
 
461
  return 0;
462
}