/*
 * $Id: vldap.c,v 1.22 2007/05/22 03:59:00 rwidmer Exp $
 * Copyright (C) 1999-2004 Inter7 Internet Technologies, Inc.
 *
 * 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 <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <utime.h>
#include <lber.h>
#include <ldap.h>
#include "config.h"
#include "vpopmail.h"
#include "vauth.h"
#include "vlimits.h"
#include "vldap.h"

LDAP *ld = NULL;
LDAPMessage *glm = NULL;

#ifdef CLEAR_PASS
#  define NUM_LDAP_FIELDS  9
#else
#  define NUM_LDAP_FIELDS  8
#endif

char *ldap_fields[NUM_LDAP_FIELDS] = {
                                         "uid",   /* 0 pw_name   */
                                         "userPassword",  /* 1 pw_passwd */
                                         "qmailUID",   /* 2 pw_uid    */
                                         "qmailGID",   /* 3 pw_gid    */
                                         "qmaildomain",  /* 4 pw_gecos  */
                                         "mailMessageStore",  /* 5 pw_dir    */
                                         "mailQuota",   /* 6 pw_shell  */
#ifndef CLEAR_PASS
                                         "objectclass"   /* 7 ldap      */
#else
                                         "clearPassword",  /* 7 pw_clear_passwd */
                                         "objectclass"   /* 8 ldap      */
#endif
                                     };

/***************************************************************************/

/* 
 * get ldap connection info
 */
int load_connection_info() {
    FILE *fp;
    char conn_info[256];
    char config[256];
    int eof;
    static int loaded = 0;
    char *port;
    char delimiters[] = "|\n";
    char *conf_read;

    if (loaded) return 0;
    loaded = 1;

    sprintf(config, "%s/etc/%s", VPOPMAILDIR, "vpopmail.ldap");

    fp = fopen(config, "r");
    if (fp == NULL) {
        fprintf(stderr, "vldap: can't read settings from %s\n", config);
        return(VA_NO_AUTH_CONNECTION);
    }
    
    /* skip comments and blank lines */
    do {
        eof = (fgets (conn_info, sizeof(conn_info), fp) == NULL);
    } while (!eof && ((*conn_info == '#') || (*conn_info == '\n')));

    if (eof) {
        /* no valid data read, return error */
        fprintf(stderr, "vldap: no valid settings in %s\n", config);
        return(VA_NO_AUTH_CONNECTION);
    }

    conf_read = strdup(conn_info);
    VLDAP_SERVER = strtok(conf_read, delimiters);
    if (VLDAP_SERVER == NULL) return VA_PARSE_ERROR;
    port = strtok(NULL, delimiters);
    if (port == NULL) return VA_PARSE_ERROR;
    VLDAP_PORT = atoi(port);
    VLDAP_USER = strtok(NULL, delimiters);
    if (VLDAP_USER == NULL) return VA_PARSE_ERROR;
    VLDAP_PASSWORD = strtok(NULL, delimiters);
    if (VLDAP_PASSWORD == NULL) return VA_PARSE_ERROR;
    VLDAP_BASEDN = strtok(NULL, delimiters);
    if (VLDAP_BASEDN == NULL) return VA_PARSE_ERROR;
    
    return 0;
}

struct vqpasswd *vauth_getpw(char *user, char *domain) {
    int ret = 0;
    size_t len = 0;
    struct vqpasswd *vpw = NULL;
    LDAPMessage *res = NULL, *msg = NULL;
    char *filter = NULL, **vals = NULL, *h = NULL, *t = NULL, *passwd = NULL;
    char *dn = NULL;
    uid_t myuid;
    uid_t uid;
    gid_t gid;

    verrori = 0;
    lowerit(user);
    lowerit(domain);

    vget_assign(domain,NULL,0,&uid,&gid);

    myuid = geteuid();
    if ( myuid != 0 && myuid != uid ) {
        return(NULL);
    }

    /* connect to the ldap server (if we havent already got a connection open) */
    if (ld == NULL ) {
        if (ldap_connect() != 0) {
            safe_free((void **) &filter);
            return NULL;
        }
    }

    /* take a given domain, and set dn to be this format :
     * ou=somedomain.com,o=vpopmail
     */
    if (compose_dn(&dn,domain) != 0)
        return NULL;

    /* take the username and create set filter ot be in this format :
     * (&(objectclass=qmailUser)(uid=someusername))
     */
    len = (strlen(user) + 32 + 1);
    filter = (char *)safe_malloc(len);
    memset((char *)filter, 0, len);
    snprintf(filter, len, "(&(objectclass=qmailUser)(uid=%s))", user);

    /* perform an ldap search
     * int ldap_search_s(ld, base, scope, filter, attrs, attrsonly, res)
     * 
     * Will search synchronously, and not return until the operation completes.
     * base : DN of the entry at which to start the search
     * scope : scope of the search
     *   LDAP_SCOPE_SUBTREE means to search the object and all of its descendents.
     * filter : filter to apply to the search
     * attrs : attribute types to return from entries that match filter
     * attrsonly : set to 0 for attributes and attributetypes are wanted. 1 if only attributes are wanted.
     */
    ret = ldap_search_s(ld, dn, LDAP_SCOPE_SUBTREE,
                        filter, vldap_attrs, 0, &res);

    safe_free((void **) &filter);

    /* see if the search ran without generating an error */
    if (ret != LDAP_SUCCESS ) {
        ldap_perror(ld,"Error");
        return NULL;
    }

    /* grab a pointer to the 1st entry in the chain of search results */
    msg = ldap_first_entry(ld, res);
    if (msg == NULL) {
        /* We had an error grabbing the pointer */
        return NULL;
    }

    /* find out how many matches we found */
    ret = ldap_count_entries(ld, msg);
    if (ret == -1 ) {
        /* an error occurred when counting the entries */
        ldap_perror(ld,"Error");
        return NULL;
    }


    /*
       Fetch userPassword first so we can make sure
       we're able to handle it's password encryption (if any)
    */

    /* userPasswd / pw_password */

