/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            daemon.cc
 *
 *  Thu Jun  9 10:27:59 CEST 2005
 *  Copyright  2005 Bent Bisballe
 *  deva@aasimon.org
 ****************************************************************************/

/*
 *  This program 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; either version 2 of the License, or
 *  (at your option) any later version.
 *  
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "daemon.h"

#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

// For getgrent and getgrent
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>

// For strcmp
#include <string.h>

/* Resolve a group's name or id into a numeric gid */
static gid_t get_gid(const char *grp)
{
  char *eptr;
  gid_t xid = strtoul(grp, &eptr, 10);
  struct group *gr;
  errno = 0;      /* Per getgrnam(3) and getgrgid(3) manual page */
  if(!*eptr)
    gr = getgrgid(xid);
  else
    gr = getgrnam(grp);
  return !gr ? 0 : gr->gr_gid;
}

/* Resolve a user's name or id into a numeric uid with associated gid */
static uid_t get_uid(const char *usr, gid_t *gid)
{
  char *eptr;
  uid_t xid = strtoul(usr, &eptr, 10);
  struct passwd *pw;
  errno = 0;      /* Per getpwnam(3) and getpwuid(3) manual page */
  if(!*eptr)
    pw = getpwuid(xid);
  else
    pw = getpwnam(usr);
  if(!pw)
    return 0;
  if(!gid)
    *gid = pw->pw_gid;
  return pw->pw_uid;
}

Daemon::Daemon()
{}

Daemon::~Daemon()
{}

int Daemon::run(const char *user, const char* group, bool detach)
{
  int f;
  int fd;

  // Fetch user and group IDs
  gid_t gid = 0;
  uid_t uid = 0;
  if(user && *user) {
    uid = get_uid(user, &gid);
    if(errno) {
      fprintf(stderr, "Could resolve user \"%s\"", user);
      perror("");
    }
  }

  if(group && *group) {
    gid = get_gid(group);
    if(errno) {
      fprintf(stderr, "Could not resolve group \"%s\"", group);
      perror("");
    }
  }

  chdir("/");
  umask(0);

  if(detach) {
    switch(fork()) {
    case -1: // Fork error
      perror("Fork in daemon.cc");
      return 1;
      
    case 0:  // Forked child
      break; // Break out of switch
      
    default: // Parent
      // exit(0);
      return 0;
    }
  }

  if(gid) {
    // Switch to given group
    if(setgid(gid) != 0) {
      fprintf(stderr, "Failed to change to group \"%s\" (gid: %d), quitting.\n", group, gid);
      perror("");
      fprintf(stderr, "Runnning daemon as current group\n");
    }
  }
    
  if(uid) {
    // Switch to given user
    if(setuid(uid) != 0) {
      fprintf(stderr, "Failed to change to user \"%s\" (uid: %d), quitting.\n", user, uid);
      perror("");
      fprintf(stderr, "Runnning daemon as current user\n");
    }
  }
  
  // Redirect stdin, stdout and stderr to /dev/null
  fd = open("/dev/null", O_NOCTTY | O_RDWR, 0666);
  
  dup2(0, fd);
  dup2(1, fd);
  dup2(2, fd);
    
  close(fd);

  setsid();

  signal (SIGTERM, SIG_IGN);
  signal (SIGHUP, SIG_IGN);
  if(detach) signal (SIGINT, SIG_IGN); // Don't disable Ctrl+c when running in foreground.

  return daemon_main();
}