/* This file is part of GNU Pies.
   Copyright (C) 2008-2022 Sergey Poznyakoff

   GNU Pies 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 3, or (at your option)
   any later version.

   GNU Pies 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 GNU Pies.  If not, see <http://www.gnu.org/licenses/>. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
#include <getopt.h>
#include <errno.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include <signal.h>
#include <time.h>
#include <sysexits.h>
#include <ctype.h>

#include <grecs.h>
#include <wordsplit.h>

#include "progname.h"
#include "inttostr.h"
#include "c-ctype.h"
#include "quotearg.h"
#include "fprintftime.h"

#include "identity.h"
#include "acl.h"
#include "libpies.h"
#include "envop.h"
#include "grecs/json.h"
#include "pies_syslog.h"

#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))

#define TESTTIME  2*60
#define SLEEPTIME 5*60
#define MAXSPAWN 10

#define DEFAULT_PASS_FD_TIMEOUT 5

#define RETR_OUT 0
#define RETR_ERR 1

enum redir_type
{
  redir_null,
  redir_syslog,
  redir_file
};

struct redirector
{
  enum redir_type type;
  union
  {
    int prio;
    char *file;
  } v;
};

typedef struct limits_rec *limits_record_t;

enum return_action
{
  action_restart,
  action_disable,
};

#define STATUS_SIG_BIT 0x80000000
#define STATUS_CODE(c) ((c) & ~STATUS_SIG_BIT)

struct action
{
  size_t nstat;
  unsigned *status;
  enum return_action act;  /* Action to take when the component terminates */
  char *addr;              /* Addresses to notify about it. */
  char *message;           /* Notification mail. */
  char *command;           /* Execute this command */
};


/* user privs */
struct pies_privs
{
  char *user;
  int allgroups;
  struct grecs_list *groups;
};

enum pies_comp_mode
  {
    /*
    ** Pies native component types.
    */
    /* Execute the component, no sockets are opened. This is the default
       Pies mode. */
    pies_comp_exec,
    /* Open a socket and start a component with stdin/stdout bound to that
       socket. Corresponds to MeTA1 notion of `start_action = accept'.
    */
    pies_comp_accept,
    /* Inetd mode: like above, but start the component only when an
       incoming connection is requested. Corresponds to
       `start_action = nostartaccept' in MeTA1.
    */
    pies_comp_inetd,
    /* Open a socket, start a component, and pass the socket fd to the
       component via the UNIX domain socket. Corresponds to
       `start_action = pass' in MeTA1. */
    pies_comp_pass_fd,

    /* Components of this type runs once on program startup. Running other
       components is delayed until the last startup component finishes. */
    pies_comp_startup,

    /* Components of this type are run right before program termination.
       They have shutdown_timeout seconds to finish their job and terminate
       gracefully, othervise they will be terminated forcefully via SIGTERM
       (and SIGKILL, for persisting ones). */
    pies_comp_shutdown,

    /*
    ** Init-style components
    */
    pies_mark_sysvinit,
    /* Start the process when the specified runlevel is entered and wait
       for its termination */
    pies_comp_wait = pies_mark_sysvinit,
    /* Execute the component once, when the specified runlevel is entered */
    pies_comp_once,
    /* Execute the component during system boot. Ignore runlevel settings. */
    pies_comp_boot,
    /* Execute the component during system boot and wait for it to terminate.
       Ignore runlevel settings. */
    pies_comp_bootwait,
    /* Execute the component when the power goes down. */
    pies_comp_powerfail,
    /* Execute the component when the power goes down. Wait for it to
       terminate. */
    pies_comp_powerwait,
    /* Execute the component when the power is restored. Wait for it to
       terminate. */
    pies_comp_powerokwait,
    /* Execute the process when SIGINT is delivered, i.e. someone has
       pressed the Ctrl+Alt+Del combination. */
    pies_comp_ctrlaltdel,
    /* Execute the component when a specified ondemand runlevel is called */
    pies_comp_ondemand,
    /* Execute the component on the system boot. */
    pies_comp_sysinit,
    /* Execute the component when running on the UPS and pies is informed that
       the UPS battery is almost empty. */
    pies_comp_powerfailnow,
    /* Execute the component a signal from the keyboard handler arrives,
       indicating that a special key combination was pressed on the console
       keyboard. */
    pies_comp_kbrequest,

    /* Restart the component wherever it terminates */
    pies_comp_respawn = pies_comp_exec,
  };