    vals = ldap_get_values(ld, msg, "userPassword");
    if (vals == NULL) {
        ldap_perror(ld,"Error");
        return NULL;
    }

    t = h = NULL;

    passwd = (char *)safe_malloc((strlen(*vals) + 1));

    memset((char *)passwd, 0, (strlen(*vals) + 1));
    memcpy((char *)passwd, (char *)(*vals), strlen(*vals));

    if (*passwd == '{') {
        for (t = h = (passwd + 1); *t; t++) {
            if (*t == '}') {
                *t++ = '\0';

                /* This is not the best, but we keep the pointer as (h - 1) */
                passwd = t;

                /*
                   Check against the encryption method, and if we see something
                   we dont recognize or support, invalidate user login.
                   vol@inter7.com
                */

                /* Steki <steki@verat.net> Thu Jan 24 17:27:18 CET 2002
                 *  Added check for MD5 crypted passwords
                 */

                if (strcmp(h, "crypt")&& strcmp(h, "MD5")) {
                    free(h - 1);
                    ldap_value_free(vals);
                    return NULL;
                }
                break;
            }
        }
        /*
           No terminating brace found, or empty password.
           vol@inter7.com
        */
        if (!(*t)) {
            ldap_value_free(vals);
            return NULL;
        }
    }

    /* create a vpw struct, which we will populate with the data we suck in from ldap */
    vpw = (struct vqpasswd *) safe_malloc(sizeof(struct vqpasswd));
    memset((struct vqpasswd *)vpw, 0, sizeof(struct vqpasswd));

    vpw->pw_passwd = (char *)safe_malloc((strlen(passwd) + 1));
    memset((char *)vpw->pw_passwd, 0, (strlen(passwd) + 1));
    memcpy((char *)vpw->pw_passwd, (char *)(passwd), strlen(passwd));

    if (vpw->pw_passwd == NULL) {
        free(h - 1);
        ldap_value_free(vals);
        return NULL;
    }

    /*
       Old passwd pointer.
       ..and don't forget to check if you even set the pointer *smack*

       vol@inter7.com
    */
    if (h)
        free(h - 1);

    ldap_value_free(vals);


    /* uid / pw_name */
    vals = ldap_get_values(ld, msg, "uid");
    if (vals == NULL) {
        safe_free((void **) &vpw->pw_passwd);
        ldap_perror(ld,"Error");
        return NULL;
    }
    vpw->pw_name = (char *)safe_malloc((strlen(*vals) + 1));
    memset((char *)vpw->pw_name, 0, (strlen(*vals) + 1));
    memcpy((char *)vpw->pw_name, (char *)(*vals), strlen(*vals));
    ldap_value_free(vals);


    /* mailQuota / pw_shell */
    vals = ldap_get_values(ld, msg, "mailQuota");
    if (vals)
        vpw->pw_shell = (char *)safe_malloc((strlen(*vals) + 1));
    else
        vpw->pw_shell = (char *)safe_malloc(1);

    if (vals) {
        memset((char *)vpw->pw_shell, 0, (strlen(*vals) + 1));
        memcpy((char *)vpw->pw_shell, (char *)(*vals), strlen(*vals));
        ldap_value_free(vals);
    } else {
        *vpw->pw_shell = '\0';
        ldap_perror(ld,"Error");
    }


    /* qmaildomain / pw_gecos */
    vals = ldap_get_values(ld, msg, "qmaildomain");
    if ( vals ) {
        vpw->pw_gecos = (char *)safe_malloc((strlen(*vals) + 1));

        memset((char *)vpw->pw_gecos, 0, (strlen(*vals) + 1));
        memcpy((char *)vpw->pw_gecos, (char *)(*vals), strlen(*vals));
        ldap_value_free(vals);
    } else
        ldap_perror(ld,"Error");


    /* mailMessageStore / pw_dir */
    vals = ldap_get_values(ld, msg, "mailMessageStore");
    if ( vals ) {
        vpw->pw_dir = (char *)safe_malloc((strlen(*vals) + 1));

        memset((char *)vpw->pw_dir, 0, (strlen(*vals) + 1));
        memcpy((char *)vpw->pw_dir, (char *)(*vals), strlen(*vals));
        ldap_value_free(vals);
    } else
        ldap_perror(ld,"Error");


    /* qmailUID / pw_uid */
    vals = ldap_get_values(ld, msg, "qmailUID");
    if ( vals ) {
        vpw->pw_uid = atoi(*vals);
        ldap_value_free(vals);
    } else
        ldap_perror(ld,"Error");


    /* qmailGID / pw_gid */
    vals = ldap_get_values(ld, msg, "qmailGID");
    if ( vals ) {
        vpw->pw_gid = atoi(*vals);
        ldap_value_free(vals);
    } else
        ldap_perror(ld,"Error");

#ifdef CLEAR_PASS
    /* clearPasswd /  pw_clear_passwd */
    vals = ldap_get_values(ld, msg, "clearPassword");
    if ( vals ) {
        vpw->pw_clear_passwd = (char *)safe_malloc((strlen(*vals) + 1));
        memset((char *)vpw->pw_clear_passwd, 0, (strlen(*vals) + 1));
        memcpy((char *)vpw->pw_clear_passwd, (char *)(*vals), strlen(*vals));
        ldap_value_free(vals);
    }
#endif


    vlimits_setflags (vpw, domain);

    return vpw;

}

/***************************************************************************/

void vauth_end_getall() {}

/***************************************************************************/

struct vqpasswd *vauth_getall(char *domain, int first, int sortit) {
    int ret = 0;
    size_t len = 0;
    struct vqpasswd *pw = NULL;
    LDAPMessage *res = NULL;
    char *filter = NULL, **vals = NULL;
    char *basedn = NULL;

