#include #include #include #include #include #include #include #include #include #include #include #include #include "mailsql.h" #define USR_SBIN_SENDMAIL "/usr/sbin/sendmail" // Error code returned to sendmail #define VERR_USER_UNKNOWN 67 #define VERR_CANTCREAT 73 #define VERR_NOPERM 77 #define VERR_TMPFAIL 78 class SEEN_LOOKUP{ public: char *name; SEEN_LOOKUP *next; SEEN_LOOKUP(const char *_name, SEEN_LOOKUP *first){ next = first; name = strdup(_name); } ~SEEN_LOOKUP(){ free (name); } }; struct DOMAIN_INFO { int quota; // Default per user quota long long dquota; // Glocal quota (sum of all user folders) int maxmsg; // Maximum size of a message long long used; // How much space used by all users folders so far bool mayreceive; SSTRING fallback; int domainid; }; /* Is that alias was already processed Return true if this alias was processed once */ static bool vdeliver_wasseen (SEEN_LOOKUP *first, const char *name) { bool ret = false; while (first != NULL){ if (strcmp(first->name,name)==0){ ret = true; break; } first = first->next; } return ret; } static SEEN_LOOKUP *tosend_local = NULL; static SEEN_LOOKUP *tosend_remote = NULL; static int doaliases ( const char *name, int domainid, int level, SEEN_LOOKUP *parents) { const char *name; SEEN_LOOKUP *recur; // Aliases collected int ret; SEEN_LOOKUP *parents; // Nesting of all aliases glocal.ret = -1; if (level == 20){ syslog(LOG_ERR,"Too many nesting in mailsql aliases %s, domainid %d" ,name,domainid); }else{ glocal.name = name; glocal.recur = NULL; SEEN_LOOKUP thisalias (name,parents); glocal.parents = &thisalias; ("select alias from aliases where name='%s' and domainid=%d" ,name,domainid); tosend_local = new SEEN_LOOKUP(glocal.name,tosend_local); glocal.ret = 0; // We can't do SQL query while processing a query, so we collect // all the alias in a list and rerun the function const char *alias = row[0]; if (strchr(alias,'@')!=NULL){ tosend_remote = new SEEN_LOOKUP(alias,tosend_remote); }else if (vdeliver_wasseen (glocal.parents,alias)){ // This is a alias defined in an alias of the sama name, so // no need to recurse indefinitly. tosend_local = new SEEN_LOOKUP(alias,tosend_local); }else{ glocal.recur = new SEEN_LOOKUP(alias,glocal.recur); } while (glocal.recur != NULL){ SEEN_LOOKUP *next = glocal.recur->next; glocal.ret = doaliases (glocal.recur->name,domainid,level+1,parents); delete glocal.recur; glocal.recur = next; } } return glocal.ret; } static int deliver ( const char *name, DOMAIN_INFO &info, struct MAILHEADER &head, const char *header, const char *message) { int ret; struct MAILHEADER *head; DOMAIN_INFO *info; const char *header; const char *message; int len; glocal.ret = -1; glocal.head = &head; glocal.info = &info; glocal.header = header; glocal.message = message; glocal.len = strlen(message)+strlen(header); ("select accounts.userid,mayreceive,quota,used,folderid from accounts,folders" " where accounts.name='%s' and accounts.domainid=%d" " and folders.userid=accounts.userid and folders.foldername='inbox'" ,name,info.domainid); glocal.ret = VERR_USER_UNKNOWN; int quota = atoi(row[2])*1024; if (quota == 0) quota = glocal.info->quota; int used = atoi(row[3]); if (quota != 0 && used + glocal.len > quota){ glocal.ret = VERR_CANTCREAT; fprintf (stderr,"Out of disk quota for this user inbox\n"); }else if (row[1][0]=='Y'){ time_t ti = mailhead_getdate(*glocal.head); struct tm *t = localtime (&ti); NSQL_ENCODE enc; glocal.ret = sql_action ("insert into messages " "(folderid,fromname,fromadr,toname,toadr,reply,subject,msgid,replyid,datesent,header,size)" " values " "(%s,'%s','%s','%s','%s','%s','%s','%s','%s'," "'%04d-%02d-%02d %02d:%02d:%02d','%s',%d)" ,row[4] ,enc.enc(glocal.head->from.name) ,enc.enc(glocal.head->from.adr) ,enc.enc(glocal.head->to.name) ,enc.enc(glocal.head->to.adr) ,enc.enc(glocal.head->reply) ,enc.enc(glocal.head->subject) ,enc.enc(glocal.head->msgid) ,enc.enc(glocal.head->replyid) ,t->tm_year+1900,t->tm_mon+1,t->tm_mday ,t->tm_hour,t->tm_min,t->tm_sec ,enc.enc(glocal.header) ,glocal.len); if (glocal.ret != -1){ int messageid = sql_getlastid(); glocal.ret = sql_action ("insert into parts (messageid,message)" " values (%d,'%s')" ,messageid,enc.enc(glocal.message)); } if (glocal.ret != -1){ sql_action ("update accounts set used=used+%d where userid=%s" ,glocal.len,row[0]); } }else{ glocal.ret = VERR_NOPERM; fprintf (stderr,"User inbox is locked\n"); } return glocal.ret; } /* 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; } static int vdeliver_send ( const char *from, const char *adr, const char *header, const char *message) { int ret = -1; if (vdeliver_validadr(adr)){ FILE *ff = fopen ("/var/run/vdeliver.send.lock","a"); if (ff != NULL){ int fd = fileno(ff); if (flock(fd, LOCK_EX) != -1) { SSTRING cmd; if (from != NULL && from[0] != '\0' && vdeliver_validadr(from)){ cmd.setfromf ("%s -i -f %s %s" ,USR_SBIN_SENDMAIL,from,adr); }else{ cmd.setfromf ("%s -i %s",USR_SBIN_SENDMAIL,adr); } for (int i=0; i<5; i++){ FILE *fout = popen (cmd.get(),"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); // fputs ("\n",fout); fputs (message,fout); ret = pclose(fout); 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_resend ( const char *name, const char *header, const char *message) { // Compute the size of the command line for remote SEEN_LOOKUP *pt = tosend_remote; int len = 0; while (pt != NULL){ len += 1 + strlen (pt->name); pt = pt->next; } char cmd[len+1]; char *ptcmd = cmd; pt = tosend_remote; while (pt != NULL){ *ptcmd++ = ' '; syslog (LOG_ERR,"send remote :%s:",pt->name); ptcmd = stpcpy (ptcmd,pt->name); pt = pt->next; } return vdeliver_send (name,cmd,header,message); } /* Get the information about the domain */ static int vdeliver_getdomainid( const char *domain, DOMAIN_INFO &info) { int ret; DOMAIN_INFO *info; glocal.ret = -1; glocal.info = &info; ("select domains.domainid,fallback,quota,mayreceive,dquota,maxmsg from daliases,domains" " where daliases.alias='%s' and domains.domainid=daliases.domainid" ,domain); glocal.ret = 0; glocal.info->domainid = atoi(row[0]); glocal.info->fallback.setfrom (row[1]); glocal.info->quota = atoi(row[2])*1024; glocal.info->mayreceive = row[3][0] == 'Y'; glocal.info->dquota = atoi(row[4])*1024; glocal.info->maxmsg = atoi(row[5])*1024; info.used = 0; ("select used from accounts where domainid=%d" ,info.domainid); glocal.info->used += atoi(row[0]); return glocal.ret; } int main (int argc, char *argv[]) { int ret = -1; tlmp_initmod(); openlog ("vdeliver_sql",LOG_PID,LOG_MAIL); if (argc != 3){ syslog (LOG_ERR,"Invalid arguments: expected user domain"); fprintf (stderr,"Invalid arguments: expected user domain\n"); }else{ vutil_opendb (); const char *name = argv[1]; const char *domain = argv[2]; struct MAILHEADER head; char header[10000]; if (mail2fax_getheader(stdin,head,header,sizeof(header)-1)!=-1){ DOMAIN_INFO info; if (vdeliver_getdomainid(domain,info)==-1){ ret = VERR_NOPERM; syslog (LOG_ERR,"Domain %s not in database. Odd",domain); }else if (!info.mayreceive){ ret = VERR_NOPERM; fprintf (stderr,"Can't write to user inbox\n"); }else{ char *message = (char*)malloc(info.maxmsg); if (message == NULL){ ret = VERR_TMPFAIL; fprintf (stderr,"Out of memory\n"); syslog (LOG_ERR,"Out of memory"); }else{ message[0] = '\0'; int nb = fread (message,1,info.maxmsg-1,stdin); if (info.dquota != 0 && nb + info.used > info.dquota){ ret = VERR_CANTCREAT; fprintf (stderr,"Out of disk quota for this domain\n"); }else if (nb >= 0){ message[nb] = '\0'; const char *from = head.reply; if (from[0] == '\0') from = head.from.adr; if (doaliases(name,info.domainid,0,NULL)==0){ SEEN_LOOKUP *pt = tosend_local; while (pt != NULL){ // syslog (LOG_ERR,"deliver :%s: %s\n",pt->name,domain); ret = deliver (pt->name,info,head,header,message); if (ret == VERR_USER_UNKNOWN && info.fallback.is_filled()){ if (info.fallback.strchr('@')==NULL){ ret = deliver (info.fallback.get() ,info,head,header,message); }else{ ret = vdeliver_send (from,info.fallback.get() ,header,message); } } pt = pt->next; } if (tosend_remote != NULL){ ret = vdeliver_resend (from,header,message); } } } free (message); } } } } return ret; }