Subversion Repositories public

Rev

Rev 69 | Rev 83 | 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 daemon user tables implementation
3
/**
4
 * \file usertable.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 <pwd.h>
18
#include <syslog.h>
19
#include <errno.h>
47 luk 20
#include <sys/wait.h>
65 luk 21
#include <unistd.h>
22
#include <grp.h>
23
#include <sys/stat.h>
45 luk 24
 
25
#include "usertable.h"
69 luk 26
#include "incroncfg.h"
45 luk 27
 
65 luk 28
#ifdef IN_DONT_FOLLOW
67 luk 29
#define DONT_FOLLOW(mask) InotifyEvent::IsType(mask, IN_DONT_FOLLOW)
65 luk 30
#else // IN_DONT_FOLLOW
67 luk 31
#define DONT_FOLLOW(mask) (false)
65 luk 32
#endif // IN_DONT_FOLLOW
45 luk 33
 
77 luk 34
// this is not enough, but...
35
#define DEFAULT_PATH "/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin"
65 luk 36
 
77 luk 37
 
69 luk 38
PROC_MAP UserTable::s_procMap;
47 luk 39
 
67 luk 40
extern volatile bool g_fFinish;
41
extern SUT_MAP g_ut;
47 luk 42
 
67 luk 43
 
47 luk 44
void on_proc_done(InotifyWatch* pW)
45
{
46
  pW->SetEnabled(true);
47
}
48
 
49
 
67 luk 50
EventDispatcher::EventDispatcher(int iPipeFd, Inotify* pIn, InotifyWatch* pSys, InotifyWatch* pUser)
45 luk 51
{
67 luk 52
  m_iPipeFd = iPipeFd;
53
  m_iMgmtFd = pIn->GetDescriptor();
54
  m_pIn = pIn;
55
  m_pSys = pSys;
56
  m_pUser = pUser;
57
  m_size = 0;
58
  m_pPoll = NULL;
45 luk 59
}
60
 
67 luk 61
EventDispatcher::~EventDispatcher()
45 luk 62
{
67 luk 63
  if (m_pPoll != NULL)
64
    delete[] m_pPoll;
65
}
66
 
67
bool EventDispatcher::ProcessEvents()
68
{
69
  // consume pipe events if any (and report back)
70
  bool pipe = (m_pPoll[0].revents & POLLIN);
71
  if (pipe) {
72
    char c;
73
    while (read(m_pPoll[0].fd, &c, 1) > 0) {}
74
    m_pPoll[0].revents = 0;
75
  }
76
 
77
  // process table management events if any
78
  if (m_pPoll[1].revents & POLLIN) {
79
    ProcessMgmtEvents();
80
    m_pPoll[1].revents = 0;
81
  }
45 luk 82
 
67 luk 83
  InotifyEvent evt;
45 luk 84
 
67 luk 85
  for (size_t i=2; i<m_size; i++) {
45 luk 86
 
67 luk 87
    // process events if occurred
88
    if (m_pPoll[i].revents & POLLIN) {
89
      FDUT_MAP::iterator it = m_maps.find(m_pPoll[i].fd);
90
      if (it != m_maps.end()) {
91
        Inotify* pIn = ((*it).second)->GetInotify();
92
        pIn->WaitForEvents(true);
93
 
94
        // process events for this object
95
        while (pIn->GetEvent(evt)) {
96
          ((*it).second)->OnEvent(evt);
97
        }
98
      }
99
      m_pPoll[i].revents = 0;
100
    }
101
  }
102
 
103
  return pipe;
45 luk 104
}
105
 
67 luk 106
void EventDispatcher::Register(UserTable* pTab)
45 luk 107
{
67 luk 108
  if (pTab != NULL) {
109
    Inotify* pIn = pTab->GetInotify();
110
    if (pIn != NULL) {
111
      int fd = pIn->GetDescriptor();
112
      if (fd != -1) {
113
        m_maps.insert(FDUT_MAP::value_type(fd, pTab));
114
        Rebuild();
115
      }
116
    }
117
  }
45 luk 118
}
119
 
67 luk 120
void EventDispatcher::Unregister(UserTable* pTab)
45 luk 121
{
67 luk 122
  FDUT_MAP::iterator it = m_maps.find(pTab->GetInotify()->GetDescriptor());
123
  if (it != m_maps.end()) {
55 luk 124
    m_maps.erase(it);
67 luk 125
    Rebuild();
126
  }
45 luk 127
}
67 luk 128
 
129
void EventDispatcher::Rebuild()
130
{
131
  // delete old data if exists
132
  if (m_pPoll != NULL)
133
    delete[] m_pPoll;
134
 
135
  // allocate memory
136
  m_size = m_maps.size() + 2;
137
  m_pPoll = new struct pollfd[m_size];
45 luk 138
 
67 luk 139
  // add pipe descriptor
140
  m_pPoll[0].fd = m_iPipeFd;
141
  m_pPoll[0].events = POLLIN;
142
  m_pPoll[0].revents = 0;
143
 
144
  // add table management descriptor
145
  m_pPoll[1].fd = m_iMgmtFd;
146
  m_pPoll[1].events = POLLIN;
147
  m_pPoll[1].revents = 0;
148
 
149
  // add all inotify descriptors
150
  FDUT_MAP::iterator it = m_maps.begin();
151
  for (size_t i=2; i<m_size; i++, it++) {
152
    m_pPoll[i].fd = (*it).first;
153
    m_pPoll[i].events = POLLIN;
154
    m_pPoll[i].revents = 0;
155
  }
156
}
157
 
158
void EventDispatcher::ProcessMgmtEvents()
45 luk 159
{
67 luk 160
  m_pIn->WaitForEvents(true);
161
 
162
  InotifyEvent e;
163
 
164
  while (m_pIn->GetEvent(e)) {
165
    if (e.GetWatch() == m_pSys) {
166
      if (e.IsType(IN_DELETE_SELF) || e.IsType(IN_UNMOUNT)) {
167
        syslog(LOG_CRIT, "base directory destroyed, exitting");
168
        g_fFinish = true;
169
      }
170
      else if (!e.GetName().empty()) {
69 luk 171
        SUT_MAP::iterator it = g_ut.find(IncronCfg::BuildPath(m_pSys->GetPath(), e.GetName()));
67 luk 172
        if (it != g_ut.end()) {
173
          UserTable* pUt = (*it).second;
174
          if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) {
175
            syslog(LOG_INFO, "system table %s changed, reloading", e.GetName().c_str());
176
            pUt->Dispose();
177
            pUt->Load();
178
          }
179
          else if (e.IsType(IN_MOVED_FROM) || e.IsType(IN_DELETE)) {
180
            syslog(LOG_INFO, "system table %s destroyed, removing", e.GetName().c_str());
181
            delete pUt;
182
            g_ut.erase(it);
183
          }
184
        }
185
        else if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) {
186
          syslog(LOG_INFO, "system table %s created, loading", e.GetName().c_str());
187
          UserTable* pUt = new UserTable(this, e.GetName(), true);
69 luk 188
          g_ut.insert(SUT_MAP::value_type(IncronTab::GetSystemTablePath(e.GetName()), pUt));
67 luk 189
          pUt->Load();
190
        }
191
      }
45 luk 192
    }
67 luk 193
    else if (e.GetWatch() == m_pUser) {
194
      if (e.IsType(IN_DELETE_SELF) || e.IsType(IN_UNMOUNT)) {
195
        syslog(LOG_CRIT, "base directory destroyed, exitting");
196
        g_fFinish = true;
197
      }
198
      else if (!e.GetName().empty()) {
69 luk 199
        SUT_MAP::iterator it = g_ut.find(IncronCfg::BuildPath(m_pUser->GetPath(), e.GetName()));
67 luk 200
        if (it != g_ut.end()) {
201
          UserTable* pUt = (*it).second;
202
          if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) {
203
            syslog(LOG_INFO, "table for user %s changed, reloading", e.GetName().c_str());
204
            pUt->Dispose();
205
            pUt->Load();
206
          }
207
          else if (e.IsType(IN_MOVED_FROM) || e.IsType(IN_DELETE)) {
208
            syslog(LOG_INFO, "table for user %s destroyed, removing",  e.GetName().c_str());
209
            delete pUt;
210
            g_ut.erase(it);
211
          }
212
        }
213
        else if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) {
214
          if (UserTable::CheckUser(e.GetName().c_str())) {
215
            syslog(LOG_INFO, "table for user %s created, loading", e.GetName().c_str());
216
            UserTable* pUt = new UserTable(this, e.GetName(), false);
69 luk 217
            g_ut.insert(SUT_MAP::value_type(IncronTab::GetUserTablePath(e.GetName()), pUt));
67 luk 218
            pUt->Load();
219
          }
220
        }
221
      }
45 luk 222
    }
223
  }
224
}
225
 
226
 
227
 
228
 
67 luk 229
UserTable::UserTable(EventDispatcher* pEd, const std::string& rUser, bool fSysTable)
230
: m_user(rUser),
231
  m_fSysTable(fSysTable)
45 luk 232
{
233
  m_pEd = pEd;
67 luk 234
 
235
  m_in.SetNonBlock(true);
236
  m_in.SetCloseOnExec(true);
45 luk 237
}
238
 
239
UserTable::~UserTable()
240
{
241
  Dispose();
242
}
243
 
244
void UserTable::Load()
245
{
67 luk 246
  m_tab.Load(m_fSysTable
69 luk 247
      ? IncronTab::GetSystemTablePath(m_user)
248
      : IncronTab::GetUserTablePath(m_user));
45 luk 249
 
250
  int cnt = m_tab.GetCount();
251
  for (int i=0; i<cnt; i++) {
69 luk 252
    IncronTabEntry& rE = m_tab.GetEntry(i);
45 luk 253
    InotifyWatch* pW = new InotifyWatch(rE.GetPath(), rE.GetMask());
65 luk 254
 
255
    // warning only - permissions may change later
67 luk 256
    if (!(m_fSysTable || MayAccess(rE.GetPath(), DONT_FOLLOW(rE.GetMask()))))
65 luk 257
      syslog(LOG_WARNING, "access denied on %s - events will be discarded silently", rE.GetPath().c_str());
258
 
47 luk 259
    try {
67 luk 260
      m_in.Add(pW);
47 luk 261
      m_map.insert(IWCE_MAP::value_type(pW, &rE));
262
    } catch (InotifyException e) {
67 luk 263
      if (m_fSysTable)
264
        syslog(LOG_ERR, "cannot create watch for system table %s: (%i) %s", m_user.c_str(), e.GetErrorNumber(), strerror(e.GetErrorNumber()));
265
      else
266
        syslog(LOG_ERR, "cannot create watch for user %s: (%i) %s", m_user.c_str(), e.GetErrorNumber(), strerror(e.GetErrorNumber()));
47 luk 267
      delete pW;
268
    }
45 luk 269
  }
67 luk 270
 
271
  m_pEd->Register(this);
45 luk 272
}
273
 
274
void UserTable::Dispose()
275
{
67 luk 276
  m_pEd->Unregister(this);
277
 
45 luk 278
  IWCE_MAP::iterator it = m_map.begin();
279
  while (it != m_map.end()) {
280
    InotifyWatch* pW = (*it).first;
67 luk 281
    m_in.Remove(pW);
69 luk 282
 
283
    PROC_MAP::iterator it2 = s_procMap.begin();
284
    while (it2 != s_procMap.end()) {
285
      if ((*it2).second.pWatch == pW) {
286
        PROC_MAP::iterator it3 = it2;
287
        it2++;
288
        s_procMap.erase(it3);
289
      }
290
      else {
291
        it2++;
292
      }
293
    }
294
 
45 luk 295
    delete pW;
296
    it++;
297
  }
298
 
299
  m_map.clear();
300
}
301
 
302
void UserTable::OnEvent(InotifyEvent& rEvt)
303
{
304
  InotifyWatch* pW = rEvt.GetWatch();
69 luk 305
  IncronTabEntry* pE = FindEntry(pW);
45 luk 306
 
65 luk 307
  // no entry found - this shouldn't occur
45 luk 308
  if (pE == NULL)
309
    return;
310
 
65 luk 311
  // discard event if user has no access rights to watch path
67 luk 312
  if (!(m_fSysTable || MayAccess(pW->GetPath(), DONT_FOLLOW(rEvt.GetMask()))))
65 luk 313
    return;
314
 
45 luk 315
  std::string cmd;
316
  const std::string& cs = pE->GetCmd();
317
  size_t pos = 0;
318
  size_t oldpos = 0;
319
  size_t len = cs.length();
320
  while ((pos = cs.find('$', oldpos)) != std::string::npos) {
321
    if (pos < len - 1) {
322
      size_t px = pos + 1;
323
      if (cs[px] == '$') {
324
        cmd.append(cs.substr(oldpos, pos-oldpos+1));
325
        oldpos = pos + 2;
326
      }
327
      else {
328
        cmd.append(cs.substr(oldpos, pos-oldpos));
55 luk 329
        if (cs[px] == '@') {          // base path
330
          cmd.append(pW->GetPath());
331
          oldpos = pos + 2;
332
        }
333
        else if (cs[px] == '#') {     // file name
334
          cmd.append(rEvt.GetName());
335
          oldpos = pos + 2;
336
        }
337
        else if (cs[px] == '%') {     // mask symbols
338
          std::string s;
339
          rEvt.DumpTypes(s);
340
          cmd.append(s);
341
          oldpos = pos + 2;
342
        }
343
        else if (cs[px] == '&') {     // numeric mask
344
          char* s;
345
          asprintf(&s, "%u", (unsigned) rEvt.GetMask());
346
          cmd.append(s);
347
          free(s);
348
          oldpos = pos + 2;
349
        }
350
        else {
351
          oldpos = pos + 1;
352
        }
45 luk 353
      }
354
    }
355
    else {
356
      cmd.append(cs.substr(oldpos, pos-oldpos));
357
      oldpos = pos + 1;
358
    }
359
  }    
360
  cmd.append(cs.substr(oldpos));
361
 
362
  int argc;
363
  char** argv;
364
  if (!PrepareArgs(cmd, argc, argv)) {
365
    syslog(LOG_ERR, "cannot prepare command arguments");
366
    return;
367
  }
368
 
67 luk 369
  if (m_fSysTable)
370
    syslog(LOG_INFO, "(system::%s) CMD (%s)", m_user.c_str(), cmd.c_str());
371
  else
372
    syslog(LOG_INFO, "(%s) CMD (%s)", m_user.c_str(), cmd.c_str());
45 luk 373
 
47 luk 374
  if (pE->IsNoLoop())
375
    pW->SetEnabled(false);
376
 
69 luk 377
  pid_t pid = fork();
378
  if (pid == 0) {
45 luk 379
 
67 luk 380
    // for system table
381
    if (m_fSysTable) {
382
      if (execvp(argv[0], argv) != 0) // exec failed
383
      {
384
        syslog(LOG_ERR, "cannot exec process: %s", strerror(errno));
385
        _exit(1);
386
      }
45 luk 387
    }
67 luk 388
    else {
389
      // for user table
77 luk 390
      RunAsUser(argv);
67 luk 391
    }
45 luk 392
  }
69 luk 393
  else if (pid > 0) {
394
    ProcData_t pd;
47 luk 395
    if (pE->IsNoLoop()) {
396
      pd.onDone = on_proc_done;
397
      pd.pWatch = pW;
398
    }
399
    else {
400
      pd.onDone = NULL;
401
      pd.pWatch = NULL;
402
    }
45 luk 403
 
69 luk 404
    s_procMap.insert(PROC_MAP::value_type(pid, pd));
45 luk 405
  }
406
  else {
47 luk 407
    if (pE->IsNoLoop())
408
      pW->SetEnabled(true);
409
 
45 luk 410
    syslog(LOG_ERR, "cannot fork process: %s", strerror(errno));
411
  }
51 luk 412
 
413
  CleanupArgs(argc, argv);
45 luk 414
}
415
 
69 luk 416
IncronTabEntry* UserTable::FindEntry(InotifyWatch* pWatch)
45 luk 417
{
418
  IWCE_MAP::iterator it = m_map.find(pWatch);
419
  if (it == m_map.end())
420
    return NULL;
421
 
422
  return (*it).second;
423
}
424
 
425
bool UserTable::PrepareArgs(const std::string& rCmd, int& argc, char**& argv)
426
{
427
  if (rCmd.empty())
428
    return false;
429
 
55 luk 430
  StringTokenizer tok(rCmd, ' ', '\\');
45 luk 431
  std::deque<std::string> args;
432
  while (tok.HasMoreTokens()) {
433
    args.push_back(tok.GetNextToken());
434
  }
435
 
436
  if (args.empty())
437
    return false;
438
 
439
  argc = (int) args.size();
440
  argv = new char*[argc+1];
441
  argv[argc] = NULL;
442
 
443
  for (int i=0; i<argc; i++) {
444
    const std::string& s = args[i];
445
    size_t len = s.length();
446
    argv[i] = new char[len+1];
447
    strcpy(argv[i], s.c_str());
448
  }
449
 
450
  return true;
451
}
452
 
51 luk 453
void UserTable::CleanupArgs(int argc, char** argv)
454
{
455
  for (int i=0; i<argc; i++) {
456
    delete[] argv[i];
457
  }
458
 
459
  delete[] argv;
460
}
461
 
47 luk 462
void UserTable::FinishDone()
463
{
69 luk 464
  pid_t res = 0;
465
  int status = 0;
466
  while ((res = waitpid(-1, &status, WNOHANG)) > 0) {
467
    PROC_MAP::iterator it = s_procMap.find(res);
468
    if (it != s_procMap.end()) {
469
      ProcData_t pd = (*it).second;
47 luk 470
      if (pd.onDone != NULL)
471
        (*pd.onDone)(pd.pWatch);
69 luk 472
      s_procMap.erase(it);
47 luk 473
    }
474
  }  
475
}
476
 
65 luk 477
bool UserTable::MayAccess(const std::string& rPath, bool fNoFollow) const
478
{
479
  // first, retrieve file permissions
480
  struct stat st;
481
  int res = fNoFollow
482
      ? lstat(rPath.c_str(), &st) // don't follow symlink
483
      : stat(rPath.c_str(), &st);
484
  if (res != 0)
485
    return false; // retrieving permissions failed
486
 
487
  // file accessible to everyone
488
  if (st.st_mode & S_IRWXO)
489
    return true;
490
 
491
  // retrieve user data
492
  struct passwd* pwd = getpwnam(m_user.c_str());
493
 
67 luk 494
  // root may always access
495
  if (pwd->pw_uid == 0)
496
    return true;
497
 
65 luk 498
  // file accesible to group
499
  if (st.st_mode & S_IRWXG) {
500
 
501
    // user's primary group
502
    if (pwd != NULL && pwd->pw_gid == st.st_gid)
503
        return true;
504
 
505
    // now check group database
506
    struct group *gr = getgrgid(st.st_gid);
507
    if (gr != NULL) {
508
      int pos = 0;
509
      const char* un;
510
      while ((un = gr->gr_mem[pos]) != NULL) {
511
        if (strcmp(un, m_user.c_str()) == 0)
512
          return true;
513
        pos++;
514
      }
515
    }
516
  }
517
 
518
  // file accessible to owner
519
  if (st.st_mode & S_IRWXU) {  
520
    if (pwd != NULL && pwd->pw_uid == st.st_uid)
521
      return true;
522
  }
523
 
524
  return false; // no access right found
525
}
47 luk 526
 
77 luk 527
void UserTable::RunAsUser(char* const* argv) const
528
{
529
  struct passwd* pwd = getpwnam(m_user.c_str());
530
  if (    pwd == NULL                 // user not found
531
      ||  setgid(pwd->pw_gid) != 0    // setting GID failed
532
      ||  setuid(pwd->pw_uid) != 0)    // setting UID failed
533
  {
534
    goto failed;
535
  }
536
 
537
  if (pwd->pw_uid != 0) {
538
    if (clearenv() != 0)
539
      goto failed;
540
 
541
    if (    setenv("LOGNAME",   pwd->pw_name,   1) != 0
542
        ||  setenv("USER",      pwd->pw_name,   1) != 0
543
        ||  setenv("USERNAME",  pwd->pw_name,   1) != 0
544
        ||  setenv("HOME",      pwd->pw_dir,    1) != 0
545
        ||  setenv("SHELL",     pwd->pw_shell,  1) != 0
546
        ||  setenv("PATH",      DEFAULT_PATH,   1) != 0)
547
    {
548
      goto failed;
549
    }
550
  }
551
 
552
  execvp(argv[0], argv);  // this may return only on failure
553
 
554
failed:
555
 
556
  syslog(LOG_ERR, "cannot exec process: %s", strerror(errno));
557
  _exit(1);
558
}
65 luk 559