/* pam_update module */

/*
 * Module for check Calculate update, pam_mail as base.
 */

#include <ctype.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#include <iniparser.h>


#ifdef HAVE_PATHS_H
#include <paths.h>
#endif

#define PAM_SM_SESSION
#define PAM_SM_AUTH

#include <security/pam_modules.h>
#include <security/_pam_macros.h>
#include <security/pam_modutil.h>
#include <security/pam_ext.h>

#define INI_ENV "/etc/calculate/ini.env"
#define PROTECT_DIR "/etc"

#define HAVE_NO_UPDATE            0x0
#define HAVE_NEW_CONFIG           0x1
#define HAVE_NEW_PACKAGES         0x2

static
int has_cfg_files(char *pdir) {
    DIR *dfd;
    struct dirent *dp;
    char filename[NAME_MAX];
    int ret=0;

    dfd = opendir(pdir);
    if(!dfd) {
        return 0;
    }
    while( (dp=readdir(dfd)) != NULL) {
        // skip internal name
        if(strcmp(dp->d_name,".") == 0 ||
           strcmp(dp->d_name,"..") == 0)
           continue;
        // found ._cfg files for dispatch-conf
        if(strncmp(dp->d_name,"._cfg", 5) == 0) {
            ret = 1;
            break;
        }
        // recursive search in directories
        if(dp->d_type == DT_DIR) {
            if(strlen(pdir) + strlen(dp->d_name) + 1 < NAME_MAX) {
                strcpy(filename,pdir);
                strcat(filename,"/");
                strcat(filename,dp->d_name);
                if(has_cfg_files(filename)) {
                    ret = 1;
                    break;
                }
            }
        }
    }
    closedir(dfd);
    return ret;
}

static
int has_new_packages() {
    dictionary *ini;
    char *value;
    int ret = 0;

    ini = iniparser_load(INI_ENV);
    if(ini==NULL) {
        fprintf(stderr, "wrong file: %s\n", INI_ENV);
        return 0;
    }
    value = iniparser_getstring(ini, "update:packages", NULL);
    if(value && strcmp(value,"on") == 0) {
        ret = 1;
    }
    iniparser_freedict(ini);
    return ret;
}

static int
get_update_status(pam_handle_t *pamh)
{
    int ret = HAVE_NO_UPDATE;
    if(has_new_packages()) ret |= HAVE_NEW_PACKAGES;
    if(has_cfg_files(PROTECT_DIR)) ret |= HAVE_NEW_CONFIG;
    return ret;
}

static int
report_update(pam_handle_t *pamh, int type)
{
    int retval;
	  switch (type)
	    {
	    case HAVE_NO_UPDATE:
        retval = PAM_SUCCESS;
	      break;
	    case HAVE_NEW_CONFIG:
	    case HAVE_NEW_PACKAGES:
	      retval = pam_info (pamh, "%s", "The system has updates.");
	      break;
      default:
        retval = PAM_SUCCESS;
	    }
    return retval;
}

static int _do_update(pam_handle_t *, int, int, const char **, int);

/* --- authentication functions --- */

PAM_EXTERN int
pam_sm_authenticate (pam_handle_t *pamh, int flags,
		     int argc, const char **argv)
{
    return PAM_IGNORE;
}

/* Checking update as part of authentication */
PAM_EXTERN
int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,
    const char **argv)
{
    if (!(flags & (PAM_ESTABLISH_CRED|PAM_DELETE_CRED)))
      return PAM_IGNORE;
    return _do_update(pamh,flags,argc,argv,(flags & PAM_ESTABLISH_CRED));
}

/* --- session management functions --- */

PAM_EXTERN
int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc
			 ,const char **argv)
{
    return _do_update(pamh,flags,argc,argv,0);
}

/* Checking update as part of the session management */
PAM_EXTERN
int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc,
    const char **argv)
{
    return _do_update(pamh,flags,argc,argv,1);
}


/* --- The Beaf (Tm) --- */

static int _do_update(pam_handle_t *pamh, int flags, int argc,
    const char **argv, int est)
{
    int retval, type;
    size_t hashcount;
    char *folder = NULL;
    const char *user;
    const char *path_mail = NULL;
    const struct passwd *pwd = NULL;

    retval = pam_get_user(pamh, &user, NULL);
    if (retval != PAM_SUCCESS || user == NULL) {
        pam_syslog(pamh, LOG_ERR, "cannot determine username");
        return PAM_USER_UNKNOWN;
    }

    pwd = pam_modutil_getpwnam (pamh, user);
    if (pwd == NULL) {
        pam_syslog(pamh, LOG_ERR, "user unknown");
        return PAM_USER_UNKNOWN;
    }
    if(strcmp(user,"root")) {
        return PAM_IGNORE;
    }

    PAM_MODUTIL_DEF_PRIVS(privs);

    if (pam_modutil_drop_priv(pamh, &privs, pwd)) {
      return PAM_SESSION_ERR;
    } else {
      type = get_update_status(pamh);
      if (pam_modutil_regain_priv(pamh, &privs)) {
        return PAM_SESSION_ERR;
      }
    }

    if (type != 0) {
        retval = report_update(pamh, type);
        type = 0;
    }

    return retval;
}

#ifdef PAM_STATIC

/* static module data */

struct pam_module _pam_update_modstruct = {
     "pam_update",
     pam_sm_authenticate,
     pam_sm_setcred,
     NULL,
     pam_sm_open_session,
     pam_sm_close_session,
     NULL,
};

#endif

/* end of module definition */
