/*
 * $Id: vpopmail.c,v 1.55 2007/07/14 04:37:15 rwidmer Exp $
 * Copyright (C) 2000-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 <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <pwd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <ctype.h>
#include <fcntl.h>
#include <time.h>
#include <dirent.h>
#include <pwd.h>
#include "config.h"
#ifdef HAVE_ERR_H
#include <err.h>
#endif
#include "md5.h"
#include "vpopmail.h"
#include "file_lock.h"
#include "vauth.h"
#include "vlimits.h"
#include "maildirquota.h"


#ifdef VPOPMAIL_DEBUG
int show_trace=0;
int show_query=0;
int dump_data=0;
#endif

#ifdef POP_AUTH_OPEN_RELAY
/* keep a output pipe to tcp.smtp file */
int tcprules_fdm;
static char relay_tempfile[MAX_BUFF];
#endif

int verrori = 0;

extern int cdb_seek();

/* Global Flags */
int NoMakeIndex = 0;
int OptimizeAddDomain = 0;

#define PS_TOKENS " \t"
#define CDB_TOKENS ":\n\r"

#ifdef IP_ALIAS_DOMAINS
int host_in_locals(char *domain);
#endif

static char gen_chars[] = "abcdefghijklmnopqrstuvwxyz" \
                          "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
                          "0123456789.@!#%*";

static char ok_env_chars[] = "abcdefghijklmnopqrstuvwxyz" \
                            "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
                            "1234567890_-.@";

 typedef struct defsortrec {
   char *key;
   char *value;
 } sortrec;


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

/* 
 * Add a domain to the email system
 *
 * input: domain name
 *        dir to put the files
 *        uid and gid to assign to the files
 */
int vadddomain( char *domain, char *dir, uid_t uid, gid_t gid )
{
 FILE *fs;
 int i;
 char *domain_hash;
 char DomainSubDir[MAX_BUFF];
 char dir_control_for_uid[MAX_BUFF];
 char tmpbuf[MAX_BUFF];
 char Dir[MAX_BUFF];
 char calling_dir[MAX_BUFF];

 char *aliases[1];
 int aliascount=0;
 
#ifdef ONCHANGE_SCRIPT
  /*  Don't execute any implied onchange in called functions  */
  allow_onchange = 0;
#endif

  /*
   * In case we need to use delete_line, build an array and count 
   * to use as its parameters 
   *
   */
  aliases[aliascount++]=strdup(domain);

  /* we only do lower case */
  lowerit(domain);

  /* reject domain names that are too short to be valid */
  if ( strlen( domain) <3) return (VA_INVALID_DOMAIN_NAME);

  /* reject domain names that exceed our max permitted/storable size */
  if ( strlen( domain ) > MAX_PW_DOMAIN ) return(VA_DOMAIN_NAME_TOO_LONG);

  /* check invalid email domain characters */
  for(i=0;domain[i]!=0;++i) {
    if (i == 0 && domain[i] == '-' ) return(VA_INVALID_DOMAIN_NAME);
    if (isalnum((int)domain[i])==0 && domain[i]!='-' && domain[i]!='.') {
      return(VA_INVALID_DOMAIN_NAME);
    }
  }
  if ( domain[i-1] == '-' ) return(VA_INVALID_DOMAIN_NAME);

  /* after the name is okay, check if it already exists */
  if ( vget_assign(domain, NULL, 0, NULL, NULL ) != NULL ) {
    return(VA_DOMAIN_ALREADY_EXISTS);
  }
 
  /* set our file creation mask for machines where the
   * sysadmin has tightened default permissions
   */
  umask(VPOPMAIL_UMASK);

  /* store the calling directory */
  getcwd(calling_dir, sizeof(calling_dir));

  /* go to the directory where our Domains dir is to be stored 
   * check for error and return error on error
   */
  if ( chdir(dir) != 0 ) return(VA_BAD_V_DIR);

  /* go into the Domains subdir */
  if ( chdir(DOMAINS_DIR) != 0 ) {

    /* if it's not there, no problem, just try to create it */
    if ( mkdir(DOMAINS_DIR, VPOPMAIL_DIR_MODE) != 0 ) {
      chdir(calling_dir);
      return(VA_CAN_NOT_MAKE_DOMAINS_DIR);
    }

    /*  set the permisions on our new Domains dir */
    chown(DOMAINS_DIR,uid,gid);

    /* now try moving into the Domains subdir again */
    if ( chdir(DOMAINS_DIR) != 0 ) {
      chdir(calling_dir);
      return(VA_BAD_D_DIR);
    }
  }

  /* since domains can be added under any /etc/passwd
   * user, we have to create dir_control information
   * for each user/domain combination
   */
  snprintf(dir_control_for_uid, sizeof(dir_control_for_uid),
   "dom_%lu", (long unsigned)uid);

  /* work out a subdir name for the domain 
   * Depending on how many domains we have, it may need to be hashed
   */
  open_big_dir(dir_control_for_uid, uid, gid);       
  domain_hash = next_big_dir(uid, gid);
  close_big_dir(dir_control_for_uid, uid, gid);      

  if ( strlen(domain_hash) > 0 ) {
    snprintf(DomainSubDir, sizeof(DomainSubDir), "%s/%s", domain_hash, domain);
  } else {
    snprintf(DomainSubDir,sizeof(DomainSubDir), "%s", domain);
  }

  /* Check to make sure length of the dir isnt going to exceed
   * the maximum storable size
   * We dont want to start creating dirs and putting entries in
   * the assign file etc if the path is going to be too long
   */
  if (strlen(dir)+strlen(DOMAINS_DIR)+strlen(DomainSubDir) > MAX_PW_DIR) {
    /* back out of changes made so far */
    dec_dir_control(dir_control_for_uid, uid, gid);
    chdir(calling_dir);
    return(VA_DIR_TOO_LONG);
  }

  /* Make the subdir for the domain */
  if ( r_mkdir(DomainSubDir, uid, gid ) != 0 ) {
    /* back out of changes made so far */
    dec_dir_control(dir_control_for_uid, uid, gid);
    chdir(calling_dir);
    return(VA_COULD_NOT_MAKE_DOMAIN_DIR);
  }
  
  if ( chdir(DomainSubDir) != 0 ) {
    /* back out of changes made so far */
    vdelfiles(DomainSubDir);
    dec_dir_control(dir_control_for_uid, uid, gid);
    chdir(calling_dir);
    return(VA_BAD_D_DIR);
  }

  /* create the .qmail-default file */
  snprintf(tmpbuf, sizeof(tmpbuf), "%s/%s/%s/.qmail-default", dir, DOMAINS_DIR, 
    DomainSubDir);
  if ( (fs = fopen(tmpbuf, "w+"))==NULL) {
    /* back out of changes made so far */
    chdir(dir); chdir(DOMAINS_DIR);
    if (vdelfiles(DomainSubDir) != 0) {
      fprintf(stderr, "Failed to delete directory tree :%s\n", DomainSubDir);
    }
    dec_dir_control(dir_control_for_uid, uid, gid);
    chdir(calling_dir);
    return(VA_COULD_NOT_OPEN_QMAIL_DEFAULT);
  } else {
    fprintf(fs, "| %s/bin/vdelivermail '' bounce-no-mailbox\n", VPOPMAILDIR);
    fclose(fs);
  }

  /* create an entry in the assign file for our new domain */
  snprintf(tmpbuf, sizeof(tmpbuf), "%s/%s/%s", dir, DOMAINS_DIR, DomainSubDir);
  if (add_domain_assign( domain, domain, tmpbuf, uid, gid ) != 0) {
    /* back out of changes made so far */
    chdir(dir); chdir(DOMAINS_DIR);
    if (vdelfiles(DomainSubDir) != 0) {
      fprintf(stderr, "Failed to delete directory tree: %s\n", DomainSubDir);
    }
    dec_dir_control(dir_control_for_uid, uid, gid);
    chdir(calling_dir);
    fprintf (stderr, "Error. Failed to add domain to assign file\n");
    return (VA_COULD_NOT_UPDATE_FILE);
  }

  /* recursively change ownership to new file system entries */
  snprintf(tmpbuf, sizeof(tmpbuf), "%s/%s/%s", dir, DOMAINS_DIR, DomainSubDir);
  r_chown(tmpbuf, uid, gid);

  /* ask the authentication module to add the domain entry */
  /* until now we checked if domain already exists in cdb and
   * setup all dirs, but vauth_adddomain may __fail__ so we need to check
   */

  if (vauth_adddomain( domain ) != VA_SUCCESS ) {

    /* ok we have run into problems here. adding domain to auth backend failed
     * so now we need to reverse the steps we have already performed above 
     */

    fprintf(stderr, "Error. Failed while attempting to add domain to auth backend\n");

    chdir(dir); chdir(DOMAINS_DIR);    
    if (vdelfiles(DomainSubDir) != 0) {
      fprintf(stderr, "Failed to delete directory tree: %s\n", DomainSubDir);
    }

    dec_dir_control(dir_control_for_uid, uid, gid);

    vget_assign(domain, Dir, sizeof(Dir), &uid, &gid );

    if ( del_domain_assign(aliases, aliascount, domain, Dir, uid, gid) != 0) {
      fprintf(stderr, "Failed while attempting to remove domain from assign file\n");
    }

    if (del_control(aliases,aliascount) !=0) {
      fprintf(stderr, "Failed while attempting to delete domain from the qmail control files\n");
    }

#ifdef USERS_BIG_DIR
    if (vdel_dir_control(domain) != 0) {
      fprintf (stderr, "Warning: Failed to delete dir_control for %s\n", domain);
    }
#endif

    /* send a HUP signal to qmail-send process to reread control files */
    signal_process("qmail-send", SIGHUP);

    return (VA_NO_AUTH_CONNECTION);
  }	
 
  /* ask qmail to re-read it's new control files */
  if ( OptimizeAddDomain == 0 ) {
    signal_process("qmail-send", SIGHUP);
  }


#ifdef ONCHANGE_SCRIPT
  allow_onchange = 1;
  /* tell other programs that data has changed */
  snprintf ( onchange_buf, MAX_BUFF, "%s", domain );
  call_onchange ( "add_domain" );
  allow_onchange = 0;
#endif

  /* return back to the callers directory and return success */
  chdir(calling_dir);

  free( aliases[0] );

  return(VA_SUCCESS);
}

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

/* Delete a domain from the entire mail system
 *
 * If we have problems at any of the following steps, it has been 
 * decided that the best course of action is to continue rather than
 * abort. The idea behind this is to allow the removal of a partially
 * installed domain. We will emit warnings should any of the expected
 * cleanup steps fail.
 */
int vdeldomain( char *domain )
{
 struct stat statbuf;
 char Dir[MAX_BUFF];
 char domain_to_del[MAX_BUFF];
 char dircontrol[MAX_BUFF];
 uid_t uid;
 gid_t gid;
 char *aliases[MAX_DOM_ALIAS];
 domain_entry *entry;
 int i=0, aliascount=0;

  /* we always convert domains to lower case */
  lowerit(domain);

  /* Check the length of the domain to del
   * If it exceeds the max storable size, 
   * then the user has made some sort of error in 
   * asking to del that domain, because such a domain
   * wouldnt be able to exist in the 1st place
   */
  if (strlen(domain) > MAX_PW_DOMAIN) return (VA_DOMAIN_NAME_TOO_LONG);

  /* now we want to check a couple for things :
   * a) if the domain to del exists in the system
   * b) if the domain to del is an aliased domain or not
   */

  /* Take a backup of the domain we want to del,
   * because when we call vget_assign, if the domain
   * is an alias, then the domain parameter will be
   * rewritten on return as the name of the real domain
   */
  snprintf(domain_to_del, sizeof(domain_to_del), "%s", domain);

  /* check if the domain exists. If so extract the dir, uid, gid */
  if (vget_assign(domain, Dir, sizeof(Dir), &uid, &gid ) == NULL) {
    return(VA_DOMAIN_DOES_NOT_EXIST);
  }

  if ( strcmp(domain_to_del, domain) != 0 ) {
     /*  This is just an alias, so add it to the list of aliases
      *  that are about to be deleted.  It will be the only one
      *  but I will use the same code as multi domains anyway.
      */
     aliases[aliascount++] = strdup( domain_to_del );

#ifdef ONCHANGE_SCRIPT
     /* tell other programs that data has changed */
     snprintf ( onchange_buf, MAX_BUFF, "%s alias of %s", domain_to_del, domain );
     call_onchange ( "del_domain" );
#endif

  } else {
    /* this is an NOT aliased domain....
   * (aliased domains dont have any filestructure of their own)
   */

    /* check if the domain's dir exists */
    if ( stat(Dir, &statbuf) != 0 ) {
      fprintf(stderr, "Warning: Could not access (%s)\n",Dir);
    }

    /*
     * Michael Bowe 23rd August 2003
     *
     * at this point, we need to write some code to check if any alias domains
     * point to this (real) domain. If we find such aliases, then I guess we
     * have a couple of options :
     * 1. Abort with an error, saying cant delete domain until all
     *    aliases are removed 1st (list them)
     * 2. Zap all the aliases in additon to this domain
     *
     * Rick Widmer 28 April 3004
     *
     * OK.  Option 2 won.  If this domain has aliases they will all
     * be deleted.  If you want to warn people about this it should
     * be done by the program calling vdeldomain() before you call it.
     * You shuould be able to find example code in vdeldomain.c.
     *
     */

     entry = get_domain_entries( domain );

     if (entry==NULL) {   //  something went wrong
       if( verrori ) {    //  could not open file
         fprintf(stderr,"%s\n", verror(verrori));

       } else {           //  domain does not exist
         fprintf(stderr,"%s\n", verror(VA_DOMAIN_DOES_NOT_EXIST));
       }
     }

     while( entry ) {
       aliases[aliascount++] = strdup(entry->domain);
       entry = get_domain_entries(NULL);
     }

//   Dump the alias list
//     for(i=0;i<aliascount;i++) {
//       fprintf(stderr,"alias %s\n", aliases[i]);
//     }


#ifdef ONCHANGE_SCRIPT
     /* tell other programs that data has changed */
     snprintf ( onchange_buf, MAX_BUFF, "%s", domain );
     call_onchange ( "del_domain" );
#endif

    /* call the auth module to delete the domain from the storage */
    /* Note !! We must del domain from auth module __before__ we delete it from
     * fs, because deletion from auth module may fail !!!!
     */

    /* del a domain from the auth backend which includes :
     * - drop the domain's table, or del all users from users table
     * - delete domain's entries from lastauth table
     * - delete domain's limit's entries
     */
    if (vauth_deldomain(domain) != VA_SUCCESS ) {
      fprintf (stderr, "Warning: Failed while attempting to delete domain from auth backend\n");
    }

    /* vdel_limits does the following :
     * If we have mysql_limits enabled,
     *  it will delete the domain's entries from the limits table
     * Or if we arent using mysql_limits,
     *  it will delete the .qmail-admin file from the domain's dir
     *
     * Note there are inconsistencies in the auth backends.  Some
     * will run vdel_limits() in vauth_deldomain(), others don't.
     * For now, we always run it to be safe.  Ultimately, the auth
     * backends should to be updated to do this.
     */  
    vdel_limits(domain);

#ifdef USERS_BIG_DIR
    /* delete the dir control info for this domain */
    if (vdel_dir_control(domain) != 0) {
      fprintf (stderr, "Warning: Failed to delete dir_control for %s\n", domain);
    }
#endif

    /* Now remove domain from filesystem */
    /* if it's a symbolic link just remove the link */
    if ( S_ISLNK(statbuf.st_mode) ) {
      if ( unlink(Dir) !=0) {
        fprintf (stderr, "Warning: Failed to remove symlink for %s\n", domain);
      }
    } else {
      char cwdbuff[MAX_BUFF];
      char *cwd;
      /* Not a symlink.. so we have to del some files structure now */
      /* zap the domain's directory tree */
      cwd = getcwd (cwdbuff, sizeof(cwdbuff));  /* save calling directory */
      if ( vdelfiles(Dir) != 0 ) {
        fprintf(stderr, "Warning: Failed to delete directory tree: %s\n", domain);
      }
      if (cwd != NULL) chdir (cwd);
    }

    /* decrement the master domain control info */
    snprintf(dircontrol, sizeof(dircontrol), "dom_%lu", (long unsigned)uid);
    dec_dir_control(dircontrol, uid, gid);
  }

  /* The following things need to happen for real and aliased domains */

  /* delete the email domain from the qmail control files :
   * rcpthosts, morercpthosts, virtualdomains
   */
  if (del_control(aliases,aliascount) != 0) {
    fprintf (stderr, "Warning: Failed to delete domain from qmail's control files\n");
  }

  /* delete the assign file line */
  if (del_domain_assign(aliases, aliascount, domain, Dir, uid, gid) != 0) {
    fprintf (stderr, "Warning: Failed to delete domain from the assign file\n");
  }

  /* send a HUP signal to qmail-send process to reread control files */
  signal_process("qmail-send", SIGHUP);

  /*  clean up memory used by the alias list  */
  for(i=0;i<aliascount;i++) {
    free( aliases[i] );
  }


  return(VA_SUCCESS);

}

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