#define PIES_COMP_DEFAULT 0
#define PIES_COMP_MASK(m) (1 << ((m)))

#define CF_DISABLED    0x001 /* The componenet is disabled */
#define CF_PRECIOUS    0x002 /* The component is precious (should not
				be disabled) */
#define CF_WAIT        0x004 /* Wait for the component instance to
				terminate. */
#define CF_TCPMUX      0x008 /* A plain TCPMUX service */
#define CF_TCPMUXPLUS  0x010 /* A TCPMUX-plus service, i.e. pies
				must emit a '+' response before starting
				it */
#define CF_INTERNAL    0x020 /* An internal inetd service */
#define CF_SOCKENV     0x040 /* Component wants socket information in
				the environment */
#define CF_RESOLVE     0x080 /* Resolve IP addresses */
#define CF_SIGGROUP    0x100 /* Send signals to the process group */
#define CF_NULLINPUT   0x200 /* Provide null input stream */
#define CF_SHELL       0x400 /* Invoke via sh -c */
#define CF_EXPANDENV   0x800 /* Expand environment variables in the command
				line */

#define CF_REMOVE      0xf000 /* Marked for removal */

#define ISCF_TCPMUX(f) ((f) & (CF_TCPMUX | CF_TCPMUXPLUS))

struct prog;

struct component
{
  struct component *prev, *next; /* Components form doubly-linked list. */
  int listidx;                   /* Index of the list. */
  size_t arridx;                 /* Index of this component. */
  size_t ref_count;              /* Reference count. */
  struct prog *prog;             /* Prog associated with this component. */

  enum pies_comp_mode mode;
  char *tag;               /* Entry tag (for diagnostics purposes) */
  char *program;           /* Program name */
  char *command;           /* Full command line */
  size_t argc;             /* Number of command line arguments */
  char **argv;             /* Program command line */
  envop_t *envop;          /* Environment modification program */
  char *dir;               /* Working directory */
  struct grecs_list *prereq;  /* Prerequisites */
  struct grecs_list *depend;  /* Dependency targets */
  int flags;               /* CF_ bitmask */
  size_t max_instances;    /* Maximum number of simultaneously running
			      instances */
  char *rmfile;            /* Try to remove this file before starting */
  struct pies_privs privs; /* UID/GIDS+groups to run as */
  mode_t umask;            /* Umask to install before starting */
  limits_record_t limits;  /* System limits */

  /* For exec (init) components */
  char *runlevels;

  /* For inetd components */
  size_t max_rate;         /* Maximum number of invocations per minute */
  size_t max_ip_connections; /* Max. number of connections per IP address */
  int socket_type;         /* Socket type */
  struct inetd_builtin *builtin; /* Builtin function */
  char *service;

  struct pies_url *socket_url; /* Socket to listen on
				  (if mode != pies_comp_exec) */
  char *pass_fd_socket;     /* Socket to pass fd on
			       (if mode == pies_comp_pass_fd) */
  unsigned pass_fd_timeout; /* Maximum time to wait for pass_fd socket to
			       become available. */
  pies_acl_t acl;           /* Connection ACL */
  char *tcpmux;             /* Master service for TCPMUX */

  /* Optional error messages to be sent back on the socket: */
  char *access_denied_message;
  char *max_instances_message;
  char *max_ip_connections_message;

  /* Redirectors: */
  struct redirector redir[2];   /* Repeaters for stdout and stderr */
  /* Actions to execute on various exit codes: */
  struct grecs_list *act_list;
  /* ACLs for control interface */
  pies_acl_t list_acl;    /* List access control list */
  pies_acl_t adm_acl;     /* Administrative ACL (stop, start, etc.) */
};

#define is_sysvinit(cp) \
	(PIES_SYSVINIT_ENABLED \
	 && ((cp)->mode >= pies_mark_sysvinit || (cp)->runlevels))

