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) {
if (e.code() != ERR_NET)
throw;
// disconnect
if (c_.connected())
c_.disconnect();
// if (socket_fs_ != 0) {
if (verbose()) // TODO: Print the time at lost
logerr << "Connexion lost. Retrying in " << options.retrydelay << " seconds.\n";
// fclose(socket_fs_);
// socket_fs_ = 0;
// }
}
}
//******************************************************************************
// network functions
//******************************************************************************
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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
244
245
246
247
248
249
// void SLDaemon::connect() {
// struct sockaddr_in daddr;
// struct hostent *h;
// // Check no existing connexion
// assert(socket_fs_ == 0);
// // 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())
// logout << "Connecting to " << h->h_name << " (" << inet_ntoa(*(struct in_addr*)h->h_addr)
// << ") on port " << options.port << "...\n";
// // connect
// if (::connect(socket_fd_, (struct sockaddr *) &daddr, sizeof daddr) == -1)
// throw Error(ERR_NET, strerror(errno));
// if (verbose())
// logout << "Connected to " << h->h_name << " (" << inet_ntoa(*(struct in_addr*)h->h_addr)
// << ") on port " << options.port << "...\n";
// // initialize socket stream
// if ((socket_fs_ = fdopen(socket_fd_, "r+")) == 0)
// throw Error(ERR_NET, strerror(errno));
// }
// void SLDaemon::disconnect() {
// if (!c_.connected())
// return;
// c_.disconnect();
// // TODO: print close time
// if (verbose())
// logout << "Connection closed.\n";
// }
// 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())
// logout << SND_DATA << s << "\n";
// }
// 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;
// }
// 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())
// logout << RCV_DATA << line;
// return line;
// }
// void SLDaemon::flush() {
// if (fflush(socket_fs_))
// throw Error(ERR_NET, strerror(errno));
// }
//******************************************************************************
// 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());
//******************************************************************************
// 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();