/* get_domain_entries
 *
 * Parses the qmail users/assign file and returns a domain_entry pointer.
 * If first parameter is not NULL, re-open users/assign file and start scanning.
 *   If first parameter is "", return all entries.  Otherwise, only return
 *   entries where "real" domain matches the first parameter.
 * If first parameter is NULL, returns the next line in already open file.
 *
 * Example 1.  Scan through all entries.
 *   domain_entry *e;
 *   e = get_domain_entries ("");
 *   while (e) {
 *     printf ("Alias: %s  Real domain: %s  uid: %d  gid: %d  path: %s\n",
 *       e->domain, e->realdomain, e->uid, e->gid, e->path);
 *     e = get_domain_entries (NULL);
 *   }
 *
 * Example 2.  Find all entries (primary and aliases) for domain.com.
 *   domain_entry *e;
 *   e = get_domain_entries ("domain.com");
 *   while (e) {
 *     printf ("Alias: %s  Real domain: %s  uid: %d  gid: %d  path: %s\n",
 *       e->domain, e->realdomain, e->uid, e->gid, e->path);
 *     e = get_domain_entries (NULL);
 *   }
 *
 *
 */

domain_entry *get_domain_entries (const char *match_real)
{
        static FILE *fs = NULL;
        static char     match_buffer[MAX_PW_DOMAIN];
        static domain_entry entry;
        static char linebuf[MAX_BUFF];
        char *p;

        if (match_real != NULL) {
                if (fs != NULL) fclose (fs);
                snprintf (linebuf, sizeof (linebuf), "%s/users/assign", QMAILDIR);
                fs = fopen (linebuf, "r");

                snprintf (match_buffer, sizeof (match_buffer), match_real);
                vget_assign(match_buffer,NULL,0,NULL,NULL);
        }

        if (fs == NULL) {
           verrori = VA_CANNOT_READ_ASSIGN;
           return NULL;
        }

        while (fgets (linebuf, sizeof (linebuf), fs) != NULL) {
                /* ignore non-domain entries */
                if (*linebuf != '+') continue;

                entry.domain = strtok (linebuf + 1, ":");
                if (entry.domain == NULL) continue;

                /* ignore entries without '.' in them */
                if (strchr (entry.domain, '.') == NULL) continue;

                entry.realdomain = strtok (NULL, ":");
                if (entry.realdomain == NULL) continue;

                /* remove trailing '-' from entry.domain */
		if (entry.realdomain <= entry.domain + 2 ||
		    *(entry.realdomain-2) != '-') continue;
                *(entry.realdomain-2) = '\0';

                if ((p = strtok (NULL, ":")) == NULL) continue;
                entry.uid = atoi (p);

                if ((p = strtok (NULL, ":")) == NULL) continue;
                entry.gid = atoi (p);

                entry.path = strtok (NULL, ":");
                if (entry.path == NULL) continue;

                if (!*match_buffer || (strcmp (match_buffer, entry.realdomain) == 0))
                        return &entry;
        }

        /* reached end of file, so we're done */
        fclose (fs);
        fs=NULL;
        return NULL;
}

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

/*
 * Add a virtual domain user
 */
int vadduser( char *username, char *domain, char *password, char *gecos, 
              int apop )
{
 char Dir[MAX_BUFF];
 char *user_hash;
 char calling_dir [MAX_BUFF];
 uid_t uid = VPOPMAILUID;
 gid_t gid = VPOPMAILGID;
 struct vlimits limits;
 char quota[50];

#ifdef ONCHANGE_SCRIPT
 int temp_onchange;
    temp_onchange = allow_onchange;
    allow_onchange = 0;
#endif

  /* check gecos for : characters - bad */
  if ( strchr(gecos,':')!=0) return(VA_BAD_CHAR);

  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(domain) < 3) return(VA_INVALID_DOMAIN_NAME);

  if ( strlen(password) > MAX_PW_CLEAR_PASSWD ) return(VA_PASSWD_TOO_LONG);
  if ( strlen(gecos) > MAX_PW_GECOS )    return(VA_GECOS_TOO_LONG);

  umask(VPOPMAIL_UMASK);
  lowerit(username);
  lowerit(domain);

  if ( is_username_valid(username) != 0 ) return(VA_ILLEGAL_USERNAME);
  if ( is_domain_valid(domain) != 0 ) return(VA_INVALID_DOMAIN_NAME);

  if ( vauth_getpw( username, domain ) != NULL ) return(VA_USERNAME_EXISTS);

  /* record the dir where the vadduser command was run from */
  getcwd(calling_dir, sizeof(calling_dir));

  /* lookup the home dir, uid and gid for the domain */
  if ( vget_assign(domain, Dir, sizeof(Dir), &uid, &gid)==NULL) {
    return(VA_DOMAIN_DOES_NOT_EXIST);
  }

  /* make sure we can load domain limits for default quota */
  if (vget_limits(domain, &limits) != 0) {
    return(VA_CANNOT_READ_LIMITS);
  }

  /* go to the domain's home dir (ie test it exists) */
  /* would a stat be a better option here? */
  if ( chdir(Dir) != 0 ) {
    return(VA_BAD_D_DIR);
  }

  /* create dir for the the user */ 
  if ( (user_hash=make_user_dir(username, domain, uid, gid)) == NULL ) {
    chdir(calling_dir);
    if (verrori != 0 ) return(verrori);
    else return(VA_BAD_U_DIR);
  }
        
  /* add the user to the auth backend */
  /* NOTE: We really need to update this method to include the quota. */
  if (vauth_adduser(username, domain, password, gecos, user_hash, apop )!=0) {
    fprintf(stderr, "Failed while attempting to add user to auth backend\n");
    /* back out of changes made so far */
    chdir(Dir); if (strlen(user_hash)>0) { chdir(user_hash);} vdelfiles(username);
    chdir(calling_dir);
    return(VA_NO_AUTH_CONNECTION);
  }

  if (limits.defaultquota > 0) {
    if (limits.defaultmaxmsgcount > 0)
      snprintf (quota, sizeof(quota), "%dS,%dC", limits.defaultquota,
        limits.defaultmaxmsgcount);
    else
      snprintf (quota, sizeof(quota), "%dS", limits.defaultquota);
  } else {
    if (limits.defaultmaxmsgcount > 0)
      snprintf (quota, sizeof(quota), "%dC", limits.defaultmaxmsgcount);
    else
      strcpy (quota, "NOQUOTA");
  }

  if (vsetuserquota (username, domain, quota) == VA_USER_DOES_NOT_EXIST) {
    /* server with replication, need to wait and try again */
    sleep(5);
    vsetuserquota (username, domain, quota);
  }

#ifdef SQWEBMAIL_PASS
  {
   /* create the sqwebmail-pass file in the user's maildir
    * This file contains a copy of the user's crypted password
    */
    struct vqpasswd *mypw;
    mypw = vauth_getpw( username, domain);
    if ( mypw != NULL ) { 
      vsqwebmail_pass( mypw->pw_dir, mypw->pw_passwd, uid, gid);
    }
  }
#endif

#ifdef ENABLE_AUTH_LOGGING
  if (vset_lastauth(username,domain,NULL_REMOTE_IP) !=0) {
    /* should we back out of all the work we have done so far? */
    chdir(calling_dir);
    fprintf (stderr, "Failed to create create lastauth entry\n");
    return (VA_NO_AUTH_CONNECTION);
  }
#endif

  /* jump back into the dir from which the vadduser was run */
  chdir(calling_dir);

#ifdef ONCHANGE_SCRIPT
  allow_onchange = temp_onchange;
  /* tell other programs that data has changed */
  snprintf ( onchange_buf, MAX_BUFF, "%s@%s", username, domain );
  call_onchange ( "add_user" );
  allow_onchange = 1;
#endif

  return(VA_SUCCESS);
}

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

char randltr(void)
{
  static const char saltchar[] =
    "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

  return saltchar[(rand() % 64)];
}

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

/*
 * encrypt a password 
 * Input
 * clearpass = pointer to clear text password
 * ssize     = size of the crypted pointer buffer
 * 
 * Output
 *  copies the encrypted password into the crypted 
 *      character pointer
 * 
 * Return code:
 *   VA_CRYPT_FAILED = encryption failed
 *   VA_SUCCESS = 0  = encryption success
 * 
 */
int mkpasswd3( char *clearpass, char *crypted, int ssize )
{
 char *tmpstr;
 char salt[12];
 static int seeded = 0;

 if (!seeded) {
   seeded = 1;
   srand (time(NULL)^(getpid()<<15));
 }

#ifdef MD5_PASSWORDS
  salt[0] = '$';
  salt[1] = '1';
  salt[2] = '$';
  salt[3] = randltr();
  salt[4] = randltr();
  salt[5] = randltr();
  salt[6] = randltr();
  salt[7] = randltr();
  salt[8] = randltr();
  salt[9] = randltr();
  salt[10] = randltr();
  salt[11] = 0;
#else
  salt[0] = randltr();
  salt[1] = randltr();
  salt[2] = 0;
#endif

  tmpstr = crypt(clearpass,salt);
  if ( tmpstr == NULL ) return(VA_CRYPT_FAILED);

#ifdef MD5_PASSWORDS
  /* Make sure this host's crypt supports MD5 passwords.  If not,
   * fall back on old-style crypt
   */
  if (tmpstr[2] != '$') {
    salt[0] = randltr();
    salt[1] = randltr();
    salt[2] = 0;
    tmpstr = crypt(clearpass,salt);
    if ( tmpstr == NULL ) return(VA_CRYPT_FAILED);
  }
#endif

  strncpy(crypted,tmpstr, ssize);
  return(VA_SUCCESS);
}

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

/* 
 * prompt the command line and get a password twice, that matches 
 */

void vgetpasswd(char *user, char *pass, size_t len)
{
 char pass2[128];
 char prompt[128];

  snprintf( prompt, sizeof(prompt), "Please enter password for %s: ", user);

  while( 1 ) {
    snprintf(pass, len, "%s", getpass(prompt));
    snprintf(pass2, sizeof(pass2), "%s", getpass("enter password again: "));

    if ( strcmp( pass, pass2 ) != 0 ) {
      printf("Passwords do not match, try again\n");
    } else {
	return;
    }
  }
}

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

/* 
 * vdelfiles : delete a directory tree
 *
 * input: directory to start the deletion
 * output: 
 *         0 on success
 *        -1 on failer
 */
int vdelfiles(char *dir)
{
 DIR *mydir;
 struct dirent *mydirent;
 struct stat statbuf;

  /* Modified By David Wartell david@actionwebservices.com to work with 
   * Solaris. Unlike Linux, Solaris will NOT return error when unlink() 
   * is called on a directory.   A correct implementation to support 
   * Linux & Solaris is to test to see if the file is a directory.  
   * If it is not a directory unlink() it. 
   * If unlink() returns an error return error.
   */

  if (lstat(dir, &statbuf) == 0) {

    /* if dir is not a directory unlink it */
    if ( !( S_ISDIR(statbuf.st_mode) ) ) {
      if ( unlink(dir) == 0 ) {
        /* return success we deleted the file */
        return(0);
      } else {
        /* error, return error to calling function, 
         * we couldn't unlink the file 
         */
        return(-1);
      }
    }

  } else {
    /* error, return error to calling function, 
     * we couldn't lstat the file 
     */
    return(-1);
  }

  /* go to the directory, and check for error */ 
  if (chdir(dir) == -1) {
    /* error, return error to calling function */
    return(-1);
  }

  /* open the directory and check for an error */
  if ( (mydir = opendir(".")) == NULL ) {
    /* error, return error */
    fprintf(stderr, "Failed to opendir()");
    return(-1);
  }

  while((mydirent=readdir(mydir))!=NULL){

    /* skip the current directory and the parent directory entries */
    if ( strncmp(mydirent->d_name,".", 2) !=0 &&
         strncmp(mydirent->d_name,"..", 3)!=0 ) {

      /* stat the file to check it's type, I/O expensive */
      stat( mydirent->d_name, &statbuf);

      /* Is the entry a directory? */
      if ( S_ISDIR(statbuf.st_mode) ) {

        /* delete the sub tree, -1 means an error */
        if ( vdelfiles ( mydirent->d_name) == -1 ) {

          /* on error, close the directory stream */
          closedir(mydir);

          /* and return error */
          return(-1);
        }

      /* the entry is not a directory, unlink it to delete */
      } else {

        /* unlink the file and check for error */
        if (unlink(mydirent->d_name) == -1) {

          /* print error message and return and error */
          fprintf (stderr, "Failed to delete directory %s", mydirent->d_name);
          return(-1);
        }
      }
    }
  }
  
  /* close the directory stream, we don't need it anymore */
  closedir(mydir);

  /* go back to the parent directory and check for error */
  if (chdir("..") == -1) {

    /* print error message and return an error */
    fprintf(stderr, "Failed to cd to parent");
    return(-1);
  }

  /* delete the directory, I/O expensive */
  rmdir(dir);

  /* return success */
  return(0);
}

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

/* 
 * Add a domain to all the control files 
 * And signal qmail
 * domain is the domain name
 * dir is the full path to the domain directory
 * uid and gid are the uid/gid to store in the assign file
 */
int add_domain_assign( char *alias_domain, char *real_domain,
                       char *dir, uid_t uid, gid_t gid )
{
 FILE *fs1 = NULL;
 struct stat mystat;
 char tmpstr1[MAX_BUFF];
 char tmpstr2[MAX_BUFF];
 char *aliases[1];
 int aliascount=0;

 aliases[aliascount++]=strdup(alias_domain);

  snprintf(tmpstr1, sizeof(tmpstr1), "%s/users/assign", QMAILDIR);

  /* stat assign file, if it's not there create one */
  if ( stat(tmpstr1,&mystat) != 0 ) {
    /* put a . on one line by itself */
    if ( (fs1 = fopen(tmpstr1, "w+"))==NULL ) {
      fprintf(stderr, "could not open assign file\n");
      return(-1);
    }
    fputs(".\n", fs1);
    fclose(fs1);
  }

  snprintf(tmpstr2, sizeof(tmpstr2), "+%s-:%s:%lu:%lu:%s:-::",
    alias_domain, real_domain, (long unsigned)uid, (long unsigned)gid, dir);

  /* update the file and add the above line and remove duplicates */
  if (update_file(tmpstr1, tmpstr2, 1) !=0 ) {
   fprintf (stderr, "Failed while attempting to update_file() the assign file\n");
   return (-1);
  }

  /* set the mode in case we are running with a strange mask */
  chmod(tmpstr1, VPOPMAIL_QMAIL_MODE ); 

  /* compile the assign file */
  /* as of the 5.4 builds, we always need an updated assign file since
   * we call vget_assign to add the postmaster account.  The correct
   * solution is to cache the information somewhere so vget_assign
   * can pull from cache instead of having to read the assign file.
   */
  /* if ( OptimizeAddDomain == 0 ) */ update_newu();

  /* If we have more than 50 domains in rcpthosts
   * make a morercpthosts and compile it
   */
  if ( count_rcpthosts() >= 50 ) {
    snprintf(tmpstr1, sizeof(tmpstr1), "%s/control/morercpthosts", QMAILDIR);
    if (update_file(tmpstr1, alias_domain, 2) !=0) {
      fprintf (stderr, "Failed while attempting to update_file() the morercpthosts file\n");
      return (-1);
    }
    snprintf(tmpstr1, sizeof(tmpstr1), "%s/control/morercpthosts", QMAILDIR);
    chmod(tmpstr1, VPOPMAIL_QMAIL_MODE ); 

    if ( OptimizeAddDomain == 0 ) compile_morercpthosts();

  /* or just add to rcpthosts */
  } else {
    snprintf(tmpstr1, sizeof(tmpstr1), "%s/control/rcpthosts", QMAILDIR);
    if (update_file(tmpstr1, alias_domain, 2) != 0) {
      fprintf (stderr, "Failed while attempting to update_file() the rcpthosts file\n");
      return (-1);
    }
    snprintf(tmpstr1, sizeof(tmpstr1), "%s/control/rcpthosts", QMAILDIR);
    chmod(tmpstr1, VPOPMAIL_QMAIL_MODE ); 
  }
    
  /* Add to virtualdomains file and remove duplicates  and set mode */
  snprintf(tmpstr1, sizeof(tmpstr1), "%s/control/virtualdomains", QMAILDIR );
  snprintf(tmpstr2, sizeof(tmpstr2), "%s:%s", alias_domain, alias_domain );
  if (update_file(tmpstr1, tmpstr2, 3) !=0 ) {
    fprintf (stderr, "Failed while attempting to update_file() the virtualdomains file\n");
    return (-1);
  };
  chmod(tmpstr1, VPOPMAIL_QMAIL_MODE ); 

  /* make sure it's not in locals and set mode */
  snprintf(tmpstr1, sizeof(tmpstr1), "%s/control/locals", QMAILDIR);
  if (remove_lines( tmpstr1, aliases, aliascount) < 0) {
    fprintf (stderr, "Failure while attempting to remove_lines() the locals file\n");
    return(-1);
  }
  chmod(tmpstr1, VPOPMAIL_QMAIL_MODE ); 

  free( aliases[0] );

  return(0);
}

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

