Subversion Repositories public

Rev

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