    /* if 1st time through, extract all users from this chosen domain */
    if (first) {
        lowerit(domain);

        len = (32 + 1);

        filter = (char *)safe_malloc(len);

        memset((char *)filter, 0, len);

        /* connect to the ldap server if we havent already done so */
        if (ld == NULL ) {
            if (ldap_connect() != 0) {
                safe_free((void **) &filter);
                return NULL;
            }
        }

        /* set basedn to be of the format :
         *   ou=somedomain,o=vpopmail
         */
        if (compose_dn(&basedn,domain) != 0)  {
            safe_free((void **) &filter);
            return NULL;
        }

        snprintf(filter, len, "(objectclass=qmailUser)");

        /* perform the lookup for all users in a given domain */
        ret = ldap_search_s(ld, basedn, LDAP_SCOPE_SUBTREE,
                            filter, vldap_attrs, 0, &res);

        safe_free((void **) &basedn);
        safe_free((void **) &filter);

        if (ret != LDAP_SUCCESS) {
            ldap_perror(ld,"Error");
            return NULL;
        }

        /* sort the entries alphabetically by username if required */
        if ( sortit ) {
            if ( ldap_sort_entries( ld, &res, "uid", &strcasecmp ) != 0)  {
                ldap_perror(ld,"Error");
                return NULL;
            }

            if (ret != LDAP_SUCCESS)
                return NULL;
        }

        /* get a pointer to the first user in the list */
        glm = ldap_first_entry(ld, res);
        if (glm == NULL)
            return NULL;

        /* grab the ldap properties of this user */
        vals = ldap_get_values(ld, glm, "uid");
        if (vals == NULL) {
            ldap_perror(ld,"Error");
            return NULL;
        }

        /* grab the vpopmail properties of this user */
        pw = vauth_getpw(*vals, domain);

        return pw;
    }
    else {
        /* not 1st time through, so get next entry from the chain */
        if (glm == NULL)  /* Just to be safe. (vol@inter7.com) */
            return NULL;

        res = glm;

        glm = ldap_next_entry(ld, res);
        if (glm == NULL)
            return NULL;

        vals = ldap_get_values(ld, glm, "uid");
        if (vals == NULL) {
            ldap_perror(ld,"Error");
            return NULL;
        }

        pw = vauth_getpw(*vals, domain);

        ldap_value_free(vals);

        return pw;
    }
}

/***************************************************************************/

/*
   Higher-level functions no longer crypt.
   Lame.
 
   vol@inter7.com
*/
int vauth_adduser(char *user, char *domain, char *password, char *gecos, char *dir, int apop ) {
    char *dn = NULL;
    char *dn_tmp = NULL;
    LDAPMod **lm = NULL;
    char dom_dir[156];
    uid_t uid;
    gid_t gid;
    int ret = 0, vd = 0;
    int i,len;
    char *b = NULL;
    char crypted[100] = { 0 };


    if ((dir) && (*dir))
        vd = 1;

    if ( gecos==0 || gecos[0]==0)
        gecos=user;

    /* take a given domain, and lookup the dom_dir, uid, gid */
    if ( vget_assign(domain, dom_dir, 156, &uid, &gid ) == NULL ) {
        fprintf(stderr, "failed to vget_assign the domain : %s", domain);
        return (-1);
    }

    if (vd) {
        ret = strlen(dom_dir) + 5 + strlen(dir) + strlen(user);
    } else {
        ret = strlen(dom_dir) + 5 + strlen(user);
    }

    b = (char *)safe_malloc(ret);

    memset((char *)b, 0, ret);

    if (vd) {
        snprintf(b, ret, "%s/%s/%s", dom_dir, dir, user);
    } else {
        snprintf(b, ret, "%s/%s", dom_dir, user);
    }

    dir = b;

    /* make an ldap connection (unless we already have one open) */
    if (ld == NULL ) {
        if (ldap_connect() != 0)
            return -99;
    }

    lm = (LDAPMod **)safe_malloc(sizeof(LDAPMod *) * (NUM_LDAP_FIELDS +1));

    for(i=0;i<NUM_LDAP_FIELDS;++i) {
        lm[i] = (LDAPMod *)safe_malloc(sizeof(LDAPMod));

        memset((LDAPMod *)lm[i], 0, sizeof(LDAPMod));
        lm[i]->mod_op = LDAP_MOD_ADD;
        lm[i]->mod_type = safe_strdup(ldap_fields[i]);
        lm[i]->mod_values = (char **)safe_malloc(sizeof(char *) * 2);
        lm[i]->mod_values[1] = NULL;
    }

    lm[NUM_LDAP_FIELDS] = NULL;

    /* lm[0] will store : uid / pw_name */
    lm[0]->mod_values[0] = safe_strdup(user);

    /* lm[1] will store : userPassword / pw_password */
    memset((char *)crypted, 0, 100);
    if ( password[0] == 0 ) {
        crypted[0] = 0;
    } else {
        mkpasswd3(password, crypted, 100);
    }

    lm[1]->mod_values[0] = (char *) safe_malloc(strlen(crypted) + 7 + 1);
#ifdef MD5_PASSWORDS

    snprintf(lm[1]->mod_values[0], strlen(crypted) + 7 + 1, "{MD5}%s", crypted);
#else

    snprintf(lm[1]->mod_values[0], strlen(crypted) + 7 + 1, "{crypt}%s", crypted);
#endif

    /* lm[2] will store : qmailUID / pw_uid */
    lm[2]->mod_values[0] = (char *) safe_malloc(10);
    if ( apop == USE_POP )
        sprintf(lm[2]->mod_values[0], "%d", 1 );
    else
        sprintf(lm[2]->mod_values[0], "%d", 2 );

    /* lm[3] will store : qmailGID / pw_gid */
    lm[3]->mod_values[0] = (char *) safe_malloc(10);
    sprintf(lm[3]->mod_values[0], "%d", 0);

    /* lm[4] will store : qmaildomain / pw_gecos */
    lm[4]->mod_values[0] = safe_strdup(gecos);

    /* lm[5] will store : mailMessageStore / pw_dir */
    lm[5]->mod_values[0] = safe_strdup(dir);

    /* lm[6] will store : mailQuota / pw_shell */
    lm[6]->mod_values[0] = safe_strdup("NOQUOTA");

    /* When running with clearpasswords enabled,
     * lm[7] will store : clearPassword / pw_clear_password
     */
#ifdef CLEAR_PASS
    /* with clear passwords,
     * lm[7] will store : clearPassword / pw_clear_password 
     * lm[8] will store : objectclass  
     */
    lm[7]->mod_values[0] = strdup(password);
    lm[8]->mod_values[0] = safe_strdup("qmailUser");
#else
    /* without clear passwords,
    * lm[7] will store : objectclass
    */
    lm[7]->mod_values[0] = safe_strdup("qmailUser");
#endif

    /* set dn_tmp to be of the format :
     *   ou=somedomain.com,o=vpopmail 
     */
    if (compose_dn(&dn_tmp,domain) != 0) {
        for(i=0;i<8;++i) {
            safe_free((void **) &lm[i]->mod_type);
            safe_free((void **) &lm[i]->mod_values[0]);
        }
        safe_free((void **) &lm);
        safe_free((void **) &dn);
        return -98;
    }

    /* set dn to be of the format :
     *   uid=someuser, ou=somedomain,o=vpopmail
     */
    len = 4 + strlen(user) + 2 + strlen(VLDAP_BASEDN) + 4 + strlen(domain) + 1;
    dn = (char *) safe_malloc(len);
    memset((char *)dn, 0, len);
    snprintf(dn, len, "uid=%s, %s", user, dn_tmp);
    safe_free((void **) &dn_tmp);

    /* add object to ldap
     *   dn is the DN of the entry to add
     *   lm is the attributes of the entry to add
     */
    ret = ldap_add_s(ld, dn, lm);
    safe_free((void **) &dn);

    for(i=0;i<NUM_LDAP_FIELDS;++i) {
        safe_free((void **) &lm[i]->mod_type);
        safe_free((void **) &lm[i]->mod_values[0]);
    }

    safe_free((void **) &lm);

    if (ret != LDAP_SUCCESS) {
        ldap_perror(ld,"Error");
        if (ret == LDAP_ALREADY_EXISTS)
            return VA_USERNAME_EXISTS;
        return -99;
    }
    return VA_SUCCESS;
}

