Subversion Repositories public

Rev

Rev 45 | Rev 51 | 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
 *
8
 * Copyright (C) 2006 Lukas Jelinek, <lukas@aiken.cz>
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
 
25
#include "incrontab.h"
26
 
27
 
28
// #define INCRON_DEFAULT_EDITOR "nano" // for vim haters like me ;-)
29
#define INCRON_DEFAULT_EDITOR "vim"
30
 
31
 
49 luk 32
const char* argp_program_version = "incrontab 0.3.0";
45 luk 33
const char* argp_program_bug_address = "<bugs@aiken.cz>";
34
 
49 luk 35
static char doc[] = "Table manipulator for incrond (inotify cron daemon)";
45 luk 36
 
37
static char args_doc[] = "FILE";
38
 
39
static struct argp_option options[] = {
40
  {"list",    'l', 0,      0,  "List current table" },
41
  {"remove",  'r', 0,      0,  "Remove table completely" },
42
  {"edit",    'e', 0,      0,  "Edit table" },
43
  {"user",    'u', "USER", 0,  "Override current user" },
44
  { 0 }
45
};
46
 
49 luk 47
/// incrontab operations
48
typedef enum
45 luk 49
{
49 luk 50
  OPER_NONE,    /// nothing
51
  OPER_LIST,    /// list table
52
  OPER_REMOVE,  /// remove table
53
  OPER_EDIT     /// edit table
54
} InCronTab_Operation_t;
45 luk 55
 
49 luk 56
/// incrontab arguments
45 luk 57
struct arguments
58
{
49 luk 59
  char *user;     /// user name
60
  int oper;       /// operation code
61
  char *file;     /// file to import
45 luk 62
};
63
 
49 luk 64
/// Parses the program options (arguments).
65
/**
66
 * \param[in] key argument key (name)
67
 * \param[in] arg argument value
68
 * \param[out] state options setting
69
 * \return 0 on success, ARGP_ERR_UNKNOWN on unknown argument(s)
70
 */
71
static error_t parse_opt(int key, char *arg, struct argp_state *state)
45 luk 72
{
73
  struct arguments* arguments = (struct arguments*) state->input;
74
 
75
  switch (key) {
76
    case 'l':
77
      arguments->oper = OPER_LIST;
78
      break;
79
    case 'r':
80
      arguments->oper = OPER_REMOVE;
81
      break;
82
    case 'e':
83
      arguments->oper = OPER_EDIT;
84
      break;
85
    case 'u':
86
      arguments->user = arg;
87
      break;
88
    case ARGP_KEY_ARG:
89
      if (state->arg_num >= 1)
90
        argp_usage(state);
91
      arguments->file = arg;
92
      break;
93
    case ARGP_KEY_END:
94
      break;
95
    default:
96
      return ARGP_ERR_UNKNOWN;
97
  }
98
 
99
  return 0;
100
}
101
 
49 luk 102
/// Program arguments
45 luk 103
static struct argp argp = { options, parse_opt, args_doc, doc };
104
 
49 luk 105
/// Unlink a file with temporarily changed UID.
106
/**
107
 * \param[in] file file to unlink
108
 * \param[in] uid UID for unlink processing
109
 *
110
 * \attention No error checking is done!
111
 */
45 luk 112
void unlink_suid(const char* file, uid_t uid)
113
{
114
  uid_t iu = geteuid();
115
  seteuid(uid);
116
  unlink(file);
117
  seteuid(iu);
118
}
119
 
49 luk 120
/// Copies a file to an user table.
121
/**
122
 * \param[in] path path to file
123
 * \param[in] user user name
124
 * \return true = success, false = failure
125
 */
45 luk 126
bool copy_from_file(const char* path, const char* user)
127
{
128
  InCronTab tab;
129
  std::string s(path);
130
  if (s == "-")
131
    s = "/dev/stdin";
132
  if (!tab.Load(s)) {
133
    fprintf(stderr, "cannot load table from file: %s\n", path);
134
    return false;
135
  }
136
 
137
  std::string out(InCronTab::GetUserTablePath(user));
138
  if (!tab.Save(out)) {
139
    fprintf(stderr, "cannot create table for user: %s\n", user);
140
    return false;
141
  }
142
 
143
  return true;
144
}
145
 
49 luk 146
/// Removes an user table.
147
/**
148
 * \param[in] user user name
149
 * \return true = success, false = failure
150
 */
45 luk 151
bool remove_table(const char* user)
152
{
153
  std::string tp(InCronTab::GetUserTablePath(user));
154
 
155
  if (unlink(tp.c_str()) != 0 && errno != ENOENT) {
156
    fprintf(stderr, "cannot remove table for user: %s\n", user);
157
    return false;
158
  }
159
 
160
  return true;
161
}
162
 
49 luk 163
/// Lists an user table.
164
/**
165
 * \param[in] user user name
166
 * \return true = success, false = failure
167
 *
168
 * \attention Listing is currently done through 'cat'.
169
 */
