Subversion Repositories public

Rev

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