/* dbase.c - MessageWall database definitions Copyright (C) 2002 Ian Gulliver This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include "messagewall.h" #include "dnsdcc.h" #include "virus.h" #include "dbase.h" static const char tagstring[] = "$Id: dbase.c,v 1.36.2.5 2002/10/17 19:04:06 ian Exp $"; const char *dbase_cidr_subnet[] = { "0.0.0.0", "128.0.0.0", "192.0.0.0", "224.0.0.0", "240.0.0.0", "248.0.0.0", "252.0.0.0", "254.0.0.0", "255.0.0.0", "255.128.0.0", "255.192.0.0", "255.224.0.0", "255.240.0.0", "255.248.0.0", "255.252.0.0", "255.254.0.0", "255.255.0.0", "255.255.128.0", "255.255.192.0", "255.255.224.0", "255.255.240.0", "255.255.248.0", "255.255.252.0", "255.255.254.0", "255.255.255.0", "255.255.255.128", "255.255.255.192", "255.255.255.224", "255.255.255.240", "255.255.255.248", "255.255.255.252", "255.255.255.254", "255.255.255.255" }; void dbase_clear() { /* release local_domains, relay_ips and special_users lists from memory */ int i; struct messagewall_local_domain_t *local_domain1, *local_domain2; struct messagewall_relay_ip_t *relay_ip1, *relay_ip2; struct messagewall_special_user_t *special_user1, *special_user2; for (i = 0; i < HASH_SIZE; i++) { local_domain1 = local_domain_hash[i]; while (local_domain1 != NULL) { local_domain2 = local_domain1; local_domain1 = local_domain1->next; firestring_estr_free(&local_domain2->domain); free(local_domain2); } local_domain_hash[i] = NULL; relay_ip1 = relay_ip_hash[i]; while (relay_ip1 != NULL) { relay_ip2 = relay_ip1; relay_ip1 = relay_ip1->next; free(relay_ip2); } relay_ip_hash[i] = NULL; special_user1 = special_user_hash[i]; while (special_user1 != NULL) { special_user2 = special_user1; special_user1 = special_user1->next; firestring_estr_free(&special_user2->address); free(special_user2); } special_user_hash[i] = NULL; } } void dbase_load_local_domains() { /* populate local_domains list + hash table from local_domains config file */ struct messagewall_local_domain_t *local_domain1; char line[257]; unsigned int u; FILE *f; if (local_domains == NULL) return; /* * we're inside the chroot; everything should be root controlled and fopen shouldn't be a problem * SECURITY: do not let users play with files inside the chroot */ f = fopen(local_domains,"r"); /* ITS4: ignore fopen */ if (f == NULL) { perror("Unable to open local_domains file"); exit(100); } /* * line really does have 257 bytes of space */ while (fgets(line,257,f) != NULL) { /* ITS4: ignore fgets */ /* * check for comment */ if (line[0] == '#') continue; firestring_chomp(line); /* * check for blank line */ if (line[0] == '\0') continue; /* * build new object to insert */ local_domain1 = messagewall_malloc(struct messagewall_local_domain_t); firestring_estr_alloc(&local_domain1->domain,strlen(line)); firestring_estr_strcpy(&local_domain1->domain,line); /* * calculate hash */ u = dbase_text_hash(&local_domain1->domain,0); /* * insert into table */ local_domain1->next = local_domain_hash[u]; local_domain_hash[u] = local_domain1; } fclose(f); } void dbase_load_relay_ips() { /* populate relay_ips list + hash table from relay_ips config file */ struct messagewall_relay_ip_t *relay_ip1; struct in_addr *ip; char *slash; char line[257]; unsigned int u; FILE *f; int i; if (relay_ips == NULL) return; /* * we're inside the chroot; everything should be root controlled and fopen shouldn't be a problem */ f = fopen(relay_ips,"r"); /* ITS4: ignore fopen */ if (f == NULL) { perror("Unable to open relay_ips file"); exit(100); } /* * line really does have 257 bytes of space */ while (fgets(line,257,f) != NULL) { /* ITS4: ignore fgets */ /* * check for comment */ if (line[0] == '#') continue; firestring_chomp(line); /* * line must contain a slash */ slash = strchr(line,'/'); if (slash == NULL) continue; slash[0] = '\0'; slash++; /* * convert IP to binary form */ ip = firedns_aton4(line); if (ip == NULL) /* * invalid ip */ continue; /* * build new object to insert */ relay_ip1 = messagewall_malloc(struct messagewall_relay_ip_t); /* * memcpy uses sizeof destination, is safe */ memcpy(relay_ip1->ip,ip,sizeof(relay_ip1->ip)); /* ITS4: ignore memcpy */ /* * try dotted quad mask notation */ ip = firedns_aton4_s(slash,(struct in_addr *)&relay_ip1->mask); if (ip == NULL) { i = atoi(slash); if (i >= 16 && i <= 32) ip = firedns_aton4_s(dbase_cidr_subnet[i],(struct in_addr *)&relay_ip1->mask); if (ip == NULL) { /* * invalid mask */ fprintf(stderr,"STARTUP/WARNING: Invalid subnet mask: %s\n",slash); free(relay_ip1); continue; } } if (relay_ip1->mask[0] != 255 || relay_ip1->mask[1] != 255) { /* * mask larger than /16 */ fprintf(stderr,"STARTUP/WARNING: Subnet mask too large: %s\n",slash); free(relay_ip1); continue; } relay_ip1->ip[2] &= relay_ip1->mask[2]; relay_ip1->ip[3] &= relay_ip1->mask[3]; /* * calculate hash */ u = dbase_ip_hash(relay_ip1->ip); /* * insert into table */ relay_ip1->next = relay_ip_hash[u]; relay_ip_hash[u] = relay_ip1; } fclose(f); } void dbase_load_special_users() { /* populate special_users list + hash table from special_users config file */ struct messagewall_special_user_t *special_user1; char *slash; char line[1025]; unsigned int u; FILE *f; if (special_users == NULL) return; /* * we're inside the chroot; everything should be root controlled and fopen shouldn't be a problem */ f = fopen(special_users,"r"); /* ITS4: ignore fopen */ if (f == NULL) { perror("Unable to open special_users file"); exit(100); } /* * line really does have 1025 bytes of space */ while (fgets(line,1025,f) != NULL) { /* ITS4: ignore fgets */ /* * check for comment */ if (line[0] == '#') continue; firestring_chomp(line); /* * check for blank line */ if (line[0] == '\0') continue; /* * make sure it's delimited */ slash = strchr(line,':'); if (slash == NULL) continue; *(slash++) = '\0'; /* * build new object to insert */ special_user1 = messagewall_malloc(struct messagewall_special_user_t); firestring_estr_alloc(&special_user1->address,strlen(line)); firestring_estr_strcpy(&special_user1->address,line); special_user1->profile = dbase_get_profile(slash); /* * calculate hash */ u = dbase_text_hash(&special_user1->address,0); /* * insert into table */ special_user1->next = special_user_hash[u]; special_user_hash[u] = special_user1; } fclose(f); } void dbase_load() { /* reload databases from config files */ dbase_clear(); dbase_load_local_domains(); dbase_load_relay_ips(); dbase_load_special_users(); } unsigned int dbase_binary_hash(struct firestring_estr_t *domain, int start) { static char s[4]; memset(s,0,4); /* * copies a maximum of 8 bytes by virtue of min macro, is safe */ memcpy(s,&domain->s[start],min(4,domain->l - start)); /* ITS4: ignore memcpy */ return ( ( (s[0] & 0x0f) << 4 ) | ( (s[1] & 0x0f) ) ) * 256 + ( ( (s[2] & 0x0f) << 4 ) | ( (s[3] & 0x0f) ) ); } unsigned int dbase_text_hash(struct firestring_estr_t *address, int start) { /* hash value= 16bit from given position in string */ if (address->l - start >= 2) return tolower(address->s[start]) * 256 + tolower(address->s[start + 1]); else return 0; } unsigned int dbase_ip_hash(unsigned char *ip) { /* hash value= most significant 16bit from IP address */ return ip[0] * 256 + ip[1]; } int dbase_domain_is_local(struct firestring_estr_t *domain) { struct messagewall_local_domain_t *iter; unsigned int u; u = dbase_text_hash(domain,0); iter = local_domain_hash[u]; while (iter != NULL) { if (firestring_estr_estrcasecmp(domain,&iter->domain,0) == 0) return 0; iter = iter->next; } return 1; } int dbase_ip_can_relay(unsigned char *ip) { struct messagewall_relay_ip_t *iter; unsigned int u; u = dbase_ip_hash(ip); iter = relay_ip_hash[u]; while (iter != NULL) { if (((ip[2] & iter->mask[2]) == iter->ip[2]) && ((ip[3] & iter->mask[3]) == iter->ip[3])) return 0; iter = iter->next; } return 1; } struct messagewall_profile_t *dbase_get_profile(const char *name) { /* find in list or insert newly-allocated profile struct, return pointer to it */ struct messagewall_profile_t *iter; iter = profile_head; while (iter != NULL) { if (strcmp(iter->name,name) == 0) return iter; iter = iter->next; } iter = profile_head; profile_head = messagewall_malloc(struct messagewall_profile_t); profile_head->next = iter; profile_head->name = firestring_strdup(name); /* * defaults get set here */ profile_head->to_cc_check = 0; profile_head->from_check = 0; profile_head->realname_check = 0; profile_head->rdns_required = 0; profile_head->rmx_required = 0; profile_head->reject = 0; profile_head->reject_score = 1; profile_head->header_reject = NULL; profile_head->header_rejecti = NULL; profile_head->body_reject = NULL; profile_head->body_rejecti = NULL; profile_head->filename_reject = NULL; profile_head->mime_strip = NULL; profile_head->mime_allow = NULL; profile_head->mime_reject = NULL; profile_head->dnsbl = NULL; profile_head->dnsbl_domain = NULL; profile_head->dnsdcc = NULL; profile_head->virus_scan = NULL; return profile_head; } struct messagewall_dnsbl_global_t *dbase_get_dnsbl(const char *dnsbl, struct messagewall_dnsbl_global_t **global_head) { struct messagewall_dnsbl_global_t *iter; int i; iter = *global_head; while (iter != NULL) { if (firestring_estr_strcasecmp(&iter->dnsbl,dnsbl) == 0) return iter; iter = iter->next; } iter = *global_head; if (iter == NULL) i = 0; else i = iter->i + 1; *global_head = messagewall_malloc(struct messagewall_dnsbl_global_t); (*global_head)->next = iter; (*global_head)->i = i; firestring_estr_alloc(&(*global_head)->dnsbl,strlen(dnsbl)); firestring_estr_strcpy(&(*global_head)->dnsbl,dnsbl); firestring_estr_0(&(*global_head)->dnsbl); return dnsbl_head; } struct messagewall_dnsdcc_global_t *dbase_get_dnsdcc(const char *dnsdcc) { struct messagewall_dnsdcc_global_t *iter; char * (*check)(struct firestring_estr_t *); int i; iter = dnsdcc_head; while (iter != NULL) { if (firestring_estr_strcasecmp(&iter->dnsdcc,dnsdcc) == 0) return iter; iter = iter->next; } if (firestring_strncasecmp(dnsdcc,"aa.",3) == 0) check = dnsdcc_checksum_aa; else if (firestring_strncasecmp(dnsdcc,"ba.",3) == 0) check = dnsdcc_checksum_ba; else { fprintf(stderr,"STARTUP/FATAL: unrecognized DNS DCC checksum protocol\n"); exit(100); } iter = dnsdcc_head; if (iter == NULL) i = 0; else i = iter->i + 1; dnsdcc_head = messagewall_malloc(struct messagewall_dnsdcc_global_t); dnsdcc_head->next = iter; dnsdcc_head->i = i; dnsdcc_head->check = check; firestring_estr_alloc(&dnsdcc_head->dnsdcc,strlen(dnsdcc)); firestring_estr_strcpy(&dnsdcc_head->dnsdcc,dnsdcc); firestring_estr_0(&dnsdcc_head->dnsdcc); return dnsdcc_head; } static int dbase_get_score(const char **text) { const char *tempstr; int i; tempstr = strchr(*text,','); /* * return default score */ if (tempstr == NULL) return 1; i = atoi(*text); *text = tempstr + 1; return i; } void dbase_load_estr_ll(struct firestring_conf_t *config, struct messagewall_estr_ll_t **head, const char *name) { struct messagewall_estr_ll_t *estr_ll; const char *tempstr; tempstr = NULL; while ((tempstr = firestring_conf_find_next(config,name,tempstr)) != NULL) { estr_ll = *head; *head = messagewall_malloc(struct messagewall_estr_ll_t); (*head)->next = estr_ll; firestring_estr_alloc(&(*head)->string,strlen(tempstr)); firestring_estr_strcpy(&(*head)->string,tempstr); } } void dbase_load_estr_score_ll(struct firestring_conf_t *config, struct messagewall_estr_score_ll_t **head, const char *name) { struct messagewall_estr_score_ll_t *estr_ll; const char *tempstr; const char *tempstr2; tempstr2 = NULL; while ((tempstr2 = firestring_conf_find_next(config,name,tempstr2)) != NULL) { tempstr = tempstr2; estr_ll = *head; *head = messagewall_malloc(struct messagewall_estr_score_ll_t); (*head)->next = estr_ll; (*head)->score = dbase_get_score(&tempstr); firestring_estr_alloc(&(*head)->string,strlen(tempstr)); firestring_estr_strcpy(&(*head)->string,tempstr); } } void dbase_load_header_reject(struct firestring_conf_t *config, struct messagewall_header_reject_t **head, const char *name) { const char *tempstr, *tempstr2, *tempstr3; struct messagewall_header_reject_t *header_reject; int s; tempstr3 = NULL; while ((tempstr3 = firestring_conf_find_next(config,name,tempstr3)) != NULL) { tempstr = tempstr3; s = dbase_get_score(&tempstr); tempstr2 = strchr(tempstr,':'); if (tempstr2 == NULL) continue; tempstr2++; header_reject = *head; *head = messagewall_malloc(struct messagewall_header_reject_t); (*head)->next = header_reject; (*head)->score = s; firestring_estr_alloc(&(*head)->header,tempstr2 - tempstr); /* * we just allocated this amount of space (estr does nil space automatically), this is safe */ memcpy((*head)->header.s,tempstr,tempstr2 - tempstr); /* ITS4: ignore memcpy */ (*head)->header.l = tempstr2 - tempstr; firestring_estr_alloc(&(*head)->content,strlen(tempstr2)); firestring_estr_strcpy(&(*head)->content,tempstr2); } } void dbase_load_int(struct firestring_conf_t *config, int *value, const char *name) { const char *tempstr; tempstr = firestring_conf_find(config,name); if (tempstr == NULL) return; *value = atoi(tempstr); } void dbase_load_int_score(struct firestring_conf_t *config, int *value, int *score, const char *name) { const char *tempstr; tempstr = firestring_conf_find(config,name); if (tempstr == NULL) { *score = 1; return; } *score = dbase_get_score(&tempstr); *value = atoi(tempstr); } void dbase_load_profiles(const char *profile_dir) { /* populate list of profile structs from files in profile dir */ struct messagewall_profile_t *profile; struct messagewall_dnsbl_profile_t *dnsbl; struct messagewall_dnsdcc_profile_t *dnsdcc; struct messagewall_virus_database_t *virus_database; struct messagewall_virus_profile_t *virus_profile; struct firestring_conf_t *config; struct dirent *e; DIR *d; struct stat s; const char *tempstr, *tempstr3; char *tempstr2; int i; d = opendir(profile_dir); /* ITS4: ignore opendir */ if (d == NULL) { perror("opendir(profile_dir)"); exit(100); } /* * profile dir should be in root controlled location * i don't see how this can be a problem * SECURITY: do not put profile dir where users can move it out of the way or write inside of it */ if (chdir(profile_dir) != 0) { /* ITS4: ignore chdir */ perror("chdir(profile_dir)"); exit(100); } while ((e = readdir(d)) != NULL) { if (stat(e->d_name,&s) != 0) { /* ITS4: ignore stat */ fprintf(stderr,"STARTUP/STATUS: skipping profile '%s'\n",e->d_name); continue; } if (S_ISREG(s.st_mode) == 0) continue; fprintf(stderr,"STARTUP/STATUS: loaded profile %s\n",e->d_name); profile = dbase_get_profile(e->d_name); config = firestring_conf_parse(e->d_name); if (config == NULL) { fprintf(stderr,"STARTUP/FATAL: failed to load profile\n"); exit(100); } dbase_load_int_score(config,&profile->to_cc_check,&profile->to_cc_score,"to_cc_check"); dbase_load_int_score(config,&profile->from_check,&profile->from_score,"from_check"); dbase_load_int_score(config,&profile->realname_check,&profile->realname_score,"realname_check"); dbase_load_int_score(config,&profile->rdns_required,&profile->rdns_score,"rdns_required"); dbase_load_int_score(config,&profile->rmx_required,&profile->rmx_score,"rmx_required"); dbase_load_header_reject(config,&profile->header_reject,"header_reject"); dbase_load_header_reject(config,&profile->header_rejecti,"header_rejecti"); dbase_load_estr_score_ll(config,&profile->body_reject,"body_reject"); dbase_load_estr_score_ll(config,&profile->body_rejecti,"body_rejecti"); dbase_load_estr_score_ll(config,&profile->filename_reject,"filename_reject"); dbase_load_estr_score_ll(config,&profile->mime_reject,"mime_reject"); dbase_load_estr_ll(config,&profile->mime_strip,"mime_strip"); dbase_load_estr_ll(config,&profile->mime_allow,"mime_allow"); dbase_load_int(config,&profile->reject,"reject"); dbase_load_int(config,&profile->reject_score,"reject_score"); tempstr3 = NULL; while ((tempstr3 = firestring_conf_find_next(config,"dnsbl",tempstr3)) != NULL) { tempstr = tempstr3; dnsbl = profile->dnsbl; profile->dnsbl = messagewall_malloc(struct messagewall_dnsbl_profile_t); profile->dnsbl->next = dnsbl; profile->dnsbl->score = dbase_get_score(&tempstr); tempstr2 = strchr(tempstr,'/'); if (tempstr2 == NULL) profile->dnsbl->have_response_ip = 0; else { *(tempstr2++) = 0; if (firedns_aton4_s(tempstr2,&profile->dnsbl->response_ip) == NULL) profile->dnsbl->have_response_ip = 0; else profile->dnsbl->have_response_ip = 1; } profile->dnsbl->global = dbase_get_dnsbl(tempstr,&dnsbl_head); } tempstr3 = NULL; while ((tempstr3 = firestring_conf_find_next(config,"dnsbl_domain",tempstr3)) != NULL) { tempstr = tempstr3; dnsbl = profile->dnsbl_domain; profile->dnsbl_domain = messagewall_malloc(struct messagewall_dnsbl_profile_t); profile->dnsbl_domain->next = dnsbl; profile->dnsbl_domain->score = dbase_get_score(&tempstr); tempstr2 = strchr(tempstr,'/'); if (tempstr2 == NULL) profile->dnsbl_domain->have_response_ip = 0; else { *(tempstr2++) = 0; if (firedns_aton4_s(tempstr2,&profile->dnsbl_domain->response_ip) == NULL) profile->dnsbl_domain->have_response_ip = 0; else profile->dnsbl_domain->have_response_ip = 1; } profile->dnsbl_domain->global = dbase_get_dnsbl(tempstr,&dnsbl_domain_head); } tempstr3 = NULL; while ((tempstr3 = firestring_conf_find_next(config,"dnsdcc",tempstr3)) != NULL) { tempstr = tempstr3; dnsdcc = profile->dnsdcc; profile->dnsdcc = messagewall_malloc(struct messagewall_dnsdcc_profile_t); profile->dnsdcc->next = dnsdcc; profile->dnsdcc->score = dbase_get_score(&tempstr); profile->dnsdcc->global = dbase_get_dnsdcc(tempstr); } tempstr3 = NULL; while ((tempstr3 = firestring_conf_find_next(config,"virus_scan",tempstr3)) != NULL) { tempstr = tempstr3; i = dbase_get_score(&tempstr); virus_database = virus_get_database(tempstr); if (virus_database == NULL) continue; virus_profile = profile->virus_scan; profile->virus_scan = messagewall_malloc(struct messagewall_virus_profile_t); profile->virus_scan->next = virus_profile; profile->virus_scan->global = virus_database; profile->virus_scan->score = i; } } closedir(d); }