#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pop3.h" #include "misc.h" #include "../paths.h" #include "mailhead.h" // Error code returned to sendmail #define VERR_USER_UNKNOWN 67 #define VERR_CANTCREAT 73 #define VERR_NOPERM 77 static void vdeliver_checkdir(const char *dirpath) { struct stat st; if (stat(dirpath,&st)==-1){ syslog (LOG_ERR,"Directory %s does not exist",dirpath); exit (-1); }else if (!S_ISDIR(st.st_mode)){ syslog (LOG_ERR,"%s is not a directory",dirpath); exit (-1); } } struct SEEN_LOOKUP{ const char *user; struct SEEN_LOOKUP *next; }; static struct SEEN_LOOKUP *new_SEEN_LOOKUP ( const char *user, struct SEEN_LOOKUP *next) { struct SEEN_LOOKUP *ret = (struct SEEN_LOOKUP*)calloc(1 ,sizeof(struct SEEN_LOOKUP)); ret->user = strdup(user); ret->next = next; return ret; } struct VDEV_CTX{ FILE *faliases[3]; char fallback[500]; struct SEEN_LOOKUP *seen; long quota; long mailsize; bool match_gecos; bool accept_lock; char filter[1000]; MAILHEADER head; char *from; // Address to use for vacation, auto-responder // and errors const char *user; // Original target of the message const char *domain; VDEV_CTX(); }; VDEV_CTX::VDEV_CTX() { memset (faliases,0,sizeof(faliases)); fallback[0] = '\0'; seen = NULL; quota = 0; mailsize = 0; match_gecos = false; accept_lock = false; filter[0] = '\0'; from = NULL; user = NULL; domain = NULL; } /* Is that alias was already processed Return != 0 if this alias was processed once */ static int vdeliver_wasseen (VDEV_CTX &ctx, const char *user) { int ret = 0; struct SEEN_LOOKUP *e = ctx.seen; while (e != NULL){ if (strcmp(e->user,user)==0){ ret = 1; break; } e = e->next; } return ret; } static bool alarm_seen = false; static void alarm_fct(int) { alarm_seen = true; } static int vdeliver_copy (FILE *fin, FILE *fout) { int ret = 0; signal (SIGALRM,alarm_fct); alarm (60); char buf[1000]; rewind (fin); alarm_seen = false; while (1){ if (alarm_seen){ ret = -1; break; }else if (fgets(buf,sizeof(buf)-1,fin)==NULL){ break; }else{ fputs (buf,fout); } } alarm (0); return ret; } /* Copy a message to a filter, itself pointed to the open mailbox Return the exit status of the filter to allow proper bouncing/return of the message if the filter fails due to an error condition. If a filter doesn't exist, the mail is delivered normally, and there is no need for an error return. However, if the message is not delivered, then it needs to be by design ( exit status of 0 ) or if there is an error, to notify the sender with the appropriate error. */ static int vdeliver_filter (FILE *fin, const char *filter, FILE *fout) { int ret = 0; int tb[2]; if (pipe(tb)==-1){ syslog (LOG_ERR,"Can't setup pipe for filtering, filtering disabled"); vdeliver_copy(fin,fout); }else{ int status = -1; pid_t pid = fork(); if (pid == 0){ close (tb[1]); dup2 (tb[0],0); dup2 (fileno(fout),1); status = system (filter); if (WIFEXITED(status)){ ret = WEXITSTATUS(status); } /** * We must completely exit otherwise all forked processes will * continue to process aliases and deliver with geometric * furvitude. */ _exit( ret ); }else if (pid != (pid_t)-1){ close (tb[0]); FILE *newfout = fdopen (tb[1],"w"); vdeliver_copy (fin,newfout); fclose (newfout); close (tb[1]); wait (&status); if (WIFEXITED(status)){ ret = WEXITSTATUS(status); } }else{ syslog (LOG_ERR,"Can't fork, filtering disabled"); vdeliver_copy(fin,fout); close (tb[0]); close (tb[1]); } } return ret; } // Copy a message, and escape lines starting with From in the body static void vdeliver_copy_from (FILE *fin, FILE *fout) { char buf[1000]; rewind (fin); bool first = true; while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ if (!first && strncmp(buf,"From ",5)==0){ fputc ('>',fout); } first = false; fputs (buf,fout); } } static FILE *vdeliver_openex (const char *fname, struct passwd *pwd) { FILE *ret = NULL; int i; for (i=0; i<20; i++){ ret = fopen (fname,"a"); if (ret == NULL){ break; }else{ int fd = fileno(ret); if (flock(fd, LOCK_EX|LOCK_NB) != -1) { struct group *grp = getgrnam ("mail"); int mailgid = 0; if (grp != NULL) mailgid = grp->gr_gid; fchown (fd,pwd->pw_uid,mailgid); fchmod (fd,0660); break; }else{ fclose(ret); ret = NULL; sleep(1); } } } if (i==20 && ret == NULL) syslog (LOG_ERR,"Can't lock %s (%m)",fname); return ret; } static int vdeliver_doaliases ( VDEV_CTX &ctx, const char *user, const char *domain, FILE *mailin); /* Check if an email address is valid. Well, it checks if there is no special shell characters, so it is suitable on the command line. */ static bool vdeliver_validadr(const char *adr) { bool ret = true; static const char *shell_chars = "&|'`\"<>?*#~!$()"; const char *pt = shell_chars; while (*pt != '\0'){ if (strchr(adr,*pt)!=NULL){ ret = false; syslog (LOG_ERR,"Email address with shell special chars rejected: %s",adr); break; } pt++; } return ret; } /* Send one copy of the message to a remote user Return -1 if any error. */ static int vdeliver_send ( const char *adr, const char *from, FILE *fin, const char *header) // Optional header to copy first { int ret = -1; if (vdeliver_validadr(adr)){ FILE *ff = fopen (VDELIVER_SEND_LOCK,"a"); if (ff != NULL){ int fd = fileno(ff); if (flock(fd, LOCK_EX) != -1) { char cmd[1000]; if (from != NULL && from[0] != '\0' && vdeliver_validadr(from)){ snprintf (cmd,sizeof(cmd)-1,"%s -i -f %s %s" ,USR_SBIN_SENDMAIL,from,adr); }else{ snprintf (cmd,sizeof(cmd)-1,"%s -i %s",USR_SBIN_SENDMAIL,adr); } for (int i=0; i<5; i++){ FILE *fout = popen (cmd,"w"); if (fout == NULL){ syslog (LOG_ERR,"vdeliver_send: (try %d) Can't talk back to sendmail for user %s (%m)",i,adr); }else{ fputs (header,fout); ret = vdeliver_copy (fin,fout); int ret2 = pclose(fout); if (ret2 == -1) ret = -1; if (ret != 0){ syslog (LOG_ERR,"vdeliver_send: (try %d) cmd return %d: Was relaying to %s (errno=%d)",i,ret,adr,errno); ret = WEXITSTATUS(ret); }else{ break; } } sleep(1); } }else{ syslog (LOG_ERR,"vdeliver_send: can't lock /var/run/vdeliver.send.lock (%m)"); } fclose (ff); }else{ syslog (LOG_ERR,"vdeliver_send: can't open /var/run/vdeliver.send.lock (%m)"); } } return ret; } static int vdeliver_send ( const char *adr, const char *from, FILE *fin) { return vdeliver_send (adr,from,fin,""); } static int vdeliver_reply ( VDEV_CTX &ctx, const char *user, const char *domain, const char *path) { int ret = -1; FILE *fin = fopen (path,"r"); if (fin == NULL){ fprintf (stderr,"can't open file %s\n",path); syslog (LOG_ERR,"Can't open file %s (%m)",path); }else{ char email[PATH_MAX],header[PATH_MAX]; snprintf (email,sizeof(email)-1,"%s@%s",user,domain); snprintf (header,sizeof(header)-1,"to: %s\n",ctx.from); ret = vdeliver_send (ctx.from,email,fin,header); fclose (fin); } return ret; } static int vdeliver_splitline( VDEV_CTX &ctx, char *ptpt, const char *domain, FILE *faliases, FILE *mailin); /* Expands aliases. Return true if one aliases was found */ static int vdeliver_checkaliases( VDEV_CTX &ctx, const char *user, const char *domain, FILE *mailin, bool &found) { int ret = 0; found = false; for (int i=0; i<3 && !found; i++){ FILE *fin = ctx.faliases[i]; if (fin != NULL){ char buf[2000]; rewind (fin); while (fgets (buf,sizeof(buf)-1,fin)!=NULL){ char *pt = str_skip (buf); char *ptpt = strchr (pt,':'); if (ptpt != NULL){ *ptpt++ = '\0'; strip_end (pt); if (strcasecmp(user,pt)==0 && !vdeliver_wasseen(ctx,pt)){ found = true; ctx.seen = new_SEEN_LOOKUP(pt,ctx.seen); // Ok, at this point, we have seen the first // line of the aliases we are looking for // but there may be other lines, so we pass the // file handle ret |= vdeliver_splitline (ctx ,ptpt,domain,fin,mailin); break; } } } } } return ret; } const long QUOTA_NOLIMIT=0x7fffffffl; static void vdeliver_vacation ( VDEV_CTX &ctx, const char *domain, const char *user) { // Make sure we do not send crap to mailing list if (strcasecmp(ctx.head.priority,"bulk")!=0 && strstr(ctx.head.from.adr,"MAILER_DAEMON")==NULL){ char path[PATH_MAX]; snprintf (path,sizeof(path)-1,"/var/spool/vmail/files/%s/%s.vacation" ,domain,user); char path_list[PATH_MAX]; snprintf (path_list,sizeof(path_list)-1,"/var/spool/vmail/files/%s/%s.vacation.db" ,domain,user); struct stat st; if (stat(path,&st)==-1){ snprintf (path,sizeof(path)-1,"/vhome/%s/home/%s/.vacation.msg",domain,user); snprintf (path_list,sizeof(path_list)-1,"/vhome/%s/home/%s/.vacation.db" ,domain,user); } if (stat(path,&st)!=-1){ // We check if we have already answered this // The db file may also contain an activation period in the // form: // #date: start_date end_date // The dates are in the following format // yyyy/mm/dd char datenow[12]; { time_t now = time(NULL); struct tm *t = localtime (&now); snprintf (datenow,sizeof(datenow)-1,"%04d/%02d/%02d" ,t->tm_year + 1900,t->tm_mon+1,t->tm_mday); } int i; for (i=0; i<10; i++){ FILE *f = fopen (path_list,"r+"); if (f == NULL) break; // No db file, no vacation if (flock (fileno(f),LOCK_EX|LOCK_NB)==-1){ sleep(1); fclose (f); }else{ rewind (f); char line[1000]; bool found = false; bool active = true; while (fgets(line,sizeof(line)-1,f)!=NULL){ strip_end (line); if (strcasecmp(line,ctx.head.from.adr)==0){ found = true; break; }else if (strncmp(line,"#date ",6)==0){ char *pt = line+6; while (isspace(*pt)) pt++; char *start = pt; while (*pt > ' ') pt++; if (isspace(*pt)) *pt++ = '\0'; while (isspace(*pt)) pt++; char *end = pt; syslog (LOG_ERR,"Compare :%s: :%s: :%s:\n",datenow,start,end); if (strcmp(start,datenow)>0){ active = false; break; }else if (isdigit(end[0]) && strcmp(end,datenow) < 0){ active = false; break; } } } if (active && !found){ fseek (f,ftell(f),SEEK_SET); fprintf (f,"%s\n",ctx.head.from.adr); // We close immediatly, so if the reply takes // time, another vdeliver may process the vacation // file fclose (f); vdeliver_reply (ctx,user,domain,path); }else{ fclose (f); } break; } } if (i==10) syslog(LOG_ERR,"Can't lock %s, no vacation",path_list); } } } static int vdeliver_pipe( const char *cmd, VDEV_CTX &ctx, struct passwd *pwd, FILE *mailin); /* Write the message to the inbox Return the status of the operation(s) If a filter is used, return the status of the filter If a message is copied, return the status of the copy opertation */ static int vdeliver_writemsg ( VDEV_CTX &ctx, struct passwd *pwd, const char *domain, FILE *filein) { int ret = 0; char user_path[PATH_MAX],domain_path[PATH_MAX]; // execute procmail if recipe exists // First check for a user .procmailrc in home directory snprintf (user_path,sizeof(user_path)-1,"/vhome/%s/home/%s/.procmailrc",domain,pwd->pw_name); snprintf (domain_path,sizeof(domain_path)-1,"/etc/vmail/procmailrc.%s",domain); struct stat st; bool do_vacation = true; if (stat(user_path,&st)!=-1){ char cmd[PATH_MAX]; snprintf (cmd,sizeof(cmd)-1,"/usr/bin/procmail -t -Y -m /etc/vmail/vprocmailrc.user %s %s",pwd->pw_name,domain); ret = vdeliver_pipe (cmd,ctx,pwd,filein); // procmail return 1 if the mail was silently dropped // so we do not want to return anything (errors) to the sender // especially no vacation stuff. if (ret == 1){ do_vacation = false; ret = 0; } }else if (stat(domain_path,&st)!=-1){ // Ok, there is a domain wide ruleset char cmd[PATH_MAX]; snprintf (cmd,sizeof(cmd)-1,"/usr/bin/procmail -t -Y -m /etc/vmail/vprocmailrc.domain %s %s",pwd->pw_name,domain); ret = vdeliver_pipe (cmd,ctx,pwd,filein); if (ret == 1){ do_vacation = false; ret = 0; } }else{ char dirpath[PATH_MAX],filepath[PATH_MAX]; snprintf (dirpath,sizeof(dirpath)-1,"%s/%s",VAR_SPOOL_VMAIL,domain); vdeliver_checkdir (dirpath); snprintf (filepath,sizeof(filepath)-1,"%s/%s",dirpath,pwd->pw_name); FILE *fout = vdeliver_openex (filepath,pwd); if (fout != NULL){ bool deliver = true; // Wait until we have the lock to check the quota long uquota = QUOTA_NOLIMIT; char uquotap[PATH_MAX]; snprintf (uquotap,sizeof(uquotap)-1,"%s/%s.quota" ,dirpath,pwd->pw_name); FILE *fin = fopen (uquotap,"r"); if (fin != NULL){ fscanf (fin,"%ld",&uquota); uquota <<= 10; // In K, put it in bytes fclose (fin); } if (ctx.quota != QUOTA_NOLIMIT || uquota != QUOTA_NOLIMIT){ struct stat st; // The limit is the user quota if defined, or the // domain wide quota. So one user may have a larger // quota than the domain default. long q = uquota != QUOTA_NOLIMIT ? uquota : ctx.quota; if (fstat(fileno(fout),&st)!=-1 && st.st_size+ctx.mailsize > q){ deliver = false; ret = VERR_CANTCREAT; fprintf (stderr,"Out of disk quota for this user inbox\n"); } } if (deliver){ if (ctx.filter[0] == '\0'){ // Return the exit status of the copy operation ret = vdeliver_copy (filein,fout); }else{ // Return the exit status of the filter operation ret = vdeliver_filter (filein,ctx.filter,fout); } } fclose (fout); }else{ syslog (LOG_ERR,"Can't open file %s (%m)",filepath); } } if (ret == 0 && do_vacation){ vdeliver_vacation (ctx,domain,pwd->pw_name); } return ret; } /* Check if there is a auto-respond file for the target acccount. If yes, send it back to the author of the message Return -1 if there is an error Return 0 if there is no auto-respond file Return 1 if there is one an all is fine */ int vdeliver_autorespond (VDEV_CTX &ctx, const char *user, const char *domain) { int ret = 0; char path[PATH_MAX]; snprintf (path,sizeof(path)-1,"/var/spool/vmail/files/%s/%s.auto",domain,user); struct stat st; if (stat(path,&st)==-1){ snprintf (path,sizeof(path)-1,"/vhome/%s/home/%s/.autoresponder.msg",domain,user); } if (stat(path,&st)!=-1){ // Make sure we do not send crap to mailing list if (strcasecmp(ctx.head.priority,"bulk")==0 || strstr(ctx.head.from.adr,"MAILER_DAEMON")!=NULL){ ret = 1; }else{ ret = vdeliver_reply (ctx,user,domain,path); if (ret != -1) ret = 1; } } return ret; } static char *vdeliver_replace ( char *ret, // String to modify, A new string will be allocated // and this one will be freed int &offset, // Offset where the %X was found. It will be replaced const char *val) // by this value { int lenval = strlen(val); int len = strlen(ret); char *newret = (char*)malloc(len+lenval+1); if (newret == NULL){ syslog (LOG_ERR,"vdeliver_format: Out of memory"); offset += 2; }else{ memcpy (newret,ret,len); strcpy (newret+offset,val); strcpy (newret+offset+lenval,ret+offset+2); free (ret); ret = newret; offset += lenval; } return ret; } /* Replace the tokens in a string. A new string is allocated. The caller must free the result */ static char *vdeliver_format ( const char *line, VDEV_CTX &ctx) { char *ret = strdup(line); int offset = 0; while (1){ char *pt = strchr(ret+offset,'%'); if (pt == NULL) break; offset = (int)(pt-ret); if (pt[1] == 'u'){ ret = vdeliver_replace (ret,offset,ctx.user); }else if (pt[1] == 'f'){ ret = vdeliver_replace (ret,offset,ctx.from); }else if (pt[1] == 'd'){ ret = vdeliver_replace (ret,offset,ctx.domain); }else{ offset += 2; } } return ret; } static void fctsig(int ) { } /* Pipe a message through a command */ static int vdeliver_pipe( const char *cmd, VDEV_CTX &ctx, struct passwd *pwd, FILE *mailin) { int ret = 0; signal (SIGCHLD,fctsig); int pid = fork(); if (pid == 0){ cmd = str_skip (cmd); int newuid = 65535, newgid = 65535; { struct passwd *p = getpwnam ("mail"); if (p != NULL) newuid = p->pw_uid; if (pwd != NULL) newuid = pwd->pw_uid; struct group *g = getgrnam ("mail"); if (g != NULL) newgid = g->gr_gid; } setgid (newgid); setuid (newuid); // Perform token replacement cmd = vdeliver_format (cmd,ctx); FILE *out = popen (cmd,"w"); if (out != NULL){ if (vdeliver_copy (mailin,out) == -1){ syslog (LOG_ERR,"vdeliver_doone: timeout while piping to %s",cmd); } int status = pclose (out); if (WIFEXITED(status)){ ret = WEXITSTATUS(status); }else{ ret = -1; } _exit (ret); } syslog (LOG_ERR,"vdeliver_doone pipe: Can't exec %s (%m)",cmd); _exit (-1); }else if (pid == -1){ syslog (LOG_ERR,"vdeliver_doone pipe: Can't fork %m"); ret = -1; }else if (pid != -1){ int status; while (wait (&status) != pid); if (WIFEXITED(status)){ ret = WEXITSTATUS(status); }else{ ret = -1; } } return ret; } /* Do the final delivery if the user exist in the mailbox Return -1 if any errors. */ static int vdeliver_do ( VDEV_CTX &ctx, const char *user, const char *domain, FILE *fin) { int ret = -1; char pwdfile[PATH_MAX],shadowfile[PATH_MAX]; struct passwd *pwd; snprintf (pwdfile,sizeof(pwdfile)-1,"%s/passwd.%s",ETC_VMAIL,domain); snprintf (shadowfile,sizeof(shadowfile)-1,"%s/shadow.%s",ETC_VMAIL,domain); pwd = vmail_getpwnam (pwdfile,shadowfile,user,true,ctx.match_gecos); if (pwd == NULL){ /* #Specification: vdeliver / fallback management If a virtual account does not exist (and there were no aliases defined, the fallback is used. There are 3 cases # -The fallback is empty. The mail is simply rejected like sendmail is normally doing. -The fallback start with the character @. This is taken as the new destination domain. The user account is used so unknown@this_domain becomes unknowns@fallback_domain. -The fallback contain a @. This is taken as a full email address and all email to unknown account is sent to this address -The fallback is a local account name. Then all processing is done once again with the fallback replacing for the target account. This means that that fallback may itself be an alias if needed. -The fallback starts with a |. The remaining of the line is taken as a command and is executed as user mail. The message is piped to the command. Special token replacement is performed: %u -> user id %f -> from %d -> domain # */ const char *fallback = ctx.fallback; if (fallback[0] == '\0'){ syslog (LOG_INFO,"Unknown user: %s",user); ret = VERR_USER_UNKNOWN; }else if (fallback[0] == '|'){ char newdest[1000]; snprintf (newdest,sizeof(newdest)-1,"%s%s",user,fallback); ret = vdeliver_pipe (fallback+1,ctx,NULL,fin); }else if (fallback[0] == '@'){ char newdest[1000]; snprintf (newdest,sizeof(newdest)-1,"%s%s",user,fallback); ret = vdeliver_send (newdest,ctx.from,fin); }else if (strchr(fallback,'@') != NULL){ ret = vdeliver_send (fallback,ctx.from,fin); }else{ static bool fallbacking = false; if (!fallbacking){ fallbacking = true; ret = vdeliver_doaliases (ctx,fallback,domain,fin); fallbacking = false; }else{ syslog (LOG_ERR,"Invalid fallback destination for domain %s :%s:",domain,fallback); } } }else{ if (strcasecmp(user,pwd->pw_name)==0){ ret = vdeliver_writemsg (ctx,pwd,domain,fin); }else{ // Ok, we got there using the gecos. Now we know the // real user id, so we check the aliases again. // If no aliases matches, then we can deliver to the inbox. bool found; ret = vdeliver_checkaliases (ctx,pwd->pw_name,domain ,fin,found); if (!found){ ret |= vdeliver_writemsg (ctx,pwd,domain,fin); } } } return ret; } /* Deliver one copy of the message to one recipient The recipient may be one of those -A filter program -A file holding a list of recipient -One user either local to this domain or remote Local recipient are check recursivly to see if they are not aliases themselves. */ static int vdeliver_doone ( struct VDEV_CTX &ctx, const char *dest, const char *domain, FILE *mailin) { int ret = 0; dest = str_skip(dest); if (dest[0] == '|'){ ret = vdeliver_pipe (dest+1,ctx,NULL,mailin); }else if (strncmp(dest,":include:",9)==0){ FILE *list; struct stat st; dest = str_skip(dest+9); list = fopen (dest,"r"); if (list == NULL){ syslog (LOG_ERR,"Can't open list file %s (%m) for domain %s" ,dest,domain); }else{ /* #Specification: vdeliver / aliases / list file Only world readable file will be used by vdeliver. Since privilege users may set aliases themselves and virtual domain co-administrator are somewhat trusted, but not much, they can only set list file using paths to world readable file. */ if (fstat (fileno(list),&st)!=-1 && st.st_mode & 4){ char buf[1000]; while (fgets(buf,sizeof(buf)-1,list)!=NULL){ char alias[1000]; if (sscanf(buf,"%s",alias)==1){ ret |= vdeliver_doone (ctx,alias,domain,mailin); } } } fclose (list); } }else if (strchr (dest,'@')!=NULL){ ret = vdeliver_send (dest,ctx.from,mailin); }else{ ret = vdeliver_doaliases (ctx,dest,domain,mailin); } return ret; } static char *str_copyupto (char *dest, const char *src, char stop) { while (*src > ' ' && *src != stop) *dest++ = *src++; *dest = '\0'; return ((char*) src); } static int vdeliver_splitline( VDEV_CTX &ctx, char *ptpt, // One line to split const char *domain, FILE *faliases, // Extra lines in the alias file FILE *mailin) { int ret = 0; char line[1000]; while (1){ // We parse ptpt to process every aliases long pos = ftell (faliases); while (1){ ptpt = str_skip (ptpt); if (ptpt[0] == '\0'){ break; }else if (ptpt[0] == ','){ ptpt++; }else if (ptpt[0] == '"'){ char word[200],*ptw; ptpt++; ptw = word; while (*ptpt != '\0' && *ptpt != '"') *ptw++ = *ptpt++; *ptw = '\0'; if (*ptpt == '"') ptpt++; ret |= vdeliver_doone (ctx,word,domain,mailin); }else{ char word[200]; ptpt = str_copyupto (word,ptpt,','); ret |= vdeliver_doone (ctx,word,domain,mailin); } } // We check if there is a continuation line // vdeliver_doone may process the aliase file again, so // we must set the file pointer back where it was before the loop fseek (faliases,pos,SEEK_SET); if (fgets(line,sizeof(line)-1,faliases)!=NULL && isspace(line[0])){ ptpt = line; }else{ break; } } return ret; } /* Process aliases for this user. Return > 0 if at least one aliases was processed. A missing alias file is not an error */ static int vdeliver_doaliases ( VDEV_CTX &ctx, const char *user, const char *domain, FILE *mailin) { int ret = 0; static int recur = 0; if (recur == 16){ syslog (LOG_ERR,"Broken recursive alias %s for vdomain %s",user,domain); ret = -1; }else{ recur++; int retauto = vdeliver_autorespond (ctx,user,domain); bool found; ret |= vdeliver_checkaliases(ctx,user,domain,mailin,found); if (!found){ ret |= vdeliver_do (ctx,user,domain,mailin); } recur--; if (retauto == 1 && ret == -1) ret = 0; } return ret; } /* Open all possible aliases files for a domain and initialise the VDEV_CTX structure. */ static void vdeliver_openaliases( const char *domain, VDEV_CTX &ctx) { // Aliases file are optionnal, so we open the file and do not // care if it succeed char fname[PATH_MAX]; ctx.fallback[0] = '\0'; ctx.faliases[0] = ctx.faliases[1] = ctx.faliases[2] = NULL; ctx.quota = QUOTA_NOLIMIT; ctx.match_gecos = false; ctx.accept_lock = false; ctx.filter[0] = '\0'; snprintf (fname,sizeof(fname)-1,"%s/aliases.%s",ETC_VMAIL,domain); ctx.faliases[0] = fopen (fname,"r"); FILE *fin = fopen (ETC_CONF_LINUXCONF,"r"); if (fin != NULL){ // Looks for alternate alias file, up to 2 per domains // so a domain may have 3 aliases files char key[200],keyf[200],keyquota[200],keygecos[200],buf[1000]; char keyfilter[200],keylock[200]; int noalias = 1; snprintf (key,sizeof(key)-1,"vdomain_alias.%s",domain); snprintf (keyf,sizeof(keyf)-1,"vdomain_fallback.%s",domain); snprintf (keyquota,sizeof(keyquota)-1,"vdomain_quota.%s",domain); snprintf (keygecos,sizeof(keygecos)-1,"vdomain_gecos.%s",domain); snprintf (keyfilter,sizeof(keyfilter)-1,"vdomain_filter.%s",domain); snprintf (keylock,sizeof(keylock)-1,"vdomain_acceptlock.%s",domain); while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ char v1[1000]; if (sscanf (buf,"%s ",v1)==1){ char *v2 = str_skip(buf+strlen(v1)); strip_end (v2); if (strcmp(keyf,v1)==0){ strcpy (ctx.fallback,v2); }else if (strcmp(key,v1)==0){ if (noalias < 3){ ctx.faliases[noalias++] = fopen (v2,"r"); } }else if (strcmp(keyquota,v1)==0){ ctx.quota = atoi(v2)*1024l; }else if (strcmp(keygecos,v1)==0){ ctx.match_gecos = v2[0] != '0'; }else if (strcmp(keylock,v1)==0){ ctx.accept_lock = v2[0] != '0'; }else if (strcmp(keyfilter,v1)==0){ strcpy (ctx.filter,v2); } } } fclose (fin); }else{ syslog (LOG_ERR,"vdeliver_openaliases: Can't open /etc/conf.linuxconf, missing information about vdomains"); } } /* Find the official email domain associate (optionally) to a domain */ static void vdeliver_alias2domain ( const char *domain, char realdomain[PATH_MAX]) { strcpy (realdomain,domain); FILE *fin = fopen (ETC_CONF_LINUXCONF,"r"); if (fin != NULL){ // Look for a line of the following format // vdomain_other.A_DOMAIN alias char buf[2000]; while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ if (strncmp(buf,"vdomain_other.",14)==0){ char v1[1000],v2[1000]; if (sscanf (buf,"%s %s",v1,v2)==2 && strcmp(domain,v2)==0){ strcpy (realdomain,v1+14); break; } } } fclose (fin); }else{ syslog (LOG_ERR,"vdeliver_alias2domain: Can't open /etc/conf.linuxconf, missing information about vdomains"); } } static int vdeliver_main (int argc, char *argv[]) { int ret = -1; openlog ("vdeliver",LOG_PID,LOG_MAIL); if (argc != 3){ syslog (LOG_ERR,"vdeliver: Invalid arguments: expected user domain"); }else{ char tmpfile[PATH_MAX]; FILE *fout; mkdir (VAR_SPOOL_VDELIVER,0700); snprintf (tmpfile,sizeof(tmpfile)-1,"%s/tmp.%d",VAR_SPOOL_VDELIVER,getpid()); fout = fopen (tmpfile,"w+"); unlink (tmpfile); if (fout == NULL){ syslog (LOG_ERR,"Can't open temporary file %s (%m)",tmpfile); }else{ VDEV_CTX ctx; const char *user = argv[1]; ctx.user = user; char domain[PATH_MAX],aliasdomain[PATH_MAX]; ctx.seen = NULL; strlwr (aliasdomain,argv[2],sizeof(aliasdomain)); vdeliver_alias2domain (aliasdomain,domain); ctx.domain = domain; vdeliver_copy_from (stdin,fout); ctx.mailsize = ftell(fout); rewind (fout); mail2fax_getheader (fout,ctx.head); ctx.from = ctx.head.reply; if (ctx.from[0] == '\0') ctx.from = ctx.head.from.adr; vdeliver_openaliases(domain,ctx); if (ctx.accept_lock){ ret = VERR_CANTCREAT; fprintf (stderr,"Domain is locked!\n"); }else{ ret = vdeliver_doaliases (ctx,user,domain,fout); } } } return ret; } static int vvacation_main (int argc, char *argv[]) { int ret = -1; openlog ("vvacation",LOG_PID,LOG_MAIL); if (argc != 4){ syslog (LOG_ERR,"vvacation: Invalid argument: expected user domain from"); fprintf (stderr,"vvacation: Invalid argument: expected user domain from\n"); }else{ VDEV_CTX ctx; strncpy (ctx.head.from.adr,argv[3],MAXHLINE); ctx.from = argv[3]; vdeliver_vacation (ctx,argv[2],argv[1]); ret = 0; } return ret; } int main (int argc, char *argv[]) { int ret = -1; // The program may be called either vdeliver or vvacation. // We only test for the later const char *prog = strrchr(argv[0],'/'); if (prog != NULL){ prog++; }else{ prog = argv[0]; } if (strcmp(prog,"vvacation")==0){ ret = vvacation_main (argc,argv); }else{ ret = vdeliver_main(argc,argv); } return ret; }