/* #Specification: mailconf / complex user routing / strategy The /etc/aliases mecanims allows one to redirect email to another user once it is knows that the email is for this user on this system. The /var/lib/mailertable mecanism allows one to redirect email for a complete domain to another email server. The complex user routing is a special case allowing you to redirect email based on user name and domain name. This was created for ISP hosting virtual domain both for WWW and email. This mecanism can differentiate between info@virtual1.com and info@virtual2.com. This capability is achieved by generating sendmail.cf rules directly. */ #include #include #include #include "mailconf.h" #include "internal.h" #include "mailconf.m" #include #include #include static MAILCONF_HELP_FILE help_cplx ("complex"); PUBLIC COMPLEX_ROUTE::COMPLEX_ROUTE( const char *buf) { SSTRING act; buf = str_extract (buf,act); active = act.getval(); buf = str_extract (buf,from); buf = str_extract (buf,to); buf = str_extract (buf,new_from); buf = str_extract (buf,new_to); buf = str_extract (buf,router); buf = str_extract (buf,mailer); buf = str_extract (buf,comment); buf = str_extract (buf,priority); } PUBLIC COMPLEX_ROUTE::COMPLEX_ROUTE() { active = 1; priority = 50; mailer.setfrom ("esmtp"); } /* Edit one rule, return 0 if the edit was successful return 1 if this record should be deleted. */ PUBLIC int COMPLEX_ROUTE::edit() { int ret = -1; DIALOG dia; dia.newf_chk ("",active,MSG_U(F_CPLXACTIVE,"This rule is active")); dia.newf_str (MSG_U(F_TO,"to"),to); dia.newf_str (MSG_U(F_NEWTO,"rewriten to"),new_to); dia.newf_str (MSG_U(F_ROUTER,"Forward to server(opt)"),router); FIELD_COMBO *comb = dia.newf_combo (MSG_R(F_MAILER),mailer); dia.newf_num (MSG_U(F_PRIORITY,"Priority"),priority); dia.newf_str (MSG_U(F_COMMENT,"Comment"),comment); dia.delwhat (MSG_U(F_DELCPLX,"Select [Del] to delete this record")); basic_setmailer (comb); int nof = 0; while (1){ char buf[100]; if (to.is_empty()){ strcpy (buf,MSG_U(T_NEWCOMPLEXROUTE,"New complex route")); }else{ sprintf (buf,MSG_U(T_COMPLEXROUTE,"complex routing for %s") ,to.get()); } MENU_STATUS code = dia.edit (buf ,MSG_U(I_COMPLEXROUTE ,"You are allowed to intercept email going to a user\n" "and redirect this to a new mail server,\n" "using a different protocol.\n" "In the process, you can rewrite the TO field\n" "and even the from field\n") ,help_cplx ,nof ,MENUBUT_DEL|MENUBUT_ACCEPT|MENUBUT_CANCEL); if (code == MENU_CANCEL || code == MENU_ESCAPE){ dia.restore(); break; }else if (code == MENU_DEL){ ret = 1; break; }else{ char status[2000]; SSTREAM_NUL out; if (rule0(out,status,NULL,NULL)==-1){ xconf_error ("%s",status); }else{ ret = 0; break; } } } return ret; } static const char CPLXMAIL[]="cplxmail"; static const char CASES[]="cases"; PUBLIC COMPLEX_ROUTES::COMPLEX_ROUTES() { SSTRINGS strs; int nb = linuxconf_getall (CPLXMAIL,CASES,strs,0); for (int i=0; iget())); } // We want the list sorted by priority, descending (*this); COMPLEX_ROUTE *r1 = (COMPLEX_ROUTE*)o1; COMPLEX_ROUTE *r2 = (COMPLEX_ROUTE*)o2; int ret = r2->priority - r1->priority; if (ret == 0){ // Not important, but this way we generate ret = r1->to.cmp(r2->to); } return ret; rstmodified(); } PUBLIC COMPLEX_ROUTE *COMPLEX_ROUTES::getitem(int no) { return (COMPLEX_ROUTE*)ARRAY::getitem(no); } PUBLIC int COMPLEX_ROUTES::write() { linuxconf_setcursys (subsys_mail); linuxconf_removeall (CPLXMAIL,CASES); int n = getnb(); for (int i=0; iactive ,c->from.get(),c->to.get() ,c->new_from.get(),c->new_to.get() ,c->router.get(),c->mailer.get() ,c->comment.get(),c->priority); linuxconf_add (CPLXMAIL,CASES,buf); } int ret = linuxconf_save(); if (ret!=-1){ rstmodified(); } return ret; } PUBLIC int COMPLEX_ROUTES::edit() { glocal int ret = 0; glocal COMPLEX_ROUTES *tb = this; (MSG_U(T_COMPLEXROUTES,"Complex routings") ,MSG_U(I_COMPLEXROUTES ,"You can setup multiple complex routing for email\n" "The key is the destination, including the user name\n") ,help_cplx); newf_head (MSG_U(H_COMPLEX,"To\tRewritten to\tServer\tPriority\tActive")); sortable(); sortpolicy ("aaada"); sethdispo ("lllrc"); addwhat (MSG_U(T_ADDCPLX,"Select [Add] to add a complex routing rule")); int n = glocal.tb->size(); for (int i=0; igetitem(i); new_menuitemf (cp->to.get(),"%s\t%s\t%d\t%s" ,cp->new_to.get(),cp->router.get(),cp->priority ,cp->active ? MSG_U(I_YES,"Yes") : MSG_U(I_NO,"No")); } if (glocal.tb->editone(no)!=-1) glocal.ret = 1; COMPLEX_ROUTE *cp = new COMPLEX_ROUTE; if (glocal.tb->manage_edit (cp,cp->edit())==0) glocal.ret = 1; return glocal.ret; } /* Return != 0 if some entry were modified */ int complex_edit () { COMPLEX_ROUTES cpl; return cpl.edit(); } /* Split and validate an addres of the form user@domain */ int complex_parse ( const SSTRING &adr, char *user, char *site) { int ret = -1; char buf[200]; adr.copy (buf); user[0] = site[0] = '\0'; char *pt = strchr (buf,'@'); if (pt != NULL){ *pt++ = '\0'; strcpy (user,buf); /* #Specification: complex routing / to field / @domain.com If the "to" field only contain the domain name beginning with the @ character, we assume that this rules apply to any user of that domain. So we generate the special sendmail rules R$*<@domain.com> ... */ if (user[0] == '\0') strcpy (user,"$*"); strcpy (site,pt); ret = 0; } return ret; } /* Generate the rules to be insert in the ruleset 0 of sendmail.cf. if fout == NULL, this function is simply called to validate the rule. */ PUBLIC int COMPLEX_ROUTE::rule0( SSTREAM &out, char *status, SSTRINGS *aliases, // Different aliases for this server // or NULL VDOMAINS *vdomains) { /* #Specification: mailconf / complex user routing / inactive An inactive rule is not validated. This means the user may left a rules half finished. */ int ret = 0; status[0] = '\0'; if (active){ ret = 0; char userto[200],siteto[200]; userto[0] = siteto[0] = '\0'; if (to.is_empty()){ status += sprintf (status ,MSG_U(F_NOEMPTY,"Field \"%s\" can't be empty\n") ,MSG_R(F_TO)); ret = -1; }else if (complex_parse (to,userto,siteto) == -1){ ret = -1; status += sprintf (status ,MSG_U(F_IVLDTO,"Field \"%s\" is invalid or incomplete\n" " Was expecting user@domain format\n") ,MSG_R(F_TO)); } char new_userto[200],new_siteto[200]; new_userto[0] = new_siteto[0] = '\0'; bool localhost = false; VDOMAIN *vdom = NULL; if (new_to.is_empty()){ status += sprintf (status ,MSG_R(F_NOEMPTY) ,MSG_R(F_NEWTO)); ret = -1; }else if (new_to.strchr('@')==NULL){ /* #Specification: mailconf / complex routing / new to no site If the "rewritten to" field only contain a name (without @) it is taken as a local user name. This is especially useful when hosting virtual domain and you have the setup # info@virtual1.com -> info1 info@virtual2.com -> joewho # */ localhost = true; new_to.copy (new_userto); }else if (complex_parse (new_to,new_userto,new_siteto)==-1){ ret = -1; status += sprintf (status,MSG_R(F_IVLDTO),MSG_R(F_NEWTO)); }else if (aliases != NULL && aliases->lookup(new_siteto)!=-1 && router.is_empty()){ localhost = true; /* #Specification: mailconf / complex routing / new to pointint to us Linuxconf understand complex rule of the form joe@virtual1.com -> otherjoe@domain.com where domain.com is one of our alias. This line is equivalent to joe@virtual1.com -> otherjoe so the message will be routed locally. */ }else if (vdomains != NULL){ vdom = vdomains->lookup(new_siteto); } if (strcmp(new_userto,"$*")==0){ if (strcmp(userto,"$*")==0){ strcpy (new_userto,"$1"); }else{ status += sprintf (status,MSG_U(E_AMBIGIOUS ,"Ambigious redirection.\n" "You must specify a user account in the \"rewriten to\"\n" "field.\n")); ret = -1; } } char str_router[200]; if (router.is_empty()){ strcpy (str_router,new_siteto); }else{ const char *rt = router.get(); if (ipnum_validip(rt,false)){ sprintf (str_router,"[%s]",rt); }else{ strcpy(str_router,rt); } } if (!localhost && vdom == NULL && mailer.is_empty()){ status += sprintf (status ,MSG_R(F_NOEMPTY) ,MSG_R(F_MAILER)); ret = -1; } if (ret == 0){ char *pt = ""; // Another rule in case the domain is qualified by the dns // and a . has been added. for (int i=0; i<2; i++, pt = "."){ if (localhost){ out.printf ("R%s<@%s%s>\t$#local $: @ %s\n" ,userto,siteto,pt ,new_userto); }else if (vdom != NULL){ out.printf ("R%s<@%s%s>\t%s<@%s>\n" ,userto,siteto,pt ,new_userto,new_siteto); }else{ out.printf ("R%s<@%s%s>\t$#%s $@%s $: %s < @ %s>\n" ,userto,siteto,pt ,mailer.get(),str_router ,new_userto,new_siteto); } } } } return ret; } /* Return true if this rule redirect mail for a user account instead of a full domain */ PUBLIC bool COMPLEX_ROUTE::isuserroute() { const char *s = to.get(); return s[0] != '@'; } /* Generate the require parts of ruleset 0. Return -1 if any errors. */ PUBLIC int COMPLEX_ROUTES::rule0( SSTREAM &out, SSTRINGS &aliases, VDOMAINS &vdomains) { int ret = 0; int n = getnb(); /* #Specification: complex user routing / order All routes to user account are generated first, followed by route to domain. This is done so # user@domain.com -> destination @domain.com -> other destination # work properly */ for (int j=0; j<2; j++){ for (int i=0; iisuserroute()){ char status[10000]; ret |= c->rule0(out,status,&aliases,&vdomains); } } } return ret; }