Subversion Repositories public

Rev

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