Newer
Older
/*
This file is part of SLD.
Copyright (C) 2008 Sebastien LUTTRINGER <contact@seblu.net>
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 <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
SLDaemon *SLDaemon::instance_ = 0;
SLDaemon &SLDaemon::Instance() {
if (instance_ == 0)
instance_ = new SLDaemon();
return *instance_;
}
void SLDaemon::check_options() const {
// Check validy of arguement
throw Error(ERR_BADPARAM, "No valid server address specified");
throw Error(ERR_BADPARAM, "No valid server port specified");
throw Error(ERR_BADPARAM, "No valid login specified");
throw Error(ERR_BADPARAM, "No valid pass specified");
throw Error(ERR_BADPARAM, "No valid scripts directory specified");
throw Error(ERR_BADPARAM, "No valid retry delay specified");
throw Error(ERR_BADPARAM, "Unable to write into script directory");
// 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
string line = c_.recvln();
// call right handler
try {
else if (!strcmp(line.c_str(), "VERSION\n"))
else if (!strcmp(line.c_str(), "STATUS\n"))
else if (!strcmp(line.c_str(), "LIST\n"))
else if (!strcmp(line.c_str(), "KILLALL\n"))
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"))
c_.sendln("Invalid command.");
}
catch (const Error &e) {
if (e.code() == ERR_NET)
throw;
else {
c_.sendln(e.message());
}
// recupere les erreurs de reseaux.
catch (const Error &e) {
// disconnect
if (c_.connected())
c_.disconnect();
if (verbose()) // TODO: Print the time at lost
logerr << "Connexion lost. Retrying in " << options.retrydelay << " seconds.\n";
}
}
//******************************************************************************
// protocol functions
//******************************************************************************
snprintf(buf, MAX_LINE_SIZE, "HOST %s", options.login.c_str());
snprintf(buf, MAX_LINE_SIZE, "PASS %s",
Cypher::sha1_64(options.pass.c_str(), options.pass.length()).c_str());
string valid = c_.recvln();
if (valid != "OK")
throw Error(ERR_AUTH, "Bad Authentification.");
//******************************************************************************
// command functions
//******************************************************************************
c_.sendln("EXIT: ok.");
c_.disconnect();
exit(ERR_NO);
void SLDaemon::cmd_exec(const char *line) {
if (sscanf(line, "EXEC %512s\n", buf) != 1) { //FIXME: Bad static magic number
c_.sendln("EXEC: Syntax error.");
// Check transversal path attack
if (strchr(buf, '/')) {
c_.sendln("EXEC: Invalid character in job name.");
// 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("EXEC: no exec flag.");
return;
}
// check file owner
if (st.st_uid != getuid()) {
c_.sendln("EXEC: Bad file owner.");
c_.sendln((string) "EXEC: Unable to fork: " + strerror(errno) + ".");
// allow sun to start
if (kill(pid, SIGCONT)) {
logerr << "sld: cmd_exec" << strerror(errno) << ".\n";
// Wait father registration stuff before doing something else
// so we suspend execution
if (kill(getpid(), SIGSTOP)) {
logerr << "sld: cmd_exec" << strerror(errno) << ".\n";
if (dup2(c_.getsocket(), STDOUT_FILENO) == -1) {
logerr << ">> dup2" << strerror(errno) << ".\n";
if (dup2(c_.getsocket(), STDERR_FILENO) == -1) {
logerr << ">> dup2" << strerror(errno) << ".\n";
execl(path.c_str(), basename(path.c_str()), 0);
logerr << "sld: execl (" << path << "): " << strerror(errno) << ".\n";
}
}
void SLDaemon::cmd_file(const char *line) {
assert(line);
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.");
string buf = c_.recvln();
int ret = sscanf(buf.c_str(), "SIZE %i\n", &size);
c_.sendln("FILE: Invalid size parameter.");
buf = c_.recvln();
ret = sscanf(buf.c_str(), "MD5 %512s\n", md5); //FIXME: bad magic size
c_.sendln("FILE: Invalid md5 parameter.");
recv2file(size, target);
}
catch (const Error &err) {
if (err.code() == ERR_FILE) {
c_.sendln("FILE: Data transfer error.");
// check local file MD5
buf = Cypher::md5_16(target);
if (buf == md5) {
c_.sendln("FILE: file " + target + ": Invalid MD5.");
// 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.");
c_.sendln("FILE: Transfer of " + target + ": OK.");
string buf = c_.recvln();
int ret = sscanf(buf.c_str(), "SIZE %i\n", &size);
c_.sendln("UPDATE: Syntax error.");
buf = c_.recvln();
ret = sscanf(buf.c_str(), "MD5 %512s\n", md5); //FIXME: bad magic size
c_.sendln("UPDATE: Invalid md5 parameter.");
// 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);
recv2file(size, tempsld);
c_.sendln("UPDATE: " + err.message() + ".");
// 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.");
// 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.");
c_.sendln("UPDATE: Transfer of " + target + ": OK.");
FILE *fls = popen(string("ls -1A " + options.scriptdir).c_str(), "r");
c_.sendln("LIST: Unable to list " + options.scriptdir + ".");
return;
}
char buf[255];
size_t len;
try {
while ((len = fread(buf, 1, 255, fls)) > 0)
}
catch (...) {
pclose(fls);
throw;
}
}
void SLDaemon::cmd_status() {
t_job::iterator i;
time_t t = time(0);
c_.sendln("STATUS of");
for (i = jobs_.begin(); i != jobs_.end(); ++i) {
" job: %s, pid: %d, start at: %ld, since: %ld seconds.",
}
}
void SLDaemon::cmd_kill(const char *line) {
t_job::iterator i;
// 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" ));
}
}
void SLDaemon::cmd_killall() {
t_job::iterator i;
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" ));
}
//******************************************************************************
// 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));
jobs_.erase(i);
break;
}
}
}
}
void sigint(int i) {
logerr << "Signal " << i << " catched.\n";
SLDaemon::Instance().c_.disconnect();