/*
 * delete a domain from the control files
 * the control files consist of :
 * - /var/qmail/control/rcpthosts
 * - /var/qmail/control/virtualdomains
 */
int del_control(char *aliases[MAX_DOM_ALIAS], int aliascount ) 
{
 char tmpbuf1[MAX_BUFF];
 char tmpbuf2[MAX_BUFF];
 struct stat statbuf;
 char *virthosts[MAX_DOM_ALIAS];

 int problem_occurred = 0, i=0;

  /* delete entry from control/rcpthosts (if it is found) */
  snprintf(tmpbuf1, sizeof(tmpbuf1), "%s/control/rcpthosts", QMAILDIR);
  switch ( remove_lines(tmpbuf1, aliases, aliascount) ) {

    case -1 :
      /* error ocurred in remove line */
      fprintf (stderr, "Failed while attempting to remove_lines() the rcpthosts file\n");
      problem_occurred = 1;
      break;

    case 0 :
      /* not found in rcpthosts, so try morercpthosts */
      snprintf(tmpbuf1, sizeof(tmpbuf1), "%s/control/morercpthosts", QMAILDIR);
  
      switch (remove_lines(tmpbuf1, aliases, aliascount) ) {

        case -1 :
          fprintf (stderr, "Failed while attempting to remove_lines() the morercpthosts file\n");
          problem_occurred = 1;
          break; 

        case 0 :
         /* not found in morercpthosts */
          break;

        case 1 :
          /* was removed from morercpthosts */
          if ( stat( tmpbuf1, &statbuf) == 0 ) {
            /* Now check to see if morercpthosts its empty */
            if ( statbuf.st_size == 0 ) {
              /* is empty. So delete it */
              unlink(tmpbuf1);
              /* also delete the morercpthosts.cdb */
              strncat(tmpbuf1, ".cdb", sizeof(tmpbuf1)-strlen(tmpbuf1)-1);
              unlink(tmpbuf1);
            } else {
              /* morercpthosts is not empty, so compile it */
              compile_morercpthosts();
              /* make sure correct permissions are set on morercpthosts */
              chmod(tmpbuf1, VPOPMAIL_QMAIL_MODE ); 
            }
          }
          break; 

      } /* switch for morercpthosts */ 
      break;

    case 1 : /* we removed the line successfully */
      /* make sure correct permissions are set on rcpthosts */
      chmod(tmpbuf1, VPOPMAIL_QMAIL_MODE );
      break; 
  } /* switch for rcpthosts */

  /* delete entry from control/virtualdomains (if it exists) */
  
  for(i=0;i<aliascount;i++) {
    snprintf(tmpbuf1, sizeof(tmpbuf1), "%s:%s", aliases[i], aliases[i]);
    virthosts[i]=strdup(tmpbuf1);
  }

  snprintf(tmpbuf2, sizeof(tmpbuf2), "%s/control/virtualdomains", QMAILDIR);
  if (remove_lines( tmpbuf2, virthosts, aliascount) < 0 ) {
    fprintf(stderr, "Failed while attempting to remove_lines() the virtualdomains file\n"); 
    problem_occurred = 1; 
  }

  for(i=0;i<aliascount;i++) {
    free( virthosts[i] );
  }

  /* make sure correct permissions are set on virtualdomains */
  chmod(tmpbuf2, VPOPMAIL_QMAIL_MODE ); 
  
  if (problem_occurred == 1) {
    return (-1);
  } else { 
    return(0);
  }
}

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

/*
 * delete a domain from the users/assign file
 * input : lots;)
 *
 * output : 0 = success no aliases
 *          less than error = failure
 *          greater than 0 = number of aliases deleted
 *
 */
int del_domain_assign( char *aliases[MAX_DOM_ALIAS], int aliascount, 
                       char *real_domain, 
                       char *dir, gid_t uid, gid_t gid )  
{
 char search_string[MAX_BUFF];
 char assign_file[MAX_BUFF];
 char *virthosts[MAX_DOM_ALIAS];
 int i;

  /* format the removal string */ 
  for(i=0;i<aliascount;i++) {
  snprintf(search_string, sizeof(search_string), "+%s-:%s:%lu:%lu:%s:-::",
      aliases[i], real_domain, (long unsigned)uid, (long unsigned)gid, dir);
    virthosts[i] = strdup( search_string );
  }

  /* format the assign file name */
  snprintf(assign_file, sizeof(assign_file), "%s/users/assign", QMAILDIR);

  /* remove the formatted string from the file */
  if (remove_lines( assign_file, virthosts, aliascount ) < 0) {
    fprintf(stderr, "Failed while attempting to remove_lines() the assign file\n");
    return (-1);
  }

  /* force the permission on the file */
  chmod(assign_file, VPOPMAIL_QMAIL_MODE ); 

  /* compile assign file */
  update_newu();

  vget_assign(NULL, NULL, 0, NULL, NULL);  //  clear cache
  return(0);
}

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

/*
 * Generic remove a line from a file utility
 * input: template to search for
 *        file to search inside
 *
 * output: -1 on failure
 *          0 on success, no match found
 *          1 on success, match was found
 */
int remove_lines( char *filename, char *aliases[MAX_DOM_ALIAS], int aliascount )
{
 FILE *fs = NULL;
 FILE *fs1 = NULL;
#ifdef FILE_LOCKING
 int fd3 = 0;
#endif
 char tmpbuf1[MAX_BUFF];
 char tmpbuf2[MAX_BUFF];
 int i, count=0, removed=0, doit=0;

//  fprintf( stderr, "\n***************************************\n" 
//                      "remove lines - file: %s\n", filename );
//  for(i=0;i<aliascount;i++) {
//    fprintf( stderr,  "               line: %s\n", aliases[i] );
//  }


#ifdef FILE_LOCKING
  snprintf(tmpbuf1, sizeof(tmpbuf1), "%s.lock", filename);
  if ( (fd3 = open(tmpbuf1, O_WRONLY | O_CREAT, S_IRUSR|S_IWUSR)) < 0 ) {
    fprintf(stderr, "could not open lock file %s\n", tmpbuf1);
    return(VA_COULD_NOT_UPDATE_FILE);
  }

  if ( get_write_lock(fd3) < 0 ) return(-1);
#endif

  snprintf(tmpbuf1, sizeof(tmpbuf1), "%s.%lu", filename, (long unsigned)getpid());
  fs1 = fopen(tmpbuf1, "w+");
  if ( fs1 == NULL ) {
#ifdef FILE_LOCKING
    unlock_lock(fd3, 0, SEEK_SET, 0);
    close(fd3);
#endif
    return(VA_COULD_NOT_UPDATE_FILE);
  }

  snprintf(tmpbuf1, sizeof(tmpbuf1), "%s", filename);
  if ( (fs = fopen(tmpbuf1, "r")) == NULL ) {
    if (errno != ENOENT)
	return VA_COULD_NOT_UPDATE_FILE;
    if ( (fs = fopen(tmpbuf1, "w+")) == NULL ) {
      fclose(fs1);
#ifdef FILE_LOCKING
      close(fd3);
      unlock_lock(fd3, 0, SEEK_SET, 0);
#endif
      return(VA_COULD_NOT_UPDATE_FILE);
    }
  }

  while( fgets(tmpbuf1,sizeof(tmpbuf1),fs) != NULL ) {
    count++;

    //  Trim \n off end of line.
    for(i=0;tmpbuf1[i]!=0;++i) {
      if (tmpbuf1[i]=='\n') {
        tmpbuf1[i]=0;
	break;
      }
    }

//    fprintf( stderr, "   Entry: %s\n", tmpbuf1 );

    doit=1;
    for(i=0;i<aliascount;i++) {
      if( 0 == strcmp(tmpbuf1,aliases[i])) {
        doit=0;
//        fprintf( stderr, "      ***  DELETE  ***\n");
	break;
        }
      }    
    if( doit ) {
      fprintf(fs1, "%s\n", tmpbuf1);
    } else {
      removed++;
    }
  }

  fclose(fs);
  fclose(fs1);

  snprintf(tmpbuf1, sizeof(tmpbuf1), "%s", filename);
  snprintf(tmpbuf2, sizeof(tmpbuf2), "%s.%lu", filename, (long unsigned)getpid());

  rename(tmpbuf2, tmpbuf1);

#ifdef FILE_LOCKING
  unlock_lock(fd3, 0, SEEK_SET, 0);
  close(fd3);
#endif

  return(removed);
}

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

/* 
 * Recursive change ownership utility 
 */
int r_chown(char *path, uid_t owner, gid_t group )
{
 DIR *mydir;
 struct dirent *mydirent;
 struct stat statbuf;

  chown(path,owner,group);
  if (chdir(path) == -1) {
    fprintf(stderr, "r_chown() : Failed to cd to directory %s", path);
    return(-1);
  }
  mydir = opendir(".");
  if ( mydir == NULL ) { 
    fprintf(stderr, "r_chown() : Failed to opendir()");
    return(-1);
  }

  while((mydirent=readdir(mydir))!=NULL){
    if ( strncmp(mydirent->d_name,".", 2)!=0 && 
         strncmp(mydirent->d_name,"..", 3)!=0 ) {
      stat( mydirent->d_name, &statbuf);
      if ( S_ISDIR(statbuf.st_mode) ) {
        r_chown( mydirent->d_name, owner, group);
      } else {
        chown(mydirent->d_name,owner,group);
      }
    }
  }
  closedir(mydir);
  if (chdir("..") == -1) {
    fprintf(stderr, "rchown() : Failed to cd to parent");
    return(-1);
  }
  return(0);
}

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

/* 
 * Send a signal to a process utility function
 *
 * name    = name of process
 * sig_num = signal number 
 */
int signal_process(char *name, int sig_num)
{
 FILE *ps;
 char *tmpstr;
 int  col;
 pid_t tmppid;
 pid_t mypid;
 int  pid_col=0;
 char pid[10];
 char tmpbuf1[1024];

  mypid = getpid();

  if ( (ps = popen(PS_COMMAND, "r")) == NULL ) {
    perror("popen on ps command");
    return(-1);
  }

  if (fgets(tmpbuf1, sizeof(tmpbuf1), ps)!= NULL ) {
    col=0;
    tmpstr = strtok(tmpbuf1, PS_TOKENS);
    while (tmpstr != NULL ) {
      if (strcmp(tmpstr, "PID") == 0 ) pid_col = col;

      tmpstr = strtok(NULL, PS_TOKENS);
      ++col;
    }
  }

  while (fgets(tmpbuf1, sizeof(tmpbuf1), ps)!= NULL ) {
    if ( strstr( tmpbuf1, name ) != NULL && 
         strstr(tmpbuf1, "supervise") == NULL &&
         strstr(tmpbuf1, "multilog") == NULL &&
         strstr(tmpbuf1, "svscan") == NULL) {
      tmpstr = strtok(tmpbuf1, PS_TOKENS);
      col = 0;
      do {
        if( col == pid_col ) {
          snprintf(pid, sizeof(pid), "%s", tmpstr);
          break;
        } 
        ++col;
        tmpstr = strtok(NULL, PS_TOKENS);
      } while ( tmpstr!=NULL );
      tmppid = atoi(pid);
      if ( tmppid && (tmppid != mypid) ) { 
        kill(tmppid,sig_num);
      }
    }
  }
  pclose(ps);
  return(0);
}

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

/*
 * Compile the users/assign file using qmail-newu program
 */
int update_newu()
{
 int pid;

  pid=vfork();
  if ( pid==0){
    execl(QMAILNEWU,"qmail-newu", NULL);
    exit(127);
  } else {
    waitpid(pid,&pid,0);
  }
  return(0);
}

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

/*
 * parse out user and domain from an email address utility function
 * 
 * email  = input email address
 * user   = parsed user
 * domain = parsed domain
 * buff_size = the size of the user and domain buffer. 
 *             These need to be the same size or potential buffer overflows
 *             could occur!
 * 
 * return 0 on success
 *       -1 on error
 */
int parse_email(char *email, char *user, char *domain, int buff_size ) 
{
 int i;
 int n;
 int len;
 char *at = NULL;

  lowerit(email);

  len = strlen(ATCHARS);
  for(i=0;i<len; ++i ) if ((at=strchr(email,ATCHARS[i]))) break;

  /* did we find an "AT" char in the email address? */
  if ( at!=NULL ) {
    /* yep we found an AT char */
    /* work out what pos it is in the email address array, store this in n */
    n = at - email + 1;
    if ( n > buff_size ) n = buff_size;
    /* suck out the username */
    snprintf(user, n, "%s", email); 
    /* now suck out the domain name */
    snprintf(domain, buff_size, "%s", ++at);
  } else {
    /* No AT char found, so populate username, leave domain blank */
    snprintf(user, buff_size, "%s", email);
    domain[0] = 0;
  }

  /* check the username for any invalid chars */
  if ( is_username_valid( user ) != 0 ) {
    fprintf(stderr, "user invalid %s\n", user);
    return(-1);
  }

  /* check the domain for any invalid chars */
  if ( is_domain_valid( domain ) != 0 ) {
    fprintf(stderr, "domain invalid %s\n", domain);
    return(-1);
  }

  /* if we havent found a domain, try and set it to the the default domain */
  vset_default_domain(domain);

  return(0);
} 

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

/*
 * update a users virtual password file entry with a different password
 */
int vpasswd( char *username, char *domain, char *password, int apop )
{
 struct vqpasswd *mypw;
 char Crypted[MAX_BUFF];
#ifdef SQWEBMAIL_PASS
 uid_t uid;
 gid_t gid;
#endif

  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(password) > MAX_PW_CLEAR_PASSWD ) return(VA_PASSWD_TOO_LONG);

  lowerit(username);
  lowerit(domain);

  /* get the password entry for this user */
  mypw = vauth_getpw( username, domain);
  if ( mypw == NULL ) return(-1); 

  /* dont update password, if password updates are disabled */
  if ( mypw->pw_flags & NO_PASSWD_CHNG ) return(-1);

  /* encrypt their supplied password, and save it */
  mkpasswd3(password,Crypted, sizeof(Crypted));
  mypw->pw_passwd = Crypted;

#ifdef CLEAR_PASS
  /* save the clear password too (if clear passwords are enabled) */
  mypw->pw_clear_passwd = password;
#endif

#ifdef SQWEBMAIL_PASS
  /* update the sqwebmail-pass file in the user's maildir (if required) */
  vget_assign(domain, NULL, 0, &uid, &gid );
  vsqwebmail_pass( mypw->pw_dir, Crypted, uid, gid);
#endif
  return (vauth_setpw( mypw, domain));
}

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

void trim( char *s )  {

//  trim spaces and tabs from beginning
int i=0, j, k;

while(( s[i]==' ')||(s[i]=='\t')) {
   i++;
   }

k = strlen(s) - i; 

if( i>0 ) {
   for( j=0; j<k; j++ )  {
      s[j] = s[j+i];
      }

   s[j] = '\0';
   }

//  trim spaces and tabs from end
i = strlen(s) - 1;
while(i >= 0 && ( s[i] == ' ' ) || ( s[i] == '\t' )) {
   i--;
   }

if( i < strlen(s) - 1 ) {
   s[i+1] = '\0';
   }
}



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


