Subversion Repositories public

Rev

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