/***************************************************************************/

int vauth_adddomain( char *domain ) {
    int ret = 0;
    char *dn = NULL;
    LDAPMod **lm = NULL;

    /* make a connection to the ldap server, if we are not already connected */
    if (ld == NULL ) {
        ret = ldap_connect();
        if (ret != 0) {
            return -99;
            /* Attention I am not quite shure, when we return NULL or -99, see above */
        }
    }

    lm = (LDAPMod **)safe_malloc(sizeof(LDAPMod *) * 2);

    lm[0] = (LDAPMod *)safe_malloc(sizeof(LDAPMod));

    lm[1] = (LDAPMod *)safe_malloc(sizeof(LDAPMod));
    lm[2] = NULL;

    memset((LDAPMod *)lm[0], 0, sizeof(LDAPMod));
    memset((LDAPMod *)lm[1], 0, sizeof(LDAPMod));

    lm[0]->mod_op = LDAP_MOD_ADD;
    lm[1]->mod_op = LDAP_MOD_ADD;

    lm[0]->mod_type = safe_strdup("ou");
    lm[1]->mod_type = safe_strdup("objectclass");

    lm[0]->mod_values = (char **)safe_malloc(sizeof(char *) * 2);
    lm[1]->mod_values = (char **)safe_malloc(sizeof(char *) * 2);

    lm[0]->mod_values[1] = NULL;
    lm[1]->mod_values[1] = NULL;

    lm[0]->mod_values[0] = safe_strdup(domain);
    lm[1]->mod_values[0] = safe_strdup("organizationalUnit");

    /* set dn to be of the format :
     *   ou=somedomain.com,o=vpopmail 
     */
    if (compose_dn(&dn,domain) != 0 ) {
        safe_free((void **) &lm[0]->mod_type);
        safe_free((void **) &lm[1]->mod_type);
        safe_free((void **) &lm[0]->mod_values[0]);
        safe_free((void **) &lm[1]->mod_values[0]);
        safe_free((void **) &lm[1]);
        safe_free((void **) &lm[0]);
        safe_free((void **) &lm);
        return -98;
    }

    /* dn will be ou=somedomain.com,o=vpopmail
     * lm will be the ldap propoerties of somedomain.com
     */
    ret = ldap_add_s(ld, dn, lm);

    if (ret != LDAP_SUCCESS) {
        ldap_perror(ld,"Error");
        return -99;
    }

    safe_free((void **) &dn);
    safe_free((void **) &lm[0]->mod_type);
    safe_free((void **) &lm[1]->mod_type);
    safe_free((void **) &lm[0]->mod_values[0]);
    safe_free((void **) &lm[1]->mod_values[0]);
    safe_free((void **) &lm[2]);
    safe_free((void **) &lm[1]);
    safe_free((void **) &lm[0]);
    safe_free((void **) &lm);

    if (ret != LDAP_SUCCESS) {
        if (ret == LDAP_ALREADY_EXISTS)
            return VA_USERNAME_EXISTS;
        return -99;
    }

    return VA_SUCCESS;
}

/***************************************************************************/

int vauth_deldomain( char *domain ) {
    int ret = 0;
    size_t len = 0;
    char *dn = NULL;
    struct vqpasswd *pw = NULL;

    /* make a connection to the ldap server, if we dont have one already */
    if (ld == NULL ) {
        if (ldap_connect() != 0)
            return -99;
    }

    len = strlen(domain) + strlen(VLDAP_BASEDN) + 4 + 1;

    /* dn will be of the format :
     *   ou=somedomain.com,o=vpopmail
     */
    if (compose_dn(&dn,domain) != 0)
        return -98;

    /* loop through all the users in the domain, deleting each one */
    for (pw = vauth_getall(domain, 1, 0); pw; pw = vauth_getall(domain, 0, 0))
        vauth_deluser(pw->pw_name, domain);

    /* next, delete the actual domain */
    ret = ldap_delete_s(ld, dn);
    safe_free((void **) &dn);

    if (ret != LDAP_SUCCESS ) {
        ldap_perror(ld,"Error");
        return -99;
    }

    return VA_SUCCESS;
}