int isCatchall( char *user, char *domain, char *dir )   {

 //  This might not be the easiest way to do this...

 char *default_action;
 char *position;
 char *name;
 char email[MAX_BUFF];
 int i, pos;

  //  get the first line of the .qmail-default file
  snprintf( email, MAX_BUFF, "default" );
  default_action = valias_select( email, domain );

  snprintf( email, MAX_BUFF, "%s@%s", user, domain );
//  fprintf( stderr, "email: %s  default action: %s\ndir: %s\n", email, default_action, dir );

  //  Make sure .qmail_default file contains a reference to vdelivermail
  if( ( position = strstr( default_action, "vdelivermail" )) == NULL ) {
//    fprintf( stderr, ".qmail_default file does not include vdelivermail. %s\n", position );
    return 0;
    }

  //  Make sure .qmail_default file continues with ''
  if( ( position = strstr( default_action, "''" )) == NULL  ) {
//    fprintf( stderr, ".qmail_default file missing ''. %s\n", position );
    return 0;
    }

  //  Make sure there is a space after ''
  if( ( position = strstr( position, " " )) == NULL  ) {
//    fprintf( stderr, ".qmail_default does not have space after ''. %s\n", position );
    return 0;
    }

  //  Remove spaces / tabs
  trim( position );

//  fprintf( stderr, "Default action for non-existant addresses: |%s|\n", position );

  if( strstr( position, "bounce-no-mailbox" ) != NULL )  {
    //  don't do anything for this default action
//    fprintf( stderr, "Default is Bounce No Mailbox\n" );
    }

  else if( strstr( position, "delete-no-mailbox" ) != NULL ) {
    //  don't do anything for this default action
    //fprintf( stderr, "Default is Delete No Mailbox\n" );
    }

  else if( '/' == position[0] ) {
    //  it is a Maildir
//    fprintf( stderr, "Maildir - %s\n", position );
    if( strstr( position, dir ) != NULL ) {
//      fprintf( stderr, "part of correct domain sub directory\n" );
      position = strrchr( position, '/' );
//      fprintf( stderr, "position: %s\n", position );
      for( i=0; i<strlen( position )-1; i++ ) {
        position[i] = position[i+1];
        }
      position[i] = 0;

//      fprintf( stderr, "position: %s\n", position );

      if( strcmp( user, position ) == 0 ) {
//        fprintf( stderr, "%s is the catchall account by path\n", position );
        return( 1 );
        }
      }
    }

  else if(( pos = strcspn( position, "@" ))) {
    //  it is a forward
    name = strtok( position, "@" );
    position = strtok( NULL, "@" );
//    fprintf( stderr, "Forward - name: %s  domain: %s\n", name, position );
    if( ( strcmp( user, name ) == 0 ) && ( strcmp( position, domain ) == 0 ) ) {
//      fprintf( stderr, "%s is the catchall account by forward", position );
      return( 1 );
      }
    }

  else {
    fprintf( stderr, "unknown .qmail-default contents %s\n", position );
    }

return 0;

}


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

/*
 * delete a user from a virtual domain password file
 */
int vdeluser( char *user, char *domain )
{
 struct vqpasswd *mypw;
 char Dir[MAX_BUFF];
 uid_t uid;
 gid_t gid;
 char calling_dir[MAX_BUFF];

  if ( user == 0 || strlen(user)<=0) return(VA_ILLEGAL_USERNAME);

  /* Michael Bowe 23rd August 2003 
   * should we do a vset_default_domain(domain) here?
   * This function is called by vdeluser.c which will ensure
   * domain is set. But what if this function is called from
   * somewhere else and is passed with a null domain?
   * Should we display en error (which is what will happen when
   * vget_assign runs below.
   *
   * Rick Widmer 4 May 2004
   * No don't use a default domain, if the domain is empty 
   * or bad, complain and exit.  It should not do any I/O, 
   * just return an error state.
   *
   * Also check the catchall account, if delete user is catchall
   * refuse to delete the user.  Error message should suggest
   * changing the catchall settings first.
   *
   */

  umask(VPOPMAIL_UMASK);

  lowerit(user);
  lowerit(domain);

  /* backup the dir where the vdeluser was run from */
  getcwd(calling_dir, sizeof(calling_dir));

  /* lookup the location of this domain's directory */
  if ( vget_assign(domain, Dir, sizeof(Dir), &uid, &gid ) == NULL ) {
    return(VA_DOMAIN_DOES_NOT_EXIST);
  }

  /* change into that directory */
  if ( chdir(Dir) != 0 ) {
    chdir(calling_dir);
    return(VA_BAD_D_DIR);
  }

  /* see if the user exists in the authentication system */
  if ((mypw = vauth_getpw(user, domain)) == NULL) { 
    return(VA_USER_DOES_NOT_EXIST);
  }

  /* Make sure we are not the email address of the catchall account */
  if ( isCatchall( user, domain, Dir )) {
    return(VA_CANNOT_DELETE_CATCHALL);
    }

#ifdef ONCHANGE_SCRIPT
  /* tell other programs that data has changed */
  snprintf ( onchange_buf, MAX_BUFF, "%s@%s", user, domain );
  call_onchange ( "del_user" );
#endif

  /* del the user from the auth system */
  if (vauth_deluser( user, domain ) !=0 ) {
    fprintf (stderr, "Failed to delete user from auth backend\n");
    chdir(calling_dir);
    return (-1);
  }

  dec_dir_control(domain, uid, gid);

  /* remove the user's directory from the file system 
   * and check for error
   */
  if ( vdelfiles(mypw->pw_dir) != 0 ) {
    fprintf(stderr, "could not remove %s\n", mypw->pw_dir);
    chdir(calling_dir);
    return(VA_BAD_DIR);
  }

  /* go back to the callers directory */
  chdir(calling_dir);
  return(VA_SUCCESS);

}

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

/*
 * make all characters in a string be lower case
 */
void lowerit(char *instr )
{
 int size;

  if (instr==NULL) return;
  for(size=0;*instr!=0;++instr,++size ) {
    if (isupper((int)*instr)) *instr = tolower(*instr);
    
    /* Michael Bowe 23rd August 2003
     * this looks like a bit of a kludge...
     * how can we improve on it?
     */

    /* add alittle size protection */
    if ( size == 156 ) {
      *instr = 0;
      return;
    }
  } 
}

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

int extract_domain(char *domain, char *update_line, int file_type )
{
int i,j;
char *parts[10];
char *t, *u;
char tmpbuf[MAX_BUFF];


//  fprintf( stderr, "extract_domain - line: %s\n", update_line );

  i=0;

  //  If users/assign - need to start at first character
  if( 1 == file_type ) {
    j=1;
  } else {
    j=0;
  }

  //  Chop our string off at the first :
  while( j < MAX_BUFF && 
         0 != update_line[j] && 
         ':' != update_line[j] ) {
     domain[i++] = update_line[j++];  
     }

  //  If users/assign - need to delete last character
  if( 1 == file_type ) {
    if (i > 0)
      domain[--i] = 0;
  } else {
    domain[i] = 0;
  }

//  fprintf( stderr, "extract_domain - result: %s\n", domain );


  //  Take the domain name string apart on '.'s.
  i=0;
  strcpy(tmpbuf, domain);

  t = strtok( tmpbuf, "." );
  while( t && i < 10 ) {
    parts[i++] = t;
    t = strtok( NULL, "." );
  }

  //  Get a look at the array before shuffle
//  for(j=0;j<i;j++) {
//    fprintf( stderr, "extract_domain - i: %d part: %s\n", j, parts[j] );
//  }
  if( i > 1 )  {
    //  Juggle the order of stuff in the domain name

    //  Save the last two terms
    t = parts[--i];
    u = parts[--i];

    //  Make room for two elements at the beginning of the name
    for(j=0;j<i;j++) {
      parts[j+2]=parts[j];
    }

    //  Put the parts you saved back in the beginning of the domain name
#ifdef SORTTLD
    parts[0] = t;
    parts[1] = u;
#else
    parts[0] = u;
    parts[1] = t;
#endif

    i=i+2;

    //  Clean out the domain variable
    memset(domain, 0, sizeof(domain));

    //  Get one last look at the array before assembling it
//    for(j=0;j<i;j++) {
//      fprintf( stderr, "extract_domain - modified i: %d part: %s\n", 
//               j, parts[j] );
//    }
  
    //  Copy the first term into the domain name
    strcpy(domain, parts[0] );

    //  Copy the rest of the terms into the domain name
    for(j=1;j<i;j++) {
      strncat( domain, ".", MAX_BUFF );
      strncat( domain, parts[j], MAX_BUFF );
    }
  
  }

//  fprintf( stderr, "extract_domain - final result: %s\n", domain );

return 0;
}


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

int sort_check(const void *a, const void *b )
//int sort_check(const sortrec *a, const sortrec *b )
{
   
return( strncmp( ((sortrec *)(a))->key, ((sortrec *)(b))->key, MAX_BUFF));
//return( strncmp( a->key, b->key, MAX_BUFF));

}

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

/*
 *
 * Note:  sortdata needs to be dynamically allocated based on the
 * number of entries specified in file_lines.
 *
 */

int sort_file(char *filename, int file_lines, int file_type )
{
 FILE *fs = NULL;
 FILE *fs1 = NULL;
#ifdef FILE_LOCKING
 int fd3 = 0;
#endif
 char tmpbuf1[MAX_BUFF];
 char tmpbuf2[MAX_BUFF];
 int i, count=0;
 char cur_domain[MAX_BUFF];

 sortrec *sortdata = NULL;

//  fprintf( stderr, "\n***************************************\n" 
//                   "sort_file: %s\n", filename );

#ifdef FILE_LOCKING
  snprintf(tmpbuf1, sizeof(tmpbuf1), "%s.lock", filename);
  if ( (fd3 = open(tmpbuf1, O_WRONLY | O_CREAT, S_IRUSR|S_IWUSR)) < 0 ) {
    fprintf(stderr, "could not open lock file %s\n", tmpbuf1);
    return(VA_COULD_NOT_UPDATE_FILE);
  }

  if ( get_write_lock(fd3) < 0 ) return(-1);
#endif

  snprintf(tmpbuf1, sizeof(tmpbuf1), "%s.%lu", filename, (long unsigned)getpid());
  fs1 = fopen(tmpbuf1, "w+");
  if ( fs1 == NULL ) {
#ifdef FILE_LOCKING
    unlock_lock(fd3, 0, SEEK_SET, 0);
    close(fd3);
#endif
    return(VA_COULD_NOT_UPDATE_FILE);
  }

  snprintf(tmpbuf1, sizeof(tmpbuf1), "%s", filename);
  if ( (fs = fopen(tmpbuf1, "r+")) == NULL ) {
    if ( (fs = fopen(tmpbuf1, "w+")) == NULL ) {
      fclose(fs1);
#ifdef FILE_LOCKING
      unlock_lock(fd3, 0, SEEK_SET, 0);
      close(fd3);
#endif
      return(VA_COULD_NOT_UPDATE_FILE);
    }
  }

  sortdata = malloc(  file_lines * sizeof( sortrec ));
  if (sortdata == NULL) {
    fclose(fs);
    fclose(fs1);
#ifdef FILE_LOCKING
    unlock_lock(fd3, 0, SEEK_SET, 0);
    close(fd3);
#endif
    return(VA_MEMORY_ALLOC_ERR);
  }

  while( fgets(tmpbuf1,sizeof(tmpbuf1),fs) != NULL ) {

    //  Trim \n off end of line.
    for(i=0;tmpbuf1[i]!=0;++i) {
      if (tmpbuf1[i]=='\n') {
        tmpbuf1[i]=0;
	break;
      }
    }

    //  Don't paint the last line of users/assign from the file
    if ( 1 == file_type && strncmp(tmpbuf1, ".", sizeof(tmpbuf1)) == 0 ) {
      continue;
    }

//    fprintf( stderr, "   Entry: %s\n", tmpbuf1 );

    // A new entry; is the allocated memory enough?
    if (count == file_lines) {
      fclose(fs);
      fclose(fs1);
#ifdef FILE_LOCKING
      unlock_lock(fd3, 0, SEEK_SET, 0);
      close(fd3);
#endif
      for (i = 0; i < count; i++) {
	free( sortdata[i].key );
	free( sortdata[i].value );
      }
      free( sortdata );
      return(VA_MEMORY_ALLOC_ERR);
    }

    extract_domain( cur_domain, tmpbuf1, file_type );

    sortdata[count].key = strdup( cur_domain );
    sortdata[count++].value = strdup( tmpbuf1 );
  }

//  fprintf( stderr, "\nSorting...\n\n" );
  qsort(sortdata, count, sizeof( sortrec ), sort_check);
//  fprintf( stderr, "\nSort done.\n\n" );

  for(i=0;i<count;i++) {
//    fprintf( stderr, "   Entry: %s\n", sortdata[i].value );
    fprintf(fs1, "%s\n", sortdata[i].value);
  }   

  //  Now we print the period line to users/assign, if needed
  if( 1 == file_type ) {
    fprintf(fs1, ".\n");
  }

  fclose(fs);
  fclose(fs1);

  snprintf(tmpbuf1, sizeof(tmpbuf1), "%s", filename);
  snprintf(tmpbuf2, sizeof(tmpbuf2), "%s.%lu", filename, (long unsigned)getpid());

  rename(tmpbuf2, tmpbuf1);

#ifdef FILE_LOCKING
  unlock_lock(fd3, 0, SEEK_SET, 0);
  close(fd3);
#endif

  for (i = 0; i < count; i++) {
    free( sortdata[i].key );
    free( sortdata[i].value );
  }
  free( sortdata );

  return(0);
}

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

int update_file(char *filename, char *update_line, int file_type )
{
 FILE *fs = NULL;
 FILE *fs1 = NULL;
#ifdef FILE_LOCKING
 int fd3 = 0;
#endif
 char tmpbuf1[MAX_BUFF];
 char tmpbuf2[MAX_BUFF];
 int i, x=0;
 char new_domain[MAX_BUFF];
 char cur_domain[MAX_BUFF];
 char prv_domain[MAX_BUFF];
 int hit=0, count=0, needsort = 0;

//  fprintf( stderr, "\n***************************************\n" 
//                   "update_file - line: %s\n", update_line );

  extract_domain( new_domain, update_line, file_type );
  strcpy(prv_domain, "");

#ifdef FILE_LOCKING
  snprintf(tmpbuf1, sizeof(tmpbuf1), "%s.lock", filename);
  if ( (fd3 = open(tmpbuf1, O_WRONLY | O_CREAT, S_IRUSR|S_IWUSR)) < 0 ) {
    fprintf(stderr, "could not open lock file %s\n", tmpbuf1);
    return(VA_COULD_NOT_UPDATE_FILE);
  }

  if ( get_write_lock(fd3) < 0 ) return(-1);
#endif

  snprintf(tmpbuf1, sizeof(tmpbuf1), "%s.%lu", filename, (long unsigned)getpid());
  fs1 = fopen(tmpbuf1, "w+");
  if ( fs1 == NULL ) {
#ifdef FILE_LOCKING
    unlock_lock(fd3, 0, SEEK_SET, 0);
    close(fd3);
    return(VA_COULD_NOT_UPDATE_FILE);
#endif
  }

  snprintf(tmpbuf1, sizeof(tmpbuf1), "%s", filename);
  if ( (fs = fopen(tmpbuf1, "r+")) == NULL ) {
    if ( (fs = fopen(tmpbuf1, "w+")) == NULL ) {
      fclose(fs1);
#ifdef FILE_LOCKING
      close(fd3);
      unlock_lock(fd3, 0, SEEK_SET, 0);
#endif
      return(VA_COULD_NOT_UPDATE_FILE);
    }
  }

  while( fgets(tmpbuf1,sizeof(tmpbuf1),fs) != NULL ) {
    count++;

    //  Trim \n off end of line.
    for(i=0;tmpbuf1[i]!=0;++i) {
      if (tmpbuf1[i]=='\n') {
        tmpbuf1[i]=0;
	break;
      }
    }

    //  Don't paint the last line of users/assign from the file
    if ( 1 == file_type && strncmp(tmpbuf1, ".", sizeof(tmpbuf1)) == 0 ) {
      continue;
    }

//    fprintf( stderr, "   Entry: %s\n", tmpbuf1 );

    extract_domain( cur_domain, tmpbuf1, file_type );
   
    if( 0 == hit && ( x=strncmp(cur_domain, new_domain, MAX_BUFF)) > 0  ) {
//      fprintf( stderr, "HIT!\n" );
      hit=1;
      fprintf(fs1, "%s\n", update_line);
    }

//    fprintf( stderr, "UpdateUsers - cur_domain: %s new_domain: %s x: %i\n", 
//             cur_domain, new_domain, x );

    if( ( x=strncmp(prv_domain, cur_domain, MAX_BUFF)) > 0  ) {
//      fprintf( stderr, "%s entry is out of order: %s  -- will sort file\n", filename, cur_domain );
      needsort=1;
    }

//    fprintf( stderr, "Chk order - prv: %s cur: %s x: %i\n", 
//             prv_domain, cur_domain, x );

    strcpy(prv_domain, cur_domain);

    fprintf(fs1, "%s\n", tmpbuf1);
  }

  if( 0 == hit ) {
//    fprintf( stderr, "Add at end\n" );
    fprintf(fs1, "%s\n", update_line);
    }

  //  Now we print the period line to users/assign, if needed
  if( 1 == file_type ) {
    fprintf(fs1, ".\n");
  }

  fclose(fs);
  fclose(fs1);

  snprintf(tmpbuf1, sizeof(tmpbuf1), "%s", filename);
  snprintf(tmpbuf2, sizeof(tmpbuf2), "%s.%lu", filename, (long unsigned)getpid());

  rename(tmpbuf2, tmpbuf1);

#ifdef FILE_LOCKING
  unlock_lock(fd3, 0, SEEK_SET, 0);
  close(fd3);
#endif

  count ++;   //  increment count because of the entry we added.
  if( needsort ) {
    fprintf( stderr, "NOTICE: Out of order entries found in %s\n   Sorting...\n\n", filename );
    sort_file(filename, count, file_type);
    }

  return(0);
}

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

