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) {
char buf[128];
// set default options
if (gethostname(buf, 128) == 0) {
options_.login = buf;
}
options_.port = 18136;
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
<< " -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
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], "-q")) {
opt.verbose = 0;
}
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");
else if (!strcmp(argv[i], "-r")) {
if (++i >= argc)
throw Error(ERR_USAGE, "No enough argument for option -r");
opt.retrydelay = atoi(argv[i]);
}
SLDaemon::Options *SLDaemon::getoptions(const string file) const {
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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 if (!strcasecmp(name, "retrydelay"))
o.retrydelay = 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->verbose >= 0) options_.verbose = opt->verbose;
if (opt->conffile != "") options_.conffile = opt->conffile;
if (opt->scriptdir != "") options_.scriptdir = opt->scriptdir;
if (opt->retrydelay >= 0) options_.retrydelay = opt->retrydelay;
}
void SLDaemon::check_options() const {
// Check validy of arguement
throw Error(ERR_BADPARAM, "No valid server address specified");
if (options_.port <= 0)
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");
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");
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
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;
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);
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;
if ((socket_fs_ = fdopen(socket_fd_, "r+")) == 0)
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 (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) {
if(feof(socket_fs_))
throw Error(ERR_NET, (string) "recvln: Connexion close");
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(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() {
}
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));