/***************************************************************************/

int vauth_vpasswd( char *user, char *domain, char *crypted, int apop ) {
    int ret = 0;
    struct vqpasswd *pw = NULL;

    pw = vauth_getpw(user, domain);
    if (pw == NULL)
        return VA_USER_DOES_NOT_EXIST;

    pw->pw_passwd = safe_strdup(crypted);

    ret = vauth_setpw(pw, domain);

    return ret;
}

/***************************************************************************/

int vauth_deluser( char *user, char *domain ) {
    int ret = 0;
    size_t len = 0;
    char *dn = NULL;
    char *dn_tmp = NULL;

    /* make a connection to the ldap server if we dont have one already */
    if (ld == NULL ) {
        if (ldap_connect() != 0)
            return -99;
    }

    len = 4 + strlen(user) + 2 + strlen(VLDAP_BASEDN) + 4 + strlen(domain) + 1;

    /* make dn_tmp to be of the format
     *  ou=somedomain.com,o=vpopmail 
     */
    if (compose_dn(&dn_tmp,domain) != 0)
        return -98;

    dn = (char *)safe_malloc(len);
    memset((char *)dn, 0, len);

    /* make dn to be of the format
     *   uid=someuser, ou=somedomain.com,o=vpopmail
     */
    snprintf(dn, len, "uid=%s, %s", user, dn_tmp);
    safe_free((void **) &dn_tmp);

    /* delete the user */
    ret = ldap_delete_s(ld, dn);

    safe_free((void **) &dn);

    if (ret != LDAP_SUCCESS) {
        ldap_perror(ld,"Error");
        return -99;
    }

    return VA_SUCCESS;
}

/***************************************************************************/

int vauth_setquota( char *username, char *domain, char *quota) {
    int ret = 0;
    struct vqpasswd *pw = NULL;

    if ( strlen(username) > MAX_PW_NAME )
        return(VA_USER_NAME_TOO_LONG);
#ifdef USERS_BIG_DIR

    if ( strlen(username) == 1 )
        return(VA_ILLEGAL_USERNAME);
#endif

    if ( strlen(domain) > MAX_PW_DOMAIN )
        return(VA_DOMAIN_NAME_TOO_LONG);
    if ( strlen(quota) > MAX_PW_QUOTA )
        return(VA_QUOTA_TOO_LONG);

    pw = vauth_getpw(username, domain);
    if ( (pw == NULL) && (verrori != 0))
        return verrori;
    else if ( pw == NULL )
        return VA_USER_DOES_NOT_EXIST;

    pw->pw_shell = safe_strdup(quota);

    ret = vauth_setpw(pw, domain);

    return ret;
}

/***************************************************************************/

int vauth_setpw( struct vqpasswd *inpw, char *domain ) {
    int ret = 0;
    size_t len = 0;
    char *dn = NULL;
    char *dn_tmp = NULL;
    LDAPMod **lm = NULL;
    int i;
#ifdef SQWEBMAIL_PASS

    uid_t uid;
    gid_t gid;
#endif

    ret = vcheck_vqpw(inpw, domain);
    if ( ret != 0 ) {
        return(ret);
    }

    if (ld == NULL ) {
        if (ldap_connect() != 0)
            return -99;
    }

    lm = (LDAPMod **)malloc(sizeof(LDAPMod *) * NUM_LDAP_FIELDS + 1);
    for(i=0;i<NUM_LDAP_FIELDS;++i) {
        lm[i] = (LDAPMod *)safe_malloc(sizeof(LDAPMod));
        memset((LDAPMod *)lm[i], 0, sizeof(LDAPMod));
        lm[i]->mod_op = LDAP_MOD_REPLACE;
        lm[i]->mod_values = (char **)safe_malloc(sizeof(char *) * 2);
        lm[i]->mod_values[1] = NULL;
        lm[i]->mod_type = safe_strdup(ldap_fields[i]);
    }
    lm[NUM_LDAP_FIELDS] = NULL;

    lm[0]->mod_values[0] = safe_strdup(inpw->pw_name);

    lm[1]->mod_values[0] = safe_malloc(strlen(inpw->pw_passwd) + 7 + 1);
#ifdef MD5_PASSWORDS

    snprintf(lm[1]->mod_values[0], strlen(inpw->pw_passwd) + 7 + 1, "{MD5}%s", inpw->pw_passwd);
#else

    snprintf(lm[1]->mod_values[0], strlen(inpw->pw_passwd) + 7 + 1, "{crypt}%s", inpw->pw_passwd);
#endif

    lm[2]->mod_values[0] = (char *)safe_malloc(10);
    sprintf(lm[2]->mod_values[0], "%d", inpw->pw_uid);

    lm[3]->mod_values[0] = (char *) safe_malloc(10);
    sprintf(lm[3]->mod_values[0], "%d", inpw->pw_gid);

    if ( inpw->pw_gecos == NULL) {
        lm[4]->mod_values[0] = safe_strdup("");
    } else {
        lm[4]->mod_values[0] = safe_strdup(inpw->pw_gecos);
    }
    lm[5]->mod_values[0] = safe_strdup(inpw->pw_dir);
    lm[6]->mod_values[0] = safe_strdup(inpw->pw_shell);
#ifdef CLEAR_PASS

    lm[7]->mod_values[0] = safe_strdup(inpw->pw_clear_passwd);
#endif

    lm[NUM_LDAP_FIELDS-1]->mod_values[0] = strdup("qmailUser");


    if (compose_dn(&dn_tmp,domain) != 0 ) {
        safe_free((void **) &lm);
        return -98;
    }

    len = 4 + strlen(inpw->pw_name) + 2 + strlen(VLDAP_BASEDN) + 4 + strlen(domain) + 1;
    dn = (char *) safe_malloc (len);
    memset((char *)dn, 0, len);

    snprintf(dn, len, "uid=%s, %s", inpw->pw_name, dn_tmp);

    ret = ldap_modify_s(ld, dn, lm);
    safe_free((void **) &dn);

    for(i=0;i<NUM_LDAP_FIELDS;++i)
        safe_free((void **) &lm);

    if (ret != LDAP_SUCCESS) {
        ldap_perror(ld,"Error");
        return -99;
    }
    /* MARK */
#ifdef SQWEBMAIL_PASS
    vget_assign(domain, NULL, 0, &uid, &gid );
    vsqwebmail_pass( inpw->pw_dir, inpw->pw_passwd, uid, gid);
#endif

#ifdef ONCHANGE_SCRIPT
    if( allow_onchange ) {
       /* tell other programs that data has changed */
       snprintf ( onchange_buf, MAX_BUFF, "%s@%s", inpw->pw_name, domain );
       call_onchange ( "mod_user" );
       }
#endif

    return VA_SUCCESS;
}