/*
 * Update a users quota
 */
int vsetuserquota( char *username, char *domain, char *quota )
{
 struct vqpasswd *mypw;
 char *formattedquota;
 int ret;

  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);

  lowerit(username);
  lowerit(domain);

  mypw = vauth_getpw( username, domain );
  if (mypw == NULL) return VA_USER_DOES_NOT_EXIST;

  /* correctly format the quota string,
   * and then store the quota into the auth backend
   */
  formattedquota = format_maildirquota(quota);
  ret = vauth_setquota( username, domain, formattedquota);
  if (ret != VA_SUCCESS ) return(ret);

  update_maildirsize(domain, mypw->pw_dir, formattedquota);
  return(0);
}

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

/*
 * count the lines in /var/qmail/control/rcpthosts
 */
int count_rcpthosts()
{
 char tmpstr1[MAX_BUFF];
 FILE *fs;
 int count;

  snprintf(tmpstr1, sizeof(tmpstr1), "%s/control/rcpthosts", QMAILDIR);
  fs = fopen(tmpstr1, "r");
  if ( fs == NULL ) return(0);

  count = 0;
  while( fgets(tmpstr1, sizeof(tmpstr1), fs) != NULL ) ++count;

  fclose(fs);
  return(count);

}

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

/*
 * compile the morercpthosts file using qmail-newmrh program
 */
int compile_morercpthosts()
{
 int pid;

  pid=vfork();
  if ( pid==0){
    execl(QMAILNEWMRH,"qmail-newmrh", NULL);
    exit(127);
  } else {
    waitpid(pid,&pid,0);
  }
  return(0);
}

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

/*
 * fill out a passwd structure from then next
 * line in a file 
 */ 
struct vqpasswd *vgetent(FILE *pw)
{
    static struct vqpasswd pwent;
    static char line[MAX_BUFF];
    int i=0,j=0;
    char *tmpstr;
    char *tmpstr1;

    if (fgets(line,sizeof(line),pw) == NULL) return NULL;

    for (i=0; line[i] != 0; i++) if (line[i] == ':') j++;
    if (j < 6) return NULL;

    tmpstr = line;
    pwent.pw_name   = line;
    while (*tmpstr!=0 && *tmpstr!=':') ++tmpstr;
    *tmpstr = 0; ++tmpstr;

    pwent.pw_passwd = tmpstr;
    while (*tmpstr!=0 && *tmpstr!=':') ++tmpstr;
    *tmpstr = 0; ++tmpstr;
 
    tmpstr1 = tmpstr; 
    while (*tmpstr!=0 && *tmpstr!=':') ++tmpstr;
    *tmpstr = 0; ++tmpstr;
    pwent.pw_uid = atoi(tmpstr1); 

    tmpstr1 = tmpstr; 
    while (*tmpstr!=0 && *tmpstr!=':') ++tmpstr;
    *tmpstr = 0; ++tmpstr;
    pwent.pw_gid = atoi(tmpstr1); 

    pwent.pw_gecos  = tmpstr; 
    while (*tmpstr!=0 && *tmpstr!=':') ++tmpstr;
    *tmpstr = 0; ++tmpstr;

    pwent.pw_dir    = tmpstr; 
    while (*tmpstr!=0 && *tmpstr!=':') ++tmpstr;
    if (*tmpstr) { *tmpstr = 0; ++tmpstr; }

    pwent.pw_shell  = tmpstr; 
    while (*tmpstr!=0 && *tmpstr!=':' && *tmpstr!='\n') ++tmpstr;
    if (*tmpstr) { *tmpstr = 0; ++tmpstr; }

#ifdef CLEAR_PASS
    pwent.pw_clear_passwd  = tmpstr; 
    while (*tmpstr!=0 && *tmpstr!='\n') ++tmpstr;
    if (*tmpstr) { *tmpstr = 0; ++tmpstr; }
#endif

    return &pwent;
}

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

/*
 * figure out where to put the user and
 * make the directories if needed
 *
 * if successfull, return a pointer to the user hash
 * on error return NULL
 */
char *make_user_dir(char *username, char *domain, uid_t uid, gid_t gid)
{
 char *user_hash;
 struct vqpasswd *mypw;
 char calling_dir[MAX_BUFF];
 char domain_dir[MAX_BUFF];
 const char *dirnames[] = {"Maildir", "Maildir/new", "Maildir/cur", 
	"Maildir/tmp"};
 int i;

  verrori = 0;
  /* record the dir where the command was run from */
  getcwd(calling_dir, sizeof(calling_dir));

  /* retrieve the dir that stores this domain */
  if (vget_assign(domain, domain_dir, sizeof(domain_dir), NULL, NULL) == NULL) {
    fprintf(stderr, "Error. vget_assign() failed for domain : %s",domain); 
    return(NULL);
  }

  /* go to the dir for our chosen domain */
  chdir(domain_dir); 

  user_hash="";
#ifdef USERS_BIG_DIR
  /* go into a user hash dir if required */
  open_big_dir(domain, uid, gid);
  user_hash = next_big_dir(uid, gid);
  close_big_dir(domain, uid, gid);
  chdir(user_hash);
#endif
  /* check the length of the dir path to make sure it is not too 
     long to save back to the auth backend */
  if ((strlen(domain_dir)+strlen(user_hash)+strlen(username)) > MAX_PW_DIR) {
    fprintf (stderr, "Error. Path exceeds maximum permitted length\n");
    chdir(calling_dir);
    return (NULL);
  }

  /* create the users dir, including all the Maildir structure */ 
  if ( mkdir(username, VPOPMAIL_DIR_MODE) != 0 ) {
    /* need to add some code to remove the hashed dirs we created above... */
    verrori = VA_EXIST_U_DIR;
    chdir(calling_dir);
    return(NULL);
  }

  if ( chdir(username) != 0 ) {
    /* back out of changes made above */
    chdir(domain_dir); chdir(user_hash); vdelfiles(username);
    chdir(calling_dir);
    fprintf(stderr, "make_user_dir: error 2\n");
    return(NULL);
  }

  for (i = 0; i < (int)(sizeof(dirnames)/sizeof(dirnames[0])); i++) {
    if (mkdir(dirnames[i],VPOPMAIL_DIR_MODE) == -1){ 
      fprintf(stderr, "make_user_dir: failed on %s\n", dirnames[i]);
      /* back out of changes made above */
      chdir("..");
      vdelfiles(username);
      chdir(calling_dir);
      return(NULL);
    }
  }

  /* set permissions on the user's dir */
  r_chown(".", uid, gid);

  /* see if the user already exists in the auth backend */
  mypw = vauth_getpw( username, domain);
  if ( mypw != NULL ) { 

    /* user does exist in the auth backend, so fill in the dir field */
    mypw->pw_dir = malloc(MAX_PW_DIR+1);
    if ( strlen(user_hash) > 0 ) {
      snprintf(mypw->pw_dir, MAX_PW_DIR+1, "%s/%s/%s", domain_dir, user_hash, username);
    } else {
      snprintf(mypw->pw_dir, MAX_PW_DIR+1, "%s/%s", domain_dir, username);
    }
    /* save these values to the auth backend */
    vauth_setpw( mypw, domain );

#ifdef SQWEBMAIL_PASS
    vsqwebmail_pass( mypw->pw_dir, mypw->pw_passwd, uid, gid);
#endif
    free (mypw->pw_dir);
  }

  chdir(calling_dir);
  return(user_hash);
}

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

int r_mkdir(char *path, uid_t uid, gid_t gid )
{
 char tmpbuf[MAX_BUFF];
 int err;
 int i;
 struct stat sb;

  if (*path == '\0') return 0;

  for(i=0; ;++i){
    if ( (i > 0) && ((path[i] == '/') || (path[i] == '\0')) ) {
      tmpbuf[i] = 0;
      err = mkdir(tmpbuf,VPOPMAIL_DIR_MODE);
      if (err == 0)
        chown(tmpbuf, uid, gid);
      else if (errno != EEXIST) {
        /* Note that if tmpbuf is a file, we'll catch the error on the
         * next directory creation (ENOTDIR) or when we verify that the
         * directory exists and is a directory at the end of the function.
         */
        warn ("Unable to create directory %s: ", tmpbuf);
        return -1;
      }
      if (path[i] == '\0') break;
    }
    tmpbuf[i] = path[i];
  }
  if (stat (path, &sb) != 0) {
    warn ("Couldn't stat %s: ", path);
    return -1;
  } else if (! S_ISDIR(sb.st_mode)) {
    fprintf (stderr, "Error: %s is not a directory.\n", path);
    return -1;
  }
  return 0;
}

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

#ifdef APOP
char *dec2hex(unsigned char *digest)
{
  static char ascii[33];
  char *hex="0123456789abcdef";
  int i,j,k;
  memset(ascii,0,sizeof(ascii));
  for (i=0; i < 16; i++) {
    j = digest[i]/16;
    k = digest[i]%16;
    ascii[i*2] = hex[j];
    ascii[(i*2)+1] = hex[k];
  }

  return ascii;
}
#endif

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

/* Function used by qmailadmin to auth users */

struct vqpasswd *vauth_user(char *user, char *domain, char* password, char *apop)
 {
  struct vqpasswd *mypw;
 
   if ( password == NULL ) return(NULL);
   mypw = vauth_getpw(user, domain);
   if ( mypw == NULL ) return(NULL);
   if ( vauth_crypt(user, domain, password, mypw) != 0 ) return(NULL);
 
   return(mypw);
 }

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

/*
 * default_domain()
 *   returns a pointer to a string, containing
 *   the default domain (or blank if not set).  Loads from
 *   ~vpopmail/etc/defaultdomain.  Only loads once per program
 *   execution.
 */
char *default_domain()
{
   static int init = 0;
   static char d[MAX_PW_DOMAIN+1];
   char path[MAX_BUFF];
   int dlen;
   FILE *fs;

   if (!init) {
     init++;
     d[0] = '\0';  /* make sure d is empty in case file doesn't exist */
     snprintf (path, sizeof(path), "%s/etc/defaultdomain", VPOPMAILDIR);

     fs = fopen (path, "r");
     if (fs != NULL) {
       fgets (d, sizeof(d), fs);
       fclose (fs);
       dlen = strlen(d) - 1;
       if (d[dlen] == '\n') { d[dlen] = '\0'; }
     }
   }
   return d;
} 

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

/*
 * If domain is blank, set it to the VPOPMAIL_DOMAIN environment
 * variable, an ip alias domain, or the default domain.
 */
void vset_default_domain( char *domain ) 
{
 char *tmpstr, *cp;
#ifdef IP_ALIAS_DOMAINS
 char host[MAX_BUFF];
#endif

  if (domain != NULL) {
    if (strlen(domain)>0) {
      /* domain isnt blank, so dont try to set it */
      return;
    }
  }

  /* domain is blank, so now try various lookups to set it */

  tmpstr = getenv("VPOPMAIL_DOMAIN");
  if ( tmpstr != NULL) {

    /* As a security precaution, remove all but good chars */
    for (cp = tmpstr; *(cp += strspn(cp, ok_env_chars)); /* */) {*cp='_';}

    /* Michael Bowe 14th August 2003
     * How can we prevent possible buffer overflows here
     * For the moment, stick with a conservative size of MAX_PW_DOMAIN
     * (plus 1 for the NULL)
     */
    snprintf(domain, MAX_PW_DOMAIN+1, "%s", tmpstr);
    return;
  }

#ifdef IP_ALIAS_DOMAINS
  tmpstr = getenv("TCPLOCALIP");

  /* courier-imap uses IPv6 */
  if ( tmpstr != NULL ) {

    /* As a security precaution, remove all but good chars */
    for (cp = tmpstr; *(cp += strspn(cp, ok_env_chars)); ) {*cp='_';}

    /* Michael Bowe 14th August 2003
     * Mmmm Yuk below. What if TCPLOCALIP=":\0"
     * Buffer overflow.
     * Need to perhaps at least check strlen of tmpstr
     */
    if ( tmpstr[0] == ':') {
      tmpstr +=2;
      while(*tmpstr!=':') ++tmpstr;
      ++tmpstr;
    }
  }

  memset(host,0,sizeof(host));
  /* take the ip address that the connection was made to
   * and go and look this up in our vip map
   * and then store the domain into the host var 
   */
  if ( vget_ip_map(tmpstr,host,sizeof(host))==0 && !host_in_locals(host)){
    if ( strlen(host) > 0 ) {
      /* Michael Bowe 14th August 2003
       * How can we prevent possible buffer overflows here
       * For the moment, stick with a conservative size of MAX_PW_DOMAIN
       * (plus 1 for the NULL)
       */
      snprintf(domain, MAX_PW_DOMAIN+1, "%s", host);
    }
    return;
  }
#endif /* IP_ALIAS_DOMAINS */

  /* Michael Bowe 14th August 2003
   * How can we prevent possible buffer overflows here
   * For the moment, stick with a conservative size of MAX_PW_DOMAIN
   * (plus 1 for the NULL)
   */
  snprintf(domain, MAX_PW_DOMAIN+1, "%s", DEFAULT_DOMAIN);
}

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

#ifdef IP_ALIAS_DOMAINS
/* look to see if the nominated domain is is locals file
 * return 1 if there is a match
 * return 0 if there is no match
 */
int host_in_locals(char *domain)
{
 int i;
 char tmpbuf[MAX_BUFF];
 FILE *fs;

  snprintf(tmpbuf, sizeof(tmpbuf), "%s/control/locals", QMAILDIR);
  if ((fs = fopen(tmpbuf,"r")) == NULL) {
    return(0);
  }

  while( fgets(tmpbuf,sizeof(tmpbuf),fs) != NULL ) {
    /* usually any newlines into nulls */
    for(i=0;tmpbuf[i]!=0;++i) if (tmpbuf[i]=='\n') { tmpbuf[i]=0; break; }
    /* Michael Bowe 14th August 2003
     * What happens if domain isnt null terminated?
     */
    if (( strcmp( domain, tmpbuf)) == 0 ) {
      /* we found a match */
      fclose(fs);
      return(1);
    }

    /* always match with localhost */
    if ( strcmp(domain, "localhost") == 0 && 
       strstr(domain,"localhost") != NULL ) {
      fclose(fs);
      return(1);
    }
  }

  fclose(fs);
  return(0);
}
#endif

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

