Subversion Repositories public

Rev

Rev 49 | Go to most recent revision | Details | 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
 
32
const char* argp_program_version = "incrontab 0.1.0";
33
const char* argp_program_bug_address = "<bugs@aiken.cz>";
34
 
35
static char doc[] = "Table manipulator for incrond (inotify Cron daemon)";
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
 
47
enum
48
{
49
  OPER_NONE,
50
  OPER_LIST,
51
  OPER_REMOVE,
52
  OPER_EDIT
53
};
54
 
55
struct arguments
56
{
57
  char *user;
58
  int oper;
59
  char *file;
60
};
61
 
62
static error_t parse_opt (int key, char *arg, struct argp_state *state)
63
{
64
  struct arguments* arguments = (struct arguments*) state->input;
65
 
66
  switch (key) {
67
    case 'l':
68
      arguments->oper = OPER_LIST;
69
      break;
70
    case 'r':
71
      arguments->oper = OPER_REMOVE;
72
      break;
73
    case 'e':
74
      arguments->oper = OPER_EDIT;
75
      break;
76
    case 'u':
77
      arguments->user = arg;
78
      break;
79
    case ARGP_KEY_ARG:
80
      if (state->arg_num >= 1)
81
        argp_usage(state);
82
      arguments->file = arg;
83
      break;
84
    case ARGP_KEY_END:
85
      break;
86
    default:
87
      return ARGP_ERR_UNKNOWN;
88
  }
89
 
90
  return 0;
91
}
92
 
93
 
94
static struct argp argp = { options, parse_opt, args_doc, doc };
95
 
96
void unlink_suid(const char* file, uid_t uid)
97
{
98
  uid_t iu = geteuid();
99
  seteuid(uid);
100
  unlink(file);
101
  seteuid(iu);
102
}
103
 
104
bool copy_from_file(const char* path, const char* user)
105
{
106
  InCronTab tab;
107
  std::string s(path);
108
  if (s == "-")
109
    s = "/dev/stdin";
110
  if (!tab.Load(s)) {
111
    fprintf(stderr, "cannot load table from file: %s\n", path);
112
    return false;
113
  }
114
 
115
  std::string out(InCronTab::GetUserTablePath(user));
116
  if (!tab.Save(out)) {
117
    fprintf(stderr, "cannot create table for user: %s\n", user);
118
    return false;
119
  }
120
 
121
  return true;
122
}
123
 
124
bool remove_table(const char* user)
125
{
126
  std::string tp(InCronTab::GetUserTablePath(user));
127
 
128
  if (unlink(tp.c_str()) != 0 && errno != ENOENT) {
129
    fprintf(stderr, "cannot remove table for user: %s\n", user);
130
    return false;
131
  }
132
 
133
  return true;
134
}
135
 
136
bool list_table(const char* user)
137
{
138
  std::string tp(InCronTab::GetUserTablePath(user));
139
 
140
  if (access(tp.c_str(), R_OK) != 0) {
141
    if (errno == ENOENT) {
142
      fprintf(stderr, "no table for %s\n", user);
143
      return true;
144
    }
145
    else {
146
      fprintf(stderr, "cannot read table for %s: %s\n", user, strerror(errno));
147
      return false;
148
    }
149
  }
150
 
151
  std::string cmd("cat ");
152
  cmd.append(tp);
153
  return system(cmd.c_str()) == 0;
154
}
155
 
156
bool edit_table(const char* user)
157
{
158
  std::string tp(InCronTab::GetUserTablePath(user));
159
 
160
  struct passwd* ppwd = getpwnam(user);
161
  if (ppwd == NULL) {
162
    fprintf(stderr, "cannot find user %s: %s\n", user, strerror(errno));
163
    return false;
164
  }
165
  uid_t uid = ppwd->pw_uid;
166
 
167
  char s[NAME_MAX];
168
  strcpy(s, "/tmp/incron.table-XXXXXX");
169
 
170
  uid_t iu = geteuid();
171
 
172
  if (seteuid(uid) != 0) {
173
    fprintf(stderr, "cannot change effective UID: %s\n", strerror(errno));
174
    return false;
175
  }
176
 
177
  int fd = mkstemp(s);
178
  if (fd == -1) {
179
    fprintf(stderr, "cannot create temporary file: %s\n", strerror(errno));
180
    return false;
181
  }
182
 
183
  if (fchmod(fd, 0644) != 0) {
184
    fprintf(stderr, "cannot change mode of temporary file: %s\n", strerror(errno));
185
    close(fd);
186
    unlink_suid(s, uid);
187
    return false;
188
  }
189
 
190
  if (seteuid(iu) != 0) {
191
    fprintf(stderr, "cannot change effective UID: %s\n", strerror(errno));
192
    close(fd);
193
    unlink_suid(s, uid);
194
    return false;
195
  }
196
 
197
  FILE* out = fdopen(fd, "w");
198
  if (out == NULL) {
199
    fprintf(stderr, "cannot write to temporary file: %s\n", strerror(errno));
200
    close(fd);
201
    unlink_suid(s, uid);
202
    return false;
203
  }
204
 
205
  FILE* in = fopen(tp.c_str(), "r");
206
  if (in == NULL) {
207
    if (errno == ENOENT) {
208
      in = fopen("/dev/null", "r");
209
      if (in == NULL) {
210
        fprintf(stderr, "cannot get empty table for %s: %s\n", user, strerror(errno));
211
        fclose(out);
212
        unlink_suid(s, uid);
213
        return false;
214
      }
215
    }
216
    else {
217
      fprintf(stderr, "cannot read old table for %s: %s\n", user, strerror(errno));
218
      fclose(out);
219
      unlink_suid(s, uid);
220
      return false;
221
    }
222
  }
223
 
224
  char buf[1024];
225
  while (fgets(buf, 1024, in) != NULL) {
226
    fputs(buf, out);
227
  }
228
  fclose(in);
229
  fclose(out);
230
 
231
  struct stat st;
232
  if (stat(s, &st) != 0) {
233
    fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno));