/***************************************************************************/

/*   Verify the connection to the authentication database   */

int vauth_open( int will_update ) {

#ifdef VPOPMAIL_DEBUG
show_trace = ( getenv("VPSHOW_TRACE") != NULL);
show_query = ( getenv("VPSHOW_QUERY") != NULL);
dump_data  = ( getenv("VPDUMP_DATA")  != NULL);
#endif

#ifdef VPOPMAIL_DEBUG
    if( show_trace ) {
        fprintf( stderr, "vauth_open()\n");
    }
#endif 



/*
 *  If the connection to this authentication database can fail
 *  you should test access here.  If it works, return 0, else 
 *  return VA_NO_AUTH_CONNECTION.  You can also set the string 
 *  sqlerr to some short descriptive text about the problem, 
 *  and allocate a much longer string, pointed to by last_query
 *  that can be displayed in an error message returned because
 *  of this problem.
 *
 */

    return( 0 );
}

/***************************************************************************/

void vclose(void) {
    if (ld) {
        ldap_unbind_s(ld);
        ld = NULL;
    }
}

/***************************************************************************/

char *dc_filename(char *domain, uid_t uid, gid_t gid)
{
 static char dir_control_file[MAX_DIR_NAME];
 struct passwd *pw;

    /* if we are lucky the domain is in the assign file */
    if ( vget_assign(domain,dir_control_file,MAX_DIR_NAME,NULL,NULL)!=NULL ) {
        strncat(dir_control_file, "/.dir-control", MAX_DIR_NAME);

    /* it isn't in the assign file so we have to get it from /etc/passwd */
    } else {

        /* save some time if this is the vpopmail user */
        if ( uid == VPOPMAILUID ) {
            strncpy(dir_control_file, VPOPMAILDIR, MAX_DIR_NAME);

        /* for other users, look them up in /etc/passwd */
        } else if ( (pw=getpwuid(uid))!=NULL ) {
            strncpy(dir_control_file, pw->pw_dir, MAX_DIR_NAME);

        /* all else fails return a blank string */
        } else {
            return("");
        }

        /* stick on the rest of the path */
        strncat(dir_control_file, "/" DOMAINS_DIR "/.dir-control", MAX_DIR_NAME);
    }
    return(dir_control_file);
}

int vread_dir_control(vdir_type *vdir, char *domain, uid_t uid, gid_t gid)
{
 FILE *fs;
 char dir_control_file[MAX_DIR_NAME];
 int i;

    strncpy(dir_control_file,dc_filename(domain, uid, gid),MAX_DIR_NAME);


    if ( (fs = fopen(dir_control_file, "r")) == NULL ) {
        vdir->cur_users = 0;
        for(i=0;i<MAX_DIR_LEVELS;++i){
            vdir->level_start[i] = 0;
            vdir->level_end[i] = MAX_DIR_LIST-1;
            vdir->level_index[i] = 0;
        }
        vdir->level_mod[0] = 0;
        vdir->level_mod[1] = 2;
        vdir->level_mod[2] = 4;
        vdir->level_cur = 0;
        vdir->level_max = MAX_DIR_LEVELS;
        vdir->the_dir[0] = 0;
        return(-1);
    }

    fgets(dir_control_file, MAX_DIR_NAME, fs );
    vdir->cur_users = atol(dir_control_file);

    fgets(dir_control_file, MAX_DIR_NAME, fs );
    vdir->level_cur = atoi(dir_control_file);

    fgets(dir_control_file, MAX_DIR_NAME, fs );
    vdir->level_max = atoi(dir_control_file);

    fgets(dir_control_file, MAX_DIR_NAME, fs );
    vdir->level_start[0] = atoi(dir_control_file);
    for(i=0;dir_control_file[i]!=' ';++i); ++i;
    vdir->level_start[1] = atoi(&dir_control_file[i]);
    for(i=0;dir_control_file[i]!=' ';++i); ++i;
    vdir->level_start[2] = atoi(&dir_control_file[i]);

    fgets(dir_control_file, MAX_DIR_NAME, fs );
    vdir->level_end[0] = atoi(dir_control_file);
    for(i=0;dir_control_file[i]!=' ';++i); ++i;
    vdir->level_end[1] = atoi(&dir_control_file[i]);
    for(i=0;dir_control_file[i]!=' ';++i); ++i;
    vdir->level_end[2] = atoi(&dir_control_file[i]);

    fgets(dir_control_file, MAX_DIR_NAME, fs );
    vdir->level_mod[0] = atoi(dir_control_file);
    for(i=0;dir_control_file[i]!=' ';++i); ++i;
    vdir->level_mod[1] = atoi(&dir_control_file[i]);
    for(i=0;dir_control_file[i]!=' ';++i); ++i;
    vdir->level_mod[2] = atoi(&dir_control_file[i]);

    fgets(dir_control_file, MAX_DIR_NAME, fs );
    vdir->level_index[0] = atoi(dir_control_file);
    for(i=0;dir_control_file[i]!=' ';++i); ++i;
    vdir->level_index[1] = atoi(&dir_control_file[i]);
    for(i=0;dir_control_file[i]!=' ';++i); ++i;
    vdir->level_index[2] = atoi(&dir_control_file[i]);

    fgets(dir_control_file, MAX_DIR_NAME, fs );
    for(i=0;dir_control_file[i]!=0;++i) {
        if (dir_control_file[i] == '\n') {
            dir_control_file[i] = 0;
        }
    }

    fgets(dir_control_file, MAX_DIR_NAME, fs );
    for(i=0;dir_control_file[i]!=0;++i) {
        if (dir_control_file[i] == '\n') {
            dir_control_file[i] = 0;
        }
    }
    strncpy(vdir->the_dir, dir_control_file, MAX_DIR_NAME);

    fclose(fs);

    return(0);
}

