#include "e.h"

#if defined(HAVE_PAM) && !defined(__FreeBSD__)  && !defined(__OpenBSD__)
# include <security/pam_appl.h>
# include <pwd.h>


typedef struct E_Auth
{
   struct
   {
      struct pam_conv conv;
      pam_handle_t   *handle;
   } pam;

   char user[4096];
   char passwd[4096];
} E_Auth;

static pid_t _e_auth_child_pid = -1;

static char *
_auth_auth_get_current_user(void)
{
   char *user;
   struct passwd *pwent = NULL;

   pwent = getpwuid(getuid());
   if (!pwent) return NULL;
   user = strdup(pwent->pw_name);
   return user;
}

static int
_auth_auth_pam_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
{
   int replies = 0;
   E_Auth *da = (E_Auth *)appdata_ptr;
   struct pam_response *reply = NULL;

   reply = (struct pam_response *)malloc(sizeof(struct pam_response) * num_msg);

   if (!reply) return PAM_CONV_ERR;

   for (replies = 0; replies < num_msg; replies++)
     {
        switch (msg[replies]->msg_style)
          {
           case PAM_PROMPT_ECHO_ON:
             reply[replies].resp_retcode = PAM_SUCCESS;
             reply[replies].resp = strdup(da->user);
             break;

           case PAM_PROMPT_ECHO_OFF:
             reply[replies].resp_retcode = PAM_SUCCESS;
             reply[replies].resp = strdup(da->passwd);
             break;

           case PAM_ERROR_MSG:
           case PAM_TEXT_INFO:
             reply[replies].resp_retcode = PAM_SUCCESS;
             reply[replies].resp = NULL;
             break;

           default:
             free(reply);
             return PAM_CONV_ERR;
          }
     }
   *resp = reply;
   return PAM_SUCCESS;
}

static int
_auth_pam_init(E_Auth *da)
{
   int pamerr;
   const char *pam_prof;
   char *current_host;
   char *current_user;

   if (!da) return -1;

   da->pam.conv.conv = _auth_auth_pam_conv;
   da->pam.conv.appdata_ptr = da;
   da->pam.handle = NULL;

   /* try other pam profiles - and system-auth (login for fbsd users) is a fallback */
   pam_prof = "login";
   if (ecore_file_exists("/etc/pam.d/enlightenment"))
     pam_prof = "enlightenment";
   else if (ecore_file_exists("/etc/pam.d/xscreensaver"))
     pam_prof = "xscreensaver";
   else if (ecore_file_exists("/etc/pam.d/kscreensaver"))
     pam_prof = "kscreensaver";
   else if (ecore_file_exists("/etc/pam.d/system-auth"))
     pam_prof = "system-auth";
   else if (ecore_file_exists("/etc/pam.d/system"))
     pam_prof = "system";
   else if (ecore_file_exists("/etc/pam.d/xdm"))
     pam_prof = "xdm";
   else if (ecore_file_exists("/etc/pam.d/gdm"))
     pam_prof = "gdm";
   else if (ecore_file_exists("/etc/pam.d/kdm"))
     pam_prof = "kdm";

   if ((pamerr = pam_start(pam_prof, da->user, &(da->pam.conv),
                           &(da->pam.handle))) != PAM_SUCCESS)
     return pamerr;

   current_user = _auth_auth_get_current_user();

   if ((pamerr = pam_set_item(da->pam.handle, PAM_USER, current_user)) != PAM_SUCCESS)
     {
        free(current_user);
        return pamerr;
     }

   current_host = e_auth_hostname_get();
   if ((pamerr = pam_set_item(da->pam.handle, PAM_RHOST, current_host)) != PAM_SUCCESS)
     {
        free(current_user);
        free(current_host);
        return pamerr;
     }

   free(current_user);
   free(current_host);
   return 0;
}
#endif  // HAVE_PAM && !__FreeBSD__ && !_OpenBSD__

