> > Sounds good. I was hoping to stay away from domain based > quotas outside of the file system controls, and just let > the file system handle that for us. > > I like the bounce message idea of coming from the virtual > domain. That's a good one ;) > > When it's ready, send it over and I'll start the process > of adding it into the next development version, 4.9.7 > > Ken > > PS: good work man! There's a problem with quota handling in courier-IMAP. If you've gone over quota, courier will not allow you in to cleanup or delete your mail. It wants to create a cache file. This is a problem with uid/filesystem based quotas, if you use courier-imap. I've been running with this for about 2 weeks now so it appears to be OK. I've labeled where my changes are with a "/* bk */" right next to the change. You just need to put the file ".domainquota" in the domains/domain.com directory. If it doesn't exist, the domain quotas are unlimited. There's more stuff that should be cleaned up, such as the filter code, and more. I just didn't have a chance to do this yet, since I don't use or plan to use filters implemented this way. Another suggestion: the information that is domain related should actually go into a database table. This would include things like domain quota, bounce message, and limits (such as those in .qmailadmin-limits). I've implemented a patch to courier-imap so I can use filesystem quotas, but I don't know if Sam is going to take it. I essentially made cache file creation a "soft" error and its ignored. Bad for performance (if your over quota), but it doesn't fail, which is what I consider to be more critical. Thanks, Brian
/***************************************************************************** ** ** $Id: vdelivermail.c,v 1.1 1998/06/16 21:00:49 chris Exp $ ** Deliver a mail to a virtual POP user - called from the .qmail-default file ** pointed to by users/assign ** ** Chris Johnson, Copyright (C) Jan '98 ** Email: [EMAIL PROTECTED] ** ** 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., 675 Mass Ave, Cambridge, MA 02139, USA. ** *****************************************************************************/ #include <errno.h> #include <fcntl.h> #include <pwd.h> #include <signal.h> #include <stdarg.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <dirent.h> #include <unistd.h> #include <ctype.h> #include <sys/stat.h> #include <sys/types.h> #include "config.h" #include "vdelivermail.h" #include "safestring.h" #include "vpopmail.h" #include "vauth.h" #include <syslog.h> #define BOUNCE_ALL "bounce-no-mailbox" #define DELETE_ALL "delete" #define TMP_BUF_SIZE 1024 #define MAX_SMALL_BUF 100 #define MAX_BUFF 512 char curdir[256]; char tmp_file[256]; char tmp_buf[TMP_BUF_SIZE]; char msgbuf[32768]; off_t msg_size; #ifdef HARD_QUOTA off_t cur_msg_bytes; off_t per_user_limit; /* bk */ off_t cur_domain_bytes; off_t per_domain_limit = 0; int CurrentDomainQuotaSizeFd = -1; #endif /* bk */ int bounce_mail(char* message); int bounce_quota(char* filename); int bounce_nouser(); int bounce_writefail(int mailfile, size_t wrote, char* msgbuf, off_t size); int notify_postmaster(); char *return_path_field = "Return-Path: "; char *from_field = "From: "; char *subject_field = "Subject: "; char TheUser[MAX_BUFF]; char TheDomain[MAX_BUFF]; int CurrentQuotaSizeFd; #ifdef USE_DELIVER_FILTER void do_filter_delivery( char *tmp_file ); /* filter vaiables */ char tmp_file_f[256]; #define MAX_LOCATION_LEN 12 #define MAX_SSTRING_LEN 256 #define MAX_TARGET_LEN 256 #define MAX_FBUFF_SIZE (MAX_LOCATION_LEN+MAX_SSTRING_LEN+MAX_TARGET_LEN+7) #define MAX_FOLDER_SIZE 512 #define FLOC_T 1 #define FLOC_F 2 #define FLOC_S 3 #define FLOC_C 4 #define FLOC_R 5 #define FLOC_AH 6 #define FLOC_AR 7 #define FLOC_B 8 #define FLOC_UNDEF 999 typedef struct _flc { char *loc; int tag; } flc; static flc filter_locs[] = { {"To", FLOC_T }, {"From", FLOC_F }, {"Subject", FLOC_S }, {"Cc", FLOC_C }, {"Reply-To", FLOC_R }, {"AnyHeaders", FLOC_AH }, {"AnyRecipents", FLOC_AR }, {"Body", FLOC_B }, {0, 0 } }; typedef struct _filter_rule { char operator; char location[MAX_LOCATION_LEN]; char loctag; char sstring[MAX_SSTRING_LEN]; char target[MAX_TARGET_LEN]; char action; char hit; struct _filter_rule *next; } filter_rule; filter_rule *filter_chain = (filter_rule *)NULL; int run_filter = 0, hit_filter = 0, hit_body = 0; char header_text[265]; int position_tag; #endif /* * Delete the temporary file */ void delete_tmp() { char message[1024]; if (unlink(tmp_file) != 0) { switch (errno) { case EACCES: scopy (message,"EACCES: permission denied",sizeof(message)); break; case EPERM: scopy (message,"EPERM: permission denied",sizeof(message)); break; case ENOENT: scopy (message,"ENOENT: path doesn't exist",sizeof(message)); break; case ENOMEM: scopy (message,"ENOMEM: out of kernel memory",sizeof(message)); break; case EROFS: scopy (message,"EROFS: filesystem read-only",sizeof(message)); break; default: sprintf (message,"Other code: %d",errno); } puts ("Yikes! Could create but can't delete temporary file!!"); puts (message); } } #ifdef USE_DELIVER_FILTER /* * Delete the temporary file used by filter_copy */ void delete_tmp_f() { char message[1024]; if (unlink(tmp_file_f) != 0) { switch (errno) { case EACCES: scopy (message,"EACCES: permission denied",sizeof(message)); break; case EPERM: scopy (message,"EPERM: permission denied",sizeof(message)); break; case ENOENT: scopy (message,"ENOENT: path doesn't exist",sizeof(message)); break; case ENOMEM: scopy (message,"ENOMEM: out of kernel memory",sizeof(message)); break; case EROFS: scopy (message,"EROFS: filesystem read-only",sizeof(message)); break; default: sprintf (message,"Other code: %d",errno); } puts ("Yikes! Could create but can't delete temporary file!! (filter)"); puts (message); } } #endif /**************************************************************************** ** Temporary fail (exit 111), & display message */ int failtemp(char *err, ...) { va_list args; va_start(args,err); vprintf(err,args); va_end(args); if (*tmp_file) delete_tmp(); exit(111); } /**************************************************************************** ** Permanant fail (exit 100), & display message */ int failperm(char *err, ...) { va_list args; puts ("Reason for failure: "); va_start(args,err); vprintf(err,args); va_end(args); if (*tmp_file) delete_tmp(); exit(100); } /**************************************************************************** ** See if the POP user exists!! */ struct passwd* pop_user_exist(char *user, char *host, char *prefix, char *bounce) { static struct passwd *pw_data; char localuser[MAX_SMALL_BUF]; char user2[MAX_SMALL_BUF]; int i; char *ttmp; char *tmpstr; char Dir[156]; int uid; int gid; if ( user== NULL || host == NULL || prefix == NULL ) { printf("gag\n"); exit(0); } for(ttmp=user;*ttmp!=0;++ttmp) if(isupper((int)*ttmp)) *ttmp = tolower((int)*ttmp); for(ttmp=host;*ttmp!=0;++ttmp) if(isupper((int)*ttmp)) *ttmp = tolower((int)*ttmp); for(ttmp=prefix;*ttmp!=0;++ttmp) if(isupper((int)*ttmp)) *ttmp = tolower((int)*ttmp); if (*prefix) { scopy(localuser,prefix,sizeof(localuser)); scat(localuser,user,sizeof(localuser)); } else { scopy(localuser,user,sizeof(localuser)); } if ( (pw_data = vauth_getpw(user, host))==NULL) { for(i=0;i<(MAX_SMALL_BUF-1)&&user[i]!=0&&user[i]!='-';++i) { user2[i] = user[i]; } user2[i] = 0; pw_data = vauth_getpw(user2, host); } /* if they don't have a dir, make one for them */ if ( pw_data != NULL && (pw_data->pw_dir == NULL || slen(pw_data->pw_dir) == 0) ) { tmpstr = vget_assign(host, Dir, 156, &uid, &gid); chdir(Dir); tmpstr = make_user_dir(pw_data->pw_name, uid, gid); i = slen(tmpstr) + slen(Dir) + 12; pw_data->pw_dir = calloc(1,i); if ( slen(tmpstr) > 0 ) { sprintf(pw_data->pw_dir, "%s/%s/%s", Dir, tmpstr, user); } else { sprintf(pw_data->pw_dir, "%s/%s", Dir, user); } vauth_setpw( pw_data, host ); } vclose(); if (!pw_data && !*bounce) { /* bk */ if (bounce_mail("Unknown email address (#5.1.1)\n")) failperm ("Unknown local POP user %s (#5.1.1)\n", localuser); } if (pw_data && pw_data->pw_gid & BOUNCE_MAIL ) { /* bk */ if (bounce_mail("Account locked, mail bounced (#5.1.2)\n")) failperm ("Account locked and mail bounced %s (#5.1.2)\n", localuser); } return pw_data; } #ifdef USE_DELIVER_FILTER /***************************************************************************** ** read filter sets */ void read_filter( void ) { FILE *ffp; char fbuff[MAX_FBUFF_SIZE+1]; int ec; int linenum = 1; filter_rule *filter_chain_act = (filter_rule *)NULL; filter_rule *filter_chain_tmp = (filter_rule *)NULL; flc *flcp; run_filter = 0; if ( (ffp = fopen( ".vpopfilter", "r" )) != NULL ) { while ( fgets( fbuff, MAX_FBUFF_SIZE, ffp ) != NULL ) { if ( *fbuff == '#' || *fbuff == '@') continue; if ( (filter_chain_tmp = (filter_rule *) malloc( sizeof( filter_rule ) )) == NULL ) failtemp ("FilterRuelRead: Out of memory (#F.0.1)\n"); if ( filter_chain == (filter_rule *)NULL ) filter_chain = filter_chain_tmp; if ( filter_chain_act == (filter_rule *)NULL ) filter_chain_act = filter_chain_tmp; else { filter_chain_act->next = filter_chain_tmp; filter_chain_act = filter_chain_tmp; } filter_chain_act->next = (filter_rule *)NULL; ec=sscanf( fbuff, "%c\t%s\t%s\t%s\t%c\n", &filter_chain_act->operator, filter_chain_act->location, filter_chain_act->sstring, filter_chain_act->target, &filter_chain_act->action ); if ( ec == 5) { filter_chain_act->hit = 0; filter_chain_act->loctag = 0; for(flcp = filter_locs; flcp->loc != (char *)0; flcp++) if (sstrcmp(flcp->loc,filter_chain_act->location) == 0 ) filter_chain_act->loctag = flcp->tag; if ( filter_chain_act->loctag == 0 ) { printf("FilterRuelRead: syntax error at line %d (%s) (#F.0.3)\n",linenum,filter_chain_act->location); run_filter = 0; return; } if ( strchr( "-&|!", filter_chain_act->operator ) == (char *)NULL ) { printf("FilterRuelRead: syntax error at line %d (invalid operator %c) (#F.0.3)\n",linenum,filter_chain_act->operator); run_filter = 0; return; } if ( strchr( "+-*", filter_chain_act->action ) == (char *)NULL ) { printf("FilterRuelRead: syntax error at line %d (invalid action %c) (#F.0.3)\n",linenum,filter_chain_act->action); run_filter = 0; return; } if ( filter_chain_act->action == '+' && sstrcmp( filter_chain_act->target, "-" ) != 0 ) { printf("FilterRuelRead: syntax error at line %d (target has to be '-' when 'inspect next rule' is set) (#F.0.3)\n",linenum); run_filter = 0; return; } linenum++; continue; } printf("FilterRuelRead: syntax error at line %d (#F.0.3)\n",linenum); run_filter = 0; return; } fclose( ffp ); } if (filter_chain != (filter_rule *)NULL) run_filter = 1; } /***************************************************************************** ** apply filter checks if one line matches filter set */ void apply_filter( char *buffer ) { filter_rule *filter_chain_act; char *ptr = buffer; flc *flcp; int len = 0; filter_chain_act = filter_chain; if ( hit_body == 0 && *buffer == '\n' ) hit_body = 1; ptr = buffer; if ( hit_body == 0 ) { if ( *buffer != '\t' && *buffer != ' ' ) /* if not continued header recalulate postion_tag */ { while( *ptr != ' ' && *ptr != '\t' ) ptr++; len = ( *ptr == ':' ) ? ptr - buffer - 2 : ptr - buffer - 1; while( *ptr == ' ' && *ptr == '\t' ) ptr++; strncpy( header_text, buffer, len ); header_text[len] = '\0'; position_tag = FLOC_UNDEF; for(flcp = filter_locs; flcp->loc != (char *)0; flcp++) if (sstrcmp(flcp->loc,header_text) == 0 ) position_tag = flcp->tag; } } else position_tag = FLOC_B; while ( *buffer != '\n' && filter_chain_act != (filter_rule *)NULL ) { if ( filter_chain_act->loctag != position_tag && filter_chain_act->loctag != FLOC_AH && (filter_chain_act->loctag != FLOC_AR && (position_tag == FLOC_T || position_tag == FLOC_C)) ) { filter_chain_act = filter_chain_act->next; continue; } switch ( filter_chain_act->loctag ) { case FLOC_T : case FLOC_F : case FLOC_S : case FLOC_C : case FLOC_R : case FLOC_AH : if ( strstr(ptr, filter_chain_act->sstring) != (char *) NULL) filter_chain_act->hit = 1; break; case FLOC_AR : if ( position_tag == FLOC_T || position_tag == FLOC_C ) if ( strstr(ptr, filter_chain_act->sstring) != (char *) NULL) filter_chain_act->hit = 1; break; case FLOC_B : if ( strstr(buffer, filter_chain_act->sstring) != (char *) NULL) filter_chain_act->hit = 1; break; } if ( filter_chain_act->hit == 1 ) hit_filter = 1; filter_chain_act = filter_chain_act->next; } } /***************************************************************************** ** copy mail accoring to filter hit */ void copy_mail( char *mailfilename, char *target_folder ) { struct stat statdata; char mailname[256]; char hostname[128]; time_t tm; int pid,i; int mailfile; size_t bytes; int inputfile; #ifdef HARD_QUOTA FILE *fs; #endif gethostname(hostname,sizeof(hostname)); pid=getpid(); for (i=0; (i < 3) && (i > -1); i++) { time (&tm); sprintf(tmp_file_f,"%s/tmp/%lu.%d.%s",target_folder,tm,pid,hostname); if (stat(tmp_file_f,&statdata) == -1) { if (errno == ENOENT) { i=-2; /* Not -1! Breaks program */ } } if (i > -1) { sleep(2); } } if (i > 0) failtemp ("Unable to stat maildir (#4.3.1)\n"); if ((mailfile = creat(tmp_file_f,S_IREAD | S_IWRITE)) == -1) { failtemp ("Can't create tempfile (#4.3.4)\n"); } if ((inputfile = open(mailfilename,S_IREAD)) == -1) { failtemp ("Can't read tempfile (#4.3.4b)\n"); } sprintf(msgbuf, "%sDelivered-To: %s@%s\n", getenv("RPLINE"), TheUser, TheDomain); msg_size = slen(msgbuf); if (write(mailfile, msgbuf, msg_size) != msg_size) { delete_tmp_f(); failtemp ("Failed to write RP & DT (#4.3.2)\n"); } #ifdef USE_DELIVER_FILTER hit_filter = 0; #endif bytes = 0; while (fgets(msgbuf,sizeof(msgbuf),stdin) != NULL) { #ifdef USE_DELIVER_FILTER /* * filer hook up #2 * pipe lines throu filter */ if ( run_filter == 1 ) apply_filter( msgbuf ); #endif msg_size += bytes; if (write(mailfile,msgbuf,bytes) != bytes) { delete_tmp_f(); failtemp ("Failed to write to tmp/ (#4.3.3)\n"); } } /* Name the new file with Maildir++ extension */ sprintf(mailname,"%s/new/%lu.%d.%s,S=%d",target_folder,tm,pid,hostname,(int)msg_size); if (bytes < 0) { delete_tmp_f(); failtemp("Error occoured reading message (#4.3.4)\n"); } if (fsync(mailfile) == -1) { delete_tmp_f(); failtemp("Unable to sync file (#4.3.5)\n"); } if (close(mailfile) == -1) { delete_tmp_f(); failtemp("Unable to close() tmp file (#4.3.6)\n"); } #ifdef USE_DELIVER_FILTER /* * filter hook up #3 * move / copy tmp file to folder(s) as to filter result * link them to "new" * delete temps */ if ( hit_filter == 1 ) { do_filter_delivery( tmp_file ); } else { if (link(tmp_file,mailname) == -1) { delete_tmp(); failtemp("Unable to link tmp to new (#4.3.7)\n"); } } #else if (link(tmp_file_f,mailname) == -1) { delete_tmp_f(); failtemp("Unable to link tmp to new (#4.3.7)\n"); } #endif delete_tmp_f(); } /***************************************************************************** ** do the actions defined in the filter chains */ void do_filter_delivery( char *tmp_file ) { filter_rule *filter_chain_act; int result = 0; filter_chain_act = filter_chain; while ( filter_chain_act != (filter_rule *)NULL ) { switch( filter_chain_act->operator ) { case '-': /* start new chain */ result = filter_chain_act->hit; break; case '&': /* and */ result = (result == 1 && filter_chain_act->hit == 1) ? 1 : 0; break; case '|': /* or */ result = (result == 1 || filter_chain_act->hit == 1) ? 1 : 0; break; case '!': /* not */ result = (filter_chain_act->hit == 1) ? 0 : result; break; default: /* undefined - error ! */ result = 0; break; } if ( (filter_chain_act->action == '-' || filter_chain_act->action == '*') && result == 1 ) { if ( *filter_chain_act->target == '.') { if ( sstrcmp( filter_chain_act->target, ".$$DELETE$$") == 0) break; copy_mail( tmp_file, filter_chain_act->target ); result = 0; } else { if ( is_email_addr( filter_chain_act->target ) == 1 ) { sprintf(tmp_buf, "/bin/cat %s | %s/bin/qmail-inject %s", tmp_file, QMAILDIR, filter_chain_act->target ); system(tmp_buf); } } } if ( filter_chain_act->action == '*' ) break; filter_chain_act = filter_chain_act->next; } } /***************************************************************************** ** prit filter chain for debugging */ void print_filter_chain() { filter_rule *filter_chain_act; filter_chain_act = filter_chain; printf("FilterChain:\n"); while ( filter_chain_act != (filter_rule *)NULL ) { printf( "op:%c\tloc:%s\tlt:%d\tss:%s\ttg:%s\tac:%c\th:%d\n", filter_chain_act->operator, filter_chain_act->location, filter_chain_act->loctag, filter_chain_act->sstring, filter_chain_act->target, filter_chain_act->action, filter_chain_act->hit ); filter_chain_act = filter_chain_act->next; } } #endif /***************************************************************************** ** Deliver mail to Maildir in directory 'deliverto' ** ** Follows procedure outlined in maildir(5). */ void deliver_mail(char *deliverto, struct passwd *pw_data ) { struct stat statdata; char mailname[256]; char hostname[128]; time_t tm; int pid,i; int mailfile; size_t bytes; size_t wrote; /* #ifdef HARD_QUOTA FILE *fs; #endif */ getcwd(curdir,256); if (chdir(deliverto) == -1) { if ( vmake_maildir(deliverto, pw_data->pw_uid, pw_data->pw_gid)== -1) { failtemp ("Could not make user dirs (#4.2.2) %s\n"); } } #ifdef USE_DELIVER_FILTER /* filter hookup #1 read filter table ".vpopfilter" */ read_filter(); #endif if (chdir("Maildir") == -1) { getcwd(mailname,256); failtemp ("Can't change to Maildir (#4.2.1) %s\n", mailname); } #ifdef HARD_QUOTA if ( pw_data!=NULL && sstrncmp(pw_data->pw_shell, "NO",2)!=0 ) { int i; per_user_limit = atol(pw_data->pw_shell); for(i=0;pw_data->pw_shell[i]!=0;++i){ if ( pw_data->pw_shell[i] == 'k' || pw_data->pw_shell[i] == 'K' ) { per_user_limit = per_user_limit * 1000; break; } if ( pw_data->pw_shell[i] == 'm' || pw_data->pw_shell[i] == 'M' ) { per_user_limit = per_user_limit * 1000000; break; } } } else { per_user_limit = HARD_QUOTA; } cur_msg_bytes = check_quota(); #endif gethostname(hostname,sizeof(hostname)); pid=getpid(); for (i=0; (i < 3) && (i > -1); i++) { time (&tm); sprintf(tmp_file,"tmp/%lu.%d.%s",tm,pid,hostname); if (stat(tmp_file,&statdata) == -1) { if (errno == ENOENT) { i=-2; /* Not -1! Breaks program */ } } if (i > -1) { sleep(2); } } if (i > 0) failtemp ("Unable to stat maildir (#4.3.1)\n"); alarm(86400); if ((mailfile = creat(tmp_file,S_IREAD | S_IWRITE)) == -1) { failtemp ("Can't create tempfile (#4.3.4)\n"); } sprintf(msgbuf, "%sDelivered-To: %s@%s\n", getenv("RPLINE"), TheUser, TheDomain); msg_size = slen(msgbuf); if ((wrote = write(mailfile, msgbuf, msg_size)) != msg_size) { /* bk: check quota */ if (errno == EDQUOT) bounce_writefail(mailfile, wrote, msgbuf, msg_size); delete_tmp(); failtemp ("Failed to write RP & DT (#4.3.2)\n"); } bytes=read(0,msgbuf,sizeof(msgbuf)); while (bytes > 0) { msg_size += bytes; if ((wrote = write(mailfile,msgbuf,bytes)) != bytes) { /* bk: check quota */ if (errno == EDQUOT) bounce_writefail(mailfile, wrote, msgbuf, bytes); delete_tmp(); failtemp ("Failed to write to tmp/ (#4.3.3)\n"); } bytes=read(0,msgbuf,sizeof(msgbuf)); } /* Name the new file with Maildir++ extension */ sprintf(mailname,"new/%lu.%d.%s,S=%d",tm,pid,hostname,(int)msg_size); if (bytes < 0) { delete_tmp(); failtemp("Error occoured reading message (#4.3.4)\n"); } if (close(mailfile) == -1) { delete_tmp(); failtemp("Unable to close() tmp file (#4.3.6)\n"); } if (link(tmp_file,mailname) == -1) { delete_tmp(); failtemp("Unable to link tmp to new (#4.3.7)\n"); } delete_tmp(); #ifdef HARD_QUOTA /*bk:*/ /* if ( pw_data != NULL && sstrncmp(pw_data->pw_shell, "NO",2)!=0 && msg_size > 1000 && cur_msg_bytes + msg_size > per_user_limit ) { cur_msg_bytes = recalc_quota("."); if ( cur_msg_bytes + msg_size > per_user_limit ) { */ if ( msg_size > 1000 && ((per_domain_limit > 0 && cur_domain_bytes + msg_size > per_domain_limit) || (pw_data != NULL && sstrncmp(pw_data->pw_shell, "NO",2)!=0 && cur_msg_bytes + msg_size > per_user_limit)) ) { cur_msg_bytes = recalc_quota("."); if ( cur_msg_bytes + msg_size > per_user_limit || (per_domain_limit > 0 && cur_domain_bytes + msg_size > per_domain_limit) ) { /*bk*/ if (bounce_quota(mailname)) { printf("User is over quota, email returned\n"); exit(100); } /* sprintf(tmp_file, "%s/domains/.over-quota.msg",VPOPMAILDIR); fs=fopen(tmp_file, "r"); if ( fs == NULL ) { printf("User is over quota email returned\n"); } else { while( fgets( tmp_file, 256, fs ) != NULL ) { fputs(tmp_file, stdout); } fclose(fs); } unlink(mailname); */ /* exit 100 will cause the email to be bounced back to sender */ /*printf("bounce\n"); */ /* exit(100); */ } } else { update_quota(cur_msg_bytes + msg_size); } #endif } /***************************************************************************** ** The main bit :) If it gets to the end of here, delivery was successful */ int main(int argc, char *argv[]) { static struct passwd *pw_data; char *deliverto; char bounce[1024]; char prefix[1024]; char *tmpstr; if (argc > 3) { failtemp ("Syntax: %s [prefix [bounceable_mail]]\n",argv[0]); } openlog("vdelivermail",LOG_PID,LOG_MAIL); *bounce = 0; *prefix = 0; *tmp_file = 0; if (argc > 1) { scopy(prefix,argv[1],sizeof(prefix)); if (argc == 3) { scopy (bounce,argv[2],sizeof(prefix)); } } scopy(TheUser, getenv("EXT"), MAX_BUFF); scopy(TheDomain, getenv("HOST"), MAX_BUFF); lowerit(TheUser); lowerit(TheDomain); vget_real_domain(TheDomain,MAX_BUFF); pw_data=pop_user_exist(TheUser,TheDomain,prefix,bounce); if (!pw_data) { if ( is_delete(bounce) ) { exit(0); } if ( !is_bounce(bounce) ) { printf ("POP user does not exist, but will deliver to %s\n",bounce); } deliverto = bounce; } else { if ( check_forward_deliver(pw_data->pw_dir) == 1 ) { exit(0); } deliverto = pw_data->pw_dir; } if ( is_email_addr(deliverto) == 1) { tmpstr = email_it(deliverto); unlink(tmpstr); } else if ( is_bounce(deliverto) == 1) { /* bk */ if (bounce_nouser()) { FILE *fs; if ( (fs=fopen(".no-user.msg","r")) != NULL ) { while( fgets( bounce, 1024, fs ) != NULL ) { printf("%s", bounce); } fclose(fs); } else { printf( "Sorry, no mailbox here by that name. vpopmail (#5.1.1)\n"); } exit(100); } } else { deliver_mail(deliverto, pw_data); } exit(0); } int is_bounce(deliverto) char *deliverto; { if ( sstrcmp( deliverto, BOUNCE_ALL ) == 0 ) { return(1); } return(0); } int is_delete(deliverto) char *deliverto; { if ( sstrcmp( deliverto, DELETE_ALL ) == 0 ) { return(1); } return(0); } #ifdef HARD_QUOTA /* * Assumes the current working directory is user/Maildir * * We go off to look at cur and tmp dirs * * return size of files * */ off_t check_quota() { off_t mail_size = 0; char tmpbuf[100]; char *cwd; int fd; if ((CurrentQuotaSizeFd= open(".current_size", O_CREAT|O_RDWR, S_IWUSR|S_IRUSR))==-1){ return(mail_size); } read(CurrentQuotaSizeFd, tmpbuf, 100); mail_size = (off_t)atoi(tmpbuf); /* bk: get domain quota size */ if (CurrentDomainQuotaSizeFd >= 0) { close(CurrentDomainQuotaSizeFd); CurrentDomainQuotaSizeFd = -1; } if ((cwd = getcwd(NULL, 128)) != NULL) { chdir("../.."); per_domain_limit = 0; cur_domain_bytes = 0; /* First get the quota, if any */ if ((fd=open(".domainquota", O_RDONLY)) >= 0) { read(fd, tmpbuf, 100); per_domain_limit = (off_t)atoi(tmpbuf); close(fd); } if (per_domain_limit > 0) CurrentDomainQuotaSizeFd=open(".current_size", O_CREAT|O_RDWR, S_IWUSR|S_IRUSR); chdir(cwd); free(cwd); if (CurrentDomainQuotaSizeFd >= 0) { read(CurrentQuotaSizeFd, tmpbuf, 100); cur_domain_bytes = (off_t)atoi(tmpbuf); } } /* bk */ return(mail_size); } off_t recalc_quota(char *dir_name) { off_t mail_size = 0; char tmpbuf[100]; char *cwd; mail_size = count_dir(dir_name); /* bk: now calculate domain quota */ if (CurrentDomainQuotaSizeFd >= 0) { if ((cwd = getcwd(NULL, 128)) != NULL) { chdir("../.."); cur_domain_bytes = count_dir("."); chdir(cwd); free(cwd); } } if ( msg_size > 1000 && (mail_size > per_user_limit || (per_domain_limit > 0 && mail_size > per_domain_limit ))) { mail_size -= msg_size; cur_domain_bytes -= msg_size; } sprintf(tmpbuf, "%d\n", (int)mail_size); lseek(CurrentQuotaSizeFd, 0L, SEEK_SET); write(CurrentQuotaSizeFd, tmpbuf, slen(tmpbuf)); close(CurrentQuotaSizeFd); /* bk: update domain quota */ if (CurrentDomainQuotaSizeFd >= 0) { sprintf(tmpbuf, "%d\n", (int)cur_domain_bytes); lseek(CurrentDomainQuotaSizeFd, 0L, SEEK_SET); write(CurrentDomainQuotaSizeFd, tmpbuf, slen(tmpbuf)); close(CurrentDomainQuotaSizeFd); CurrentDomainQuotaSizeFd = -1; } return(mail_size); } void update_quota(off_t new_size) { char tmpbuf[100]; sprintf(tmpbuf, "%d\n", (int)new_size); lseek(CurrentQuotaSizeFd, 0L, SEEK_SET); write(CurrentQuotaSizeFd, tmpbuf, slen(tmpbuf)); close(CurrentQuotaSizeFd); /* bk: update domain quota */ if (CurrentDomainQuotaSizeFd >= 0) { sprintf(tmpbuf, "%d\n", (int)cur_domain_bytes); lseek(CurrentDomainQuotaSizeFd, 0L, SEEK_SET); write(CurrentDomainQuotaSizeFd, tmpbuf, slen(tmpbuf)); close(CurrentDomainQuotaSizeFd); CurrentDomainQuotaSizeFd = -1; } } off_t count_dir(dir_name) char *dir_name; { DIR *mydir; struct dirent *mydirent; struct stat statbuf; off_t file_size = 0; char *tmpstr; if ( dir_name != NULL ) if (chdir(dir_name) == -1) return(0); if ( (mydir = opendir(".")) == NULL ) return(0); while( (mydirent=readdir(mydir)) != NULL ) { if ( sstrcmp( mydirent->d_name, "..") == 0 ) continue; if ( sstrcmp( mydirent->d_name, ".") == 0 ) continue; if ( (tmpstr=strstr(mydirent->d_name, ",S="))!=NULL) { tmpstr += 3; file_size += atoi(tmpstr); } else if (stat(mydirent->d_name,&statbuf)==0 && (statbuf.st_mode & S_IFDIR) ) { file_size += count_dir(mydirent->d_name); } } closedir(mydir); if ( dir_name != NULL && sstrcmp(dir_name, ".." )!=0 && sstrcmp(dir_name, "." )!=0) { chdir(".."); } return(file_size); } #endif int is_email_addr(deliverto) char *deliverto; { int i; for(i=0;deliverto[i]!=0;++i){ if ( deliverto[i] == '@' ) return(1); } return(0); } int email_file(char *filename, char *address) { char tmp_buf[256]; sprintf(tmp_buf, "/bin/cat %s | %s/bin/qmail-inject %s", filename, QMAILDIR, address ); system(tmp_buf); return(0); } char *email_it(deliverto) char *deliverto; { static char tmp_file[256]; char hostname[128]; time_t tm; int pid; int mailfile; size_t bytes; gethostname(hostname,sizeof(hostname)); pid=getpid(); time (&tm); sprintf(tmp_file,"/tmp/%lu.%d.%s",tm,pid,hostname); if ((mailfile = creat(tmp_file,S_IREAD | S_IWRITE)) == -1) { failtemp ("Can't create tempfile (#4.3.8)\n"); } sprintf(msgbuf, "%sDelivered-To: %s@%s\n", getenv("RPLINE"), TheUser, TheDomain); if (write(mailfile, msgbuf, slen(msgbuf)) != slen(msgbuf)) { delete_tmp(); failtemp ("Failed to write RP & DT (#4.3.9)\n"); } while((bytes=read(0,msgbuf,sizeof(msgbuf)))>0) { if (write(mailfile,msgbuf,bytes) != bytes) { delete_tmp(); failtemp ("Failed to write to tmp/ (#4.3.10)\n"); } } if (bytes < 0) { delete_tmp(); failtemp("Error occoured reading message (#4.3.11)\n"); } if (fsync(mailfile) == -1) { delete_tmp(); failtemp("Unable to sync file (#4.3.12)\n"); } if (close(mailfile) == -1) { delete_tmp(); failtemp("Unable to close() tmp file (#4.3.13)\n"); } sprintf(tmp_buf, "/bin/cat %s | %s/bin/qmail-inject %s", tmp_file, QMAILDIR, deliverto ); system(tmp_buf); return(tmp_file); } int check_forward_deliver(char *dir) { static char email_address[100]; char tmpbuf[500]; FILE *fs; int i; int return_value = 0; char *tmpstr = NULL; sprintf(tmpbuf, "%s/.qmail", dir); fs = fopen(tmpbuf,"r"); if ( fs == NULL ) { return(-1); } while ( fgets(email_address, 100, fs ) != NULL ) { for(i=0;email_address[i]!=0;++i) { if (email_address[i] == '\n') email_address[i] = 0; } if ( return_value == 0 ) { tmpstr = email_it(email_address); } else { email_file(tmpstr, email_address); } return_value = 1; } fclose(fs); if ( tmpstr != NULL ) { unlink(tmpstr); } return(return_value); } int bounce_it_back( char *filename ) { FILE *fs; FILE *fs1; char tmpbuf1[300]; int i,j; int first_line = 0; int subject_found = 0; int from_found = 0; if ( (fs = fopen(filename,"r+")) == NULL ) { unlink(filename); return(-1); } /* find return path or from variable */ while ( fgets(tmp_buf, TMP_BUF_SIZE, fs ) != NULL ) { for(i=0;return_path_field[i] != 0; ++i){ if ( tmp_buf[i] != return_path_field[i] ) break; } if ( return_path_field[i] == 0 ) { while(tmp_buf[i] == ' ' || tmp_buf[i] == '<') ++i; for(j=0;tmp_buf[i]!='>' && tmp_buf[i]!=0;++i,++j){ tmp_file[j] = tmp_buf[i]; } tmp_file[j] = 0; break; } else { for(i=0;from_field[i] != 0; ++i){ if ( tmp_buf[i] != return_path_field[i] ) break; } if ( from_field[i] == 0 ) { while(tmp_buf[i]==' '|| tmp_buf[i] == '<') ++i; for(j=0;tmp_buf[i]!='>'&&tmp_buf[i]!=0;++i,++j){ tmp_file[j] = tmp_buf[i]; } tmp_file[j] = 0; break; } } } rewind(fs); sprintf(tmpbuf1,"%s.bak", filename); if ( (fs1 = fopen(tmpbuf1,"w+")) == NULL ) { unlink(filename); return(-1); } while ( fgets(tmp_buf, TMP_BUF_SIZE, fs ) != NULL ) { if ( subject_found == 0 ) { for(i=0;subject_field[i] != 0; ++i){ if ( tmp_buf[i] != subject_field[i] ) break; } if ( subject_field[i] == 0 ) { subject_found = 1; fprintf( fs1, "Subject: User over quota RE: %s", &tmp_buf[i]); continue; } } if ( from_found == 0 ) { for(i=0;from_field[i] != 0; ++i){ if ( tmp_buf[i] != from_field[i] ) break; } if ( from_field[i] == 0 ) { from_found = 1; fprintf( fs1, "From: postmaster@%s\n", getenv("HOST")); continue; } } if ( first_line == 0 ) { for(i=0;tmp_buf[i]!=0;++i) if ( !isspace(tmp_buf[i]) ) break; if ( tmp_buf[i] == 0 ) { first_line = 1; fputs(tmp_buf, fs1); fprintf(fs1, "User is over quota, message bounced.\n\n"); continue; } } fputs(tmp_buf, fs1); /* only return 15 lines of the message */ if (first_line > 0) { ++first_line; if ( first_line > 15 ) break; } } fclose(fs); fclose(fs1); sprintf(tmp_buf, "/bin/cat %s | %s/bin/qmail-inject %s", tmpbuf1, QMAILDIR, tmp_file ); system(tmp_buf); unlink(filename); unlink(tmpbuf1); return(0); } int bounce_quota(char* filename) { char* to = getenv("EXT"); char* domain = getenv("HOST"); char* sender = getenv("SENDER"); char tmp_filename[256]; char buf[300]; FILE* fp1; FILE* fp2; syslog(LOG_INFO, "bounce quota to <%s@%s> from <%s>", to, domain, sender); sprintf(buf, "%s/bin/qmail-inject -f postmaster@%s %s", QMAILDIR, domain, sender); if ((fp1 = popen(buf, "w")) == NULL) return(-1); fprintf(fp1, "From: postmaster@%s\n", domain); fprintf(fp1, "To: %s\n", sender); fprintf(fp1, "Subject: delivery failure notice\n"); fprintf(fp1, "\n<%s@%s>:\n", to, domain); /* get message, use default if none supplied */ sprintf(tmp_filename, "%s/domains/.over-quota.msg",VPOPMAILDIR); if ((fp2 = fopen(tmp_filename, "r")) != NULL) { while (fgets(buf, sizeof(buf), fp2) != NULL) { fputs(buf, fp1); } fclose(fp2); } else { fprintf(fp1, "This mailbox is currently full, email returned.\n"); } if ((fp2 = fopen(filename, "r")) != NULL) { fprintf(fp1, "\n\n--- Below is a copy of the message.\n\n"); while (fgets(buf, sizeof(buf), fp2) != NULL) { fputs(buf, fp1); } fclose(fp2); } fclose(fp1); unlink(filename); notify_postmaster(); return(0); } int bounce_mail(char* message) { char* to = getenv("EXT"); char* domain = getenv("HOST"); char* sender = getenv("SENDER"); char buf[300]; FILE* fp1; int bytes; syslog(LOG_INFO, "bounce mail - quota to <%s@%s> from <%s>", to, domain, sender); sprintf(buf, "%s/bin/qmail-inject -f postmaster@%s %s", QMAILDIR, domain, sender); if ((fp1 = popen(buf, "w")) == NULL) return(-1); fprintf(fp1, "From: postmaster@%s\n", domain); fprintf(fp1, "To: %s\n", sender); fprintf(fp1, "Subject: delivery failure notice\n"); fprintf(fp1, "\n<%s@%s>:\n", to, domain); fprintf(fp1, "%s\n", message); fprintf(fp1, "\n\n--- Below is a copy of the message.\n\n"); bytes = read(0, buf, sizeof(buf)); while (bytes > 0) { if (fwrite(buf, 1, bytes, fp1) != bytes) { failperm("User is over quota, email returned\n"); } bytes = read(0, buf, sizeof(buf)); } fclose(fp1); notify_postmaster(); return(0); } int bounce_nouser() { char* to = getenv("EXT"); char* domain = getenv("HOST"); char* sender = getenv("SENDER"); char tmp_filename1[256]; char buf[300]; FILE* fp1; FILE* fp2; int bytes; syslog(LOG_INFO, "bounce nouser to <%s@%s> from <%s>", to, domain, sender); sprintf(buf, "%s/bin/qmail-inject -f postmaster@%s %s", QMAILDIR, domain, sender); if ((fp1 = popen(buf, "w")) == NULL) return(-1); fprintf(fp1, "From: postmaster@%s\n", domain); fprintf(fp1, "To: %s\n", sender); fprintf(fp1, "Subject: delivery failure notice\n"); fprintf(fp1, "\n<%s@%s>:\n", to, domain); if ((fp2 = fopen(".no-user.msg","r")) != NULL ) { while (fgets(buf, sizeof(buf), fp2) != NULL) { fprintf(fp1, "%s", buf); } fclose(fp2); } else { fprintf(fp1, "Sorry, no mailbox here by that name. vpopmail (#5.1.1)\n"); } fprintf(fp1, "\n\n--- Below is a copy of the message.\n\n"); bytes = read(0, buf, sizeof(buf)); while (bytes > 0) { if (fwrite(buf, 1, bytes, fp1) != bytes) { failperm("Sorry, no mailbox here by that name. vpopmail (#5.1.1)\n"); } bytes = read(0, buf, sizeof(buf)); } fclose(fp1); unlink(tmp_filename1); return(0); } int bounce_writefail(int mailfile, size_t wrote, char* msgbuf, off_t size) { char* to = getenv("EXT"); char* domain = getenv("HOST"); char* sender = getenv("SENDER"); char tmp_filename[256]; char buf[300]; int bytes; FILE* fp1; FILE* fp2; syslog(LOG_INFO, "bounce writefail to <%s@%s> from <%s>", to, domain, sender); /* * drop message on floor if both sender and recipient are same * postmaster for local domain */ sprintf(buf, "postmaster@%s", domain); if (!strcmp(sender, buf)) { syslog(LOG_INFO, "bounce writefail - dropping postmaster notification"); delete_tmp(); exit(0); } sprintf(buf, "%s/bin/qmail-inject -f postmaster@%s %s", QMAILDIR, domain, sender); if ((fp1 = popen(buf, "w")) == NULL) return(-1); fprintf(fp1, "From: postmaster@%s\n", domain); fprintf(fp1, "To: %s\n", sender); fprintf(fp1, "Subject: delivery failure notice\n\n"); fprintf(fp1, "\n<%s@%s>:\n", to, domain); /* get message, use default if none supplied */ sprintf(tmp_filename, "%s/domains/.over-quota.msg",VPOPMAILDIR); if ((fp2 = fopen(tmp_filename, "r")) != NULL) { while (fgets(buf, sizeof(buf), fp2) != NULL) { fputs(buf, fp1); } fclose(fp2); } else { fprintf(fp1, "This mailbox is currently full, email returned.\n"); } fprintf(fp1, "\n\n--- Below is a copy of the message.\n\n"); /* forward what made it to file */ if (mailfile > 0) { lseek(mailfile, 0, SEEK_SET); bytes = read(mailfile, buf, sizeof(buf)); while (bytes > 0) { if (fwrite(buf, 1, bytes, fp1) != bytes) { failperm("This mailbox is currently full, email returned\n"); } bytes = read(0, buf, sizeof(buf)); } } if (size > 0 && size > wrote) { if (wrote < 0) wrote = 0; /* forward what was left in buffer */ if (fwrite(msgbuf, 1, size - wrote, fp1) != size - wrote) { failperm("This mailbox is currently full, email returned\n"); } } /* forward rest of STDIN */ bytes = read(0, buf, sizeof(buf)); while (bytes > 0) { if (fwrite(buf, 1, bytes, fp1) != bytes) { failperm("This mailbox is currently full, email returned\n"); } bytes = read(0, buf, sizeof(buf)); } fclose(fp1); delete_tmp(); notify_postmaster(); exit(0); } int notify_postmaster() { char* to = getenv("EXT"); char* domain = getenv("HOST"); char* sender = getenv("SENDER"); char buf[300]; FILE* fp1; syslog(LOG_INFO, "notify postmaster of quota bounce to <%s@%s> from <%s>", to, domain, sender); sprintf(buf, "%s/bin/qmail-inject -f postmaster@%s postmaster@%s", QMAILDIR, domain, domain); if ((fp1 = popen(buf, "w")) == NULL) return(-1); fprintf(fp1, "From: postmaster@%s\n", domain); fprintf(fp1, "To: postmaster@%s\n", domain); fprintf(fp1, "Subject: delivery failure notice\n\n"); fprintf(fp1, "Delivery From: <%s>\n", sender); fprintf(fp1, " To: <%s@%s>\n\n", to, domain); fprintf(fp1, "Over Quota\n"); fclose(fp1); return(0); }