234
    unlink_suid(s, uid);
235
    return false;
236
  }
237
 
238
  time_t mt = st.st_mtime;
239
 
240
  const char* e = getenv("EDITOR");
241
  if (e == NULL)
242
    e = INCRON_DEFAULT_EDITOR;
243
 
244
  pid_t pid = fork();
245
  if (pid == 0) {
246
    if (setuid(uid) != 0) {
247
      fprintf(stderr, "cannot set user %s: %s\n", user, strerror(errno));
248
      return false;
249
    }    
250
 
251
    execlp(e, e, s, NULL);
252
    _exit(1);
253
  }
254
  else if (pid > 0) {
255
    int status;
256
    if (wait(&status) != pid) {
257
      perror("error while waiting for editor");
258
      unlink_suid(s, uid);
259
      return false;
260
    }
261
    if (!(WIFEXITED(status)) || WEXITSTATUS(status) != 0) {
262
      perror("editor finished with error");
263
      unlink_suid(s, uid);
264
      return false;
265
    }
266
  }
267
  else {
268
    perror("cannot start editor");
269
    unlink_suid(s, uid);
270
    return false;
271
  }
272
 
273
  if (stat(s, &st) != 0) {
274
    fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno));
275
    unlink_suid(s, uid);
276
    return false;
277
  }
278
 
279
  if (st.st_mtime == mt) {
280
    fprintf(stderr, "table unchanged\n");
281
    unlink_suid(s, uid);
282
    return true;
283
  }
284
 
285
  InCronTab ict;
286
  if (!ict.Load(s) || !ict.Save(tp)) {
287
    fprintf(stderr, "cannot move temporary table: %s\n", strerror(errno));
288
    unlink(s);
289
    return false;
290
  }
291
 
292
  fprintf(stderr, "table updated\n");
293
 
294
  unlink_suid(s, uid);
295
  return true;
296
}
297
 
298
 
299
int main(int argc, char** argv)
300
{
301
  struct arguments arguments;
302
 
303
  arguments.user = NULL;
304
  arguments.oper = OPER_NONE;
305
  arguments.file = NULL;
306
 
307
  argp_parse (&argp, argc, argv, 0, 0, &arguments);
308
 
309
  if (arguments.file != NULL && arguments.oper != OPER_NONE) {
310
    fprintf(stderr, "invalid arguments - specify source file or operation\n");
311
    return 1;
312
  }
313
  if (arguments.file == NULL && arguments.oper == OPER_NONE) {
314
    fprintf(stderr, "invalid arguments - specify source file or operation\n");
315
    return 1;
316
  }
317
 
318
  uid_t uid = getuid();
319
 
320
  if (uid != 0 && arguments.user != NULL) {
321
    fprintf(stderr, "cannot access table for user %s: permission denied\n", arguments.user);
322
    return 1;
323
  }
324
 
325
  struct passwd pwd;
326
 
327
  if (arguments.user == NULL) {
328
    struct passwd* ppwd = getpwuid(uid);
329
    if (ppwd == NULL) {
330
      fprintf(stderr, "cannot determine current user\n");
331
      return 1;
332
    }
333
    memcpy(&pwd, ppwd, sizeof(pwd));
334
    arguments.user = pwd.pw_name;
335
  }
336
  else if (getpwnam(arguments.user) == NULL) {
337
    fprintf(stderr, "user %s not found\n", arguments.user);
338
    return 1;
339
  }
340
 
341
  if (!InCronTab::CheckUser(arguments.user)) {
342
    fprintf(stderr, "user %s is not allowed to use incron\n", arguments.user);
343
    return 1;
344
  }
345
 
346
  switch (arguments.oper) {
347
    case OPER_NONE:
348
      fprintf(stderr, "copying table from file: %s\n", arguments.file);
349
      if (!copy_from_file(arguments.file, arguments.user))
350
        return 1;
351
      break;
352
    case OPER_LIST:
353
      if (!list_table(arguments.user))
354
        return 1;
355
      break;
356
    case OPER_REMOVE:
357
      fprintf(stderr, "removing table for user %s\n", arguments.user);
358
      if (!remove_table(arguments.user))
359
        return 1;
360
      break;
361
    case OPER_EDIT:
362
      if (!edit_table(arguments.user))
363
        return 1;
364
      break;
365
    default:
366
      fprintf(stderr, "invalid usage\n");
367
      return 1;
368
  }
369
 
370
  return 0;
371
}