/* * $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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #ifdef HAVE_ERR_H #include #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;idomain, 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;id_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 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= 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; ipw_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 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;jkey, ((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 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=""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=""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 */ 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 */ /* 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 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