Subversion Repositories public

Rev

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