int vwrite_dir_control(vdir_type *vdir, char *domain, uid_t uid, gid_t gid)
{
 FILE *fs;
 char dir_control_file[MAX_DIR_NAME];
 char dir_control_tmp_file[MAX_DIR_NAME];

    strncpy(dir_control_file,dc_filename(domain, uid, gid),MAX_DIR_NAME);
    snprintf(dir_control_tmp_file, MAX_DIR_NAME,
        "%s.%d", dir_control_file, getpid());

    if ( (fs = fopen(dir_control_tmp_file, "w+")) == NULL ) {
        return(-1);
    }

    fprintf(fs, "%lu\n", vdir->cur_users);
    fprintf(fs, "%d\n", vdir->level_cur);
    fprintf(fs, "%d\n", vdir->level_max);
    fprintf(fs, "%d %d %d\n",
        vdir->level_start[0],
        vdir->level_start[1],
        vdir->level_start[2]);
    fprintf(fs, "%d %d %d\n",
        vdir->level_end[0],
        vdir->level_end[1],
        vdir->level_end[2]);
    fprintf(fs, "%d %d %d\n",
        vdir->level_mod[0],
        vdir->level_mod[1],
        vdir->level_mod[2]);
    fprintf(fs, "%d %d %d\n",
        vdir->level_index[0],
        vdir->level_index[1],
        vdir->level_index[2]);
    fprintf(fs, "%s\n", vdir->the_dir);

    fclose(fs);

    rename( dir_control_tmp_file, dir_control_file);

    chown(dir_control_file,uid, gid);

    return(0);
}

int vdel_dir_control(char *domain)
{
 char dir_control_file[MAX_DIR_NAME];

    vget_assign(domain, dir_control_file, 156, NULL,NULL);
    strncat(dir_control_file,"/.dir-control", MAX_DIR_NAME);
    return(unlink(dir_control_file));
}

/***************************************************************************/

#ifdef ENABLE_AUTH_LOGGING
int vset_lastauth_time(char *user, char *domain, char *remoteip, time_t cur_time ) {
    char *tmpbuf;
    FILE *fs;
    struct vqpasswd *vpw;
    struct utimbuf ubuf;
    uid_t uid;
    gid_t gid;

    if ((vpw = vauth_getpw( user, domain )) == NULL)
        return (0);

    tmpbuf = (char *) safe_malloc(MAX_BUFF);
    sprintf(tmpbuf, "%s/lastauth", vpw->pw_dir);
    if ( (fs = fopen(tmpbuf,"w+")) == NULL ) {
        safe_free((void **) &tmpbuf);
        return(-1);
    }
    fprintf(fs, "%s", remoteip);
    fclose(fs);
    ubuf.actime = cur_time;
    ubuf.modtime = cur_time;
    utime(tmpbuf, &ubuf);
    vget_assign(domain,NULL,0,&uid,&gid);
    chown(tmpbuf,uid,gid);
    safe_free((void **) &tmpbuf);
    return(0);
}


int vset_lastauth(char *user, char *domain, char *remoteip ) {
    return(vset_lastauth_time(user, domain, remoteip, time(NULL) ));
}


time_t vget_lastauth( struct vqpasswd *pw, char *domain) {
    char *tmpbuf;
    struct stat mystatbuf;

    tmpbuf = (char *) safe_malloc(MAX_BUFF);
    sprintf(tmpbuf, "%s/lastauth", pw->pw_dir);
    if ( stat(tmpbuf,&mystatbuf) == -1 ) {
        safe_free((void **) &tmpbuf);
        return(0);
    }
    safe_free((void **) &tmpbuf);
    return(mystatbuf.st_mtime);
}


char *vget_lastauthip( struct vqpasswd *pw, char *domain) {
    static char tmpbuf[MAX_BUFF];
    FILE *fs;

    snprintf(tmpbuf, MAX_BUFF, "%s/lastauth", pw->pw_dir);
    if ( (fs=fopen(tmpbuf,"r"))==NULL)
        return(NULL);
    fgets(tmpbuf,MAX_BUFF,fs);
    fclose(fs);
    return(tmpbuf);
}
#endif /* ENABLE_AUTH_LOGGING */

/***************************************************************************/

#ifdef IP_ALIAS_DOMAINS
int vget_ip_map( char *ip, char *domain, int domain_size) {
    FILE *fs;
    char tmpbuf[156];
    char *tmpstr;

    if ( ip == NULL || strlen(ip) <= 0 )
        return(-1);

    /* open the ip_alias_map file */
    snprintf(tmpbuf, 156, "%s/%s", VPOPMAILDIR, IP_ALIAS_MAP_FILE);
    if ( (fs = fopen(tmpbuf,"r")) == NULL )
        return(-1);

    while( fgets(tmpbuf, 156, fs) != NULL ) {
        tmpstr = strtok(tmpbuf, IP_ALIAS_TOKENS);
        if ( tmpstr == NULL )
            continue;
        if ( strcmp(ip, tmpstr) != 0 )
            continue;

        tmpstr = strtok(NULL, IP_ALIAS_TOKENS);
        if ( tmpstr == NULL )
            continue;
        strncpy(domain, tmpstr, domain_size);
        fclose(fs);
        return(0);

    }
    fclose(fs);
    return(-1);
}

