Subversion Repositories public

Rev

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