Newer
Older
#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>
#include <openssl/sha.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>
SLDaemon::SLDaemon() : socket_fs_(0) {
options_.conffile = "/etc/sldrc";
}
SLDaemon &SLDaemon::Instance() {
if (instance_ == 0)
instance_ = new SLDaemon();
return *instance_;
}
<< " [-f conffile] [-d scriptdir] [-h] [-v] [-l login]"
<< " -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
<< " -l host : Set login to host." << 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." << std::endl
<< " -V : Print version and exit." << std::endl;
}
SLDaemon::Options *SLDaemon::getoptions(int argc, char *argv[]) const {
Options opt, *mopt;
for (int i = 1; i < argc; ++i) {
if (!strcmp(argv[i], "-h")) {
usage(*argv);
}
else if (!strcmp(argv[i], "-v")) {
opt.verbose = 1;
}
else if (!strcmp(argv[i], "-V")) {
std::cout << "sl daemon version : " << VERSION << std::endl;
}
else if (!strcmp(argv[i], "-H")) {
if (++i >= argc)
throw Error(ERR_USAGE, "No enough argument for option -h");
}
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");
throw Error(ERR_USAGE, "No enough argument for option -l");
}
else if (!strcmp(argv[i], "-p")) {
if (++i >= argc)
throw Error(ERR_USAGE, "No enough argument for option -p");
SLDaemon::Options *SLDaemon::getoptions(const string file) const {
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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.is_open())
return 0;
while (fs.good()) {
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
std::cerr << "Invalid line " << i << ": " << line;
}
fs.close();
return new SLDaemon::Options(o);
void SLDaemon::applyoptions(const Options *opt) {
if (opt->server != "") options_.server = opt->server;
if (opt->pass != "") options_.pass = opt->pass;
if (opt->verbose != 3) options_.verbose = opt->verbose;
if (opt->conffile != "") options_.conffile = opt->conffile;
if (opt->scriptdir != "") options_.scriptdir = opt->scriptdir;
}
void SLDaemon::check_options() const {
// print info in verbose mode
if (verbose()) {
std::cout << "Server host is : " << options_.server << "." << std::endl;
std::cout << "Server port is : " << options_.port << "." << std::endl;
std::cout << "Daemon login is : " << options_.login << "." << std::endl;
std::cout << "Daemon pass is : " << options_.pass << "." << std::endl;
std::cout << "Daemon scripts directory is : " << options_.scriptdir << "." << std::endl;
std::cout << "Verbose mode : " << verbose() << "." << std::endl;
}
// Check validy of arguement
throw Error(ERR_BADPARAM, "No server address specified");
throw Error(ERR_BADPARAM, "No server port specified");
throw Error(ERR_BADPARAM, "Bad server port number (1 - 65535)");
throw Error(ERR_BADPARAM, "No login specified");
throw Error(ERR_BADPARAM, "No pass specified");
throw Error(ERR_BADPARAM, "No scripts directory 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;
check_options();
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, "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);
sendln(e.message());
std::cerr << "!! " << e.message() << std::endl;
}
}
//******************************************************************************
// network functions
//******************************************************************************
void SLDaemon::connect() {
struct sockaddr_in daddr;
struct hostent *h;
disconnect();
// retrieve remote host info
h = gethostbyname(options_.server.c_str());
// 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);
// connect
if (::connect(socket_fd_, (struct sockaddr *) &daddr, sizeof daddr) == -1)
throw Error(ERR_NET, strerror(errno));
if ((socket_fs_ = fdopen(socket_fd_, "r+")) == 0)
if (fclose(socket_fs_))
throw Error(ERR_NET, strerror(errno));
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 (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) {
char *data = recv(size);
FILE *fs = fopen(filename.c_str(), "w");
throw Error(ERR_FILE, (string) "recv: fopen: " + strerror(errno));
throw Error(ERR_FILE, (string) "recv: fwrite: " + strerror(errno));
throw Error(ERR_FILE, (string) "recv: fclose: " + strerror(errno));
char *SLDaemon::recvln() {
char *line = new char[MAX_LINE_SIZE];
if (fgets(line, MAX_LINE_SIZE, socket_fs_) == 0) {
throw Error(ERR_FILE, (string) "recvln: " + strerror(errno));
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;
//******************************************************************************
// command functions
//******************************************************************************
exit(ERR_OK);
}
void SLDaemon::cmd_version() {
void SLDaemon::cmd_exec(const char *line) {
if (sscanf(line, "EXEC %512s\n", buf) != 1) { //FIXME: Bad static magic number
sendln("EXEC: Syntax error.");
return;
}
// 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 file owner
if (st.st_uid != getuid()) {
sendln((string) "EXEC: Unable to fork: " + strerror(errno) + ".");
sendln(buf);
// allow sun to start
if (kill(pid, SIGCONT)) {
perror("sld: cmd_exec");
jobs_.erase(j);
}
// 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
execl(path.c_str(), basename(path.c_str()), 0);
}
}
void SLDaemon::cmd_file(const char *line) {
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.");
string target = options_.scriptdir + "/" + filename;
//get size
buf = recvln();
ret = sscanf(buf, "SIZE %i\n", &size);
sendln("FILE: Invalid size parameter.");
ret = sscanf(buf, "MD5 %512s\n", md5); //FIXME: bad magic size
return;
}
//get data
try {
recv(size, target);
}
catch (const Error &err) {
if (err.code() == ERR_FILE) {
return;
}
throw;
}
// check MD5
if (SLDaemon::md5(target) != string(md5)) {
sendln("FILE: file " + target + ": Invalid MD5.");
// 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.");
sendln("FILE: Transfer of " + target + ": OK.");
void SLDaemon::cmd_update() {
char *buf;
int ret;
// get filename
buf = recvln();
ret = sscanf(buf, "MD5 %512s\n", md5); //FIXME: bad magic size
sendln("UPDATE: Invalid md5 parameter.");
// 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);
sendln("UPDATE: " + err.message() + ".");
unlink(tempsld);
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);
// 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.");
sendln("UPDATE: Transfer of " + target + ": OK.");
void SLDaemon::cmd_list() {
FILE *fls = popen(string("ls -1A " + options_.scriptdir).c_str(), "r");
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);
}
catch (...) {
pclose(fls);
throw;
}
}
void SLDaemon::cmd_status() {
t_job::iterator i;
time_t t = time(0);
if (jobs_.size() == 0)
return;
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) {
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
//******************************************************************************
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 "";
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(de->d_name)) {
closedir(ds);
throw Error(ERR_FILE,(string) "clear_dir: remove("+ de->d_name + "): " + strerror(errno));
}
}
closedir(ds);
}
//******************************************************************************
// Class Options functions
//******************************************************************************
SLDaemon::Options::Options() {
this->port = 0;
this->verbose = 3;
}
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 << "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));