#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 }; /* 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 static void vdeliver_filter (FILE *fin, const char *filter, FILE *fout) { int tb[2]; if (pipe(tb)==-1){ syslog (LOG_ERR,"Can't setup pipe for filtering, filtering disabled"); vdeliver_copy(fin,fout); }else{ pid_t pid = fork(); if (pid == 0){ close (tb[1]); dup2 (tb[0],0); dup2 (fileno(fout),1); system (filter); }else if (pid != (pid_t)-1){ close (tb[0]); FILE *newfout = fdopen (tb[1],"w"); vdeliver_copy (fin,newfout); fclose (newfout); close (tb[1]); int status; wait (&status); }else{ syslog (LOG_ERR,"Can't fork, filtering disabled"); vdeliver_copy(fin,fout); close (tb[0]); close (tb[1]); } } } // 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,2000); fchmod (fd,0600); 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) { 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{ 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_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]; snprintf (email,sizeof(email)-1,"%s@%s",user,domain); ret = vdeliver_send (ctx.from,email,fin); 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 */ char *home = NULL; static int vdeliver_checkaliases( VDEV_CTX &ctx, const char *user, const char *domain, FILE *mailin, bool &found) { int ret = 0; found = false; char path[PATH_MAX]; snprintf (path,sizeof(path)-1,"%s/%s/.forward",home,user); for (int i=0; i<4 && !found; i++){ FILE *fin = i!=3? ctx.faliases[i] : fopen (path,"r"); if (fin != NULL){ char buf[2000]; rewind (fin); while (fgets (buf,sizeof(buf)-1,fin)!=NULL){ char *pt = i!=3? str_skip (buf) : strdup (user); char *ptpt = i!=3? strchr (pt,':') : str_skip (buf); if (ptpt != NULL){ if (i!=3) *ptpt++ = '\0'; if (i!=3) strip_end (pt); if (i==3 || 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); if (i!=3) 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,"%s/%s/.vacation.msg",home,user); snprintf (path_list,sizeof(path_list)-1,"%s/%s/.vacation.db",home,user); // 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 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; while (fgets(line,sizeof(line)-1,f)!=NULL){ strip_end (line); if (strcasecmp(line,ctx.head.from.adr)==0){ found = true; break; } } if (!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); } } } /* Write the message to the inbox */ static int vdeliver_writemsg ( VDEV_CTX &ctx, struct passwd *pwd, const char *domain, FILE *filein) { int ret = 0; vdeliver_vacation (ctx,domain,pwd->pw_name); 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'){ vdeliver_copy (filein,fout); }else{ vdeliver_filter (filein,ctx.filter,fout); } ret = fclose (fout); }else{ fclose (fout); } }else{ syslog (LOG_ERR,"Can't open file %s (%m)",filepath); } 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,"%s/%s/.autoresponder.msg",home,user); // 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; } /* 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; vdeliver_autorespond (ctx,user,domain); char pwdfile[PATH_MAX],shadowfile[PATH_MAX]; struct passwd *pwd; sprintf (pwdfile,"%s/passwd.%s",ETC_VMAIL,domain); sprintf (shadowfile,"%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. */ 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_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",domain); } } }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; } static void fctsig(int ) { } /* 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. */ int uid = 0; 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] == '|'){ int pid; signal (SIGCHLD,fctsig); pid = fork(); if (pid == 0){ FILE * out; dest = str_skip (dest+1); int newuid = 65535, newgid = 65535; { struct passwd *p = getpwnam ("mail"); if (p != NULL) newuid = p->pw_uid; struct group *g = getgrnam ("mail"); if (g != NULL) newgid = g->gr_gid; } if (uid != 0) { newgid = 2000; newuid = uid; } setgid (newgid); setuid (newuid); out = popen (dest,"w"); if (out != NULL){ if (vdeliver_copy (mailin,out) == -1){ syslog (LOG_ERR,"vdeliver_doone: timeout while piping to %s",dest); } pclose (out); _exit (0); } syslog (LOG_ERR,"vdeliver_doone pipe: Can't exec %s (%m)",dest); _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); ret |= status; } }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 if (dest[0] == '\\'){ dest++; ret = vdeliver_do (ctx,dest,domain,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 if (fgets(line,sizeof(line)-1,faliases)!=NULL && isspace(line[0])){ ptpt = line; }else{ fseek (faliases,pos,SEEK_SET); 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'; sprintf (fname,"%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],v2[1000]; if (sscanf (buf,"%s %s",v1,v2)==2){ 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){ char *pt = buf; while (*pt > ' ') pt++; pt = str_skip(pt); strip_end (pt); strcpy (ctx.filter,pt); } } } fclose (fin); }else{ syslog (LOG_ERR,"vdeliver_openaliases: Can't open /etc/vdeliver, 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/vdeliver, missing information about vdomains"); } } int main (int argc, char *argv[]) { int ret = -1; openlog ("vdeliver",LOG_PID,LOG_MAIL); if (argc < 3 || argc > 5){ syslog (LOG_ERR,"vdeliver: Invalid arguments: expected user domain"); }else{ if (argc >= 4) uid = atoi(argv[3]); if (argc >= 5) home = argv[4]; char tmpfile[PATH_MAX]; FILE *fout; mkdir (VAR_SPOOL_VDELIVER,0700); sprintf (tmpfile,"%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]; char domain[PATH_MAX],aliasdomain[PATH_MAX]; ctx.seen = NULL; strlwr (aliasdomain,argv[2],sizeof(aliasdomain)); vdeliver_alias2domain (aliasdomain,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; }