#define SYSVINIT_ACTIVE (PIES_SYSVINIT_ENABLED && init_process)

enum pies_action {
	ACTION_CONT,
	ACTION_STOP,
	ACTION_RESTART,
	ACTION_RELOAD,
	ACTION_CTRLALTDEL,
	ACTION_KBREQUEST,
	ACTION_POWER,
	ACTION_COMMIT
};

extern char *instance;
extern unsigned long shutdown_timeout;
extern struct component default_component;
extern pies_acl_t pies_acl;
extern limits_record_t pies_limits;
extern char *mailer_program;
extern char *mailer_command_line;
extern int mailer_argc;
extern char **mailer_argv;
extern size_t default_max_rate;
extern char *qotdfile;

extern int init_process;
extern char *console_device;
extern int initdefault;

extern size_t pies_master_argc;
extern char **pies_master_argv;
extern char *default_control_url[2];

enum config_syntax_type
  {
    CONF_PIES,
    CONF_META1,
    CONF_INETD,
    CONF_INITTAB
  };

struct config_syntax;

struct config_syntax *str_to_config_syntax (const char *str);
void config_file_add (struct config_syntax *syntax, const char *name);
void config_file_add_type (enum config_syntax_type syntax, const char *name);
void config_file_list_serialize (struct json_value *ar);

int config_file_remove (const char *name);
void config_file_remove_all (void);

void free_redirector (struct redirector *rp);

void pies_schedule_action (int act);
void free_action (struct action *act);

void argv_free (char **argv);

#define PIES_CHLD_NONE             0
#define PIES_CHLD_CLEANUP          0x01
#define PIES_CHLD_WAKEUP           0x02
#define PIES_CHLD_GC               0x04
#define PIES_CHLD_RESCHEDULE_ALARM 0x08

void pies_schedule_children (int op);

int pies_read_config (void);
int pies_reread_config (void);

void register_prog (struct component *comp);
void program_init_startup (void);
int progman_waiting_p (void);
void progman_start (void);
void progman_gc (void);
void progman_wake_sleeping (int);
void progman_stop (void);
void progman_cleanup (int expect_term);
void progman_filter (int (*filter) (struct component *, void *data),
		     void *data);
int progman_accept (int socket, void *data);

void progman_create_sockets (void);
struct component *progman_lookup_component (const char *tag);
struct component *progman_lookup_tcpmux (const char *service,
					 const char *master);

void progman_run_comp (struct component *comp, int fd,
		       union pies_sockaddr_storage *sa, socklen_t salen);
void progman_recompute_alarm (void);

void fd_report (int fd, const char *msg);

int check_acl (pies_acl_t acl, struct sockaddr *s, socklen_t salen,
	       pies_identity_t identity);

void log_setup (int want_stderr);
void signal_setup (RETSIGTYPE (*sf)(int));
void setsigvhan (RETSIGTYPE (*handler) (int signo), int *sigv, int sigc);
void add_extra_sigv (int *sigv, int sigc);

typedef struct pies_depmap *pies_depmap_t;
typedef struct pies_depmap_pos *pies_depmap_pos_t;
enum pies_depmap_direction
  {
    depmap_row = 0,
    depmap_col = !depmap_row
  };

pies_depmap_t depmap_alloc (size_t count);
pies_depmap_t depmap_copy (pies_depmap_t dpm);
size_t depmap_dim (struct pies_depmap *dmap);
void depmap_free (pies_depmap_t dmap);
void depmap_set (pies_depmap_t dmap, size_t row, size_t col);
int depmap_isset (pies_depmap_t dmap, size_t row, size_t col);
void depmap_clear (pies_depmap_t dmap, size_t row, size_t col);
void depmap_remove (pies_depmap_t dmap, size_t n);

void depmap_tc (pies_depmap_t dmap);
size_t depmap_first (pies_depmap_t dmap, enum pies_depmap_direction dir,
		     size_t coord, pies_depmap_pos_t *ppos);
size_t depmap_next (pies_depmap_t dmap, pies_depmap_pos_t pos);
void depmap_end (pies_depmap_pos_t pos);


