#include #include #include #include #include #include #include #include #include #include "TrieD.h" // globals char *my_socket = "unix:/var/rcpt-users-milter/rcpt-users-milter.sock"; char *my_userdir = "/home/synacct/users"; char *my_aliasdir = "/home/synacct/aliases"; //time in seconds before check cache validity #define VALIDITY_INTERVAL 300 pthread_mutex_t mutex0 = PTHREAD_MUTEX_INITIALIZER; Trie user_map = NULL; Trie aliases_map = NULL; // when did we last have a successful update? time_t user_lastcheck; time_t aliases_lastcheck; time_t user_lastupdate; time_t aliases_lastupdate; // char *CheckCache(Trie map, time_t *lastupdate, char *mydir, time_t *lastcheck); int BuildCache(Trie map, char *mydir, char *filename); int Process(Trie map, int fd); int FindMax(char *dirname, time_t *time, char **filename); char *reverse(char *str); sfsistat check_recipient(SMFICTX *ctx, char **argv) { char *user_file, *aliases_file; pthread_mutex_lock(&mutex0); if(user_file = CheckCache(user_map,&user_lastupdate,my_userdir,&user_lastcheck)) { if(BuildCache(user_map,my_userdir,user_file)) { syslog(LOG_ERR,"Built cache for '%s' based on '%s'...\n",user_map->name,user_file); } } if(aliases_file = CheckCache(aliases_map,&aliases_lastcheck,my_aliasdir,&aliases_lastcheck)) { if(BuildCache(aliases_map,my_aliasdir,aliases_file)) { syslog(LOG_ERR,"Built cache for '%s' based on '%s'...\n",user_map->name,aliases_file); } } char *copy = strdup(smfi_getsymval(ctx,"{rcpt_addr}")); reverse(copy); syslog(LOG_ERR,"Check recipient called with full rcpt_addr = '%s'...\n",copy); // assuming that the domain check is also done elsewhere, what about stripping the "@DOMAINNAME" bit // at this point? // // also, this is silently rejecting all non-qualified addresses. char *at_mark = index(copy,'@'); if(at_mark) { // it looks now we will add the domain in the trie // *at_mark = '\0'; } else { pthread_mutex_unlock(&mutex0); if(smfi_setreply(ctx, "550", "5.1.1", "No unqualified addresses accepted -- you must use domain name 'user@domain'") == MI_FAILURE) { syslog(LOG_ERR, "rcpt-users-milter.c: check_recipient() had smfi_setreply() return MI_FAILURE"); return SMFIS_TEMPFAIL; } return SMFIS_REJECT; } int *user_result, *alias_result; user_result = user_map->search(user_map,copy); alias_result = aliases_map->search(aliases_map,copy); if(user_result && *user_result) { syslog(LOG_ERR,"Check recipient as USER called with truncated rcpt_addr = '%s' returns '%d'...\n",copy,*user_result); pthread_mutex_unlock(&mutex0); return SMFIS_CONTINUE; } if(alias_result && *alias_result) { syslog(LOG_ERR,"Check recipient as ALIAS called with truncated rcpt_addr = '%s' returns '%d'...\n",copy,*alias_result); pthread_mutex_unlock(&mutex0); return SMFIS_CONTINUE; } if(smfi_setreply(ctx, "550", "5.1.1", "User unknown") == MI_FAILURE) { syslog(LOG_ERR, "rcpt-users-milter.c: check_recipient() had smfi_setreply() return MI_FAILURE"); pthread_mutex_unlock(&mutex0); return SMFIS_TEMPFAIL; } syslog(LOG_ERR,"Check recipient as USER and ALIAS called with truncated rcpt_addr = '%s' returned 'NULL'\n",copy); pthread_mutex_unlock(&mutex0); return SMFIS_REJECT; } time_t user_last = 0; time_t alias_last = 0; struct smfiDesc smfilter = { "Simple rcpt filter",/* filter name */ SMFI_VERSION,/* version code -- do not change */ 0, /* flags */ NULL,/* connection info filter */ NULL,/* SMTP HELO command filter */ NULL,/* envelope sender filter */ check_recipient,/* envelope recipient filter */ NULL,/* header filter */ NULL,/* end of header */ NULL,/* body block filter */ NULL,/* end of message */ NULL,/* message aborted */ NULL,/* connection cleanup */ }; main(int argc, char **argv) { char *divider; int ret; time_t new_userlast, new_aliaseslast; char *user_filename, *aliases_filename; user_map = trieCreate("users"); aliases_map = trieCreate("aliases"); FindMax(my_userdir, &user_lastupdate, &user_filename); FindMax(my_aliasdir, &aliases_lastupdate, &aliases_filename); BuildCache(user_map,my_userdir,user_filename); BuildCache(aliases_map,my_aliasdir,aliases_filename); user_lastcheck = user_lastupdate; aliases_lastcheck = aliases_lastupdate; openlog("rcpt-users-milter", LOG_PID|LOG_NDELAY, LOG_MAIL); // check to see if the socket exists since smfi_opensocket seems broken! divider = index(my_socket,':'); if(divider) { unlink(divider+1); } else { unlink(my_socket); } // smfi_setconn(my_socket); // seems broken to me! // ret = smfi_opensocket((bool)1); ret = smfi_register(smfilter); if(ret != MI_SUCCESS) { syslog(LOG_ERR,"Failed on smfi_register\n"); exit(1); } daemon(0,0); int i; for(i = 0; i<256; i++) close(i); if(smfi_main() == MI_FAILURE) { syslog(LOG_ERR,"failed on smfi_main\n"); } } char *CheckCache(Trie map, time_t *lastupdate, char *mydir, time_t *lastcheck) { int okay = 0; int changed = 0; time_t elapsed; time_t new_last; char *filename; // pthread_mutex_lock(&mutex0); elapsed = time(NULL) - *lastcheck; if(elapsed < VALIDITY_INTERVAL) { syslog(LOG_ERR,"No need to invalidate cache '%s' since only %d < %d seconds have passed...\n",map->name,elapsed,VALIDITY_INTERVAL); // pthread_mutex_unlock(&mutex0); return NULL; } *lastcheck = time(NULL); syslog(LOG_ERR,"Possibly invalidating cache '%s' since %d > %d seconds have passed, check to see if new files are there...\n",map->name,elapsed,VALIDITY_INTERVAL); okay = FindMax(mydir, &new_last, &filename); if(!okay) { syslog(LOG_ERR,"CheckCache(): FindMax(%s,..,..) did not return okay!...\n",mydir); // pthread_mutex_unlock(&mutex0); return NULL; } if(new_last > *lastupdate) { syslog(LOG_ERR,"Most current filenames for map '%s' is '%s' ...\n",map->name,filename); return filename; } else { syslog(LOG_ERR,"No, did not rebuild cache for '%s' based on %s...\n",map->name,filename); return NULL; } // free() becomes caller's responsibility! } BuildCache(Trie map, char *mydir, char *filename) { int fd; // printf("map == '%s', directory == '%s', filename = '%s'\n",map->name,mydir,filename); chdir(mydir); fd = open(filename,O_RDONLY); if(fd == -1) { syslog(LOG_ERR,"Cannot open file %s/%s...\n",mydir,filename); return 0; } chdir(mydir); Process(map,fd); close(fd); return 1; } Process(Trie map, int fd) { FILE *fp; fp = fdopen(fd,"r"); char buffer[256]; while(fgets(buffer,256,fp)) { // add each line before the colon char *colon; colon = index(buffer,':'); if(colon) { int one = 1; *colon = '\0'; reverse(buffer); map->add(map,buffer,&one,sizeof(int)); // printf("Adding '%s'...\n",buffer); } else { syslog(LOG_ERR,"Process(): incomplete line '%s'...",buffer); } } } FindMax(char *dirname, time_t *time, char **filename) { DIR *dir; struct dirent *dirp; char string[256]; int okay = 0; time_t last = 0; dir = opendir(dirname); if(dir == NULL) { return; } chdir(dirname); while(dirp = readdir(dir)) { struct stat statbuf; char *which_file; // printf("found %s...\n",dirp->d_name); if(*(dirp->d_name) == '.') { continue; } // printf("Stat-ing %s...\n",dirp->d_name); if(stat(dirp->d_name, &statbuf) == 0) // okay { okay = 1; if(statbuf.st_mtime > last) { last = statbuf.st_mtime; strncpy(string,dirp->d_name,256); // printf("Last updated to %d...\n",last); } } } *time = last; if(okay) *filename = strdup(string); else *filename = NULL; return okay; } // swaps a single character pointed by the pointer void swap(char *c, char *d) { char save; save = *c; *c = *d; *d = save; } char *reverse(char *str) { if(!str) return str; // printf("reverse: %s\n",str); int len = strlen(str); if(len) len--; else return NULL; int half = len / 2; int i; for(i = 0; i <= half; i++) { // printf("Swapping %x (%c), %x (%c)...\n",str+i,*(str+i),str+len-i,*(str+len-i)); swap(str+i,str+len-i); } return str; }