Subversion Repositories public

Rev

Rev 61 | Rev 65 | 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
 *
156
 * \attention Listing is currently done through 'cat'.
157
 */
45 luk 158
bool list_table(const char* user)
159
{
160
  std::string tp(InCronTab::GetUserTablePath(user));
161
 
162
  if (access(tp.c_str(), R_OK) != 0) {
163
    if (errno == ENOENT) {
164
      fprintf(stderr, "no table for %s\n", user);
165
      return true;
166
    }
167
    else {
168
      fprintf(stderr, "cannot read table for %s: %s\n", user, strerror(errno));
169
      return false;
170
    }
171
  }
172
 
173
  std::string cmd("cat ");
174
  cmd.append(tp);
175
  return system(cmd.c_str()) == 0;
176
}
177
 
49 luk 178
/// Allows to edit an user table.
179
/**
180
 * \param[in] user user name
181
 * \return true = success, false = failure
182
 *
183
 * \attention This function is very complex and may contain
184
 *            various bugs including security ones. Please keep
185
 *            it in mind..
186
 */
45 luk 187
bool edit_table(const char* user)
188
{
189
  std::string tp(InCronTab::GetUserTablePath(user));
190
 
191
  struct passwd* ppwd = getpwnam(user);
192
  if (ppwd == NULL) {
193
    fprintf(stderr, "cannot find user %s: %s\n", user, strerror(errno));
194
    return false;
195
  }
61 luk 196
 
45 luk 197
  uid_t uid = ppwd->pw_uid;
61 luk 198
  uid_t gid = ppwd->pw_gid;
45 luk 199
 
200
  char s[NAME_MAX];
201
  strcpy(s, "/tmp/incron.table-XXXXXX");
202
 
203
  uid_t iu = geteuid();
61 luk 204
  uid_t ig = getegid();
55 luk 205
 
63 luk 206
  if (setegid(gid) != 0 || seteuid(uid) != 0) {
61 luk 207
    fprintf(stderr, "cannot change effective UID/GID for user %s: %s\n", user, strerror(errno));
45 luk 208
    return false;
209
  }
210
 
211
  int fd = mkstemp(s);
212
  if (fd == -1) {
213
    fprintf(stderr, "cannot create temporary file: %s\n", strerror(errno));
214
    return false;
215
  }
216
 
61 luk 217
  bool ok = false;
218
  FILE* out = NULL;
219
  FILE* in = NULL;
220
  time_t mt = (time_t) 0;
221
  const char* e = NULL;
222
 
63 luk 223
  if (setegid(ig) != 0 || seteuid(iu) != 0) {
61 luk 224
    fprintf(stderr, "cannot change effective UID/GID: %s\n", strerror(errno));
45 luk 225
    close(fd);
61 luk 226
    goto end;
45 luk 227
  }
228
 
61 luk 229
  out = fdopen(fd, "w");
45 luk 230
  if (out == NULL) {
231
    fprintf(stderr, "cannot write to temporary file: %s\n", strerror(errno));
232
    close(fd);
61 luk 233
    goto end;
45 luk 234
  }
235
 
61 luk 236
  in = fopen(tp.c_str(), "r");
45 luk 237
  if (in == NULL) {
238
    if (errno == ENOENT) {
239
      in = fopen("/dev/null", "r");
240
      if (in == NULL) {
241
        fprintf(stderr, "cannot get empty table for %s: %s\n", user, strerror(errno));
242
        fclose(out);
61 luk 243
        goto end;
45 luk 244
      }
245
    }
246
    else {
247
      fprintf(stderr, "cannot read old table for %s: %s\n", user, strerror(errno));
248
      fclose(out);
61 luk 249
      goto end;
45 luk 250
    }
251
  }
252
 
253
  char buf[1024];
254
  while (fgets(buf, 1024, in) != NULL) {
255
    fputs(buf, out);
256
  }
257
  fclose(in);
258
  fclose(out);
259
 
260
  struct stat st;
261
  if (stat(s, &st) != 0) {
262
    fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno));
61 luk 263
    goto end;
45 luk 264
  }
265
 
61 luk 266
  mt = st.st_mtime;
45 luk 267
 
61 luk 268
  e = getenv("EDITOR");
45 luk 269
  if (e == NULL)
270
    e = INCRON_DEFAULT_EDITOR;
271
 