45 luk 170
bool list_table(const char* user)
171
{
172
  std::string tp(InCronTab::GetUserTablePath(user));
173
 
174
  if (access(tp.c_str(), R_OK) != 0) {
175
    if (errno == ENOENT) {
176
      fprintf(stderr, "no table for %s\n", user);
177
      return true;
178
    }
179
    else {
180
      fprintf(stderr, "cannot read table for %s: %s\n", user, strerror(errno));
181
      return false;
182
    }
183
  }
184
 
185
  std::string cmd("cat ");
186
  cmd.append(tp);
187
  return system(cmd.c_str()) == 0;
188
}
189
 
49 luk 190
/// Allows to edit an user table.
191
/**
192
 * \param[in] user user name
193
 * \return true = success, false = failure
194
 *
195
 * \attention This function is very complex and may contain
196
 *            various bugs including security ones. Please keep
197
 *            it in mind..
198
 */
45 luk 199
bool edit_table(const char* user)
200
{
201
  std::string tp(InCronTab::GetUserTablePath(user));
202
 
203
  struct passwd* ppwd = getpwnam(user);
204
  if (ppwd == NULL) {
205
    fprintf(stderr, "cannot find user %s: %s\n", user, strerror(errno));
206
    return false;
207
  }
208
  uid_t uid = ppwd->pw_uid;
209
 
210
  char s[NAME_MAX];
211
  strcpy(s, "/tmp/incron.table-XXXXXX");
212
 
213
  uid_t iu = geteuid();
214
 
215
  if (seteuid(uid) != 0) {
216
    fprintf(stderr, "cannot change effective UID: %s\n", strerror(errno));
217
    return false;
218
  }
219
 
220
  int fd = mkstemp(s);
221
  if (fd == -1) {
222
    fprintf(stderr, "cannot create temporary file: %s\n", strerror(errno));
223
    return false;
224
  }
225
 
226
  if (fchmod(fd, 0644) != 0) {
227
    fprintf(stderr, "cannot change mode of temporary file: %s\n", strerror(errno));
228
    close(fd);
229
    unlink_suid(s, uid);
230
    return false;
231
  }
232
 
233
  if (seteuid(iu) != 0) {
234
    fprintf(stderr, "cannot change effective UID: %s\n", strerror(errno));
235
    close(fd);
236
    unlink_suid(s, uid);
237
    return false;
238
  }
239
 
240
  FILE* out = fdopen(fd, "w");
241
  if (out == NULL) {
242
    fprintf(stderr, "cannot write to temporary file: %s\n", strerror(errno));
243
    close(fd);
244
    unlink_suid(s, uid);
245
    return false;
246
  }
247
 
248
  FILE* in = fopen(tp.c_str(), "r");
249
  if (in == NULL) {
250
    if (errno == ENOENT) {
251
      in = fopen("/dev/null", "r");
252
      if (in == NULL) {
253
        fprintf(stderr, "cannot get empty table for %s: %s\n", user, strerror(errno));
254
        fclose(out);
255
        unlink_suid(s, uid);
256
        return false;
257
      }
258
    }
259
    else {
260
      fprintf(stderr, "cannot read old table for %s: %s\n", user, strerror(errno));
261
      fclose(out);
262
      unlink_suid(s, uid);
263
      return false;
264
    }
265
  }
266
 
267
  char buf[1024];
268
  while (fgets(buf, 1024, in) != NULL) {
269
    fputs(buf, out);
270
  }
271
  fclose(in);
272
  fclose(out);
273
 
274
  struct stat st;
275
  if (stat(s, &st) != 0) {
276
    fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno));
277
    unlink_suid(s, uid);
278
    return false;
279
  }
280
 
281
  time_t mt = st.st_mtime;
282
 
283
  const char* e = getenv("EDITOR");
284
  if (e == NULL)
285
    e = INCRON_DEFAULT_EDITOR;
286
 
287
  pid_t pid = fork();
288
  if (pid == 0) {
289
    if (setuid(uid) != 0) {
290
      fprintf(stderr, "cannot set user %s: %s\n", user, strerror(errno));
291
      return false;
292
    }    
293
 
294
    execlp(e, e, s, NULL);
295
    _exit(1);
296
  }
297
  else if (pid > 0) {
298
    int status;
299
    if (wait(&status) != pid) {
300
      perror("error while waiting for editor");
301
      unlink_suid(s, uid);
302
      return false;
303
    }
304
    if (!(WIFEXITED(status)) || WEXITSTATUS(status) != 0) {
305
      perror("editor finished with error");
306
      unlink_suid(s, uid);
307
      return false;
308
    }
309
  }
310
  else {
311
    perror("cannot start editor");
312
    unlink_suid(s, uid);
313
    return false;
314
  }
315
 
316
  if (stat(s, &st) != 0) {
317
    fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno));
318
    unlink_suid(s, uid);
319
    return false;
320
  }
321
 
322
  if (st.st_mtime == mt) {
323
    fprintf(stderr, "table unchanged\n");
324
    unlink_suid(s, uid);
325
    return true;
326
  }
327
 
328
  InCronTab ict;
329
  if (!ict.Load(s) || !ict.Save(tp)) {
330
    fprintf(stderr, "cannot move temporary table: %s\n", strerror(errno));
331
    unlink(s);
332
    return false;
333
  }
334
 
335
  fprintf(stderr, "table updated\n");
336
 
337
  unlink_suid(s, uid);
338
  return true;
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
}