/***************************************************************************/

/*
 * Add an ip to domain mapping
 * It will remove any duplicate entry before adding it
 *
 */
int vadd_ip_map( char *ip, char *domain) {
    FILE *fs;
    char tmpbuf[156];

    if ( ip == NULL || strlen(ip) <= 0 )
        return(-1);
    if ( domain == NULL || strlen(domain) <= 0 )
        return(-10);

    vdel_ip_map( ip, domain );

    snprintf(tmpbuf, 156, "%s/%s", VPOPMAILDIR, IP_ALIAS_MAP_FILE);
    if ( (fs = fopen(tmpbuf,"a+")) == NULL )
        return(-1);
    fprintf( fs, "%s %s\n", ip, domain);
    fclose(fs);

    return(0);
}

int vdel_ip_map( char *ip, char *domain) {
    FILE *fs;
    FILE *fs1;
    char file1[156];
    char file2[156];
    char tmpbuf[156];
    char tmpbuf1[156];
    char *ip_f;
    char *domain_f;

    if ( ip == NULL || strlen(ip) <= 0 )
        return(-1);
    if ( domain == NULL || strlen(domain) <= 0 )
        return(-1);

    snprintf(file1, 156, "%s/%s", VPOPMAILDIR, IP_ALIAS_MAP_FILE);
    if ( (fs = fopen(file1,"r")) == NULL )
        return(-1);

    snprintf(file2, 156,
             "%s/%s.%d", VPOPMAILDIR, IP_ALIAS_MAP_FILE, getpid());
    if ( (fs1 = fopen(file2,"w")) == NULL ) {
        fclose(fs);
        return(-1);
    }

    while( fgets(tmpbuf, 156, fs) != NULL ) {
        strncpy(tmpbuf1,tmpbuf, 156);

        ip_f = strtok(tmpbuf, IP_ALIAS_TOKENS);
        if ( ip_f == NULL )
            continue;

        domain_f = strtok(NULL, IP_ALIAS_TOKENS);
        if ( domain_f == NULL )
            continue;

        if ( strcmp(ip, ip_f) == 0 && strcmp(domain,domain_f) == 0)
            continue;

        fprintf(fs1, tmpbuf1);

    }
    fclose(fs);
    fclose(fs1);

    if ( rename( file2, file1) < 0 )
        return(-1);

    return(0);
}

int vshow_ip_map( int first, char *ip, char *domain) {
    static FILE *fs = NULL;
    char tmpbuf[156];
    char *tmpstr;

    if ( ip == NULL )
        return(-1);
    if ( domain == NULL )
        return(-1);

    if ( first == 1 ) {
        if ( fs != NULL ) {
            fclose(fs);
            fs = NULL;
        }
        snprintf(tmpbuf, 156, "%s/%s", VPOPMAILDIR, IP_ALIAS_MAP_FILE);
        if ( (fs = fopen(tmpbuf,"r")) == NULL )
            return(-1);
    }
    if ( fs == NULL )
        return(-1);

    while (1) {
        if (fgets(tmpbuf, 156, fs) == NULL ) {
            fclose(fs);
            fs = NULL;
            return(0);
        }

        tmpstr = strtok(tmpbuf, IP_ALIAS_TOKENS);
        if ( tmpstr == NULL )
            continue;
        strcpy( ip, tmpstr);

        tmpstr = strtok(NULL, IP_ALIAS_TOKENS);
        if ( tmpstr == NULL )
            continue;
        strcpy( domain, tmpstr);

        return(1);
    }
    return(-1);

}
#endif

/***************************************************************************/

/* take a given domain, and set dn to be a string of this format :
 * ou=somedomain,o=vpopmail
 */
int compose_dn (char **dn, char *domain) {
    size_t len = 0;

    len = strlen(domain) + strlen(VLDAP_BASEDN) + 5;

    *dn = (char *)safe_malloc(len);
    memset((char *)*dn, 0, len);

    snprintf(*dn,len,"ou=%s,%s",domain,VLDAP_BASEDN);

    return 0;

}

/***************************************************************************/

int ldap_connect () {
    int ret = 0;
    
    /* Set verror here and unset it when successful, is ok, because if one of these
    three steps fail the whole auth_connection failed */
    verrori = load_connection_info();
    if (verrori) return -1;

    ld = ldap_init(VLDAP_SERVER, VLDAP_PORT);
    if (ld == NULL) {
        ldap_perror(ld,"Failed to inititialize LDAP-Connection");
        return -99;
    }
    ret = ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &ldapversion );
    if (ret != LDAP_OPT_SUCCESS) {
        ldap_perror(ld,"Failed to set LDAP-Option");
        return -99;
    }
    ret = ldap_simple_bind_s(ld, VLDAP_USER, VLDAP_PASSWORD);
    if (ret != LDAP_SUCCESS) {
        ldap_perror(ld,"Error");
        return (VA_NO_AUTH_CONNECTION);
    }

    verrori = 0;
    return VA_SUCCESS;
}

/***************************************************************************/

void safe_free (void **p) {
    if (*p) {
        free (*p);
        *p = 0;
    }
}

/***************************************************************************/

char *safe_strdup (const char *s) {
    char *p;
    size_t l;

    if (!s || !*s)
        return 0;
    l = strlen (s) + 1;
    p = (char *)safe_malloc (l);
    memcpy (p, s, l);
    return (p);
}

/***************************************************************************/

void *safe_malloc (size_t siz) {
    void *p;

    if (siz == 0)
        return 0;
    if ((p = (void *) malloc (siz)) == 0) {
        printf("No more memory...exiting\n");
        exit (1);
    }
    return (p);
}

/***************************************************************************/

int vauth_crypt(char *user,char *domain,char *clear_pass,struct vqpasswd *vpw) {
    if ( vpw == NULL )
        return(-1);

    return(strcmp(crypt(clear_pass,vpw->pw_passwd),vpw->pw_passwd));
}

/***************************************************************************/



syntax highlighted by Code2HTML, v. 0.9.1