Skip to content
daemon.cc 14.3 KiB
Newer Older
Seblu's avatar
Seblu committed
/*
  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
Seblu's avatar
Seblu committed
  the Free Software Foundation; version 2 of the License.
Seblu's avatar
Seblu committed

  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"
Seblu's avatar
Seblu committed
#include "sll/cypher.hh"
Seblu's avatar
Seblu committed
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
Seblu's avatar
Seblu committed
#include <stdio.h>
Seblu's avatar
Seblu committed
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
Seblu's avatar
Seblu committed
#include <sys/stat.h>
#include <dirent.h>
Seblu's avatar
Seblu committed
#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_;
}
Seblu's avatar
Seblu committed

Seblu's avatar
Seblu committed
void SLDaemon::check_options() const {

  // Check validy of arguement
Seblu's avatar
Seblu committed
  if (options.server == "")
    throw Error(ERR_BADPARAM, "No valid server address specified");
Seblu's avatar
Seblu committed
  if (options.port <= 0)
    throw Error(ERR_BADPARAM, "No valid server port specified");
Seblu's avatar
Seblu committed
  if (options.login == "")
    throw Error(ERR_BADPARAM, "No valid login specified");
Seblu's avatar
Seblu committed
  if (options.pass == "")
    throw Error(ERR_BADPARAM, "No valid pass specified");
Seblu's avatar
Seblu committed
  if (options.scriptdir == "")
    throw Error(ERR_BADPARAM, "No valid scripts directory specified");
Seblu's avatar
Seblu committed
  if (options.retrydelay < 1)
    throw Error(ERR_BADPARAM, "No valid retry delay specified");
Seblu's avatar
Seblu committed

  // Check dir script exist
Seblu's avatar
Seblu committed
  if (euidaccess(options.scriptdir.c_str(), W_OK))
    throw Error(ERR_BADPARAM, "Unable to write into script directory");
Seblu's avatar
Seblu committed
}
Seblu's avatar
Seblu committed

Seblu's avatar
Seblu committed
void SLDaemon::run() {
Seblu's avatar
Seblu committed
  if (verbose())
Seblu's avatar
Seblu committed
    logout << options;
Seblu's avatar
Seblu committed
  check_options();
Seblu's avatar
Seblu committed
  clean_dir(options.scriptdir);
    // 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());
Seblu's avatar
Seblu committed
	  logerr << "!! " << e.message() << "\n";
Seblu's avatar
Seblu committed
    }
  }
  // recupere les erreurs de reseaux.
  catch (const Error &e) {
Seblu's avatar
Seblu committed
    if (e.code() != ERR_NET && e.code() != ERR_AUTH)

    // disconnect
    if (c_.connected())
      c_.disconnect();

Seblu's avatar
Seblu committed
    if (verbose()) // TODO: Print the time at lost
      logerr << "Connexion lost. Retrying in " << options.retrydelay << " seconds.\n";

Seblu's avatar
Seblu committed
    sleep(options.retrydelay);
    goto net_connect;
Seblu's avatar
Seblu committed
  }
}

//******************************************************************************
// protocol functions
//******************************************************************************

Seblu's avatar
Seblu committed
void SLDaemon::auth() {
  char buf[MAX_LINE_SIZE];
Seblu's avatar
Seblu committed

Seblu's avatar
Seblu committed
  snprintf(buf, MAX_LINE_SIZE, "HOST %s", options.login.c_str());
Seblu's avatar
Seblu committed

Seblu's avatar
Seblu committed
  snprintf(buf, MAX_LINE_SIZE, "PASS %s",
	   Cypher::sha1_64(options.pass.c_str(), options.pass.length()).c_str());
Seblu's avatar
Seblu committed

  string valid = c_.recvln();

  if (valid != "OK")
    throw Error(ERR_AUTH, "Bad Authentification.");
Seblu's avatar
Seblu committed
}

Seblu's avatar
Seblu committed
//******************************************************************************
// command functions
//******************************************************************************

Seblu's avatar
Seblu committed
void SLDaemon::cmd_exit() {
  //TODO: print date
  c_.sendln("EXIT: ok.");
  c_.disconnect();
  exit(ERR_NO);
Seblu's avatar
Seblu committed
}

void SLDaemon::cmd_version() {
Seblu's avatar
Seblu committed
}

void SLDaemon::cmd_exec(const char *line) {
Seblu's avatar
Seblu committed
  assert(line);

  // build path
Seblu's avatar
Seblu committed
  char buf[MAX_LINE_SIZE];
  if (sscanf(line, "EXEC %512s\n", buf) != 1) {  //FIXME: Bad static magic number
    c_.sendln("EXEC: Syntax error.");
Seblu's avatar
Seblu committed
  string path = options.scriptdir + "/" +  buf;
  // Check transversal path attack
  if (strchr(buf, '/')) {
    c_.sendln("EXEC: Invalid character in job name.");
Seblu's avatar
Seblu committed
  // 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));
Seblu's avatar
Seblu committed
    return;
  }
  // check for exec flag
Seblu's avatar
Seblu committed
  if (st.st_mode & S_IXUSR != S_IXUSR) {
    c_.sendln("EXEC: no exec flag.");
Seblu's avatar
Seblu committed
    return;
  }
  // check file owner
  if (st.st_uid != getuid()) {
    c_.sendln("EXEC: Bad file owner.");
Seblu's avatar
Seblu committed
    return;
  }

  // flush before fork

  //Create new process
Seblu's avatar
Seblu committed
  pid_t pid = fork();
Seblu's avatar
Seblu committed
  if (pid == -1) {
    c_.sendln((string) "EXEC: Unable to fork: " + strerror(errno) + ".");
Seblu's avatar
Seblu committed
    return;
  }
Seblu's avatar
Seblu committed

  // Father job
Seblu's avatar
Seblu committed
  if (pid > 0) {
    // reg job
Seblu's avatar
Seblu committed
    SLDaemon::Job j(pid, buf, time(0));
    jobs_.insert(j);
Seblu's avatar
Seblu committed
    // send job info
    snprintf(buf, MAX_LINE_SIZE,
	     "EXEC: %s, pid: %d, start at: %ld.",
Seblu's avatar
Seblu committed
	     j.name.c_str(), j.pid, j.start_time);

    // allow sun to start
    if (kill(pid, SIGCONT)) {
Seblu's avatar
Seblu committed
      logerr << "sld: cmd_exec" << strerror(errno) << ".\n";
      jobs_.erase(j);
    }
Seblu's avatar
Seblu committed
  }
  // Son job
Seblu's avatar
Seblu committed
  else if (pid == 0) {
    // Wait father registration stuff before doing something else
    // so we suspend execution
    if (kill(getpid(), SIGSTOP)) {
Seblu's avatar
Seblu committed
      logerr << "sld: cmd_exec" << strerror(errno) << ".\n";
      return;
    }

    // try to run job
    if (dup2(c_.getsocket(), STDOUT_FILENO) == -1) {
Seblu's avatar
Seblu committed
      logerr << ">> dup2" << strerror(errno) << ".\n";
Seblu's avatar
Seblu committed
      exit(ERR_UNKNOWN);
Seblu's avatar
Seblu committed
    }
    if (dup2(c_.getsocket(), STDERR_FILENO) == -1) {
Seblu's avatar
Seblu committed
      logerr << ">> dup2" << strerror(errno) << ".\n";
Seblu's avatar
Seblu committed
      exit(ERR_UNKNOWN);
Seblu's avatar
Seblu committed
    }
    execl(path.c_str(), basename(path.c_str()), 0);
Seblu's avatar
Seblu committed
    logerr << "sld: execl (" << path << "): " << strerror(errno) << ".\n";
    exit(ERR_UNKNOWN);
Seblu's avatar
Seblu committed
  }
}

void SLDaemon::cmd_file(const char *line) {
  assert(line);
Seblu's avatar
Seblu committed

  // get filename
Seblu's avatar
Seblu committed
  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.");
Seblu's avatar
Seblu committed
    return;
  }
Seblu's avatar
Seblu committed
  string target = options.scriptdir + "/" + filename;
Seblu's avatar
Seblu committed

  //get size
  int size;
Seblu's avatar
Seblu committed
  string buf = c_.recvln();
  int ret = sscanf(buf.c_str(), "SIZE %i\n", &size);
Seblu's avatar
Seblu committed
  if (ret != 1) {
    c_.sendln("FILE: Invalid size parameter.");
Seblu's avatar
Seblu committed
    return;
  }
Seblu's avatar
Seblu committed
  //get md5
  char md5[MAX_LINE_SIZE];
  buf = c_.recvln();
  ret = sscanf(buf.c_str(), "MD5 %512s\n", md5); //FIXME: bad magic size
Seblu's avatar
Seblu committed
  if (ret != 1) {
    c_.sendln("FILE: Invalid md5 parameter.");
Seblu's avatar
Seblu committed
    return;
  }

  //get data
  try {
    recv2file(size, target);
Seblu's avatar
Seblu committed
  }
  catch (const Error &err) {
    if (err.code() == ERR_FILE) {
      c_.sendln("FILE: Data transfer error.");
Seblu's avatar
Seblu committed
      return;
    }
    throw;
  }

Seblu's avatar
Seblu committed
  // check local file MD5
  buf = Cypher::md5_16(target);
  if (buf == md5) {
    c_.sendln("FILE: file " + target + ": Invalid MD5.");
Seblu's avatar
Seblu committed
    unlink(target.c_str());
    return;
Seblu's avatar
Seblu committed
  }
Seblu's avatar
Seblu committed

  // proceed chown
  if (chown(target.c_str(), getuid(), getgid())) {
    c_.sendln("FILE: chown of " + target + ": Unable to chown.");
Seblu's avatar
Seblu committed
    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.");
Seblu's avatar
Seblu committed
    unlink(target.c_str());
    return;
  }

  c_.sendln("FILE: Transfer of " + target + ": OK.");
Seblu's avatar
Seblu committed
}
Seblu's avatar
Seblu committed
void SLDaemon::cmd_update() {
  // get filename
  const string &target = getbinpath();
Seblu's avatar
Seblu committed

  //get size
  int size;
Seblu's avatar
Seblu committed
  string buf = c_.recvln();
  int ret = sscanf(buf.c_str(), "SIZE %i\n", &size);
Seblu's avatar
Seblu committed
  if (ret != 1) {
    c_.sendln("UPDATE: Syntax error.");
Seblu's avatar
Seblu committed
    return;
  }
Seblu's avatar
Seblu committed
  //get remote md5
  char md5[MAX_LINE_SIZE];
  buf = c_.recvln();
  ret = sscanf(buf.c_str(), "MD5 %512s\n", md5); //FIXME: bad magic size
Seblu's avatar
Seblu committed
  if (ret != 1) {
    c_.sendln("UPDATE: Invalid md5 parameter.");
Seblu's avatar
Seblu committed
    return;
  }

  // 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);

Seblu's avatar
Seblu committed
  //get data
  try {
    recv2file(size, tempsld);
Seblu's avatar
Seblu committed
  }
  catch (const Error &err) {
    if (err.code() == ERR_FILE) {
      c_.sendln("UPDATE: " + err.message() + ".");
      unlink(tempsld);
Seblu's avatar
Seblu committed
      return;
    }
    throw;
  }

Seblu's avatar
Seblu committed
  // 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());


Seblu's avatar
Seblu committed
  // check final file MD5
  buf = Cypher::md5_16(tempsld);
  if (buf == md5) {
    c_.sendln("UPDATE: file " + target + ": Invalid MD5.");
    unlink(tempsld);
Seblu's avatar
Seblu committed
    return;
Seblu's avatar
Seblu committed
  }
Seblu's avatar
Seblu committed
  // proceed chown
  if (chown(target.c_str(), getuid(), getgid())) {
    c_.sendln("FILE: chown of " + target + ": Unable to chown.");
Seblu's avatar
Seblu committed
    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.");
Seblu's avatar
Seblu committed
    unlink(target.c_str());
    return;
  }

  c_.sendln("UPDATE: Transfer of " + target + ": OK.");
Seblu's avatar
Seblu committed
}

Seblu's avatar
Seblu committed
void SLDaemon::cmd_list() {
Seblu's avatar
Seblu committed
  FILE *fls = popen(string("ls -1A " + options.scriptdir).c_str(), "r");
  if (fls == 0) {
    c_.sendln("LIST: Unable to list " + options.scriptdir + ".");
Seblu's avatar
Seblu committed
    return;
  }

  char buf[255];
  size_t len;
  try {
    while ((len = fread(buf, 1, 255, fls)) > 0)
Seblu's avatar
Seblu committed
    if (verbose())
      logout << "LIST: data send.\n";
Seblu's avatar
Seblu committed
  }
  catch (...) {
    pclose(fls);
    throw;
  }
}

void SLDaemon::cmd_status() {
  t_job::iterator i;
  time_t t = time(0);
Seblu's avatar
Seblu committed
  char buf[MAX_LINE_SIZE];
Seblu's avatar
Seblu committed

  if (jobs_.size() == 0)
    return;

  c_.sendln("STATUS of");
  for (i = jobs_.begin(); i != jobs_.end(); ++i) {
Seblu's avatar
Seblu committed
    snprintf(buf, MAX_LINE_SIZE,
	     " job: %s, pid: %d, start at: %ld, since: %ld seconds.",
Seblu's avatar
Seblu committed
	     i->name.c_str(), i->pid, i->start_time,t - i->start_time);
  }
}

void SLDaemon::cmd_kill(const char *line) {
  t_job::iterator i;
Seblu's avatar
Seblu committed
  char buf[MAX_LINE_SIZE];

  // 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).",
Seblu's avatar
Seblu committed
	       i->pid, i->name.c_str(), ret, ((ret == -1) ? strerror(errno) : "OK" ));
    }
}

void SLDaemon::cmd_killall() {
  t_job::iterator i;
Seblu's avatar
Seblu committed
  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).",
Seblu's avatar
Seblu committed
	     i->pid, i->name.c_str(), ret, ((ret == -1) ? strerror(errno) : "OK" ));
Seblu's avatar
Seblu committed
  }
Seblu's avatar
Seblu committed
}

//******************************************************************************
// 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);
}

Seblu's avatar
Seblu committed
//******************************************************************************
// Class Job functions
//******************************************************************************

Seblu's avatar
Seblu committed
SLDaemon::Job::Job(int p, const string &s, time_t t) : pid(p), name(s), start_time(t) {}
Seblu's avatar
Seblu committed

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));
Seblu's avatar
Seblu committed
	if (d.verbose())
Seblu's avatar
Seblu committed
	  logout << SND_DATA << buf;
	jobs_.erase(i);
	break;
      }
    }
  }
}

void sigint(int i) {
  logerr << "Signal " << i << " catched.\n";
  SLDaemon::Instance().c_.disconnect();
  exit(ERR_SIGNAL);
}