61 luk 272
  {
273
    pid_t pid = fork();
274
    if (pid == 0) {
63 luk 275
      if (setgid(gid) != 0 || setuid(uid) != 0) {
61 luk 276
        fprintf(stderr, "cannot set user %s: %s\n", user, strerror(errno));
277
        goto end;
278
      }    
279
 
280
      execlp(e, e, s, NULL);
281
      _exit(1);
45 luk 282
    }
61 luk 283
    else if (pid > 0) {
284
      int status;
285
      if (wait(&status) != pid) {
286
        perror("error while waiting for editor");
287
        goto end;
288
      }
289
      if (!(WIFEXITED(status)) || WEXITSTATUS(status) != 0) {
290
        perror("editor finished with error");
291
        goto end;
292
      }
45 luk 293
    }
61 luk 294
    else {
295
      perror("cannot start editor");
296
      goto end;
297
    }
45 luk 298
  }
299
 
300
  if (stat(s, &st) != 0) {
301
    fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno));
61 luk 302
    goto end;
45 luk 303
  }
304
 
305
  if (st.st_mtime == mt) {
306
    fprintf(stderr, "table unchanged\n");
61 luk 307
    ok = true;
308
    goto end;
45 luk 309
  }
310
 
61 luk 311
  {
312
    InCronTab ict;
63 luk 313
    if (ict.Load(s) && ict.Save(tp)) {
314
      if (chmod(tp.c_str(), S_IRUSR | S_IWUSR) != 0) {
315
        fprintf(stderr, "cannot change mode of temporary file: %s\n", strerror(errno));
316
      }
317
    }
318
    else {
61 luk 319
      fprintf(stderr, "cannot move temporary table: %s\n", strerror(errno));
320
      goto end;
321
    }
63 luk 322
 
45 luk 323
  }
324
 
61 luk 325
  ok = true;
45 luk 326
  fprintf(stderr, "table updated\n");
327
 
61 luk 328
end:  
329
 
330
  unlink(s);
331
  return ok;
45 luk 332
}
333
 
334
 
335
int main(int argc, char** argv)
336
{
337
  struct arguments arguments;
338
 
339
  arguments.user = NULL;
340
  arguments.oper = OPER_NONE;
341
  arguments.file = NULL;
342
 
343
  argp_parse (&argp, argc, argv, 0, 0, &arguments);
344
 
345
  if (arguments.file != NULL && arguments.oper != OPER_NONE) {
346
    fprintf(stderr, "invalid arguments - specify source file or operation\n");
347
    return 1;
348
  }
349
  if (arguments.file == NULL && arguments.oper == OPER_NONE) {
350
    fprintf(stderr, "invalid arguments - specify source file or operation\n");
351
    return 1;
352
  }
353
 
354
  uid_t uid = getuid();
355
 
356
  if (uid != 0 && arguments.user != NULL) {
357
    fprintf(stderr, "cannot access table for user %s: permission denied\n", arguments.user);
358
    return 1;
359
  }
360
 
361
  struct passwd pwd;
362
 
363
  if (arguments.user == NULL) {
364
    struct passwd* ppwd = getpwuid(uid);
365
    if (ppwd == NULL) {
366
      fprintf(stderr, "cannot determine current user\n");
367
      return 1;
368
    }
369
    memcpy(&pwd, ppwd, sizeof(pwd));
370
    arguments.user = pwd.pw_name;
371
  }
372
  else if (getpwnam(arguments.user) == NULL) {
373
    fprintf(stderr, "user %s not found\n", arguments.user);
374
    return 1;
375
  }
376
 
377
  if (!InCronTab::CheckUser(arguments.user)) {
378
    fprintf(stderr, "user %s is not allowed to use incron\n", arguments.user);
379
    return 1;
380
  }
381
 
382
  switch (arguments.oper) {
383
    case OPER_NONE:
384
      fprintf(stderr, "copying table from file: %s\n", arguments.file);
385
      if (!copy_from_file(arguments.file, arguments.user))
386
        return 1;
387
      break;
388
    case OPER_LIST:
389
      if (!list_table(arguments.user))
390
        return 1;
391
      break;
392
    case OPER_REMOVE:
393
      fprintf(stderr, "removing table for user %s\n", arguments.user);
394
      if (!remove_table(arguments.user))
395
        return 1;
396
      break;
397
    case OPER_EDIT:
398
      if (!edit_table(arguments.user))
399
        return 1;
400
      break;
401
    default:
402
      fprintf(stderr, "invalid usage\n");
403
      return 1;
404
  }
405
 
406
  return 0;
407
}