/* Convert error flag to text */
char *verror(int va_err )
{
  switch(va_err) {
   case VA_SUCCESS:
    return("Success");
   case VA_ILLEGAL_USERNAME:
    return("Illegal username");
   case VA_USERNAME_EXISTS:
    return("Username exists");
   case VA_BAD_DIR:
    return("Unable to chdir to vpopmail directory");
   case VA_BAD_U_DIR:
    return("Unable to chdir to vpopmail/users directory");
   case VA_BAD_D_DIR:
    return("Unable to chdir to vpopmail/" DOMAINS_DIR " directory");
   case VA_BAD_V_DIR:
    return("Unable to chdir to vpopmail/" DOMAINS_DIR "/domain directory");
   case VA_EXIST_U_DIR:
    return("User's directory already exists");
   case VA_BAD_U_DIR2:
    return("Unable to chdir to user's directory");
   case VA_SUBDIR_CREATION:
    return("Creation of user's subdirectories failed");
   case VA_USER_DOES_NOT_EXIST:
    return("User does not exist");
   case VA_DOMAIN_DOES_NOT_EXIST:
    return("Domain does not exist");
   case VA_INVALID_DOMAIN_NAME:
    return("Invalid domain name");
   case VA_DOMAIN_ALREADY_EXISTS:
    return("Domain already exists");
   case VA_COULD_NOT_MAKE_DOMAIN_DIR:
    return("Could not make domain dir");
   case VA_COULD_NOT_OPEN_QMAIL_DEFAULT:
    return("Could not open qmail default");
   case VA_CAN_NOT_MAKE_DOMAINS_DIR:
    return("Can not make " DOMAINS_DIR " directory");
   case VA_COULD_NOT_UPDATE_FILE:
    return("Could not update file");
   case VA_CRYPT_FAILED:
    return("Crypt failed");
   case VA_COULD_NOT_OPEN_DOT_QMAIL:
    return("Could not open dot qmail file");
   case VA_BAD_CHAR:
    return("bad character");
   case VA_BAD_UID:
    return("running as invalid uid");
   case VA_NO_AUTH_CONNECTION:
    return("no authentication database connection");
   case VA_MEMORY_ALLOC_ERR:
    return("memory allocation error");
   case VA_USER_NAME_TOO_LONG:
    return("user name too long");
   case VA_DOMAIN_NAME_TOO_LONG:
    return("domain name too long");
   case VA_PASSWD_TOO_LONG:
    return("password too long");
   case VA_GECOS_TOO_LONG:
    return("gecos too long");
   case VA_QUOTA_TOO_LONG:
    return("quota too long");
   case VA_DIR_TOO_LONG:
    return("dir too long");
   case VA_CLEAR_PASSWD_TOO_LONG:
    return("clear password too long");
   case VA_ALIAS_LINE_TOO_LONG:
    return("alias line too long");
   case VA_NULL_POINTER:
    return("null pointer");
   case VA_INVALID_EMAIL_CHAR:
    return("invalid email character");
   case VA_PARSE_ERROR:
    return("parsing database configuration file");
   case VA_PARSE_ERROR01:
    return("parsing database configuration file - update server");
   case VA_PARSE_ERROR02:
    return("parsing database configuration file - update port");
   case VA_PARSE_ERROR03:
    return("parsing database configuration file - update user");
   case VA_PARSE_ERROR04:
    return("parsing database configuration file - update password");
   case VA_PARSE_ERROR05:
    return("parsing database configuration file - update database");
   case VA_PARSE_ERROR06:
    return("parsing database configuration file - readonly server");
   case VA_PARSE_ERROR07:
    return("parsing database configuration file - readonly port");
   case VA_PARSE_ERROR08:
    return("parsing database configuration file - readonly user");
   case VA_PARSE_ERROR09:
    return("parsing database configuration file - readonly password");
   case VA_PARSE_ERROR10:
    return("parsing database configuration file - readonly database");
   case VA_CANNOT_READ_LIMITS:
    return("can't read domain limits");
   case VA_CANNOT_READ_ASSIGN:
    return("can't read users/assign file");
   case VA_CANNOT_DELETE_CATCHALL:
    return("can't delete catchall account");
   default:
    return("Unknown error");
  }
}

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


void vsqlerror( FILE *f, char *comment )
{
    fprintf( f, "Error - %s. %s\n", verror( verrori ), comment );
/*
    if( NULL != sqlerr && strlen(sqlerr) > 0 ) {
        fprintf( f,"%s",sqlerr);
    }

    if( NULL != last_query && strlen( last_query ) > 0 ) {
        fprintf( f,"%s", last_query);
    }
*/
}


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

int vexiterror( FILE *f, char *comment )
{

    vsqlerror( f, comment );
    vclose();
    exit(verrori);
}

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

/* Michael Bowe 21st Aug 2003
 * This function doesnt appear to be used by vpopmail or qmailadmin 
 * Consider it for removal perhaps
 */
/* Add an entry to a domain/.qmail-alias file */
int vadddotqmail( char *alias, char *domain,... ) 
{
 struct vqpasswd *mypw = NULL; 
 FILE *fs;
 va_list args;
 char *email;
 char Dir[MAX_BUFF];
 uid_t uid;
 gid_t gid;
 char tmpbuf[MAX_BUFF];

  /* extract the details for the domain (Dir, uid, gid) */
  if ( vget_assign(domain, Dir, sizeof(Dir), &uid, &gid ) == NULL) {
    return(VA_DOMAIN_DOES_NOT_EXIST);
  }

  /* open the .qmail-alias file for writing */
  snprintf(tmpbuf, sizeof(tmpbuf), "%s/.qmail-%s", Dir, alias);
  if ((fs=fopen(tmpbuf, "w")) == NULL) return(VA_COULD_NOT_OPEN_DOT_QMAIL);

  va_start(args,domain);
  while ( (email=va_arg(args, char *)) != NULL ) {
    /* are we dealing with an email address? */
    if ( strstr(email, "@") == NULL ) {
      /* not an email address */
      /* get passwd entry for this user */
      mypw = vauth_getpw( email, domain );
      if ( mypw == NULL ) return(VA_USER_DOES_NOT_EXIST);
      /* write out the appropriate maildir entry for this user */
      fprintf(fs, "%s/Maildir/\n", mypw->pw_dir);
    } else {
      /* yes, we have an email address, so write it out */
      fprintf(fs, "%s\n", email);
    }
  }
  fclose(fs);

  /* setup the permission of the .qmail-alias file */
  snprintf(tmpbuf, sizeof(tmpbuf), "%s/.qmail-%s", Dir, alias);
  chown(tmpbuf,uid,gid);

  va_end(args);
  return(VA_SUCCESS);
}

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

/* Michael Bowe 21st Aug 2003
 * This function doesnt appear to be used by vpopmail or qmailadmin 
 * Consider it for removal perhaps
 */ 
/* delete a domain/qmail-alias file */
int vdeldotqmail( char *alias, char *domain )
{
 char Dir[MAX_BUFF];
 uid_t uid;
 gid_t gid;
 char tmpbuf[MAX_BUFF];

  if ( vget_assign(domain, Dir, sizeof(Dir), &uid, &gid ) == NULL) {
    return(VA_DOMAIN_DOES_NOT_EXIST);
  }

  snprintf(tmpbuf, sizeof(tmpbuf), "%s/.qmail-%s", Dir, alias);
  if ( unlink(tmpbuf) < 0 ) return(VA_COULD_NOT_OPEN_DOT_QMAIL);
  return(VA_SUCCESS);
}

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

/*
 * Given the domain name:
 * 
 *   get dir, uid, gid from the users/cdb file (if they are not passed as NULL)
 *
 *   If domain is an alias domain, then domain gets updated to be the real domain
 *   
 * Function will return the domain directory on success
 * or return NULL if the domain does not exist.
 * 
 * This function caches last lookup in memory to increase speed
 */
char *vget_assign(char *domain, char *dir, int dir_len, uid_t *uid, gid_t *gid)
{
 FILE *fs;
 int dlen;
 int i;
 char *ptr;

 static char *in_domain = NULL;
 static int in_domain_size = 0;
 static char *in_dir = NULL;
 static int in_dir_size = 0;

 static uid_t in_uid = -1;
 static gid_t in_gid = -1;

 char cdb_key[MAX_BUFF]; 
 char cdb_file[MAX_BUFF];
 char *cdb_buf;

  /* cant lookup a null domain! -- but it does clear the cache */
  if ( domain == NULL || *domain == 0) {
    if ( in_domain != NULL ) {
      free(in_domain);
      in_domain = NULL;
    }
    return(NULL);
  }

  /* if domain matches last lookup, use cached values */
  lowerit(domain);
  if ( in_domain_size != 0 && in_domain != NULL 
    && in_dir != NULL && strcmp( in_domain, domain )==0 ) {

    /* return the vars, if the user has asked for them */
    if ( uid!=NULL ) *uid = in_uid;
    if ( gid!=NULL ) *gid = in_gid;
    if ( dir!=NULL ) snprintf(dir, dir_len, "%s", in_dir);

    /* cached lookup complete, exit out now */
    return(in_dir);
  }

  /* this is a new lookup, free memory from last lookup if necc. */
  if ( in_domain != NULL ) {
    free(in_domain);
    in_domain = NULL;
  }
  if ( in_dir != NULL ) {
    free(in_dir);
    in_dir = NULL;
  }

  /* build up a search string so we can search the cdb file */
  snprintf(cdb_key, sizeof(cdb_key), "!%s-", domain);
  
  /* work out the location of the cdb file */
  snprintf(cdb_file, sizeof(cdb_file), "%s/users/cdb", QMAILDIR);

  /* try to open the cdb file */
  if ( (fs = fopen(cdb_file, "r")) == 0 ) {
    return(NULL);
  }

  /* search the cdb file for our requested domain */
  i = cdb_seek(fileno(fs), cdb_key, strlen(cdb_key), &dlen);
  in_uid = -1;
  in_gid = -1;

  if ( i == 1 ) { 
    /* we found a matching record in the cdb file
     * so next create a storage buffer, and then read it in
     */
    cdb_buf = malloc(dlen);
    i = fread(cdb_buf, sizeof(char), dlen, fs);

    /* format of cdb_buf is :
     * realdomain.com\0uid\0gid\0path\0
     */

    /* get the real domain */
    ptr = cdb_buf;                      /* point to start of cdb_buf (ie realdomain) */
    in_domain_size = strlen(ptr)+1;     /* how long is the domain name? cache the length */
    in_domain = malloc(in_domain_size); /* create storage space for domain cache */
    snprintf(in_domain, in_domain_size, "%s", ptr); /* suck out the domain, store into cache */

    /* get the uid */
    while( *ptr != 0 ) ++ptr;           /* advance pointer past the realdomain */
    ++ptr;                              /* skip over the null */
    in_uid = atoi(ptr);                 /* suck out the uid */
    if ( uid!=NULL) *uid = in_uid;      /* if the user requested the uid, give it to them */

    /* get the gid */
    while( *ptr != 0 ) ++ptr;           /* skip over the uid */
    ++ptr;                              /* skip over the null */
    in_gid = atoi(ptr);                 /* suck out the gid */
    if ( gid!=NULL) *gid = in_gid;      /* if the user requested the gid, give it to them */

    /* get the domain directory */
    while( *ptr != 0 ) ++ptr;           /* skip over the gid */
    ++ptr;                              /* skip over the null */
    if ( dir!=NULL ) strncpy( dir, ptr, dir_len); /* if user requested dir, give it */
    in_dir_size = strlen(ptr)+1;        /* how long is dir? cache the length */
    in_dir = malloc(in_dir_size);       /* create storage space for dir cache */
    snprintf(in_dir, in_dir_size, "%s", ptr); /* suck out the dir, and store it in cache */

    free(cdb_buf);

    /* when vget_assign is called with the domain parameter set as an alias domain,
     * it is meant to replace this alias domain with the real domain
     *
     * in_domain contains the real domain, so do this replacement now.
     *
     * Michael Bowe 21st Aug 2003. Need to watch out for buffer overflows here.
     * We dont know what size domain is, so stick with a conservative limit of MAX_PW_DOMAIN
     * (plus 1 for the NULL)
     * Not sure if this is our best option? the pw entry shouldnt contain any dirs larger
     * than this.
     */
    snprintf(domain, MAX_PW_DOMAIN+1, "%s", in_domain); 

  } else {
    free(in_domain);
    in_domain = NULL;
    in_domain_size = 0;
  }
  fclose(fs);
  return(in_dir);
}

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

/* THE USE OF THIS FUNCTION IS DEPRECIATED.
 *
 * None of the vpopmail code uses this function,
 * but it has been left in the source for the time being,
 * to ensure backwards compatibility with some of the popular
 * patches such as Tonix's chkusr
 *
 * This function is scheduled to be removed at a future date 
 *
 * You can obtain same functionality by calling
 *   vget_assign (domain, NULL, 0, NULL, NULL)
 * 
 */

int vget_real_domain (char *domain, int len)
{
  if (domain == NULL) return (0);
  vget_assign (domain, NULL, 0, NULL, NULL);
  return (0);
}

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

/* This function is typically used to create a user's maildir
 * on-the-fly should it not exist
 * Basically, a dir for the user has been been allocated/stored
 * in the auth backend, but it does not yet exist in the filesystem
 * so we are going to make the dirs now so that mail can be delivered
 *
 * Main use is to call it from vchkpw.c and vdelivermail.c
 * in this format :
 *  vmake_maildir(TheDomain, vpw->pw_dir)
 */

int vmake_maildir(char *domain, char *dir )
{
 char tmpbuf[MAX_BUFF];
 char calling_dir[MAX_BUFF];
 uid_t uid;
 gid_t gid;
 char *tmpstr;
 int i;

  /* record which dir the command was launched from */
  getcwd(calling_dir, sizeof(calling_dir));

  /* set the mask for file creation */
  umask(VPOPMAIL_UMASK);
 
  /* check if domain exists.
   * if domain exists, store the dir into tmpbuf, and store uid and gid
   */
  if ( vget_assign(domain, tmpbuf, sizeof(tmpbuf), &uid, &gid ) == NULL ) {
    return( VA_DOMAIN_DOES_NOT_EXIST );
  }

  /* so, we should have some variables like this now :
   *   dir:    /home/vpopmail/domains/[x]/somedomain.com/[x]/someuser
   *   tmpbuf: /home/vpopmail/domains/[x]/somedomain.com
   */

  /* walk to where the sub directory starts */
  for(i=0,tmpstr=dir;tmpbuf[i]==*tmpstr&&tmpbuf[i]!=0&&*dir!=0;++i,++tmpstr);

  /* walk past trailing slash */
  while ( *tmpstr == '/'  ) ++tmpstr;

  /* tmpstr should now contain : [x]/someuser */

  /* so 1st cd into the domain dir (which should already exist) */
  if ( chdir(tmpbuf) == -1 ) { chdir(calling_dir); return( VA_BAD_DIR); }

  /* Next, create the user's dir
   * ie [x]/someuser
   */
  r_mkdir(tmpstr, uid, gid);

  /* we should now be able to cd into the user's dir */
  if ( chdir(dir) != 0 ) { chdir(calling_dir); return(-1); }

  /* now create the Maildir */
  if (mkdir("Maildir",VPOPMAIL_DIR_MODE) == -1) { chdir(calling_dir); return(-1); }
  if (chdir("Maildir") == -1) { chdir(calling_dir); return(-1); }
  if (mkdir("cur",VPOPMAIL_DIR_MODE) == -1) { chdir(calling_dir); return(-1); }
  if (mkdir("new",VPOPMAIL_DIR_MODE) == -1) { chdir(calling_dir); return(-1); }
  if (mkdir("tmp",VPOPMAIL_DIR_MODE) == -1) { chdir(calling_dir); return(-1); }

  /* set permissions on the user's dir */
  chdir(dir);
  r_chown(dir, uid, gid);

  /* change back to the orignal dir */
  chdir(calling_dir);
  return(0);
}

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

/* This function allows us to store an crypted password in the user's maildir
 * for use by sqwebmail
 */
int vsqwebmail_pass( char *dir, char *crypted, uid_t uid, gid_t gid )
{
 FILE *fs;
 char tmpbuf1[MAX_BUFF];

  if ( dir == NULL ) return(VA_SUCCESS);
  snprintf(tmpbuf1, sizeof(tmpbuf1), "%s/Maildir/sqwebmail-pass", dir);
  if ( (fs = fopen(tmpbuf1, "w")) == NULL ) {
    return(VA_SQWEBMAIL_PASS_FAIL);
  }
  fprintf(fs, "\t%s\n", crypted);
  fclose(fs);
  chown(tmpbuf1,uid,gid);
  return(0);
}

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

