Subversion Repositories public

Rev

Rev 45 | Rev 51 | Go to most recent revision | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

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