/* This file is part of SLD. Copyright (C) 2008 Sebastien LUTTRINGER SLD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. SLD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SLD; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "sld.hh" #include "daemon.hh" #include "sll/cypher.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include SLDaemon *SLDaemon::instance_ = 0; SLDaemon &SLDaemon::Instance() { if (instance_ == 0) instance_ = new SLDaemon(); return *instance_; } void SLDaemon::check_options() const { // Check validy of arguement if (options.server == "") throw Error(ERR_BADPARAM, "No valid server address specified"); if (options.port <= 0) throw Error(ERR_BADPARAM, "No valid server port specified"); if (options.login == "") throw Error(ERR_BADPARAM, "No valid login specified"); if (options.pass == "") throw Error(ERR_BADPARAM, "No valid pass specified"); if (options.scriptdir == "") throw Error(ERR_BADPARAM, "No valid scripts directory specified"); if (options.retrydelay < 1) throw Error(ERR_BADPARAM, "No valid retry delay specified"); // Check dir script exist if (euidaccess(options.scriptdir.c_str(), W_OK)) throw Error(ERR_BADPARAM, "Unable to write into script directory"); } void SLDaemon::run() { if (verbose()) logout << options; check_options(); clean_dir(options.scriptdir); net_connect: try { // run connection if (verbose()) logout << "Connecting to " << options.server.c_str() << " on port " << options.port << "...\n"; c_.connect(options.server.c_str(), options.port); if (verbose()) logout << "Connected to " << c_.getremotehostname() << " (" << c_.getremoteip() << ") on port " << c_.getremoteport() << "...\n"; // run auth auth(); // parse line while (1) { string line = c_.recvln(); // call right handler try { if (line == "EXIT") cmd_exit(); else if (!strcmp(line.c_str(), "VERSION\n")) cmd_version(); else if (!strcmp(line.c_str(), "STATUS\n")) cmd_status(); else if (!strcmp(line.c_str(), "LIST\n")) cmd_list(); else if (!strcmp(line.c_str(), "KILLALL\n")) cmd_killall(); else if (!strcmp(line.c_str(), "KILL\n")) cmd_kill(line.c_str()); else if (!strncmp(line.c_str(), "EXEC ", 4)) cmd_exec(line.c_str()); else if (!strncmp(line.c_str(), "FILE ", 5)) cmd_file(line.c_str()); else if (!strcmp(line.c_str(), "UPDATE\n")) cmd_update(); else c_.sendln("Invalid command."); } catch (const Error &e) { if (e.code() == ERR_NET) throw; else { c_.sendln(e.message()); logerr << "!! " << e.message() << "\n"; } } } } // recupere les erreurs de reseaux. catch (const Error &e) { if (e.code() != ERR_NET && e.code() != ERR_AUTH) throw; // disconnect if (c_.connected()) c_.disconnect(); if (verbose()) // TODO: Print the time at lost logerr << "Connexion lost. Retrying in " << options.retrydelay << " seconds.\n"; sleep(options.retrydelay); goto net_connect; } } //****************************************************************************** // protocol functions //****************************************************************************** void SLDaemon::auth() { char buf[MAX_LINE_SIZE]; snprintf(buf, MAX_LINE_SIZE, "HOST %s", options.login.c_str()); c_.sendln(buf); snprintf(buf, MAX_LINE_SIZE, "PASS %s", Cypher::sha1_64(options.pass.c_str(), options.pass.length()).c_str()); c_.sendln(buf); string valid = c_.recvln(); if (valid != "OK") throw Error(ERR_AUTH, "Bad Authentification."); } //****************************************************************************** // command functions //****************************************************************************** void SLDaemon::cmd_exit() { //TODO: print date c_.sendln("EXIT: ok."); c_.disconnect(); exit(ERR_NO); } void SLDaemon::cmd_version() { c_.sendln(VERSION); } void SLDaemon::cmd_exec(const char *line) { assert(line); // build path char buf[MAX_LINE_SIZE]; if (sscanf(line, "EXEC %512s\n", buf) != 1) { //FIXME: Bad static magic number c_.sendln("EXEC: Syntax error."); return; } string path = options.scriptdir + "/" + buf; // Check transversal path attack if (strchr(buf, '/')) { c_.sendln("EXEC: Invalid character in job name."); return; } // Check if file exist struct stat st; int ret = lstat(path.c_str(), &st); if (ret) { char msg[MAX_LINE_SIZE]; snprintf(msg, MAX_LINE_SIZE, "EXEC: %s: %s.", buf, strerror(errno)); c_.sendln(msg); return; } // check for exec flag if (st.st_mode & S_IXUSR != S_IXUSR) { c_.sendln("EXEC: no exec flag."); return; } // check file owner if (st.st_uid != getuid()) { c_.sendln("EXEC: Bad file owner."); return; } // flush before fork c_.flush(); //Create new process pid_t pid = fork(); if (pid == -1) { c_.sendln((string) "EXEC: Unable to fork: " + strerror(errno) + "."); return; } // Father job if (pid > 0) { // reg job SLDaemon::Job j(pid, buf, time(0)); jobs_.insert(j); // send job info snprintf(buf, MAX_LINE_SIZE, "EXEC: %s, pid: %d, start at: %ld.", j.name.c_str(), j.pid, j.start_time); c_.sendln(buf); // allow sun to start if (kill(pid, SIGCONT)) { logerr << "sld: cmd_exec" << strerror(errno) << ".\n"; jobs_.erase(j); } } // Son job else if (pid == 0) { // Wait father registration stuff before doing something else // so we suspend execution if (kill(getpid(), SIGSTOP)) { logerr << "sld: cmd_exec" << strerror(errno) << ".\n"; return; } // try to run job if (dup2(c_.getsocket(), STDOUT_FILENO) == -1) { logerr << ">> dup2" << strerror(errno) << ".\n"; exit(ERR_UNKNOWN); } if (dup2(c_.getsocket(), STDERR_FILENO) == -1) { logerr << ">> dup2" << strerror(errno) << ".\n"; exit(ERR_UNKNOWN); } execl(path.c_str(), basename(path.c_str()), 0); logerr << "sld: execl (" << path << "): " << strerror(errno) << ".\n"; exit(ERR_UNKNOWN); } } void SLDaemon::cmd_file(const char *line) { assert(line); // get filename char filename[MAX_LINE_SIZE]; if (sscanf(line, "FILE %512s\n", filename) != 1) { //FIXME: bad magic size c_.sendln("FILE: Syntax error."); return; } // Check transversal path attack if (strchr(filename, '/')) { c_.sendln("FILE: Invalid character in filename."); return; } string target = options.scriptdir + "/" + filename; //get size int size; string buf = c_.recvln(); int ret = sscanf(buf.c_str(), "SIZE %i\n", &size); if (ret != 1) { c_.sendln("FILE: Invalid size parameter."); return; } //get md5 char md5[MAX_LINE_SIZE]; buf = c_.recvln(); ret = sscanf(buf.c_str(), "MD5 %512s\n", md5); //FIXME: bad magic size if (ret != 1) { c_.sendln("FILE: Invalid md5 parameter."); return; } //get data try { recv2file(size, target); } catch (const Error &err) { if (err.code() == ERR_FILE) { c_.sendln("FILE: Data transfer error."); return; } throw; } // check local file MD5 buf = Cypher::md5_16(target); if (buf == md5) { c_.sendln("FILE: file " + target + ": Invalid MD5."); unlink(target.c_str()); return; } // proceed chown if (chown(target.c_str(), getuid(), getgid())) { c_.sendln("FILE: chown of " + target + ": Unable to chown."); unlink(target.c_str()); return; } // proceed chmod if (chmod(target.c_str(), S_IRUSR | S_IWUSR | S_IXUSR)) { c_.sendln("FILE: chmod of " + target + ": Unable to chmod."); unlink(target.c_str()); return; } c_.sendln("FILE: Transfer of " + target + ": OK."); } void SLDaemon::cmd_update() { // get filename const string &target = getbinpath(); //get size int size; string buf = c_.recvln(); int ret = sscanf(buf.c_str(), "SIZE %i\n", &size); if (ret != 1) { c_.sendln("UPDATE: Syntax error."); return; } //get remote md5 char md5[MAX_LINE_SIZE]; buf = c_.recvln(); ret = sscanf(buf.c_str(), "MD5 %512s\n", md5); //FIXME: bad magic size if (ret != 1) { c_.sendln("UPDATE: Invalid md5 parameter."); return; } // find tempory filename char tempsld[PATH_MAX] = "/tmp/sldXXXXXX"; int tempsld_fd = mkstemp(tempsld); if (tempsld_fd == 0) { c_.sendln((string) "UPDATE: mkstemp: " + strerror(errno) + "."); return; } close(tempsld_fd); //get data try { recv2file(size, tempsld); } catch (const Error &err) { if (err.code() == ERR_FILE) { c_.sendln("UPDATE: " + err.message() + "."); unlink(tempsld); return; } throw; } // check tmp file MD5 buf = Cypher::md5_16(tempsld); if (buf == md5) { c_.sendln((string) "UPDATE: file " + tempsld + ": Invalid MD5."); unlink(tempsld); return; } // copy file to its right destination unlink(target.c_str()); cp(tempsld, target.c_str()); // check final file MD5 buf = Cypher::md5_16(tempsld); if (buf == md5) { c_.sendln("UPDATE: file " + target + ": Invalid MD5."); unlink(tempsld); return; } // proceed chown if (chown(target.c_str(), getuid(), getgid())) { c_.sendln("FILE: chown of " + target + ": Unable to chown."); unlink(target.c_str()); return; } // proceed chmod if (chmod(target.c_str(), S_IRUSR | S_IWUSR | S_IXUSR)) { c_.sendln("FILE: chmod of " + target + ": Unable to chmod."); unlink(target.c_str()); return; } c_.sendln("UPDATE: Transfer of " + target + ": OK."); } void SLDaemon::cmd_list() { FILE *fls = popen(string("ls -1A " + options.scriptdir).c_str(), "r"); if (fls == 0) { c_.sendln("LIST: Unable to list " + options.scriptdir + "."); return; } char buf[255]; size_t len; try { while ((len = fread(buf, 1, 255, fls)) > 0) c_.send(buf, len); if (verbose()) logout << "LIST: data send.\n"; c_.flush(); } catch (...) { pclose(fls); throw; } } void SLDaemon::cmd_status() { t_job::iterator i; time_t t = time(0); char buf[MAX_LINE_SIZE]; if (jobs_.size() == 0) return; c_.sendln("STATUS of"); for (i = jobs_.begin(); i != jobs_.end(); ++i) { snprintf(buf, MAX_LINE_SIZE, " job: %s, pid: %d, start at: %ld, since: %ld seconds.", i->name.c_str(), i->pid, i->start_time,t - i->start_time); c_.sendln(buf); } } void SLDaemon::cmd_kill(const char *line) { t_job::iterator i; char buf[MAX_LINE_SIZE]; // retrieve pid int pid; if (sscanf(line, "KILL %i\n", &pid) != 1) { c_.sendln("KILL: Syntax error."); return; } for (i = jobs_.begin(); i != jobs_.end(); ++i) if (pid == i->pid) { int ret = kill(i->pid, SIGKILL); snprintf(buf, MAX_LINE_SIZE, "KILL: kill -SIGKILL %d (%s), return %d (%s).", i->pid, i->name.c_str(), ret, ((ret == -1) ? strerror(errno) : "OK" )); c_.sendln(buf); } } void SLDaemon::cmd_killall() { t_job::iterator i; char buf[MAX_LINE_SIZE]; for (i = jobs_.begin(); i != jobs_.end(); ++i) { int ret = kill(i->pid, SIGKILL); snprintf(buf, MAX_LINE_SIZE, "KILL: kill -SIGKILL %d (%s), return %d (%s).", i->pid, i->name.c_str(), ret, ((ret == -1) ? strerror(errno) : "OK" )); c_.sendln(buf); } } //****************************************************************************** // others functions //****************************************************************************** void SLDaemon::recv2file(size_t size, const string &filename) { char *data = c_.recv(size); FILE *fs = fopen(filename.c_str(), "w"); if (fs == 0) throw Error(ERR_FILE, (string) "recv: fopen: " + strerror(errno)); if (fwrite(data, 1, size, fs) != size) throw Error(ERR_FILE, (string) "recv: fwrite: " + strerror(errno)); if (fclose(fs)) throw Error(ERR_FILE, (string) "recv: fclose: " + strerror(errno)); } void SLDaemon::clean_dir(const string &dir) const { DIR *ds; struct dirent *de; ds = opendir(dir.c_str()); if (ds == 0) throw Error(ERR_FILE, "clear_dir: Unable to read dir script"); while ((de = readdir(ds))) { if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue; if (remove(string(dir + "/" + de->d_name).c_str())) { closedir(ds); throw Error(ERR_FILE,(string) "clear_dir: remove("+ de->d_name + "): " + strerror(errno)); } } closedir(ds); } //****************************************************************************** // Class Job functions //****************************************************************************** SLDaemon::Job::Job(int p, const string &s, time_t t) : pid(p), name(s), start_time(t) {} bool SLDaemon::Job::operator()(const SLDaemon::Job &a, const SLDaemon::Job &b) { return a.pid < b.pid; } //****************************************************************************** // Friends Class functions //****************************************************************************** void sigchild(int) { int status; SLDaemon &d = SLDaemon::Instance(); typedef SLDaemon::t_job t_job; t_job::iterator i; t_job &jobs_ = d.jobs_; pid_t pid; // Retrieve a pid while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { time_t t = time(0); for (i = jobs_.begin(); i != jobs_.end(); ++i) { if (i->pid == pid) { char buf[MAX_LINE_SIZE]; snprintf(buf, MAX_LINE_SIZE, "JOB: %s, pid: %d, end at: %ld, since: %ld, return: %d", i->name.c_str(), i->pid, t, t - i->start_time, WEXITSTATUS(status)); d.c_.sendln(buf); if (d.verbose()) logout << SND_DATA << buf; jobs_.erase(i); break; } } } } void sigint(int i) { logerr << "Signal " << i << " catched.\n"; SLDaemon::Instance().c_.disconnect(); exit(ERR_SIGNAL); }