E_API int
#if defined(__FreeBSD__)
e_auth_begin(char *passwd)
{
   char buf[PATH_MAX], *p;
   Ecore_Exe *exe = NULL;
   int ret = 0;

   if (strlen(passwd) == 0) goto out;

   snprintf(buf, sizeof(buf), "%s/enlightenment/utils/enlightenment_ckpasswd",
            e_prefix_lib_get());

   exe = ecore_exe_pipe_run(buf, ECORE_EXE_PIPE_WRITE, NULL);
   if (ecore_exe_send(exe, passwd, strlen(passwd)) != EINA_TRUE) goto out;
   ecore_exe_close_stdin(exe);

   ret = ecore_exe_pid_get(exe);
   if (ret == -1)
     {
        ret = 0;
        goto out;
     }

   exe = NULL;
out:
   if (exe) ecore_exe_free(exe);

   /* security - null out passwd string once we are done with it */
   for (p = passwd; *p; p++)
     *p = 0;
   if (passwd[0] || passwd[3]) fprintf(stderr, "ACK!\n");

   return ret;
}
#elif defined(__OpenBSD__)
e_auth_begin(char *passwd)
{
   char exe_path[PATH_MAX], *p;
   Ecore_Exe *exe = NULL;
   int ret = 0;
   int len = strlen(passwd);

   if (len == 0) goto out;

   snprintf(exe_path, sizeof(exe_path), "%s/enlightenment/utils/enlightenment_sys -z",
            e_prefix_lib_get());

   exe = ecore_exe_pipe_run(exe_path, ECORE_EXE_PIPE_WRITE, NULL);
   if (!exe) goto out;
   if (ecore_exe_send(exe, passwd, len) != EINA_TRUE) goto out;
   if (ecore_exe_send(exe, "\n", 1) != EINA_TRUE) goto out;
   ecore_exe_close_stdin(exe);

   ret = ecore_exe_pid_get(exe);
   if (ret == -1)
     {
       ret = 0;
       goto out;
     }
 
   exe = NULL;
out:
   if (exe) ecore_exe_free(exe);

   for (p = passwd; *p; p++)
     *p = 0;

   return ret;
}
#elif defined(HAVE_PAM)
e_auth_begin(char *passwd)
{
   /* child */
   int pamerr;
   E_Auth da;
   char *current_user;
   struct sigaction action;

   _e_auth_child_pid = fork();
   if (_e_auth_child_pid > 0) return _e_auth_child_pid;
   if (_e_auth_child_pid < 0) return -1;

   action.sa_handler = SIG_DFL;
   action.sa_flags = SA_ONSTACK | SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
   sigemptyset(&action.sa_mask);
   sigaction(SIGSEGV, &action, NULL);
   sigaction(SIGILL, &action, NULL);
   sigaction(SIGFPE, &action, NULL);
   sigaction(SIGBUS, &action, NULL);
   sigaction(SIGABRT, &action, NULL);

   current_user = _auth_auth_get_current_user();
   eina_strlcpy(da.user, current_user, sizeof(da.user));
   eina_strlcpy(da.passwd, passwd, sizeof(da.passwd));
   /* security - null out passwd string once we are done with it */
   e_util_memclear(passwd, strlen(passwd));

   da.pam.handle = NULL;
   da.pam.conv.conv = NULL;
   da.pam.conv.appdata_ptr = NULL;

   pamerr = _auth_pam_init(&da);
   if (pamerr != PAM_SUCCESS)
     {
        free(current_user);
        _exit(1);
     }
   pamerr = pam_authenticate(da.pam.handle, 0);
   pam_end(da.pam.handle, pamerr);
   /* security - null out passwd string once we are done with it */
   e_util_memclear(da.passwd, sizeof(da.passwd));

   if (pamerr == PAM_SUCCESS)
     {
        free(current_user);
        _exit(0);
     }
   free(current_user);
   _exit(-1);

   return 0;
}
#else
e_auth_begin(char *passwd EINA_UNUSED)
{
   return 0;
}
#endif

E_API char *
e_auth_hostname_get(void)
{
   return strdup("localhost");
}
