/** * This function returns no value at all and on error it * throws an exception. */ void prepare_pipe() { g_cldPipe[0] = -1; g_cldPipe[1] = -1; if (pipe(g_cldPipe) != 0) throw InotifyException("cannot create notification pipe", errno, NULL); for (int i=0; i<2; i++) { int res = fcntl(g_cldPipe[i], F_GETFL); if (res == -1) throw InotifyException("cannot get pipe flags", errno, NULL); res |= O_NONBLOCK; if (fcntl(g_cldPipe[i], F_SETFL, res) == -1) throw InotifyException("cannot set pipe flags", errno, NULL); res = fcntl(g_cldPipe[i], F_GETFD); if (res == -1) throw InotifyException("cannot get pipe flags", errno, NULL); res |= FD_CLOEXEC; if (fcntl(g_cldPipe[i], F_SETFD, res) == -1) throw InotifyException("cannot set pipe flags", errno, NULL); } }
void Inotify::Remove(InotifyWatch* pWatch) throw (InotifyException) { if (m_fd == -1) throw InotifyException(IN_EXC_MSG("invalid file descriptor"), EBUSY, this); if (inotify_rm_watch(m_fd, pWatch->GetMask()) == -1) throw InotifyException(IN_EXC_MSG("removing watch failed"), errno, this); m_watches.erase(pWatch->m_wd); pWatch->m_wd = -1; pWatch->m_pInotify = NULL; }
void Inotify::Add(InotifyWatch* pWatch) throw (InotifyException) { if (m_fd == -1) throw InotifyException(IN_EXC_MSG("invalid file descriptor"), EBUSY, this); pWatch->m_wd = inotify_add_watch(m_fd, pWatch->GetPath().c_str(), pWatch->GetMask()); if (pWatch->m_wd == -1) throw InotifyException(IN_EXC_MSG("adding watch failed"), errno, this); m_watches.insert(IN_WATCH_MAP::value_type(pWatch->m_wd, pWatch)); pWatch->m_pInotify = this; }
/** * Loaded tables are registered for processing events. * * \param[in] pIn inotify object * \param[in] pEd inotify event dispatcher * * \throw InotifyException thrown if base table directory cannot be read */ void load_tables(Inotify* pIn, EventDispatcher* pEd) throw (InotifyException) { DIR* d = opendir(INCRON_TABLE_BASE); if (d == NULL) throw InotifyException("cannot open table directory", errno); syslog(LOG_NOTICE, "loading user tables"); struct dirent* pDe = NULL; while ((pDe = readdir(d)) != NULL) { std::string un(pDe->d_name); if (pDe->d_type == DT_REG && un != "." && un != "..") { if (check_user(pDe->d_name)) { syslog(LOG_INFO, "loading table for user %s", pDe->d_name); UserTable* pUt = new UserTable(pIn, pEd, un); g_ut.insert(SUT_MAP::value_type(un, pUt)); pUt->Load(); } else { syslog(LOG_WARNING, "table for invalid user %s found (ignored)", pDe->d_name); } } } closedir(d); }
std::string IncronTab::GetSystemTablePath(const std::string& rName) { std::string s; if (!IncronCfg::GetValue("system_table_dir", s)) throw InotifyException("configuration is corrupted", EINVAL); return IncronCfg::BuildPath(s, rName); }
std::string IncronTab::GetUserTablePath(const std::string& rUser) { std::string s; if (!IncronCfg::GetValue("user_table_dir", s)) throw InotifyException("configuration is corrupted", EINVAL); return IncronCfg::BuildPath(s, rUser); }
bool IncronTab::CheckUser(const std::string& rUser) { char s[100], u[100]; std::string path; if (!IncronCfg::GetValue("allowed_users", path)) throw InotifyException("configuration is corrupted", EINVAL); FILE* f = fopen(path.c_str(), "r"); if (f == NULL) { if (errno == ENOENT) { if (!IncronCfg::GetValue("denied_users", path)) throw InotifyException("configuration is corrupted", EINVAL); f = fopen(path.c_str(), "r"); if (f == NULL) { return errno == ENOENT; } while (fgets(s, 100, f) != NULL) { if (sscanf(s, "%s", u) == 1) { if (rUser == u) { fclose(f); return false; } } } fclose(f); return true; } return false; } while (fgets(s, 100, f) != NULL) { if (sscanf(s, "%s", u) == 1) { if (rUser == u) { fclose(f); return true; } } } fclose(f); return false; }
void Inotify::SetNonBlock(bool fNonBlock) throw (InotifyException) { if (m_fd == -1) throw InotifyException(IN_EXC_MSG("invalid file descriptor"), EBUSY, this); int res = fcntl(m_fd, F_GETFL); if (res == -1) throw InotifyException(IN_EXC_MSG("cannot get inotify flags"), errno, this); if (fNonBlock) { res |= O_NONBLOCK; } else { res &= ~O_NONBLOCK; } if (fcntl(m_fd, F_SETFL, res) == -1) throw InotifyException(IN_EXC_MSG("cannot set inotify flags"), errno, this); }
bool Inotify::PeekEvent(InotifyEvent* pEvt) throw (InotifyException) { if (pEvt == NULL) throw InotifyException(IN_EXC_MSG("null pointer to event"), EINVAL, this); if (!m_events.empty()) { *pEvt = m_events.front(); return true; } return false; }
void Inotify::WaitForEvents(bool fNoIntr) throw (InotifyException) { ssize_t len = 0; do { len = read(m_fd, m_buf, INOTIFY_BUFLEN); } while (fNoIntr && len == -1 && errno == EINTR); if (len < 0) throw InotifyException(IN_EXC_MSG("reading events failed"), errno, this); ssize_t i = 0; while (i < len) { struct inotify_event* pEvt = (struct inotify_event*) &m_buf[i]; InotifyWatch* pW = FindWatch(pEvt->wd); if (pW != NULL && pW->IsEnabled()) { InotifyEvent evt(pEvt, pW); m_events.push_back(evt); } i += INOTIFY_EVENT_SIZE + (ssize_t) pEvt->len; } }
/** * \param[in] rUser user name * \return true = success, false = failure * * \attention This function is very complex and may contain * various bugs including security ones. Please keep * it in mind.. */ bool edit_table(const std::string& rUser) { std::string tp(IncronTab::GetUserTablePath(rUser)); struct passwd* ppwd = getpwnam(rUser.c_str()); if (ppwd == NULL) { fprintf(stderr, "cannot find user '%s': %s\n", rUser.c_str(), strerror(errno)); return false; } uid_t uid = ppwd->pw_uid; uid_t gid = ppwd->pw_gid; char s[NAME_MAX]; strcpy(s, "/tmp/incron.table-XXXXXX"); uid_t iu = geteuid(); uid_t ig = getegid(); if (setegid(gid) != 0 || seteuid(uid) != 0) { fprintf(stderr, "cannot change effective UID/GID for user '%s': %s\n", rUser.c_str(), strerror(errno)); return false; } int fd = mkstemp(s); if (fd == -1) { fprintf(stderr, "cannot create temporary file: %s\n", strerror(errno)); return false; } bool ok = false; FILE* out = NULL; FILE* in = NULL; time_t mt = (time_t) 0; const char* e = NULL; std::string ed; if (setegid(ig) != 0 || seteuid(iu) != 0) { fprintf(stderr, "cannot change effective UID/GID: %s\n", strerror(errno)); close(fd); goto end; } out = fdopen(fd, "w"); if (out == NULL) { fprintf(stderr, "cannot write to temporary file: %s\n", strerror(errno)); close(fd); goto end; } in = fopen(tp.c_str(), "r"); if (in == NULL) { if (errno == ENOENT) { in = fopen("/dev/null", "r"); if (in == NULL) { fprintf(stderr, "cannot get empty table for '%s': %s\n", rUser.c_str(), strerror(errno)); fclose(out); goto end; } } else { fprintf(stderr, "cannot read old table for '%s': %s\n", rUser.c_str(), strerror(errno)); fclose(out); goto end; } } char buf[1024]; while (fgets(buf, 1024, in) != NULL) { fputs(buf, out); } fclose(in); fclose(out); struct stat st; if (stat(s, &st) != 0) { fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno)); goto end; } mt = st.st_mtime; // save modification time for detecting its change // Editor selecting algorithm: // 1. Check EDITOR environment variable // 2. Check VISUAL environment variable // 3. Try to get from configuration // 4. Check presence of /etc/alternatives/editor // 5. Use hard-wired editor e = getenv("EDITOR"); if (e == NULL) { e = getenv("VISUAL"); if (e == NULL) { if (!IncronCfg::GetValue("editor", ed)) throw InotifyException("configuration is corrupted", EINVAL); if (!ed.empty()) { e = ed.c_str(); } else { if (access(INCRON_ALT_EDITOR, X_OK) == 0) e = INCRON_ALT_EDITOR; else e = INCRON_DEFAULT_EDITOR; } } } // this block is explicite due to gotos' usage simplification { pid_t pid = fork(); if (pid == 0) { if (setgid(gid) != 0 || setuid(uid) != 0) { fprintf(stderr, "cannot set user '%s': %s\n", rUser.c_str(), strerror(errno)); goto end; } execlp(e, e, s, (const char*) NULL); _exit(1); } else if (pid > 0) { int status; if (wait(&status) != pid) { perror("error while waiting for editor"); goto end; } if (!(WIFEXITED(status)) || WEXITSTATUS(status) != 0) { perror("editor finished with error"); goto end; } } else { perror("cannot start editor"); goto end; } } if (stat(s, &st) != 0) { fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno)); goto end; } if (st.st_mtime == mt) { fprintf(stderr, "table unchanged\n"); ok = true; goto end; } { IncronTab ict; if (ict.Load(s) && ict.Save(tp)) { if (chmod(tp.c_str(), S_IRUSR | S_IWUSR) != 0) { fprintf(stderr, "cannot change mode of temporary file: %s\n", strerror(errno)); } } else { fprintf(stderr, "cannot move temporary table: %s\n", strerror(errno)); goto end; } } ok = true; fprintf(stderr, "table updated\n"); end: unlink(s); return ok; }
Inotify::Inotify() throw (InotifyException) { m_fd = inotify_init(); if (m_fd == -1) throw InotifyException(std::string(__PRETTY_FUNCTION__) + ": inotify init failed", errno, NULL); }
/** * \param[in] argc argument count * \param[in] argv argument array * \return 0 on success, 1 on error * * \attention In daemon mode, it finishes immediately. */ int main(int argc, char** argv) { openlog(INCRON_DAEMON_NAME, INCRON_LOG_OPTS, INCRON_LOG_FACIL); syslog(LOG_NOTICE, "starting service (version %s, built on %s %s)", INCRON_VERSION, __DATE__, __TIME__); try { Inotify in; in.SetNonBlock(true); EventDispatcher ed(&in); try { load_tables(&in, &ed); } catch (InotifyException e) { int err = e.GetErrorNumber(); syslog(LOG_CRIT, "%s: (%i) %s", e.GetMessage().c_str(), err, strerror(err)); syslog(LOG_NOTICE, "stopping service"); closelog(); return 1; } signal(SIGTERM, on_signal); signal(SIGINT, on_signal); signal(SIGCHLD, on_signal); if (DAEMON) daemon(0, 0); uint32_t wm = IN_CLOSE_WRITE | IN_DELETE | IN_MOVE | IN_DELETE_SELF | IN_UNMOUNT; InotifyWatch watch(INCRON_TABLE_BASE, wm); in.Add(watch); syslog(LOG_NOTICE, "ready to process filesystem events"); InotifyEvent e; struct pollfd pfd; pfd.fd = in.GetDescriptor(); pfd.events = (short) POLLIN; pfd.revents = (short) 0; while (!g_fFinish) { int res = poll(&pfd, 1, -1); if (res > 0) { in.WaitForEvents(true); } else if (res < 0) { if (errno != EINTR) throw InotifyException("polling failed", errno, NULL); } UserTable::FinishDone(); while (in.GetEvent(e)) { if (e.GetWatch() == &watch) { if (e.IsType(IN_DELETE_SELF) || e.IsType(IN_UNMOUNT)) { syslog(LOG_CRIT, "base directory destroyed, exitting"); g_fFinish = true; } else if (!e.GetName().empty()) { SUT_MAP::iterator it = g_ut.find(e.GetName()); if (it != g_ut.end()) { UserTable* pUt = (*it).second; if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) { syslog(LOG_INFO, "table for user %s changed, reloading", e.GetName().c_str()); pUt->Dispose(); pUt->Load(); } else if (e.IsType(IN_MOVED_FROM) || e.IsType(IN_DELETE)) { syslog(LOG_INFO, "table for user %s destroyed, removing", e.GetName().c_str()); delete pUt; g_ut.erase(it); } } else if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) { if (check_user(e.GetName().c_str())) { syslog(LOG_INFO, "table for user %s created, loading", e.GetName().c_str()); UserTable* pUt = new UserTable(&in, &ed, e.GetName()); g_ut.insert(SUT_MAP::value_type(e.GetName(), pUt)); pUt->Load(); } } } } else { ed.DispatchEvent(e); } } } } catch (InotifyException e) { int err = e.GetErrorNumber(); syslog(LOG_CRIT, "*** unhandled exception occurred ***"); syslog(LOG_CRIT, " %s", e.GetMessage().c_str()); syslog(LOG_CRIT, " error: (%i) %s", err, strerror(err)); } syslog(LOG_NOTICE, "stopping service"); closelog(); return 0; }
/** * \param[in] argc argument count * \param[in] argv argument array * \return 0 on success, 1 on error * * \attention In daemon mode, it finishes immediately. */ int main(int argc, char** argv) { AppArgs::Init(); if (!( AppArgs::AddOption("about", '?', AAT_NO_VALUE, false) && AppArgs::AddOption("help", 'h', AAT_NO_VALUE, false) && AppArgs::AddOption("foreground", 'n', AAT_NO_VALUE, false) && AppArgs::AddOption("kill", 'k', AAT_NO_VALUE, false) && AppArgs::AddOption("config", 'f', AAT_MANDATORY_VALUE, false) && AppArgs::AddOption("version", 'V', AAT_NO_VALUE, false))) { fprintf(stderr, "error while initializing application"); return 1; } AppArgs::Parse(argc, argv); if (AppArgs::ExistsOption("help")) { fprintf(stderr, "%s\n", INCROND_HELP); return 0; } if (AppArgs::ExistsOption("about")) { fprintf(stderr, "%s\n", INCROND_DESCRIPTION); return 0; } if (AppArgs::ExistsOption("version")) { fprintf(stderr, "%s\n", INCROND_VERSION); return 0; } IncronCfg::Init(); std::string cfg; if (!AppArgs::GetOption("config", cfg)) cfg = INCRON_CONFIG; IncronCfg::Load(cfg); std::string lckdir; IncronCfg::GetValue("lockfile_dir", lckdir); std::string lckfile; IncronCfg::GetValue("lockfile_name", lckfile); AppInstance app(lckfile, lckdir); if (AppArgs::ExistsOption("kill")) { fprintf(stderr, "attempting to terminate a running instance of incrond...\n"); if (app.Terminate()) { fprintf(stderr, "the instance notified, going down\n"); return 0; } else { fprintf(stderr, "error - incrond probably not running\n"); return 1; } } if (AppArgs::ExistsOption("foreground")) g_daemon = false; openlog(INCROND_NAME, INCRON_LOG_OPTS, INCRON_LOG_FACIL); syslog(LOG_NOTICE, "starting service (version %s, built on %s %s)", INCRON_VERSION, __DATE__, __TIME__); AppArgs::Destroy(); int ret = 0; std::string sysBase; std::string userBase; if (!IncronCfg::GetValue("system_table_dir", sysBase)) throw InotifyException("configuration is corrupted", EINVAL); if (access(sysBase.c_str(), R_OK) != 0) { syslog(LOG_CRIT, "cannot read directory for system tables (%s): (%i) %s", sysBase.c_str(), errno, strerror(errno)); if (!g_daemon) fprintf(stderr, "cannot read directory for system tables (%s): (%i) %s", sysBase.c_str(), errno, strerror(errno)); ret = 1; goto error; } if (!IncronCfg::GetValue("user_table_dir", userBase)) throw InotifyException("configuration is corrupted", EINVAL); if (access(userBase.c_str(), R_OK) != 0) { syslog(LOG_CRIT, "cannot read directory for user tables (%s): (%i) %s", userBase.c_str(), errno, strerror(errno)); if (!g_daemon) fprintf(stderr, "cannot read directory for user tables (%s): (%i) %s", userBase.c_str(), errno, strerror(errno)); ret = 1; goto error; } try { if (g_daemon) if (daemon(0, 0) == -1) { syslog(LOG_CRIT, "daemonizing failed: (%i) %s", errno, strerror(errno)); fprintf(stderr, "daemonizing failed: (%i) %s\n", errno, strerror(errno)); ret = 1; goto error; } try { if (!app.Lock()) { syslog(LOG_CRIT, "another instance of incrond already running"); if (!g_daemon) fprintf(stderr, "another instance of incrond already running\n"); ret = 1; goto error; } } catch (AppInstException e) { syslog(LOG_CRIT, "instance lookup failed: (%i) %s", e.GetErrorNumber(), strerror(e.GetErrorNumber())); if (!g_daemon) fprintf(stderr, "instance lookup failed: (%i) %s\n", e.GetErrorNumber(), strerror(e.GetErrorNumber())); ret = 1; goto error; } prepare_pipe(); Inotify in; in.SetNonBlock(true); in.SetCloseOnExec(true); uint32_t wm = IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_MOVE | IN_DELETE_SELF | IN_UNMOUNT; InotifyWatch stw(sysBase, wm); in.Add(stw); InotifyWatch utw(userBase, wm); in.Add(utw); EventDispatcher ed(g_cldPipe[0], &in, &stw, &utw); try { load_tables(&ed); } catch (InotifyException e) { int err = e.GetErrorNumber(); syslog(LOG_CRIT, "%s: (%i) %s", e.GetMessage().c_str(), err, strerror(err)); ret = 1; goto error; } ed.Rebuild(); // not too efficient, but simple signal(SIGTERM, on_signal); signal(SIGINT, on_signal); signal(SIGCHLD, on_signal); syslog(LOG_NOTICE, "ready to process filesystem events"); while (!g_fFinish) { int res = poll(ed.GetPollData(), ed.GetSize(), -1); if (res > 0) { if (ed.ProcessEvents()) UserTable::FinishDone(); } else if (res < 0) { switch (errno) { case EINTR: // syscall interrupted - continue polling break; case EAGAIN: // not enough resources - wait a moment and try again syslog(LOG_WARNING, "polling failed due to resource shortage, retrying later..."); sleep(POLL_EAGAIN_WAIT); break; default: throw InotifyException("polling failed", errno, NULL); } } } free_tables(&ed); if (g_cldPipe[0] != -1) close(g_cldPipe[0]); if (g_cldPipe[1] != -1) close(g_cldPipe[1]); } catch (InotifyException e) { int err = e.GetErrorNumber(); syslog(LOG_CRIT, "*** unhandled exception occurred ***"); syslog(LOG_CRIT, " %s", e.GetMessage().c_str()); syslog(LOG_CRIT, " error: (%i) %s", err, strerror(err)); ret = 1; } error: syslog(LOG_NOTICE, "stopping service"); closelog(); return ret; }
/** * Loaded tables are registered for processing events. * * \param[in] pEd inotify event dispatcher * * \throw InotifyException thrown if base table directory cannot be read */ void load_tables(EventDispatcher* pEd) throw (InotifyException) { // WARNING - this function has not been optimized!!! std::string s; if (!IncronCfg::GetValue("system_table_dir", s)) throw InotifyException("configuration system is corrupted", EINVAL); DIR* d = opendir(s.c_str()); if (d != NULL) { syslog(LOG_NOTICE, "loading system tables"); struct dirent* pDe = NULL; while ((pDe = readdir(d)) != NULL) { std::string un(pDe->d_name); std::string path(IncronCfg::BuildPath(s, pDe->d_name)); bool ok = pDe->d_type == DT_REG; if (pDe->d_type == DT_UNKNOWN) { struct stat st; if (stat(path.c_str(), &st) == 0) ok = S_ISREG(st.st_mode); } if (ok) { syslog(LOG_INFO, "loading table %s", pDe->d_name); UserTable* pUt = new UserTable(pEd, un, true); g_ut.insert(SUT_MAP::value_type(path, pUt)); pUt->Load(); } } closedir(d); } else { syslog(LOG_WARNING, "cannot open system table directory (ignoring)"); } if (!IncronCfg::GetValue("user_table_dir", s)) throw InotifyException("configuration system is corrupted", EINVAL); d = opendir(s.c_str()); if (d == NULL) throw InotifyException("cannot open user table directory", errno); syslog(LOG_NOTICE, "loading user tables"); struct dirent* pDe = NULL; while ((pDe = readdir(d)) != NULL) { std::string un(pDe->d_name); std::string path(IncronCfg::BuildPath(s, pDe->d_name)); bool ok = pDe->d_type == DT_REG; if (pDe->d_type == DT_UNKNOWN) { struct stat st; if (stat(path.c_str(), &st) == 0) ok = S_ISREG(st.st_mode); } if (ok) { if (UserTable::CheckUser(pDe->d_name)) { syslog(LOG_INFO, "loading table for user %s", pDe->d_name); UserTable* pUt = new UserTable(pEd, un, false); g_ut.insert(SUT_MAP::value_type(path, pUt)); pUt->Load(); } else { syslog(LOG_WARNING, "table for invalid user %s found (ignored)", pDe->d_name); } } } closedir(d); }