#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sld.hh" SLDaemon *SLDaemon::instance_ = 0; SLDaemon::SLDaemon() : socket_fs_(0) {} SLDaemon &SLDaemon::Instance() { if (instance_ == 0) instance_ = new SLDaemon(); return *instance_; } void SLDaemon::usage(const char *argv0) const { std::cerr << "usage: " << argv0 << " [-f conffile] [-d scriptdir] [-h] [-v] [-l login]" << "[-p pass] [-H host] [-P port] [-r] [-V]" << std::endl << " -f conffile : read and load conf file (def: /etc/sldrc)." << std::endl << " -d scriptdir : Scripts directory." << std::endl << " -h : Print this usage." << std::endl << " -v : Verbose mode." << std::endl << " -q : Quiet mode." << std::endl << " -l host : Set login to host. (def: check hostname)" << std::endl << " -p secret : Set pass to secret." << std::endl << " -H name : Set server host to name." << std::endl << " -P number : Set server port to number. (def: 18136)" << std::endl << " -r val : Set retry delay to val in seconds (def: 60)" << std::endl << " -V : Print version and exit." << std::endl; } SLDaemon::Options SLDaemon::getoptions(int argc, char *argv[]) const { Options opt; for (int i = 1; i < argc; ++i) { if (!strcmp(argv[i], "-h")) { usage(*argv); throw Error(ERR_USAGE); } else if (!strcmp(argv[i], "-v")) { opt.verbose = 1; } else if (!strcmp(argv[i], "-q")) { opt.verbose = 0; } else if (!strcmp(argv[i], "-V")) { std::cout << "sl daemon version : " << VERSION << std::endl; exit(ERR_OK); } else if (!strcmp(argv[i], "-H")) { if (++i >= argc) throw Error(ERR_USAGE, "No enough argument for option -h"); opt.server = string(argv[i]); } else if (!strcmp(argv[i], "-P")) { if (++i >= argc) throw Error(ERR_USAGE, "No enough argument for option -p"); char *endptr; opt.port = strtol(argv[i], &endptr, 10); if (!(*argv[i] != '\0' && *endptr == '\0')) throw Error(ERR_USAGE, "Unable to convert port to a number"); } else if (!strcmp(argv[i], "-f")) { if (++i >= argc) throw Error(ERR_USAGE, "No enough argument for option -f"); opt.conffile = string(argv[i]); } else if (!strcmp(argv[i], "-d")) { if (++i >= argc) throw Error(ERR_USAGE, "No enough argument for option -d"); opt.scriptdir = string(argv[i]); } else if (!strcmp(argv[i], "-l")) { if (++i >= argc) throw Error(ERR_USAGE, "No enough argument for option -l"); opt.login = string(argv[i]); } else if (!strcmp(argv[i], "-p")) { if (++i >= argc) throw Error(ERR_USAGE, "No enough argument for option -p"); opt.pass = string(argv[i]); } else if (!strcmp(argv[i], "-r")) { if (++i >= argc) throw Error(ERR_USAGE, "No enough argument for option -r"); opt.retrydelay = atoi(argv[i]); } else throw Error(ERR_USAGE, (string) "Invalid options : " + argv[i]); } return opt; } SLDaemon::Options SLDaemon::getoptions(const string file) const { ifstream fs; char line[MAX_CONF_LINE_SIZE]; int i=0; char name[11], value[MAX_CONF_LINE_SIZE - 10 + 1]; SLDaemon::Options o; fs.open(file.c_str(), ifstream::in); if (fs.fail()) throw Error(ERR_FILE, "Unable to open configuration file"); while (!fs.eof()) { fs.getline(line, MAX_CONF_LINE_SIZE); // if (fs.eof()) // break; sscanf(line, "%10[^=]=%2039s\n", name, value); //TODO: fix magic number if (!strcasecmp(name, "host")) o.login = value; else if (!strcasecmp(name, "pass")) o.pass = value; else if (!strcasecmp(name, "server")) o.server = value; else if (!strcasecmp(name, "port")) o.port = atoi(value); else if (!strcasecmp(name, "scriptdir")) o.scriptdir = value; else if (!strcasecmp(name, "verbose")) o.verbose = atoi(value); else if (!strcasecmp(name, "retrydelay")) o.retrydelay = atoi(value); else std::cerr << "Invalid line " << i << ": " << line; } fs.close(); return o; } void SLDaemon::applyoptions(const Options &opt) { options_.merge(opt); } 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() { char *line; if (verbose()) options_.print(); check_options(); clean_dir(options_.scriptdir); net_connect: try { connect(); auth(); while (1) { line = recvln(); // call right handler try { if (!strcmp(line, "EXIT\n")) cmd_exit(); else if (!strcmp(line, "VERSION\n")) cmd_version(); else if (!strcmp(line, "STATUS\n")) cmd_status(); else if (!strcmp(line, "LIST\n")) cmd_list(); else if (!strcmp(line, "KILLALL\n")) cmd_killall(); else if (!strcmp(line, "KILL\n")) cmd_kill(line); else if (!strncmp(line, "EXEC ", 4)) cmd_exec(line); else if (!strncmp(line, "FILE ", 5)) cmd_file(line); else if (!strcmp(line, "UPDATE\n")) cmd_update(); else sendln("Invalid command."); } catch (const Error &e) { delete [] line; if (e.code() == ERR_NET) throw; else { sendln(e.message()); std::cerr << "!! " << e.message() << std::endl; } } delete[] line; } } // recupere les erreurs de reseaux. catch (const Error &e) { if (e.code() != ERR_NET) throw; sleep(options_.retrydelay); goto net_connect; } } //****************************************************************************** // network functions //****************************************************************************** void SLDaemon::connect() { struct sockaddr_in daddr; struct hostent *h; // close existing connexion if (socket_fs_ != 0) disconnect(); // retrieve remote host info h = gethostbyname(options_.server.c_str()); if (h == 0) throw Error(ERR_NET, hstrerror(h_errno)); // create socket socket_fd_ = socket(PF_INET, SOCK_STREAM, 0); if (socket_fd_ == -1) throw Error(ERR_NET, strerror(errno)); daddr.sin_family = AF_INET; daddr.sin_port = htons(options_.port); daddr.sin_addr = *((struct in_addr *) h->h_addr); memset(daddr.sin_zero, '\0', sizeof daddr.sin_zero); if (verbose()) std::cout << "Connecting to " << h->h_name << " (" << inet_ntoa(*(struct in_addr*)h->h_addr) << ") on port " << options_.port << "..." << std::endl; // connect if (::connect(socket_fd_, (struct sockaddr *) &daddr, sizeof daddr) == -1) throw Error(ERR_NET, strerror(errno)); if (verbose()) std::cout << "Connected to " << h->h_name << " (" << inet_ntoa(*(struct in_addr*)h->h_addr) << ") on port " << options_.port << "..." << std::endl; // initialize socket stream if ((socket_fs_ = fdopen(socket_fd_, "r+")) == 0) throw Error(ERR_NET, strerror(errno)); } void SLDaemon::disconnect() { if (socket_fs_ == 0) return; if (fclose(socket_fs_)) throw Error(ERR_NET, strerror(errno)); if (verbose()) std::cout << "Connection closed." << std::endl; socket_fs_ = 0; } void SLDaemon::send(long int i) { fprintf(socket_fs_, "%li", i); } void SLDaemon::send(const string str, bool buf) { send(str.c_str(), str.length(), buf); } void SLDaemon::send(const char *data, size_t len, bool buf) { if (len == 0) return; if (fwrite(data, 1, len, socket_fs_) != len) throw Error(ERR_NET, strerror(errno)); if (!buf && fflush(socket_fs_)) throw Error(ERR_NET, strerror(errno)); } void SLDaemon::sendln(const string &s) { if (!fprintf(socket_fs_, "%s\n", s.c_str())) throw Error(ERR_NET, strerror(errno)); if (verbose()) std::cout << SND_DATA << s << std::endl; } char *SLDaemon::recv(size_t size) { char *data = new char[size]; if (fread(data, 1, size, socket_fs_) != size) { delete[] data; throw Error(ERR_NET, strerror(errno)); } return data; } void SLDaemon::recv(size_t size, const string &filename) { char *data = 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)); } char *SLDaemon::recvln() { char *line = new char[MAX_LINE_SIZE]; errno=0; if (fgets(line, MAX_LINE_SIZE, socket_fs_) == 0) { delete[] line; if(feof(socket_fs_)) throw Error(ERR_NET, (string) "recvln: Connexion close"); throw Error(ERR_FILE, (string) "recvln: " + strerror(errno)); } if (verbose()) std::cout << RCV_DATA << line; return line; } void SLDaemon::flush() { if (fflush(socket_fs_)) throw Error(ERR_NET, strerror(errno)); } //****************************************************************************** // protocol functions //****************************************************************************** void SLDaemon::auth() { unsigned char md[SHA_DIGEST_LENGTH]; char buf[MAX_LINE_SIZE]; char *buf2 = ""; BIO *bmem, *b64; BUF_MEM *bptr; SHA1((const unsigned char *) options_.pass.c_str(), options_.pass.length(), md); b64 = BIO_new(BIO_f_base64()); bmem = BIO_new(BIO_s_mem()); b64 = BIO_push(b64, bmem); BIO_write(b64, md, SHA_DIGEST_LENGTH); BIO_flush(b64); BIO_get_mem_ptr(b64, &bptr); if (bptr->length > 0) { buf2 = new char[bptr->length]; memcpy(buf2, bptr->data, bptr->length-1); buf2[bptr->length-1] = 0; } snprintf(buf, MAX_LINE_SIZE, "HOST %s", options_.login.c_str()); sendln(buf); snprintf(buf, MAX_LINE_SIZE, "PASS %s", buf2); sendln(buf); if (bptr->length > 0) delete[] buf2; BIO_free_all(b64); return; } //****************************************************************************** // command functions //****************************************************************************** void SLDaemon::cmd_exit() { sendln("Bye."); disconnect(); exit(ERR_OK); } void SLDaemon::cmd_version() { 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 sendln("EXEC: Syntax error."); return; } string path = options_.scriptdir + "/" + buf; // Check transversal path attack if (strchr(buf, '/')) { 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)); sendln(msg); return; } // check for exec flag if (st.st_mode & S_IXUSR != S_IXUSR) { sendln("EXEC: no exec flag."); return; } // check file owner if (st.st_uid != getuid()) { sendln("EXEC: Bad file owner."); return; } // flush before fork flush(); //Create new process pid_t pid = fork(); if (pid == -1) { 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); sendln(buf); // allow sun to start if (kill(pid, SIGCONT)) { perror("sld: cmd_exec"); 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)) { perror("sld: cmd_exec"); return; } // try to run job if (dup2(socket_fd_, STDOUT_FILENO) == -1) { perror(">> dup2"); exit(ERR_UNKNOWN); } if (dup2(socket_fd_, STDERR_FILENO) == -1) { perror(">> dup2"); exit(ERR_UNKNOWN); } execl(path.c_str(), basename(path.c_str()), 0); perror(string("sld: execl " + path).c_str()); exit(ERR_UNKNOWN); } } void SLDaemon::cmd_file(const char *line) { char *buf; int ret; assert(line); // get filename char filename[MAX_LINE_SIZE]; if (sscanf(line, "FILE %512s\n", filename) != 1) { //FIXME: bad magic size sendln("FILE: Syntax error."); return; } // Check transversal path attack if (strchr(filename, '/')) { sendln("FILE: Invalid character in filename."); return; } string target = options_.scriptdir + "/" + filename; //get size int size; buf = recvln(); ret = sscanf(buf, "SIZE %i\n", &size); delete[] buf; if (ret != 1) { sendln("FILE: Invalid size parameter."); return; } //get md5 char md5[MAX_LINE_SIZE]; buf = recvln(); ret = sscanf(buf, "MD5 %512s\n", md5); //FIXME: bad magic size delete[] buf; if (ret != 1) { sendln("FILE: Invalid md5 parameter."); return; } //get data try { recv(size, target); } catch (const Error &err) { if (err.code() == ERR_FILE) { sendln("FILE: Data transfer error."); return; } throw; } // check MD5 if (SLDaemon::md5(target) != string(md5)) { sendln("FILE: file " + target + ": Invalid MD5."); unlink(target.c_str()); return; } // proceed chown if (chown(target.c_str(), getuid(), getgid())) { 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)) { sendln("FILE: chmod of " + target + ": Unable to chmod."); unlink(target.c_str()); return; } sendln("FILE: Transfer of " + target + ": OK."); } void SLDaemon::cmd_update() { char *buf; int ret; // get filename const string &target = getbinpath(); //get size int size; buf = recvln(); ret = sscanf(buf, "SIZE %i\n", &size); delete[] buf; if (ret != 1) { sendln("UPDATE: Syntax error."); return; } //get md5 char md5[MAX_LINE_SIZE]; buf = recvln(); ret = sscanf(buf, "MD5 %512s\n", md5); //FIXME: bad magic size delete[] buf; if (ret != 1) { sendln("UPDATE: Invalid md5 parameter."); return; } // find tempory filename char tempsld[PATH_MAX] = "/tmp/sldXXXXXX"; int tempsld_fd = mkstemp(tempsld); if (tempsld_fd == 0) { sendln((string) "UPDATE: mkstemp: " + strerror(errno) + "."); return; } close(tempsld_fd); //get data try { recv(size, tempsld); } catch (const Error &err) { if (err.code() == ERR_FILE) { sendln("UPDATE: " + err.message() + "."); unlink(tempsld); return; } throw; } // check MD5 if (SLDaemon::md5(tempsld) != string(md5)) { 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 MD5 if (SLDaemon::md5(target.c_str()) != string(md5)) { sendln("UPDATE: file " + target + ": Invalid MD5."); unlink(tempsld); return; } // proceed chown if (chown(target.c_str(), getuid(), getgid())) { 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)) { sendln("FILE: chmod of " + target + ": Unable to chmod."); unlink(target.c_str()); return; } sendln("UPDATE: Transfer of " + target + ": OK."); } void SLDaemon::cmd_list() { FILE *fls = popen(string("ls -1A " + options_.scriptdir).c_str(), "r"); if (fls == 0) { sendln("LIST: Unable to list " + options_.scriptdir + "."); return; } char buf[255]; size_t len; try { while ((len = fread(buf, 1, 255, fls)) > 0) send(buf, len); sendln("LIST: data send."); } 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; 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); 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) { 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" )); 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" )); sendln(buf); } } //****************************************************************************** // others functions //****************************************************************************** string SLDaemon::md5(const string &file) const { MD5_CTX ctx; FILE *fs; size_t len; char buf[512]; char md[MD5_DIGEST_LENGTH]; char digest[MD5_DIGEST_LENGTH * 2 + 1]; if (!MD5_Init(&ctx)) return ""; if ((fs = fopen(file.c_str(), "r")) == 0) return ""; while ((len = fread(buf, 1, 512, fs)) > 0) if (!MD5_Update(&ctx, buf, len)) break; if (!MD5_Final((unsigned char*)md, &ctx)) return ""; for(len = 0; len < MD5_DIGEST_LENGTH; ++len) { sprintf(digest + (len * 2), "%02x", (unsigned char) md[len]); } digest[MD5_DIGEST_LENGTH * 2] = 0; return string(digest); } 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 Options functions //****************************************************************************** SLDaemon::Options::Options() { port = -1; verbose = -1; retrydelay = -1; } void SLDaemon::Options::merge(const SLDaemon::Options &opt) { if (opt.server != "") server = opt.server; if (opt.port >= 0) port = opt.port; if (opt.login != "") login = opt.login; if (opt.pass != "") pass = opt.pass; if (opt.verbose >= 0) verbose = opt.verbose; if (opt.conffile != "") conffile = opt.conffile; if (opt.scriptdir != "") scriptdir = opt.scriptdir; if (opt.retrydelay >= 0) retrydelay = opt.retrydelay; } void SLDaemon::Options::print() { std::cout << "server: " << server << std::endl; std::cout << "port: " << port << std::endl; std::cout << "host login: " << login << std::endl; std::cout << "host pass: " << pass << std::endl; std::cout << "script directory: " << scriptdir << std::endl; std::cout << "configuration file: " << conffile << std::endl; std::cout << "retry delay: " << retrydelay << std::endl; std::cout << "verbose level: " << verbose << std::endl; } //****************************************************************************** // 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\n", i->name.c_str(), i->pid, t, t - i->start_time, WEXITSTATUS(status)); write(d.socket_fd_, buf, strlen(buf)); if (d.verbose()) std::cout << SND_DATA << buf; jobs_.erase(i); break; } } } }