#ifdef POP_AUTH_OPEN_RELAY 
/* This function is used to grab the user's ip address
 * and add it to the ip's that are allowed to relay mail
 * through this server.
 *
 * For mysql backend, the ip is added to the relay table
 * For cdb backend, the ip is added to the ~vpopmail/etc/open-smtp file
 * 
 * Then the update_rules() function is called which 
 * combines the tcp.smtp rules with the relay-table/open-smtp rules
 * to build a new tcp.smtp.cdb file for tcpserver to use
 *
 * This function is called after a successful pop-auth by vchkpw,
 * (assuming that roaming users are enabled)
 */
int open_smtp_relay()
{

#ifdef USE_SQL

int result;

//  NOTE: vopen_smtp_relay returns <0 on error 0 on duplicate 1 added
//  check for failure.

  /* store the user's ip address into the sql relay table */
  if (( result = vopen_smtp_relay()) < 0 ) {   //   database error
      vsqlerror( stderr, "Error. vopen_smtp_relay failed" );
      return (verrori);
  } else if ( result == 1 ) {
    /* generate a new tcp.smtp.cdb file */
    if (update_rules()) {
      vsqlerror( stderr, "Error. vupdate_rules failed" );
      return (verrori);
    }
  }
#else
/* if we arent using SQL backend, then we have to maintain the
 * info via the tcp.smtp file
 */
 FILE *fs_cur_file;
 FILE *fs_tmp_file;
#ifdef FILE_LOCKING
 int fd_lok_file;
#endif /* FILE_LOCKING */
 char *ipaddr;
 char *tmpstr;
 time_t mytime;
 int rebuild_cdb = 1;
 char open_smtp_tmp_filename[MAX_BUFF];
 char tmpbuf1[MAX_BUFF];
 char tmpbuf2[MAX_BUFF];

  mytime = time(NULL);

  ipaddr = get_remote_ip();
  if ( ipaddr == NULL ) {
      return 0;
  }

#ifdef FILE_LOCKING
  /* by default the OPEN_SMTP_LOK_FILE is ~vpopmail/etc/open-smtp.lock */
  if ( (fd_lok_file=open(OPEN_SMTP_LOK_FILE, O_WRONLY | O_CREAT, S_IRUSR|S_IWUSR))<0) return(-1);
  get_write_lock(fd_lok_file);
#endif /* FILE_LOCKING */

  /* by default the OPEN_SMTP_CUR_FILE is ~vpopmail/etc/open-smtp */
  if ( (fs_cur_file = fopen(OPEN_SMTP_CUR_FILE, "r+")) == NULL ) {
    /* open for read/write failed, so try creating it from scratch */
    if ( (fs_cur_file = fopen(OPEN_SMTP_CUR_FILE, "w+")) == NULL ) {
#ifdef FILE_LOCKING
      unlock_lock(fd_lok_file, 0, SEEK_SET, 0);
      close(fd_lok_file);
#endif /* FILE_LOCKING */
      /* failed trying to access the open-smtp file */
      return(-1);
    }
  }

  /* by default the OPEN_SMTP_TMP_FILE is ~vpopmail/etc/open-smtp.tmp.pid */
  snprintf(open_smtp_tmp_filename, sizeof(open_smtp_tmp_filename),
           "%s.%lu", OPEN_SMTP_TMP_FILE, (long unsigned)getpid());
  /* create the tmp file */
  fs_tmp_file = fopen(open_smtp_tmp_filename, "w+");

  if ( fs_tmp_file == NULL ) {
#ifdef FILE_LOCKING
    unlock_lock(fd_lok_file, 0, SEEK_SET, 0);
    close(fd_lok_file);
#endif /* FILE_LOCKING */
    /* failed to create the tmp file */
    return(-1);
  }

  /* read in the current open-smtp file */
  while ( fgets(tmpbuf1, sizeof(tmpbuf1), fs_cur_file ) != NULL ) {
    snprintf(tmpbuf2, sizeof(tmpbuf2), "%s", tmpbuf1);
    /* extract the ip address from this line */
    tmpstr = strtok( tmpbuf2, ":");
    /* is this a match for our current ip? */
    if ( strcmp( tmpstr, ipaddr ) != 0 ) {
      /* no match, so copy the line out to our tmp file */
      fputs(tmpbuf1, fs_tmp_file);
    } else {
      /* Found a match. Dont copy this line out to the tmp file.
       * We dont want to echo this same line out, because we are going
       * to write a new version of the line below, with an updated
       * timestamp attached.
       * Also clear the rebuild_cdb flag, because we arent adding
       * any new entries in this case
       */
      rebuild_cdb = 0;
    }
  }
  /* append the current ip address to the tmp file
   * using the format x.x.x.x:ALLOW,RELAYCLIENT="",RBLSMTPD=""<TAB>timestamp
   */
  fprintf( fs_tmp_file, "%s:allow,RELAYCLIENT=\"\",RBLSMTPD=\"\"\t%d\n", 
    ipaddr, (int)mytime);
  fclose(fs_cur_file);
  fclose(fs_tmp_file);

  /* rename the open-smtp.tmp to the be open-smtp */
  rename(open_smtp_tmp_filename, OPEN_SMTP_CUR_FILE);

  /* if we added new entries to the file (or created it for the 1st time)
   * then we need to rebuild our tcp.smtp.cdb based on our newly built
   * open-smtp file.
   */
  if ( rebuild_cdb ) {
    if (update_rules() != 0) {
      fprintf(stderr, "Error. update_rules() failed\n");
      #ifdef FILE_LOCKING
        unlock_lock(fd_lok_file, 0, SEEK_SET, 0);
        close(fd_lok_file);
      #endif /* FILE_LOCKING */
      return (-1);
    }
  }

#ifdef FILE_LOCKING
  unlock_lock(fd_lok_file, 0, SEEK_SET, 0);
  close(fd_lok_file);
#endif /* FILE_LOCKING */
#endif /* USE_SQL */
  return(0);
}
#endif /* POP_AUTH_OPEN_RELAY */

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

#ifdef POP_AUTH_OPEN_RELAY 
/* This function is called by update_rules()
 *
 * It will create a tcprules task sitting and waiting for a new ruleset to be
 * piped into it. It will then compile these rules into a new 
 * tcp.smtp.cdb file
 */
long unsigned tcprules_open()
{
 int pim[2];
 long unsigned pid;
 char bin0[MAX_BUFF];
 char bin1[MAX_BUFF];
 char bin2[MAX_BUFF];
 char *binqqargs[4];

  /* create a filename for use as a tmp file */
  snprintf(relay_tempfile, sizeof(relay_tempfile), "%s.tmp.%ld", TCP_FILE, (long unsigned)getpid());

  /* create a pair of filedescriptors for our pipe */
  if (pipe(pim) == -1)  { return(-1);}

  switch( pid=vfork()){
   case -1:
    /* vfork error. close pipes and exit */
    close(pim[0]); close(pim[1]);
    return(-1);
   case 0:
    close(pim[1]);
    if (vfd_move(0,pim[0]) == -1) _exit(120);

    /* build the command line to update the tcp rules file 
     * It will be of this format :
     * TCPRULES_PROG TCP_FILE.cdb TCP_FILE.cbd.tmp.pid
     * eg /usr/local/bin/tcprules /home/vpopmail/etc/tcp.smtp.cdb  /home/vpopmail/etc/tcp.smtp.tmp.pid
     */ 
    snprintf( bin0, sizeof(bin0), "%s", TCPRULES_PROG);
    snprintf( bin1, sizeof(bin1), "%s.cdb", TCP_FILE);
    snprintf( bin2, sizeof(bin2), "%s", relay_tempfile);

    /* put these strings into an argv style array */
    binqqargs[0] = bin0;
    binqqargs[1] = bin1;
    binqqargs[2] = bin2;
    binqqargs[3] = 0;

    /* run this command now (it will sit waiting for input to be piped in */
    execv(*binqqargs,binqqargs);
  }

  /* tcprules_fdm is a filehandle to this process, which we can pipe rules into */
  tcprules_fdm = pim[1]; close(pim[0]);

  return(pid);
}
#endif /* POP_AUTH_OPEN_RELAY */

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

int vfd_copy(int to, int from)
{
  if (to == from) return 0;
  if (fcntl(from,F_GETFL,0) == -1) return -1;

  close(to);

  if (fcntl(from,F_DUPFD,to) == -1) return -1;

  return 0;
}

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

int vfd_move(int to, int from)
{
  if (to == from) return 0;
  if (vfd_copy(to,from) == -1) return -1;
  close(from);
  return 0;
}

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

#ifdef POP_AUTH_OPEN_RELAY 
/* update_rules() is run whenever 
 * - a new ip added (via open_smtp_relay())
 * or
 * - an old ip removed (via clearopensmtp)
 * from the current list of pop connections
 *
 * It generates a new tcp.smtp.cdb file by doing these steps :
 *   for mysql backend :
 *     copy the tcp.smtp file to a tmp file
 *     append the ip's from the relay table to the tmp file
 *     compile the tmp file into a new tcp.smtp.cdb file 
 *   for cdb backend :
 *     copy the tcp.smtp file to a tmp file
 *     append the ip's from the open-smtp file to the tmp file
 *     compile the tmp file into a new tcp.smtp.cdb file 
 */ 
int update_rules()
{
 FILE *fs;
 long unsigned pid;
 int wstat;
 char tmpbuf1[MAX_BUFF];

#ifndef USE_SQL
 char tmpbuf2[MAX_BUFF];
 char *tmpstr;
#endif

#ifndef REBUILD_TCPSERVER
  return(0);
#endif

  umask(VPOPMAIL_TCPRULES_UMASK);

  /* open up a tcprules task, and leave it sitting waiting for the
   * new set of rules to be piped in (via the filehandle "tcprules_fdm")
   */
  if ((pid = tcprules_open()) < 0) return(-1);

  /* Open the TCP_FILE if it exists.
   * it is typically named /home/vpopmail/etc/tcp.smtp
   */
  fs = fopen(TCP_FILE, "r");
  if ( fs != NULL ) {
    /* copy the contents of the current tcp.smtp file into the tcprules pipe */
    while ( fgets(tmpbuf1, sizeof(tmpbuf1), fs ) != NULL ) {
      write(tcprules_fdm,tmpbuf1, strlen(tmpbuf1));
    }
    fclose(fs);
  }

#ifdef USE_SQL
  /* suck out a list of ips stored in the 'relay' table
   * and write these into 'tcp.smtp' format for the tcprules pipe
   */
  vupdate_rules(tcprules_fdm);

#else

  /* open up the file that contains the list of recent open connections
   * (by default this is ~vpopmail/etc/open-smtp)
   * This file is generated by the open_smtp() function
   * the file has the following format :
   * x.x.x.x:ALLOW,RELAYCLIENT="",RBLSMTPD=""<TAB>timestamp
   */
  fs = fopen(OPEN_SMTP_CUR_FILE, "r");
  if ( fs != NULL ) {
    /* read each of the recently open connections. */
    while ( fgets(tmpbuf1, sizeof(tmpbuf1), fs ) != NULL ) {
      snprintf(tmpbuf2, sizeof(tmpbuf2), "%s", tmpbuf1);
      /* dump the TAB and everything after it */
      tmpstr = strtok( tmpbuf2, "\t");
      strncat(tmpstr, "\n", sizeof(tmpstr)-strlen(tmpstr)-1);
      /* write the line out to the tcprules pipe */
      write(tcprules_fdm,tmpstr, strlen(tmpstr));
    }
    fclose(fs);
  }
#endif

  /* close the pipe to the tcprules process. This will cause
   * tcprules to generate a new tcp.smtp.cdb file 
   */
  close(tcprules_fdm);  

  /* wait untill tcprules finishes so we don't have zombies */
  waitpid(pid,&wstat,0);

  /* if tcprules encounters an error, then the tempfile will be
   * left behind on the disk. We dont want this because we could
   * possibly end up with a large number of these files cluttering
   * the directory. Therefore we will use unlink now to make
   * sure to zap the temp file if it still exists
   */
  if ( unlink(relay_tempfile) == 0 ) {
    fprintf(stderr, "Warning: update_rules() - tcprules failed\n");
  }

  /* correctly set the ownership of the tcp.smtp.cdb file */
  snprintf(tmpbuf1, sizeof(tmpbuf1), "%s.cdb", TCP_FILE);
  chown(tmpbuf1,VPOPMAILUID,VPOPMAILGID);

  return(0);
}
#endif

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

int vexit(int err)
{
  vclose();
  exit(err);
}

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

/* zap the maildirsize file from a users dir */
void remove_maildirsize(char *dir) {
 char maildirsize[MAX_BUFF];
 FILE *fs;

  snprintf(maildirsize, sizeof(maildirsize), "%s/Maildir/maildirsize", dir);
  if ( (fs = fopen(maildirsize, "r+"))!=NULL) {
    fclose(fs);
    unlink(maildirsize);
  }
}

/************************************************************************/
/* update_maildirsize first appeared in 5.4.8 */
void update_maildirsize (char *domain, char *dir, char *quota)
{
  uid_t uid;
  gid_t gid;
  char maildir[MAX_BUFF];

  remove_maildirsize(dir);
  if (strcmp (quota, "NOQUOTA") != 0) {
    snprintf(maildir, sizeof(maildir), "%s/Maildir/", dir);
    umask(VPOPMAIL_UMASK);
    (void)vmaildir_readquota(maildir, quota);
    if ( vget_assign(domain, NULL, 0, &uid, &gid)!=NULL) {
      strcat(maildir, "maildirsize");
      chown(maildir,uid,gid);
    }
  }
}

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

/* run some tests on the contents of a vpqw struct */
int vcheck_vqpw(struct vqpasswd *inpw, char *domain)
{

  if ( inpw == NULL )   return(VA_NULL_POINTER );
  if ( domain == NULL ) return(VA_NULL_POINTER);

  if ( inpw->pw_name == NULL )         return(VA_NULL_POINTER);
  if ( inpw->pw_passwd == NULL )       return(VA_NULL_POINTER);
  if ( inpw->pw_gecos == NULL )        return(VA_NULL_POINTER);
  if ( inpw->pw_dir == NULL )          return(VA_NULL_POINTER);
  if ( inpw->pw_shell == NULL )        return(VA_NULL_POINTER);
#ifdef CLEAR_PASS
  if ( inpw->pw_clear_passwd == NULL ) return(VA_NULL_POINTER);
#endif

  /* when checking for excess size using strlen, the check needs use >= because you
   * have to allow 1 char for null termination
   */ 
  if ( strlen(inpw->pw_name) > MAX_PW_NAME )    return(VA_USER_NAME_TOO_LONG);
#ifdef USERS_BIG_DIR
  if ( strlen(inpw->pw_name) == 1 )             return(VA_ILLEGAL_USERNAME);
#endif
  if ( strlen(domain) > MAX_PW_DOMAIN )         return(VA_DOMAIN_NAME_TOO_LONG);
  if ( strlen(inpw->pw_passwd) > MAX_PW_PASS )  return(VA_PASSWD_TOO_LONG);
  if ( strlen(inpw->pw_gecos) > MAX_PW_GECOS )  return(VA_GECOS_TOO_LONG);
  if ( strlen(inpw->pw_dir) > MAX_PW_DIR )      return(VA_DIR_TOO_LONG);
  if ( strlen(inpw->pw_shell) > MAX_PW_QUOTA )  return(VA_QUOTA_TOO_LONG);
#ifdef CLEAR_PASS
  if ( strlen(inpw->pw_clear_passwd) > MAX_PW_CLEAR_PASSWD )
                                                return(VA_CLEAR_PASSWD_TOO_LONG);
#endif
  return(VA_SUCCESS);

}

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

char *vrandom_pass(char *buffer, int len)
/* write a random password of 'len' characters to buffer and return it */
{
  int gen_char_len; 
  int i, k; 
  static int seeded = 0;

  if (buffer == NULL) return buffer;

  gen_char_len = strlen(gen_chars);

  if (!seeded) {
    seeded = 1;
    srand(time(NULL)^(getpid()<<15));
  }

  for (i = 0; i < len; i++) {
    k = rand()%gen_char_len;
    buffer[i] = gen_chars[k];
  }
  buffer[len] = '\0';  /* NULL terminator */

  return buffer;
}

char *vgen_pass(int len)
/* old function to generate a random password (replaced by vrandom_pass) */
{
  char *p;

  p = malloc(len + 1);
  if (p == NULL) return NULL;
  return (vrandom_pass (p, len));
}


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