struct depmap_path_elem
{
  int idx;
  struct depmap_path_elem *next;
};

struct depmap_path
{
  size_t len;
  struct depmap_path_elem *head, *tail;
  struct depmap_path *next;
};

void depmap_path_free (struct depmap_path *path);
struct depmap_path *depmap_cycle_detect (pies_depmap_t dmap);

int assert_grecs_value_type (grecs_locus_t *locus,
			     const grecs_value_t *value, int type);

int str_to_socket_type (const char *str, int *pret);
int socket_type_to_str (int socket_type, const char **pres);

struct component *component_create (const char *name);
void component_free (struct component *comp);
void component_ref_incr (struct component *comp);
void component_ref_decr (struct component *comp);

int component_list_is_empty (void);

void component_config_begin (void);
void component_config_rollback (void);
void component_config_commit (void);

int component_is_active (struct component *comp);

void component_finish (struct component *comp, grecs_locus_t *locus);
struct grecs_keyword *find_component_keyword (const char *ident);
int component_foreach (int (*filter) (struct component *, void *),
		       void *data);

void components_dump_depmap (void);
void components_trace (char **argv, enum pies_depmap_direction dir);

struct component *component_depmap_first (enum pies_depmap_direction dir,
					  size_t idx, pies_depmap_pos_t *ppos);
struct component *component_depmap_next (pies_depmap_pos_t pos);



void pies_set_hook (int (*f) (void));
void pies_pause (void);

enum
  {
    PIES_EVT_RD,
    PIES_EVT_WR,
    PIES_EVT_EX
  };

typedef int (*socket_handler_t) (int, void *);

void *register_socket (int fd,
		       socket_handler_t rd,
		       socket_handler_t wr,
		       socket_handler_t ex,
		       void *data,
		       void (*free_data)(void*));
void deregister_socket (int fd);
void update_socket (int fd, int evt, socket_handler_t f);

int register_program_socket (int socktype, int fd, void *data,
			     void (*free_data)(void*));
int pass_fd (const char *socket, int fd, unsigned time_out);
int create_socket (struct pies_url *url, int socket_type,
		   const char *user, mode_t umask);
void disable_socket (int fd);
void enable_socket (int fd);


int parse_limits (limits_record_t *plrec, char *str, char **endp);
int set_limits (const char *name, limits_record_t lrec);
void free_limits (limits_record_t rec);
int limits_cmp (limits_record_t a, limits_record_t b);


void meta1_parser_set_debug (void);
int meta1lex (void);
int meta1error (char const *s);
int meta1parse (void);


/* diag.c */
#define DIAG_TO_SYSLOG  0x01
#define DIAG_TO_STDERR  0x02
#define DIAG_TO_MASK    0x0f
#define DIAG_REOPEN_LOG 0x10

#define DIAG_ALL (DIAG_REOPEN_LOG|DIAG_TO_STDERR|DIAG_TO_SYSLOG)

extern int diag_output;

#define DIAG_OUTPUT(x) (diag_output & (x))

void diag_setup (int flags);

#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
#  define __attribute__(x)
#endif

#ifndef PIES_PRINTFLIKE
# define PIES_PRINTFLIKE(fmt,narg) __attribute__ ((__format__ (__printf__, fmt, narg)))
#endif

void diagmsg (int logf, int prio, const char *fmt, ...)
  PIES_PRINTFLIKE(3,4);

void vlogmsg (int prio, const char *fmt, va_list ap);
void logmsg (int prio, const char *fmt, ...) PIES_PRINTFLIKE(2,3);
void logmsg_printf (int prio, const char *fmt, ...) PIES_PRINTFLIKE(2,3);
void logmsg_vprintf (int prio, const char *fmt, va_list ap);
void logfuncall (const char *fun, const char *arg, int err);

void pies_diag_printer (grecs_locus_t const *locus, int err, int errcode,
			const char *msg);

extern unsigned debug_level;
extern int source_info_option;
void debug_msg (const char *fmt, ...) PIES_PRINTFLIKE(1,2);