/* if inchar is valid, return 1
 * if inchar is invalid, return 0
 *
 * Michael Bowe 15th August 2003
 * This  function isnt used by vpopmail, cantidate for removal?
 */
int vvalidchar( char inchar ) 
{
 
 /* check lower case a to lower case z */
 if ( inchar >= 'a' && inchar <= 'z' ) return(1);

 /* check upper case a to upper case z */
 if ( inchar >= 'A' && inchar <= 'Z' ) return(1);

 /* check numbers */
 if ( inchar >= '0' && inchar <= '9' ) return(1);

 /* check for '-' and '.' */
 if ( inchar == '-' || inchar == '.' || inchar == '_' ) return(1);

 /* everything else is invalid */
 verrori = VA_INVALID_EMAIL_CHAR;
 return(0);
 
}

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

/* support all the valid characters except %
 * which might be exploitable in a printf
 */
int is_username_valid( char *user ) 
{
  while(*user != 0 ) {
    if ( (*user == 33) || 
         (*user == 35 ) || 
         (*user == 36 ) || 
         (*user == 38 ) || 
         (*user == 39 ) || 
         (*user == 42 ) || (*user == 43) ||
         (*user >= 45 && *user <= 57) ||
         (*user == 61 ) || (*user == 63 ) ||
         (*user >= 65 && *user <= 90) ||
         (*user >= 94 && *user <= 126 ) ) {
      ++user;
    } else {
      return(VA_ILLEGAL_USERNAME);
    }
  }
  return(0);
}

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

int is_domain_valid( char *domain ) 
{
  while(*domain != 0 ) {
    if ( (*domain == 45) || (*domain == 46) || 
         (*domain >= 48 && *domain <= 57) ||
         (*domain >= 65 && *domain <= 90) ||
         (*domain >= 97 && *domain <= 122) ) {
      ++domain;
    } else {
      return(VA_INVALID_DOMAIN_NAME);
    }
  }
  return(0);
}

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

/* add an alias domain to the system  */
int vaddaliasdomain( char *alias_domain, char *real_domain)
{
 int err;
 uid_t uid;
 gid_t gid;
 char Dir[MAX_BUFF];
 
  lowerit(alias_domain);
  lowerit(real_domain);

  if ( (err=is_domain_valid(real_domain)) != VA_SUCCESS ) return(err);
  if ( (err=is_domain_valid(alias_domain)) != VA_SUCCESS ) return(err);

  /* make sure the alias domain does not exceed the max storable size */
  if (strlen(alias_domain) > MAX_PW_DOMAIN) {
    return(VA_DOMAIN_NAME_TOO_LONG);
  }

  /* Make sure that the alias_domain doesnt already exist */
  /* Michael Bowe 21st Aug 2003 
   * Will the alias_domain get overwritten with the real_domain
   * by the call below?
   * Could this mess things up for the calling function?
   */
  if (( vget_assign(alias_domain, NULL, 0, NULL, NULL)) != NULL) {
    return(VA_DOMAIN_ALREADY_EXISTS);
  }

  /* Make sure the real domain exists */
  if (( vget_assign(real_domain, Dir, sizeof(Dir), &uid, &gid)) == NULL) {
    return(VA_DOMAIN_DOES_NOT_EXIST);
  }

  if (strcmp(alias_domain, real_domain)==0) {
    fprintf(stderr, "Error. alias and real domain are the same\n");
    return(VA_DOMAIN_ALREADY_EXISTS);
  }

  /* Add the domain to the assign file */
  add_domain_assign( alias_domain, real_domain, Dir, uid, gid );

  /* signal qmail-send, so it can see the changes */
  signal_process("qmail-send", SIGHUP);


#ifdef ONCHANGE_SCRIPT
  /* tell other programs that data has changed */
  snprintf ( onchange_buf, MAX_BUFF, "%s %s", alias_domain, real_domain );
  call_onchange ( "add_alias_domain" );
#endif

  return(VA_SUCCESS);
}

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

                /* properly handle the following formats:
                 * "1M", "1024K", "1048576" (set 1 MB quota)
                 * "1MB", "1024KB" (set 1 MB quota)
                 * "NOQUOTA" (no quota)
                 * "1mbs,1000C" (1 MB size, 1000 message limit)
                 * "1048576S,1000C" (1 MB size, 1000 message limit)
                 * "1000C,10MBS" (10 MB size, 1000 message limit)
                 */

char *format_maildirquota(const char *q) {
int     i;
double	quota_size;
long	quota_count;
char	*p;
static char    tempquota[128];

    if (strcmp (q, "NOQUOTA") == 0) {
      strcpy (tempquota, "NOQUOTA");
      return tempquota;
    }

    /* translate the quota to a number, or leave it */
    quota_size = -1.0;
    quota_count = -1;
    snprintf (tempquota, sizeof(tempquota), "%s", q);
    p = strtok (tempquota, ",");
    while (p != NULL) {
      i = strlen(p) - 1;
      if (p[i] == 'C') { /* specify a limit on the number of messages (COUNT) */
        quota_count = atol(p);
      } else { /* specify a limit on the size */
        /* strip optional trailing S */
        if ((p[i] == 'S') || (p[i] == 's')) p[i--] = '\0';
        /* strip optional trailing B (for KB, MB) */
        if ((p[i] == 'B') || (p[i] == 'b')) p[i--] = '\0';

        quota_size = atof(p);
        if ((p[i] == 'M') || (p[i] == 'm')) quota_size *= 1024 * 1024;
        if ((p[i] == 'K') || (p[i] == 'k')) quota_size *= 1024;
      }
      p = strtok (NULL, ",");
    }

    if (quota_count == -1)
      if (quota_size == -1.0) strcpy (tempquota, ""); /* invalid quota */
      else sprintf (tempquota, "%.0fS", quota_size);
    else if (quota_size == -1.0)
      sprintf (tempquota, "%luC", quota_count);
    else
      sprintf (tempquota, "%.0fS,%luC", quota_size, quota_count);

    return tempquota;
}

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

/* returns a 39 character Date: header with trailing newline and NULL */
char *date_header()
{
  static char dh[39];
  time_t now;
  struct tm *tm;

  static char *montab[12] = {
  "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
  };
  static char *wday[7] = {
  "Sun","Mon","Tue","Wed","Thu","Fri","Sat"
  };

  /* look up current time and fill tm structure */
  time(&now);
  tm = gmtime(&now);

  snprintf (dh, sizeof(dh),
    "Date: %s, %02u %s %u %02u:%02u:%02u +0000\n",
    wday[tm->tm_wday], tm->tm_mday, montab[tm->tm_mon], tm->tm_year + 1900,
    tm->tm_hour, tm->tm_min, tm->tm_sec);

  return dh;
}

char *get_remote_ip()
{
  char *ipenv;
  static char ipbuf[30];
  char *ipaddr;
  char *p;

  ipenv = getenv("TCPREMOTEIP"); /* tcpserver from daemontools */
  if (ipenv == NULL) ipenv = getenv("REMOTE_HOST"); /* xinetd */
  if ((ipenv == NULL) || (strlen(ipenv) > sizeof(ipbuf))) return ipenv;

  strcpy (ipbuf, ipenv);
  ipaddr = ipbuf;

  /* Convert ::ffff:127.0.0.1 format to 127.0.0.1
   * While avoiding buffer overflow.
   */
  if (*ipaddr == ':') {
    ipaddr++;
    if (*ipaddr != '\0') ipaddr++;
    while((*ipaddr != ':') && (*ipaddr != '\0')) ipaddr++;
    if (*ipaddr != '\0') ipaddr++;
  }

  /* remove invalid characters */
  for (p = ipaddr; *(p += strspn(p, ok_env_chars)); ) {*p='_';}

  return ipaddr;  
}

char *maildir_to_email (const char *maildir)
{
	static char email[256];
	int i;
	char *pnt, *last;
	char *mdcopy;
	char *user;
	int sawdot;
	
	mdcopy = malloc (strlen (maildir) + 1);
	if (mdcopy == NULL) return "";
	strcpy (mdcopy, maildir);
	
	/* find the last occurrence of /Maildir/ */
	pnt = mdcopy;
	do {
		last = pnt;
		pnt = strstr (pnt + 1, "/Maildir/");
	} while (pnt != NULL);

	if (last == pnt) {
		/* no occurrences of "/Maildir/" in path */
		free (mdcopy);
		return "";
	}
	
	/* last points to /Maildir/ after username, so null terminate
	 * username by changing the '/' to a NUL
	 */
	*last = '\0';
 
 	/* find start of username */
 	i = (int) (last - mdcopy);
 	while (i > 0 && mdcopy[i] != '/') { i--; }
 	
 	if (i == 0) {
 		/* invalid maildir path */
 		free (mdcopy);
 		return "";
	}
	
	user = &mdcopy[i+1];
	
	/* look for first directory name that contains a '.', that's the domain */
	sawdot = 0;
	do {
		mdcopy[i] = '\0';	/* change '/' to NUL to NUL-terminate domain */
		/* search backwards for '/' */
		while (i > 0 && (mdcopy[i] != '/')) {
			if (mdcopy[i] == '.') sawdot = 1;
			i--;
		}
	} while ((i > 0) && !sawdot);
	
	if (i == 0) {
		/* couldn't find domain name */
		free (mdcopy);
		return "";
	}
		
	snprintf (email, sizeof(email), "%s@%s", user, &mdcopy[i+1]);
	free (mdcopy);
	
	return email;
}

/* escape these characters out of strings: ', \, " */
#define ESCAPE_CHARS "'\"\\"

/* qnprintf - Custom version of snprintf for creating SQL queries with escaped
 *            strings.
 *
 * int qnprintf (char *buffer, size_t size, const char *format, ...)
 *
 *   buffer - buffer to print string to
 *   size   - size of buffer
 *   format - a printf-style format string*
 *   ...    - variable arguments for the format string
 *
 *  NOTE: Currently supported formats: %%, %s, %d/%i, %u, %ld/%li, %lu
 *  Since this function was designed to build SQL queries with escaped data,
 *  the formats don't support any extended options.
 *
 * Returns the number of characters that would have been printed to buffer
 * if it was big enough.  (i.e., if return value is larger than (size-1),
 * buffer received an incomplete copy of the formatted string).
 *
 * It is possible to call qnprintf with a NULL buffer of 0 bytes to determine
 * how large the buffer needs to be.  This is inefficient, as qnprintf has
 * to run twice.
 *
 * qnprintf written February 2004 by Tom Collins <tom@tomlogic.com>
 */
int qnprintf (char *buffer, size_t size, const char *format, ...)
{
	va_list ap;
	int printed;   /* number of characters printed */
	const char *f; /* current position in format string */
	char *b;       /* current position in output buffer */
	char n[20];    /* buffer to hold string representation of number */
	
        int argn = 0;  /* used for numbered arguments */
        char argstr[10];

	char *s;       /* pointer to string to insert */

	if (buffer == NULL && size > 0) return -1;

	va_start (ap, format);

	printed = 0;
	b = buffer;
	for (f = format; *f != '\0'; f++) {
		if (*f != '%') {
			if (++printed < (int)size) *b++ = *f;
		} else {
			f++;
			s = n;
			switch (*f) {
				case '%':
					strcpy (n, "%");
					break;
					
				case 'd':
				case 'i':
					snprintf (n, sizeof(n), "%d", va_arg (ap, int));
					break;
					
				case 'u':
					snprintf (n, sizeof(n), "%u", va_arg (ap, unsigned int));
					break;
					
				case 'l':
					f++;
					switch (*f) {
						case 'd':
						case 'i':
							snprintf (n, sizeof(n), "%ld", va_arg (ap, long));
							break;
					
						case 'u':
							snprintf (n, sizeof(n), "%lu", va_arg (ap, unsigned long));
							break;

						default:
							strcpy (n, "*");
					}
					break;
										
				case 's':
					s = va_arg (ap, char *);
					break;
					
                                default:
                                        argn = 0;
                                        while ((*f >= '0') && (*f <= '9')) {
                                          argn = argn * 10 + atoi(f);
                                          f++;
                                        }
                                        if ((argn > 0) && (*f == '$')) {
                                          f++;
                                          if (*f == 'l') {
                                            f++;
                                            switch (*f) {
                                              case 'i':
                                                snprintf(argstr, sizeof(argstr), "%%%d$ld", argn);
                                                break;
 
                                              case 'u':
                                                snprintf(argstr, sizeof(argstr), "%%%d$lu", argn);
                                                break;
 
                                              default:
                                                snprintf(argstr, sizeof(argstr), "%%%d$l%c", argn, *f);
                                            }
                                          } else {
                                            snprintf(argstr, sizeof(argstr), "%%%d$%c", argn, *f);
                                          }
                                          vsprintf(s, argstr, ap);
                                        } else if(argn > 0) {
                                          while (argn > 10) {
                                            argn = argn / 10;
                                            f--;
                                          }
                                          strcpy (n, "*");
                                        }


			}
			while (*s != '\0') {
				if (strchr (ESCAPE_CHARS, *s) != NULL) {
					if (++printed < (int)size) *b++ = '\\';
				}
				if (++printed < (int)size) *b++ = *s;
				s++;
			}
		}
	}

	va_end (ap);

	*b = '\0';

	/* If the query doesn't fit in the buffer, zero out the buffer.  An
	 * incomplete query could be very dangerous (say if a WHERE clause
	 * got dropped from a DELETE).
	 */
	if (printed >= (int)size) {
		memset (buffer, '\0', size);
	}
	
	return printed;
}

/* Linked-list code for handling valias entries in SQL database (read
 * all entries into memory, close connection to DB, return one entry
 * at a time, freeing old entries as we go.)
 *
 * linklist_{add|del} written September 2004 by Tom Collins <tom@tomlogic.com>
 */

/* create a new entry for data, point list->next to it and return a pointer to it */
struct linklist * linklist_add (struct linklist *list, const char *d1, const char *d2) {
	size_t dlen;
	int i;
	struct linklist *entry;
	
	dlen = strlen (d1) + 1 + strlen (d2);
	/* new entry to hold string, NULL and a pointer to the next entry */
	entry = (struct linklist *) malloc (dlen + 1 + sizeof(struct linklist*) + sizeof(char *));
	if (entry != NULL) {
		if (list != NULL) list->next = entry;
		entry->next = NULL;
		i = sprintf (entry->data, "%s", d1);
		entry->d2 = &entry->data[i+1];
		sprintf (entry->d2, "%s", d2);
	}
	return entry;
}

/* delete the passed entry and return a pointer to the next entry */
struct linklist * linklist_del (struct linklist *list) {
	struct linklist *next;
	
	next = list->next;
	free (list);
	return next;
}

#ifdef ONCHANGE_SCRIPT
/************************************************************************
 *
 * Run an external program to notify other systems that something changed
 * John Simpson <jms1@jms1.net> 2005-01-22
 *
 * 2006-03-30 jms1 - added command line parameters for external program
 *
 * 2007-01-09 jms1 - cleanup, now returns onchange script exit code,
 *   error messages are now accurate.
 *
 * 2007-07-14 jms1 - suppressing "ONCHANGE script not found" message.
 */
char onchange_buf[MAX_BUFF];
int allow_onchange=1;
int call_onchange ( const char *cmd )
{
	char path[MAX_BUFF];
	int pid, rv;

        if( !allow_onchange )  {
           return(0);
           }

	/* build the name */
	snprintf ( path, sizeof(path), "%s/etc/onchange", VPOPMAILDIR );

	/* if it doesn't exist, we're done */
	if( access(path,F_OK) ) { 
           return(0);
           }

	/* it does exist, make sure we're allowed to run it */
	if( access(path,X_OK) ) { 
           fprintf(stderr, "ONCHANGE script %s not executable.\n", path);
           return(EACCES);
           }

	/* okay, let's do it */
	pid = vfork();
	if ( 0 == pid )
	{
		execl ( path, "onchange", cmd, onchange_buf, NULL );
           	fprintf(stderr, "ONCHANGE script %s unable to exec.\n", path);
	        return(0); /* would "_exit(-1)" make more sense here ??? */
	}
	else if ( pid > 0 )
	{
		if (waitpid ( pid, &rv, 0 ) < 0 ||
		    !WIFEXITED( rv ) || WEXITSTATUS( rv ) != 0 ) {
			fprintf(stderr,
			    "ONCHANGE script %s did not exit gracefully.\n",
			    path);
			return(rv);
		}
		return(0);
	}

	rv = errno;
	fprintf(stderr, "ONCHANGE script %s unable to fork: %s\n", path,
	    strerror(rv));
	return(rv);
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1