#define debug(lev, args)						\
  do									\
    if (debug_level >= lev)						\
      {									\
	if (source_info_option)						\
	  logmsg_printf (LOG_DEBUG, "%s:%d:%s: ",			\
			 __FILE__, __LINE__, __FUNCTION__);		\
	debug_msg args;							\
      }									\
  while (0)

/* userprivs.c */
int switch_to_privs (uid_t uid, gid_t gid, struct grecs_list *retain_groups);

void pies_priv_setup (struct pies_privs *);
void pies_epriv_setup (struct pies_privs *);

int pies_privs_cmp (struct pies_privs const *a, struct pies_privs const *b);
void pies_privs_free (struct pies_privs *p);


/* inetd.c */
int inetd_config_parse (const char *file);

/* inetd-bi.c */
struct inetd_builtin
{
  const char *service;
  int socktype;
  int single_process;
  int flags;
  void (*fun) (int, struct component const *);
};

struct inetd_builtin *inetd_builtin_lookup (const char *service, int socktype);

/* sysvinit.c */
void sysvinit_begin (void);
int is_comp_wait (struct component *comp);
int is_valid_runlevel (int c);
int console_open (int mode);
int telinit (int argc, char **argv);
int inittab_parse (const char *file);
int sysvinit_sigtrans (int sig, int *pact);
void sysvinit_runlevel_setup (int mask);
void sysvinit_sysdep_begin (void);
void sysvinit_power (void);

void sysvinit_report (struct json_value *obj);
int sysvinit_set_runlevel (int newlevel);
void sysvinit_parse_argv (int argc, char **argv);

int sysvinit_envlocate (char const *name, char **value);
int sysvinit_envdelete (char const *name);
int sysvinit_envupdate (char const *var);

int cb_initdefault (enum grecs_callback_command cmd,
		    grecs_node_t *node,
		    void *varptr, void *cb_data);
int cb_runlevels (enum grecs_callback_command cmd,
		  grecs_node_t *node,
		  void *varptr, void *cb_data);

extern char *sysvinit_environ_hint[];
extern char *init_fifo;

#ifndef INIT_FIFO
# define INIT_FIFO  "/dev/initctl"
#endif

#ifndef POWER_STAT_FILE
# define POWER_STAT_FILE "/var/run/powerstatus"
#endif

/* Power status values */
#define POWER_STAT_FAIL 'F'
#define POWER_STAT_LOW  'L'
#define POWER_STAT_OK   'O'

/* Request codes */
#define INIT_MAGIC 0x03091969
#define INIT_CMD_START		0
#define INIT_CMD_RUNLVL		1
#define INIT_CMD_POWERFAIL	2
#define INIT_CMD_POWERFAILNOW	3
#define INIT_CMD_POWEROK	4
#define INIT_CMD_BSD		5
#define INIT_CMD_SETENV		6
#define INIT_CMD_UNSETENV	7

#define INIT_CMD_CHANGECONS	12345

struct sysvinit_request
{
  int	magic;			/* Magic number                 */
  int	cmd;			/* What kind of request         */
  int	runlevel;		/* Runlevel to change to        */
  int	sleeptime;		/* Time between TERM and KILL   */
  char	data[368];
};



/* utmp.c */
#define SYSV_ACCT_BOOT 0
#define SYSV_ACCT_RUNLEVEL 1
#define SYSV_ACCT_PROC_START 2
#define SYSV_ACCT_PROC_STOP 3

void sysvinit_acct (int what, const char *user, const char *id, pid_t pid,
		    const char *line);


/* ctl.c */
struct control
{
  struct pies_url *url;           /* Control socket URL */
  pies_acl_t conn_acl;            /* Connection ACL */
  pies_acl_t adm_acl;             /* Administrative ACL */
  pies_acl_t usr_acl;             /* User ACL */
  unsigned int idle_timeout;      /* Session idle timeout */
  char *realm;                    /* Authentication realm */
};

extern struct control control;

int ctl_open(void);

void json_object_set_string (struct json_value *obj,
			     char const *name, char const *fmt, ...);
void json_object_set_number (struct json_value *obj,
			     char const *name, double val);
void json_object_set_bool (struct json_value *obj, char const *name, int val);
