From: Wietse Venema Date: Tue, 31 Jul 2001 05:00:00 +0000 (-0500) Subject: postfix-20010228-pl04 X-Git-Tag: v20010228-pl04^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ac12a73eda1424fd875540a8eb9ecae2dc5b25fb;p=thirdparty%2Fpostfix.git postfix-20010228-pl04 --- diff --git a/postfix/HISTORY b/postfix/HISTORY index 13a9bcfad..ab2d0fbb5 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -5063,3 +5063,36 @@ Apologies for any names omitted. sending QUIT after process idle timeout while the LMTP server had disconnected. Files: smtp/smtp_proto.c, lmtp/lmtp_proto.c. + +20010727 + + Bugfix: updated LDAP client module from LaMont Jones, HP. + This also introduces new LDAP query filter patterns: %u + (address localpart) and %d (domain part). Files: + conf/sample-ldap.cf, util/dict_ldap.c. + +20010729 + + Bugfix: recursive smtpd_whatever_restrictions clobbered + intermediate results when switching between sender and + recipient address restrictions. Problem found by Victor + Duchovni, morganstanley.com. In order to fix, introduced + address resolver result caching, which should also help to + speed up sender/recipient address restriction processing. + + Bugfix: the not yet announced DUNNO access table lookup + result did not prevent lookups with substrings of the same + lookup key. Found by Victor Duchovni, morganstanley.com. + +20010730 + + Robustness: trim trailing whitespace from regexp and pcre + right-hand sides, for consistency with DB/DBM tables. + Files: util/dict_pcre.c, util/dict_regexp.c. + +20010731 + + Robustness: eliminate duplicate IP addresses after expansion + of hostnames in $inet_interfaces, so that Postfix does not + suddenly refuse to start up after someone changes the DNS. + Files: util/inet_addr_list.c global/own_inet_addr.c. diff --git a/postfix/MYSQL_README b/postfix/MYSQL_README index faecf80a2..2f11bd956 100644 --- a/postfix/MYSQL_README +++ b/postfix/MYSQL_README @@ -12,9 +12,10 @@ the mysqlclient library (and libm) to AUXLIBS, for example: make -f Makefile.init makefiles \ 'CCARGS=-DHAS_MYSQL -I/usr/local/mysql/include' \ - 'AUXLIBS=-L/usr/local/mysql/lib -lmysqlclient -lm' + 'AUXLIBS=-L/usr/local/mysql/lib -lmysqlclient -lz -lm' -then, just run 'make'. +then, just run 'make'. This requires libz, the compression library. +Older mysql implementations build without libz. Postfix installations which may benefit from using mysql map types include sites that have a need for instantaneous updates of diff --git a/postfix/conf/sample-ldap.cf b/postfix/conf/sample-ldap.cf index c503df0bc..8badb4026 100644 --- a/postfix/conf/sample-ldap.cf +++ b/postfix/conf/sample-ldap.cf @@ -23,6 +23,13 @@ #ldap_server_port = 389 # The ldap_query_filter parameter specifies the filter used for queries. +# The replacement for "%s" is the address input into the map; e.g. +# for alias maps, the "user" part (the RFC 2822 local-part) of +# "user@domain.com" for To: addresses destined for local delivery +# (those matching $mydestination or a virtual domain), and all of +# "user@domain.com" (the RFC 2822 addr-spec) for other addresses. +# "%u" provides just the user portion of the input, and "%d" provides +# just the hostname. # #ldap_query_filter = (mailacceptinggeneralid=%s) @@ -31,6 +38,13 @@ # #ldap_result_attribute = maildrop +# The ldap_special_result_attribute lists the attribute(s) of an +# entry which contain links, either ldap url's or distinguished names. +# The entries referenced by these links are (recursively) treated as if +# they were contained in the referencing entity. +# +# ldap_special_result_attribute = + # The ldap_scope parameter specifies the LDAP search scope: sub, base, or one. # #ldap_scope = sub diff --git a/postfix/html/faq.html b/postfix/html/faq.html index a2b8b627a..425304fdd 100644 --- a/postfix/html/faq.html +++ b/postfix/html/faq.html @@ -1027,7 +1027,7 @@ Berkeley DB library version.
-

sendmail has set-uid root file permissions, or is run from a +

sendmail has set-uid root file permissions, or is run from a set-uid root process

Traditionally, the UNIX sendmail command is installed with diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index f725dbca0..4ccf192f8 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -15,7 +15,7 @@ * Version of this program. */ #define VAR_MAIL_VERSION "mail_version" -#define DEF_MAIL_VERSION "Postfix-20010228-pl03" +#define DEF_MAIL_VERSION "Postfix-20010228-pl04" extern char *var_mail_version; /* LICENSE diff --git a/postfix/src/global/own_inet_addr.c b/postfix/src/global/own_inet_addr.c index f06232989..1dc8aaf4f 100644 --- a/postfix/src/global/own_inet_addr.c +++ b/postfix/src/global/own_inet_addr.c @@ -107,6 +107,14 @@ static void own_inet_addr_init(INET_ADDR_LIST *addr_list, VAR_INET_INTERFACES, host); myfree(hosts); + /* + * Weed out duplicate IP addresses. Duplicates happen when the same + * IP address is listed under multiple hostnames. If we don't weed + * out duplicates, Postfix can suddenly stop working after the DNS is + * changed. + */ + inet_addr_list_uniq(addr_list); + inet_addr_list_init(&local_addrs); inet_addr_list_init(&local_masks); if (inet_addr_local(&local_addrs, &local_masks) == 0) diff --git a/postfix/src/smtp/smtp_connect.c b/postfix/src/smtp/smtp_connect.c index 6b9c58f6e..8c9470520 100644 --- a/postfix/src/smtp/smtp_connect.c +++ b/postfix/src/smtp/smtp_connect.c @@ -394,7 +394,7 @@ SMTP_SESSION *smtp_connect(char *destination, VSTRING *why) char *save; char *dest; char *cp; - int found_myself; + int found_myself = 0; /* * First try to deliver to the indicated destination, then try to deliver diff --git a/postfix/src/smtpd/Makefile.in b/postfix/src/smtpd/Makefile.in index 90d9aa96b..aebfcaeac 100644 --- a/postfix/src/smtpd/Makefile.in +++ b/postfix/src/smtpd/Makefile.in @@ -67,7 +67,7 @@ depend: $(MAKES) done) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in @$(EXPORT) make -f Makefile.in Makefile 1>&2 -tests: smtpd_check_test smtpd_check_test2 smtpd_token_test +tests: smtpd_check_test smtpd_check_test2 smtpd_acl_test smtpd_token_test smtpd_check_test: smtpd_check smtpd_check.in smtpd_check.ref ../postmap/postmap smtpd_check_access @@ -81,6 +81,12 @@ smtpd_check_test2: smtpd_check smtpd_check.in2 smtpd_check.ref2 diff smtpd_check.ref2 smtpd_check.tmp rm -f smtpd_check.tmp smtpd_check_access.* +smtpd_acl_test: smtpd_check smtpd_acl.in smtpd_acl.ref + ../postmap/postmap smtpd_check_access + ./smtpd_check smtpd_check.tmp 2>&1 + diff smtpd_acl.ref smtpd_check.tmp + rm -f smtpd_check.tmp smtpd_check_access.* + smtpd_token_test: smtpd_token smtpd_token.in smtpd_token.ref ./smtpd_token smtpd_token.tmp 2>&1 diff smtpd_token.ref smtpd_token.tmp @@ -164,6 +170,7 @@ smtpd_check.o: ../../include/mymalloc.h smtpd_check.o: ../../include/dict.h smtpd_check.o: ../../include/vstream.h smtpd_check.o: ../../include/htable.h +smtpd_check.o: ../../include/ctable.h smtpd_check.o: ../../include/dns.h smtpd_check.o: ../../include/namadr_list.h smtpd_check.o: ../../include/domain_list.h diff --git a/postfix/src/smtpd/smtpd_acl.in b/postfix/src/smtpd/smtpd_acl.in new file mode 100644 index 000000000..e633c48d5 --- /dev/null +++ b/postfix/src/smtpd/smtpd_acl.in @@ -0,0 +1,107 @@ +# +# Initialize +# +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.189.0/28 +relay_domains porcupine.org +# +# Test check_domain_access() +# +helo_restrictions hash:./smtpd_check_access +# Expect: REJECT +helo foo.dunno.com +# Expect: OK +helo bar.dunno.com +# Expect: OK +helo foo.duuno.com +# +# Test check_namadr_access(), domain part +# +client_restrictions hash:./smtpd_check_access +# Expect: REJECT +client foo.dunno.com 131.155.210.17 +# Expect: OK +client bar.dunno.com 131.155.210.17 +# Expect: OK +client bar.dunno.com 131.155.210.19 +# +# Test check_namadr_access(), address part +# +# Expect: OK +client bar.duno.com 131.155.210.17 +# Expect: REJECT +client bar.duno.com 131.155.210.19 +# Expect: REJECT +client bar.duno.com 44.33.22.11 +# Expect: OK +client bar.duno.com 44.33.22.55 +# Expect: REJECT +client bar.duno.com 44.33.44.33 +# +# Test check_mail_access() +# +sender_restrictions hash:./smtpd_check_access +# Expect: REJECT +mail reject@dunno.domain +# Expect: OK +mail ok@dunno.domain +# Expect: OK +mail anyone@dunno.domain +# Expect: OK +mail bad-sender@dunno.domain +# +# Again, with a domain that rejects by default +# +# Expect: REJECT +mail reject@reject.domain +# Expect: OK +mail ok@reject.domain +# Expect: REJECT +mail anyone@reject.domain +# Expect: REJECT +mail good-sender@reject.domain +# +# Again, with a domain that accepts by default +# +# Expect: REJECT +mail reject@ok.domain +# Expect: OK +mail ok@ok.domain +# Expect: OK +mail anyone@ok.domain +# Expect: OK +mail bad-sender@ok.domain +# +# Test check_mail_access() +# +recipient_restrictions hash:./smtpd_check_access +# Expect: REJECT +rcpt reject@dunno.domain +# Expect: OK +rcpt ok@dunno.domain +# Expect: OK +rcpt anyone@dunno.domain +# Expect: OK +rcpt bad-sender@dunno.domain +# +# Again, with a domain that rejects by default +# +# Expect: REJECT +rcpt reject@reject.domain +# Expect: OK +rcpt ok@reject.domain +# Expect: REJECT +rcpt anyone@reject.domain +# Expect: REJECT +rcpt good-sender@reject.domain +# +# Again, with a domain that accepts by default +# +# Expect: REJECT +rcpt reject@ok.domain +# Expect: OK +rcpt ok@ok.domain +# Expect: OK +rcpt anyone@ok.domain +# Expect: OK +rcpt bad-sender@ok.domain diff --git a/postfix/src/smtpd/smtpd_acl.ref b/postfix/src/smtpd/smtpd_acl.ref new file mode 100644 index 000000000..34e351866 --- /dev/null +++ b/postfix/src/smtpd/smtpd_acl.ref @@ -0,0 +1,164 @@ +>>> # +>>> # Initialize +>>> # +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.189.0/28 +OK +>>> relay_domains porcupine.org +OK +>>> # +>>> # Test check_domain_access() +>>> # +>>> helo_restrictions hash:./smtpd_check_access +OK +>>> # Expect: REJECT +>>> helo foo.dunno.com +./smtpd_check: reject: HELO from localhost[127.0.0.1]: 554 : Helo command rejected: Access denied +554 : Helo command rejected: Access denied +>>> # Expect: OK +>>> helo bar.dunno.com +OK +>>> # Expect: OK +>>> helo foo.duuno.com +OK +>>> # +>>> # Test check_namadr_access(), domain part +>>> # +>>> client_restrictions hash:./smtpd_check_access +OK +>>> # Expect: REJECT +>>> client foo.dunno.com 131.155.210.17 +./smtpd_check: reject: CONNECT from foo.dunno.com[131.155.210.17]: 554 : Client host rejected: Access denied +554 : Client host rejected: Access denied +>>> # Expect: OK +>>> client bar.dunno.com 131.155.210.17 +OK +>>> # Expect: OK +>>> client bar.dunno.com 131.155.210.19 +OK +>>> # +>>> # Test check_namadr_access(), address part +>>> # +>>> # Expect: OK +>>> client bar.duno.com 131.155.210.17 +OK +>>> # Expect: REJECT +>>> client bar.duno.com 131.155.210.19 +./smtpd_check: reject: CONNECT from bar.duno.com[131.155.210.19]: 554 : Client host rejected: Access denied +554 : Client host rejected: Access denied +>>> # Expect: REJECT +>>> client bar.duno.com 44.33.22.11 +./smtpd_check: reject: CONNECT from bar.duno.com[44.33.22.11]: 554 : Client host rejected: Access denied +554 : Client host rejected: Access denied +>>> # Expect: OK +>>> client bar.duno.com 44.33.22.55 +OK +>>> # Expect: REJECT +>>> client bar.duno.com 44.33.44.33 +./smtpd_check: reject: CONNECT from bar.duno.com[44.33.44.33]: 554 : Client host rejected: Access denied +554 : Client host rejected: Access denied +>>> # +>>> # Test check_mail_access() +>>> # +>>> sender_restrictions hash:./smtpd_check_access +OK +>>> # Expect: REJECT +>>> mail reject@dunno.domain +./smtpd_check: reject: MAIL from bar.duno.com[44.33.44.33]: 554 : Sender address rejected: Access denied; from= +554 : Sender address rejected: Access denied +>>> # Expect: OK +>>> mail ok@dunno.domain +OK +>>> # Expect: OK +>>> mail anyone@dunno.domain +OK +>>> # Expect: OK +>>> mail bad-sender@dunno.domain +OK +>>> # +>>> # Again, with a domain that rejects by default +>>> # +>>> # Expect: REJECT +>>> mail reject@reject.domain +./smtpd_check: reject: MAIL from bar.duno.com[44.33.44.33]: 554 : Sender address rejected: Access denied; from= +554 : Sender address rejected: Access denied +>>> # Expect: OK +>>> mail ok@reject.domain +OK +>>> # Expect: REJECT +>>> mail anyone@reject.domain +./smtpd_check: reject: MAIL from bar.duno.com[44.33.44.33]: 554 : Sender address rejected: Access denied; from= +554 : Sender address rejected: Access denied +>>> # Expect: REJECT +>>> mail good-sender@reject.domain +./smtpd_check: reject: MAIL from bar.duno.com[44.33.44.33]: 554 : Sender address rejected: Access denied; from= +554 : Sender address rejected: Access denied +>>> # +>>> # Again, with a domain that accepts by default +>>> # +>>> # Expect: REJECT +>>> mail reject@ok.domain +./smtpd_check: reject: MAIL from bar.duno.com[44.33.44.33]: 554 : Sender address rejected: Access denied; from= +554 : Sender address rejected: Access denied +>>> # Expect: OK +>>> mail ok@ok.domain +OK +>>> # Expect: OK +>>> mail anyone@ok.domain +OK +>>> # Expect: OK +>>> mail bad-sender@ok.domain +OK +>>> # +>>> # Test check_mail_access() +>>> # +>>> recipient_restrictions hash:./smtpd_check_access +OK +>>> # Expect: REJECT +>>> rcpt reject@dunno.domain +./smtpd_check: reject: RCPT from bar.duno.com[44.33.44.33]: 554 : Recipient address rejected: Access denied; from= to= +554 : Recipient address rejected: Access denied +>>> # Expect: OK +>>> rcpt ok@dunno.domain +OK +>>> # Expect: OK +>>> rcpt anyone@dunno.domain +OK +>>> # Expect: OK +>>> rcpt bad-sender@dunno.domain +OK +>>> # +>>> # Again, with a domain that rejects by default +>>> # +>>> # Expect: REJECT +>>> rcpt reject@reject.domain +./smtpd_check: reject: RCPT from bar.duno.com[44.33.44.33]: 554 : Recipient address rejected: Access denied; from= to= +554 : Recipient address rejected: Access denied +>>> # Expect: OK +>>> rcpt ok@reject.domain +OK +>>> # Expect: REJECT +>>> rcpt anyone@reject.domain +./smtpd_check: reject: RCPT from bar.duno.com[44.33.44.33]: 554 : Recipient address rejected: Access denied; from= to= +554 : Recipient address rejected: Access denied +>>> # Expect: REJECT +>>> rcpt good-sender@reject.domain +./smtpd_check: reject: RCPT from bar.duno.com[44.33.44.33]: 554 : Recipient address rejected: Access denied; from= to= +554 : Recipient address rejected: Access denied +>>> # +>>> # Again, with a domain that accepts by default +>>> # +>>> # Expect: REJECT +>>> rcpt reject@ok.domain +./smtpd_check: reject: RCPT from bar.duno.com[44.33.44.33]: 554 : Recipient address rejected: Access denied; from= to= +554 : Recipient address rejected: Access denied +>>> # Expect: OK +>>> rcpt ok@ok.domain +OK +>>> # Expect: OK +>>> rcpt anyone@ok.domain +OK +>>> # Expect: OK +>>> rcpt bad-sender@ok.domain +OK diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c index 5f9596d3d..a8ed776f6 100644 --- a/postfix/src/smtpd/smtpd_check.c +++ b/postfix/src/smtpd/smtpd_check.c @@ -269,6 +269,7 @@ #include #include #include +#include /* DNS library. */ @@ -310,9 +311,8 @@ static jmp_buf smtpd_check_buf; * Intermediate results. These are static to avoid unnecessary stress on the * memory manager routines. */ -static RESOLVE_REPLY reply; -static VSTRING *query; static VSTRING *error_text; +static CTABLE *smtpd_resolve_cache; /* * Pre-opened SMTP recipient maps so we can reject mail for unknown users. @@ -345,7 +345,7 @@ static HTABLE *smtpd_rest_classes; /* * The routine that recursively applies restrictions. */ -static int generic_checks(SMTPD_STATE *, ARGV *, char *, char *, char *); +static int generic_checks(SMTPD_STATE *, ARGV *, const char *, const char *, const char *); /* * Reject context. @@ -360,6 +360,49 @@ static int generic_checks(SMTPD_STATE *, ARGV *, char *, char *, char *); * YASLM. */ #define STR vstring_str +#define CONST_STR(x) ((const char *) vstring_str(x)) + +/* resolve_pagein - page in an address resolver result */ + +static void *resolve_pagein(const char *addr, void *unused_context) +{ + static VSTRING *query; + RESOLVE_REPLY *reply; + + /* + * Initialize on the fly. + */ + if (query == 0) + query = vstring_alloc(10); + + /* + * Initialize. + */ + reply = (RESOLVE_REPLY *) mymalloc(sizeof(*reply)); + resolve_clnt_init(reply); + + /* + * Resolve the address. + */ + canon_addr_internal(query, addr); + resolve_clnt_query(STR(query), reply); + lowercase(STR(reply->recipient)); + + /* + * Save the result. + */ + return ((void *) reply); +} + +/* resolve_pageout - page out an address resolver result */ + +static void resolve_pageout(void *data, void *unused_context) +{ + RESOLVE_REPLY *reply = (RESOLVE_REPLY *) data; + + resolve_clnt_free(reply); + myfree((void *) reply); +} /* smtpd_check_parse - pre-parse restrictions */ @@ -471,13 +514,16 @@ void smtpd_check_init(void) DICT_FLAG_LOCK); /* - * Reply is used as a cache for resolved addresses, and error_text is - * used for returning error responses. + * error_text is used for returning error responses. */ - resolve_clnt_init(&reply); - query = vstring_alloc(10); error_text = vstring_alloc(10); + /* + * Initialize the resolved address cache. + */ + smtpd_resolve_cache = ctable_create(100, resolve_pagein, + resolve_pageout, (void *) 0); + /* * Pre-parse the restriction lists. At the same time, pre-open tables * before going to jail. @@ -620,8 +666,10 @@ static const char *check_maps_find(SMTPD_STATE *state, const char *reply_name, /* check_mail_addr_find - reject with temporary failure if dict lookup fails */ -static const char *check_mail_addr_find(SMTPD_STATE *state, const char *reply_name, - MAPS *maps, const char *key, char **ext) +static const char *check_mail_addr_find(SMTPD_STATE *state, + const char *reply_name, + MAPS *maps, const char *key, + char **ext) { const char *result; @@ -816,8 +864,8 @@ static int reject_unknown_hostname(SMTPD_STATE *state, char *name, /* reject_unknown_mailhost - fail if name has no A or MX record */ -static int reject_unknown_mailhost(SMTPD_STATE *state, char *name, - char *reply_name, char *reply_class) +static int reject_unknown_mailhost(SMTPD_STATE *state, const char *name, + const char *reply_name, const char *reply_class) { char *myname = "reject_unknown_mailhost"; int dns_status; @@ -873,7 +921,8 @@ static int check_relay_domains(SMTPD_STATE *state, char *recipient, static int permit_auth_destination(SMTPD_STATE *state, char *recipient) { char *myname = "permit_auth_destination"; - char *domain; + const RESOLVE_REPLY *reply; + const char *domain; if (msg_verbose) msg_info("%s: %s", myname, recipient); @@ -881,14 +930,13 @@ static int permit_auth_destination(SMTPD_STATE *state, char *recipient) /* * Resolve the address. */ - canon_addr_internal(query, recipient); - resolve_clnt_query(STR(query), &reply); - lowercase(STR(reply.recipient)); + reply = (const RESOLVE_REPLY *) + ctable_locate(smtpd_resolve_cache, recipient); /* * Handle special case that is not supposed to happen. */ - if ((domain = strrchr(STR(reply.recipient), '@')) == 0) + if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0) return (SMTPD_CHECK_OK); domain += 1; @@ -904,7 +952,7 @@ static int permit_auth_destination(SMTPD_STATE *state, char *recipient) /* * Skip source-routed mail (uncertain destination). */ - if (var_allow_untrust_route == 0 && (reply.flags & RESOLVE_FLAG_ROUTED)) + if (var_allow_untrust_route == 0 && (reply->flags & RESOLVE_FLAG_ROUTED)) return (SMTPD_CHECK_DUNNO); /* @@ -963,7 +1011,7 @@ static int reject_unauth_pipelining(SMTPD_STATE *state) /* has_my_addr - see if this host name lists one of my network addresses */ -static int has_my_addr(char *host) +static int has_my_addr(const char *host) { char *myname = "has_my_addr"; struct in_addr addr; @@ -1007,7 +1055,8 @@ static int has_my_addr(char *host) static int permit_mx_backup(SMTPD_STATE *state, const char *recipient) { char *myname = "permit_mx_backup"; - char *domain; + const RESOLVE_REPLY *reply; + const char *domain; DNS_RR *mx_list; DNS_RR *mx; @@ -1019,15 +1068,14 @@ static int permit_mx_backup(SMTPD_STATE *state, const char *recipient) /* * Resolve the address. */ - canon_addr_internal(query, recipient); - resolve_clnt_query(STR(query), &reply); - lowercase(STR(reply.recipient)); + reply = (const RESOLVE_REPLY *) + ctable_locate(smtpd_resolve_cache, recipient); /* * If the destination is local, it is acceptable, because we are * supposedly MX for our own address. */ - if ((domain = strrchr(STR(reply.recipient), '@')) == 0) + if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0) return (SMTPD_CHECK_OK); domain += 1; if (resolve_local(domain) @@ -1041,7 +1089,7 @@ static int permit_mx_backup(SMTPD_STATE *state, const char *recipient) /* * Skip source-routed mail (uncertain destination). */ - if (var_allow_untrust_route == 0 && (reply.flags & RESOLVE_FLAG_ROUTED)) + if (var_allow_untrust_route == 0 && (reply->flags & RESOLVE_FLAG_ROUTED)) return (SMTPD_CHECK_DUNNO); /* @@ -1150,11 +1198,12 @@ static int reject_non_fqdn_address(SMTPD_STATE *state, char *addr, /* reject_unknown_address - fail if address does not resolve */ -static int reject_unknown_address(SMTPD_STATE *state, char *addr, - char *reply_name, char *reply_class) +static int reject_unknown_address(SMTPD_STATE *state, const char *addr, + const char *reply_name, const char *reply_class) { char *myname = "reject_unknown_address"; - char *domain; + const RESOLVE_REPLY *reply; + const char *domain; if (msg_verbose) msg_info("%s: %s", myname, addr); @@ -1162,14 +1211,12 @@ static int reject_unknown_address(SMTPD_STATE *state, char *addr, /* * Resolve the address. */ - canon_addr_internal(query, addr); - resolve_clnt_query(STR(query), &reply); - lowercase(STR(reply.recipient)); + reply = (const RESOLVE_REPLY *) ctable_locate(smtpd_resolve_cache, addr); /* * Skip local destinations and non-DNS forms. */ - if ((domain = strrchr(STR(reply.recipient), '@')) == 0) + if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0) return (SMTPD_CHECK_DUNNO); domain += 1; if (resolve_local(domain) @@ -1189,10 +1236,11 @@ static int reject_unknown_address(SMTPD_STATE *state, char *addr, /* check_table_result - translate table lookup result into pass/reject */ -static int check_table_result(SMTPD_STATE *state, char *table, +static int check_table_result(SMTPD_STATE *state, const char *table, const char *value, const char *datum, - char *reply_name, char *reply_class, - char *def_acl) + const char *reply_name, + const char *reply_class, + const char *def_acl) { char *myname = "check_table_result"; int code; @@ -1287,17 +1335,20 @@ static int check_table_result(SMTPD_STATE *state, char *table, /* check_access - table lookup without substring magic */ -static int check_access(SMTPD_STATE *state, char *table, char *name, int flags, - char *reply_name, char *reply_class, char *def_acl) +static int check_access(SMTPD_STATE *state, const char *table, const char *name, + int flags, int *found, const char *reply_name, + const char *reply_class, const char *def_acl) { char *myname = "check_access"; char *low_name = lowercase(mystrdup(name)); const char *value; DICT *dict; -#define CHK_ACCESS_RETURN(x) { myfree(low_name); return(x); } +#define CHK_ACCESS_RETURN(x,y) { *found = y; myfree(low_name); return(x); } #define FULL 0 #define PARTIAL DICT_FLAG_FIXED +#define FOUND 1 +#define MISSED 0 if (msg_verbose) msg_info("%s: %s", myname, name); @@ -1308,19 +1359,20 @@ static int check_access(SMTPD_STATE *state, char *table, char *name, int flags, if ((value = dict_get(dict, low_name)) != 0) CHK_ACCESS_RETURN(check_table_result(state, table, value, name, reply_name, reply_class, - def_acl)); + def_acl), FOUND); if (dict_errno != 0) msg_fatal("%s: table lookup problem", table); } - CHK_ACCESS_RETURN(SMTPD_CHECK_DUNNO); + CHK_ACCESS_RETURN(SMTPD_CHECK_DUNNO, MISSED); } /* check_domain_access - domainname-based table lookup */ -static int check_domain_access(SMTPD_STATE *state, char *table, - char *domain, int flags, - char *reply_name, char *reply_class, - char *def_acl) +static int check_domain_access(SMTPD_STATE *state, const char *table, + const char *domain, int flags, + int *found, const char *reply_name, + const char *reply_class, + const char *def_acl) { char *myname = "check_domain_access"; char *low_domain = lowercase(mystrdup(domain)); @@ -1335,7 +1387,7 @@ static int check_domain_access(SMTPD_STATE *state, char *table, /* * Try the name and its parent domains. Including top-level domains. */ -#define CHK_DOMAIN_RETURN(x) { myfree(low_domain); return(x); } +#define CHK_DOMAIN_RETURN(x,y) { *found = y; myfree(low_domain); return(x); } if ((dict = dict_handle(table)) == 0) msg_panic("%s: dictionary not found: %s", myname, table); @@ -1344,7 +1396,7 @@ static int check_domain_access(SMTPD_STATE *state, char *table, if ((value = dict_get(dict, name)) != 0) CHK_DOMAIN_RETURN(check_table_result(state, table, value, domain, reply_name, reply_class, - def_acl)); + def_acl), FOUND); if (dict_errno != 0) msg_fatal("%s: table lookup problem", table); } @@ -1352,15 +1404,16 @@ static int check_domain_access(SMTPD_STATE *state, char *table, break; flags = PARTIAL; } - CHK_DOMAIN_RETURN(SMTPD_CHECK_DUNNO); + CHK_DOMAIN_RETURN(SMTPD_CHECK_DUNNO, MISSED); } /* check_addr_access - address-based table lookup */ -static int check_addr_access(SMTPD_STATE *state, char *table, - char *address, int flags, - char *reply_name, char *reply_class, - char *def_acl) +static int check_addr_access(SMTPD_STATE *state, const char *table, + const char *address, int flags, + int *found, const char *reply_name, + const char *reply_class, + const char *def_acl) { char *myname = "check_addr_access"; char *addr; @@ -1373,6 +1426,8 @@ static int check_addr_access(SMTPD_STATE *state, char *table, /* * Try the address and its parent networks. */ +#define CHK_ADDR_RETURN(x,y) { *found = y; return(x); } + addr = STR(vstring_strcpy(error_text, address)); if ((dict = dict_handle(table)) == 0) @@ -1380,24 +1435,26 @@ static int check_addr_access(SMTPD_STATE *state, char *table, do { if (flags == 0 || (flags & dict->flags) != 0) { if ((value = dict_get(dict, addr)) != 0) - return (check_table_result(state, table, value, address, - reply_name, reply_class, - def_acl)); + CHK_ADDR_RETURN(check_table_result(state, table, value, address, + reply_name, reply_class, + def_acl), FOUND); if (dict_errno != 0) msg_fatal("%s: table lookup problem", table); } flags = PARTIAL; } while (split_at_right(addr, '.')); - return (SMTPD_CHECK_DUNNO); + CHK_ADDR_RETURN(SMTPD_CHECK_DUNNO, MISSED); } /* check_namadr_access - OK/FAIL based on host name/address lookup */ -static int check_namadr_access(SMTPD_STATE *state, char *table, - char *name, char *addr, int flags, - char *reply_name, char *reply_class, - char *def_acl) +static int check_namadr_access(SMTPD_STATE *state, const char *table, + const char *name, const char *addr, + int flags, int *found, + const char *reply_name, + const char *reply_class, + const char *def_acl) { char *myname = "check_namadr_access"; int status; @@ -1410,16 +1467,16 @@ static int check_namadr_access(SMTPD_STATE *state, char *table, * wildcard may pre-empt a more specific address table entry. */ if ((status = check_domain_access(state, table, name, flags, - reply_name, reply_class, - def_acl)) != 0) + found, reply_name, reply_class, + def_acl)) != 0 || *found) return (status); /* * Look up the network address, or parent networks thereof. */ if ((status = check_addr_access(state, table, addr, flags, - reply_name, reply_class, - def_acl)) != 0) + found, reply_name, reply_class, + def_acl)) != 0 || *found) return (status); /* @@ -1430,12 +1487,15 @@ static int check_namadr_access(SMTPD_STATE *state, char *table, /* check_mail_access - OK/FAIL based on mail address lookup */ -static int check_mail_access(SMTPD_STATE *state, char *table, char *addr, - char *reply_name, char *reply_class, - char *def_acl) +static int check_mail_access(SMTPD_STATE *state, const char *table, + const char *addr, int *found, + const char *reply_name, + const char *reply_class, + const char *def_acl) { char *myname = "check_mail_access"; - char *ratsign; + const RESOLVE_REPLY *reply; + const char *ratsign; int status; char *local_at; @@ -1445,42 +1505,42 @@ static int check_mail_access(SMTPD_STATE *state, char *table, char *addr, /* * Resolve the address. */ - canon_addr_internal(query, addr); - resolve_clnt_query(STR(query), &reply); - lowercase(STR(reply.recipient)); + reply = (const RESOLVE_REPLY *) ctable_locate(smtpd_resolve_cache, addr); /* * Garbage in, garbage out. Every address from canon_addr_internal() and * from resolve_clnt_query() must be fully qualified. */ - if ((ratsign = strrchr(STR(reply.recipient), '@')) == 0) { - msg_warn("%s: no @domain in address: %s", myname, STR(reply.recipient)); + if ((ratsign = strrchr(CONST_STR(reply->recipient), '@')) == 0) { + msg_warn("%s: no @domain in address: %s", myname, CONST_STR(reply->recipient)); return (0); } /* * Look up the full address. */ - if ((status = check_access(state, table, STR(reply.recipient), FULL, - reply_name, reply_class, def_acl)) != 0) + if ((status = check_access(state, table, CONST_STR(reply->recipient), FULL, + found, reply_name, reply_class, def_acl)) != 0 + || *found) return (status); /* * Look up the domain name, or parent domains thereof. */ if ((status = check_domain_access(state, table, ratsign + 1, PARTIAL, - reply_name, reply_class, def_acl)) != 0) + found, reply_name, reply_class, def_acl)) != 0 + || *found) return (status); /* * Look up localpart@ */ - local_at = mystrndup(STR(reply.recipient), - ratsign - STR(reply.recipient) + 1); - status = check_access(state, table, local_at, PARTIAL, + local_at = mystrndup(CONST_STR(reply->recipient), + ratsign - CONST_STR(reply->recipient) + 1); + status = check_access(state, table, local_at, PARTIAL, found, reply_name, reply_class, def_acl); myfree(local_at); - if (status != 0) + if (status != 0 || *found) return (status); /* @@ -1573,7 +1633,7 @@ static int reject_maps_rbl(SMTPD_STATE *state) /* is_map_command - restriction has form: check_xxx_access type:name */ -static int is_map_command(char *name, char *command, char ***argp) +static int is_map_command(const char *name, const char *command, char ***argp) { /* @@ -1596,13 +1656,16 @@ static int is_map_command(char *name, char *command, char ***argp) /* generic_checks - generic restrictions */ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions, - char *reply_name, char *reply_class, char *def_acl) + const char *reply_name, + const char *reply_class, + const char *def_acl) { char *myname = "generic_checks"; char **cpp; - char *name; + const char *name; int status = 0; ARGV *list; + int found; if (msg_verbose) msg_info("%s: START", myname); @@ -1649,7 +1712,7 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions, status = permit_mynetworks(state); } else if (is_map_command(name, CHECK_CLIENT_ACL, &cpp)) { status = check_namadr_access(state, *cpp, state->name, state->addr, - FULL, state->namaddr, + FULL, &found, state->namaddr, SMTPD_NAME_CLIENT, def_acl); } else if (strcasecmp(name, REJECT_MAPS_RBL) == 0) { status = reject_maps_rbl(state); @@ -1661,7 +1724,7 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions, else if (is_map_command(name, CHECK_HELO_ACL, &cpp)) { if (state->helo_name) status = check_domain_access(state, *cpp, state->helo_name, - FULL, state->helo_name, + FULL, &found, state->helo_name, SMTPD_NAME_HELO, def_acl); } else if (strcasecmp(name, REJECT_INVALID_HOSTNAME) == 0) { if (state->helo_name) { @@ -1705,7 +1768,7 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions, else if (is_map_command(name, CHECK_SENDER_ACL, &cpp)) { if (state->sender && *state->sender) status = check_mail_access(state, *cpp, state->sender, - state->sender, + &found, state->sender, SMTPD_NAME_SENDER, def_acl); } else if (strcasecmp(name, REJECT_UNKNOWN_ADDRESS) == 0) { if (state->sender && *state->sender) @@ -1727,7 +1790,7 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions, else if (is_map_command(name, CHECK_RECIP_ACL, &cpp)) { if (state->recipient) status = check_mail_access(state, *cpp, state->recipient, - state->recipient, + &found, state->recipient, SMTPD_NAME_RECIPIENT, def_acl); } else if (strcasecmp(name, PERMIT_MX_BACKUP) == 0) { if (state->recipient) @@ -1767,7 +1830,7 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions, else if (is_map_command(name, CHECK_ETRN_ACL, &cpp)) { if (state->etrn_name) status = check_domain_access(state, *cpp, state->etrn_name, - FULL, state->etrn_name, + FULL, &found, state->etrn_name, SMTPD_NAME_ETRN, def_acl); } @@ -2002,7 +2065,8 @@ char *smtpd_check_rcptmap(SMTPD_STATE *state, char *recipient) { char *myname = "smtpd_check_rcptmap"; char *saved_recipient; - char *domain; + const RESOLVE_REPLY *reply; + const char *domain; int status; /* @@ -2029,14 +2093,13 @@ char *smtpd_check_rcptmap(SMTPD_STATE *state, char *recipient) /* * Resolve the address. */ - canon_addr_internal(query, recipient); - resolve_clnt_query(STR(query), &reply); - lowercase(STR(reply.recipient)); + reply = (const RESOLVE_REPLY *) + ctable_locate(smtpd_resolve_cache, recipient); /* * Skip non-DNS forms. Skip non-local numerical forms. */ - if ((domain = strrchr(STR(reply.recipient), '@')) == 0) + if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0) SMTPD_CHECK_RCPT_RETURN(0); domain += 1; if (domain[0] == '#' || domain[0] == '[') @@ -2051,10 +2114,10 @@ char *smtpd_check_rcptmap(SMTPD_STATE *state, char *recipient) */ if (*var_virtual_maps && (check_maps_find(state, recipient, virtual_maps, domain, 0))) { - if (NOMATCH(rcpt_canon_maps, STR(reply.recipient)) - && NOMATCH(canonical_maps, STR(reply.recipient)) - && NOMATCH(relocated_maps, STR(reply.recipient)) - && NOMATCH(virtual_maps, STR(reply.recipient))) { + if (NOMATCH(rcpt_canon_maps, CONST_STR(reply->recipient)) + && NOMATCH(canonical_maps, CONST_STR(reply->recipient)) + && NOMATCH(relocated_maps, CONST_STR(reply->recipient)) + && NOMATCH(virtual_maps, CONST_STR(reply->recipient))) { (void) smtpd_check_reject(state, MAIL_ERROR_BOUNCE, "%d <%s>: User unknown", 550, recipient); SMTPD_CHECK_RCPT_RETURN(STR(error_text)); @@ -2067,11 +2130,11 @@ char *smtpd_check_rcptmap(SMTPD_STATE *state, char *recipient) * Sendmail-style virtual domains. */ if (*var_local_rcpt_maps && resolve_local(domain)) { - if (NOMATCH(rcpt_canon_maps, STR(reply.recipient)) - && NOMATCH(canonical_maps, STR(reply.recipient)) - && NOMATCH(relocated_maps, STR(reply.recipient)) - && NOMATCH(virtual_maps, STR(reply.recipient)) - && NOMATCH(local_rcpt_maps, STR(reply.recipient))) { + if (NOMATCH(rcpt_canon_maps, CONST_STR(reply->recipient)) + && NOMATCH(canonical_maps, CONST_STR(reply->recipient)) + && NOMATCH(relocated_maps, CONST_STR(reply->recipient)) + && NOMATCH(virtual_maps, CONST_STR(reply->recipient)) + && NOMATCH(local_rcpt_maps, CONST_STR(reply->recipient))) { (void) smtpd_check_reject(state, MAIL_ERROR_BOUNCE, "%d <%s>: User unknown", 550, recipient); SMTPD_CHECK_RCPT_RETURN(STR(error_text)); @@ -2336,11 +2399,19 @@ static void rest_class(char *class) void resolve_clnt_init(RESOLVE_REPLY *reply) { + reply->flags = 0; reply->transport = vstring_alloc(100); reply->nexthop = vstring_alloc(100); reply->recipient = vstring_alloc(100); } +void resolve_clnt_free(RESOLVE_REPLY *reply) +{ + vstring_free(reply->transport); + vstring_free(reply->nexthop); + vstring_free(reply->recipient); +} + #ifdef USE_SASL_AUTH bool var_smtpd_sasl_enable = 0; @@ -2383,7 +2454,7 @@ VSTRING *canon_addr_internal(VSTRING *result, const char *addr) void resolve_clnt_query(const char *addr, RESOLVE_REPLY *reply) { - if (addr == STR(reply->recipient)) + if (addr == CONST_STR(reply->recipient)) msg_panic("resolve_clnt_query: result clobbers input"); vstring_strcpy(reply->transport, "foo"); vstring_strcpy(reply->nexthop, "foo"); @@ -2405,7 +2476,7 @@ static NORETURN usage(char *myname) msg_fatal("usage: %s", myname); } -main(int argc, char **argv) +int main(int argc, char **argv) { VSTRING *buf = vstring_alloc(100); SMTPD_STATE state; diff --git a/postfix/src/smtpd/smtpd_check_access b/postfix/src/smtpd/smtpd_check_access index f7f6e42d7..afb5487c9 100644 --- a/postfix/src/smtpd/smtpd_check_access +++ b/postfix/src/smtpd/smtpd_check_access @@ -2,9 +2,30 @@ bad.domain 554 match bad.domain friend.bad.domain OK bad-sender@ 554 match bad-sender@ bad-sender@good.domain OK +good-sender@ OK 131.155.210 554 match 131.155.210 131.155.210.17 OK +131.155.210.19 REJECT reject@this.address 554 match reject@this.address open_user@some.site open strict_user@some.site strict auth_client 123456 + +dunno.com dunno +foo.dunno.com reject + +44.33.22 dunno +44.33.22.11 REJECT +44.33 REJECT + +reject@dunno.domain REJECT +ok@dunno.domain OK +dunno.domain DUNNO + +reject@reject.domain REJECT +ok@reject.domain OK +reject.domain REJECT + +reject@ok.domain REJECT +ok@ok.domain OK +ok.domain OK diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in index f792599c2..0f9afce80 100644 --- a/postfix/src/util/Makefile.in +++ b/postfix/src/util/Makefile.in @@ -23,7 +23,7 @@ SRCS = argv.c argv_split.c attr.c basename.c binhash.c chroot_uid.c \ clean_env.c watchdog.c spawn_command.c duplex_pipe.c sane_rename.c \ sane_link.c unescape.c timed_read.c timed_write.c dict_tcp.c \ hex_quote.c dict_alloc.c rand_sleep.c sane_time.c dict_debug.c \ - sane_socketpair.c + sane_socketpair.c ctable.c OBJS = argv.o argv_split.o attr.o basename.o binhash.o chroot_uid.o \ close_on_exec.o concatenate.o dict.o dict_db.o dict_dbm.o \ dict_env.o dict_ht.o dict_ldap.o dict_mysql.o dict_ni.o dict_nis.o \ @@ -48,7 +48,7 @@ OBJS = argv.o argv_split.o attr.o basename.o binhash.o chroot_uid.o \ clean_env.o watchdog.o spawn_command.o duplex_pipe.o sane_rename.o \ sane_link.o unescape.o timed_read.o timed_write.o dict_tcp.o \ hex_quote.o dict_alloc.o rand_sleep.o sane_time.o dict_debug.o \ - sane_socketpair.o + sane_socketpair.o ctable.o HDRS = argv.h attr.h binhash.h chroot_uid.h connect.h dict.h dict_db.h \ dict_dbm.h dict_env.h dict_ht.h dict_ldap.h dict_mysql.h \ dict_ni.h dict_nis.h dict_nisplus.h dir_forest.h events.h \ @@ -64,7 +64,7 @@ HDRS = argv.h attr.h binhash.h chroot_uid.h connect.h dict.h dict_db.h \ vbuf.h vbuf_print.h vstream.h vstring.h vstring_vstream.h \ dict_unix.h dict_pcre.h dict_regexp.h mac_expand.h clean_env.h \ watchdog.h spawn_command.h sane_fsops.h dict_tcp.h hex_quote.h \ - sane_time.h sane_socketpair.h + sane_time.h sane_socketpair.h ctable.h TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \ stream_test.c dup2_pass_on_exec.c WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ @@ -80,7 +80,8 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \ inet_addr_host inet_addr_local mac_parse make_dirs msg_syslog \ mystrtok sigdelay translit valid_hostname vstream_popen \ vstring vstring_vstream doze select_bug stream_test mac_expand \ - watchdog unescape hex_quote name_mask rand_sleep sane_time + watchdog unescape hex_quote name_mask rand_sleep sane_time ctable \ + inet_addr_list LIB_DIR = ../../lib INC_DIR = ../../include @@ -274,6 +275,16 @@ sane_time: $(LIB) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) mv junk $@.o +ctable: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +inet_addr_list: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + depend: $(MAKES) (sed '1,/^# do not edit/!d' Makefile.in; \ set -e; for i in [a-z][a-z0-9]*.c; do \ @@ -286,7 +297,7 @@ stream_test: stream_test.c $(LIB) $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS) tests: valid_hostname_test mac_expand_test dict_test unescape_test \ - hex_quote_test + hex_quote_test ctable_test inet_addr_list_test valid_hostname_test: valid_hostname valid_hostname.in valid_hostname.ref ./valid_hostname valid_hostname.tmp @@ -309,6 +320,16 @@ hex_quote_test: hex_quote cmp hex_quote.ref hex_quote.tmp rm -f hex_quote.ref hex_quote.tmp +ctable_test: ctable + ./ctable ctable.tmp 2>&1 + diff ctable.ref ctable.tmp + rm -f ctable.tmp + +inet_addr_list_test: inet_addr_list + ./inet_addr_list `cat inet_addr_list.in` >inet_addr_list.tmp 2>&1 + diff inet_addr_list.ref inet_addr_list.tmp + rm -f inet_addr_list.tmp + DB_TYPE = `../postconf/postconf -h default_database_type` dict_test: dict_open testdb dict_test.in dict_test.ref @@ -368,6 +389,13 @@ concatenate.o: mymalloc.h concatenate.o: stringops.h concatenate.o: vstring.h concatenate.o: vbuf.h +ctable.o: ctable.c +ctable.o: sys_defs.h +ctable.o: msg.h +ctable.o: mymalloc.h +ctable.o: ring.h +ctable.o: htable.h +ctable.o: ctable.h dict.o: dict.c dict.o: sys_defs.h dict.o: msg.h diff --git a/postfix/src/util/ctable.c b/postfix/src/util/ctable.c new file mode 100644 index 000000000..4e6d18f5e --- /dev/null +++ b/postfix/src/util/ctable.c @@ -0,0 +1,273 @@ +/*++ +/* NAME +/* ctable 3 +/* SUMMARY +/* cache manager +/* SYNOPSIS +/* #include +/* +/* CTABLE *ctable_create(limit, create, delete, context) +/* int limit; +/* void *(*create)(const char *key, void *context); +/* void (*delete)(void *value, void *context); +/* void *context; +/* +/* const void *ctable_locate(cache, key) +/* CTABLE *cache; +/* const char *key; +/* +/* void ctable_free(cache) +/* CTABLE *cache; +/* +/* void ctable_walk(cache, action) +/* CTABLE *cache; +/* void (*action)(const char *key, const void *value); +/* DESCRIPTION +/* This module maintains multiple caches. Cache items are purged +/* automatically when the number of items exceeds a configurable +/* limit. Caches never shrink. Each cache entry consists of a +/* string-valued lookup key and a generic data pointer value. +/* +/* ctable_create() creates a cache with the specified size limit, and +/* returns a pointer to the result. The create and delete arguments +/* specify pointers to call-back functions that create a value, given +/* a key, and delete a given value, respectively. The context argument +/* is passed on to the call-back routines. +/* +/* ctable_locate() looks up or generates the value that corresponds to +/* the specified key, and returns that value. +/* +/* ctable_free() destroys the specified cache, including its contents. +/* +/* ctable_walk() iterates over all elements in the cache, and invokes +/* the action function for each cache element with the corresponding +/* key and value as arguments. This function is useful mainly for +/* cache performance debugging. +/* DIAGNOSTICS +/* Fatal errors: out of memory. Panic: interface violation. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + + /* + * Cache entries are kept in most-recently used order. We use a hash table + * to quickly locate cache entries. + */ +#define CTABLE_ENTRY struct ctable_entry + +struct ctable_entry { + RING ring; /* MRU linkage */ + const char *key; /* lookup key */ + void *value; /* corresponding value */ +}; + +#define RING_TO_CTABLE_ENTRY(ring_ptr) \ + RING_TO_APPL(ring_ptr, CTABLE_ENTRY, ring) +#define RING_PTR_OF(x) (&((x)->ring)) + +struct ctable { + HTABLE *table; /* table with key, ctable_entry pairs */ + unsigned limit; /* max nr of entries */ + unsigned used; /* current nr of entries */ + CTABLE_CREATE_FN create; /* constructor */ + CTABLE_DELETE_FN delete; /* destructor */ + RING ring; /* MRU linkage */ + void *context; /* application context */ +}; + +#define CTABLE_MIN_SIZE 5 + +/* ctable_create - create empty cache */ + +CTABLE *ctable_create(int limit, CTABLE_CREATE_FN create, + CTABLE_DELETE_FN delete, void *context) +{ + CTABLE *cache = (CTABLE *) mymalloc(sizeof(CTABLE)); + char *myname = "ctable_create"; + + if (limit < 1) + msg_panic("%s: bad cache limit: %d", myname, limit); + + cache->table = htable_create(limit); + cache->limit = (limit < CTABLE_MIN_SIZE ? CTABLE_MIN_SIZE : limit); + cache->used = 0; + cache->create = create; + cache->delete = delete; + ring_init(RING_PTR_OF(cache)); + cache->context = context; + return (cache); +} + +/* ctable_locate - look up or create cache item */ + +const void *ctable_locate(CTABLE *cache, const char *key) +{ + char *myname = "ctable_locate"; + CTABLE_ENTRY *entry; + + /* + * If the entry is not in the cache, make sure there is room for a new + * entry and install it at the front of the MRU chain. Otherwise, move + * the entry to the front of the MRU chain if it is not already there. + * All this means that the cache never shrinks. + */ + if ((entry = (CTABLE_ENTRY *) htable_find(cache->table, key)) == 0) { + if (cache->used >= cache->limit) { + entry = RING_TO_CTABLE_ENTRY(ring_pred(RING_PTR_OF(cache))); + if (msg_verbose) + msg_info("%s: purge entry key %s", myname, entry->key); + ring_detach(RING_PTR_OF(entry)); + cache->delete(entry->value, cache->context); + htable_delete(cache->table, entry->key, (void (*) (char *)) 0); + } else { + entry = (CTABLE_ENTRY *) mymalloc(sizeof(CTABLE_ENTRY)); + cache->used++; + } + entry->value = cache->create(key, cache->context); + entry->key = htable_enter(cache->table, key, (char *) entry)->key; + ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry)); + if (msg_verbose) + msg_info("%s: install entry key %s", myname, entry->key); + } else if (entry == RING_TO_CTABLE_ENTRY(ring_succ(RING_PTR_OF(cache)))) { + if (msg_verbose) + msg_info("%s: leave existing entry key %s", myname, entry->key); + } else { + ring_detach(RING_PTR_OF(entry)); + ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry)); + if (msg_verbose) + msg_info("%s: move existing entry key %s", myname, entry->key); + } + return (entry->value); +} + +static CTABLE *ctable_free_cache; + +/* ctable_free_callback - callback function */ + +static void ctable_free_callback(char *ptr) +{ + CTABLE_ENTRY *entry = (CTABLE_ENTRY *) ptr; + + ctable_free_cache->delete(entry->value, ctable_free_cache->context); + myfree((char *) entry); +} + +/* ctable_free - destroy cache and contents */ + +void ctable_free(CTABLE *cache) +{ + CTABLE *saved_cache = ctable_free_cache; + + /* + * XXX the hash table does not pass application context so we have to + * store it in a global variable. + */ + ctable_free_cache = cache; + htable_free(cache->table, ctable_free_callback); + myfree((char *) cache); + ctable_free_cache = saved_cache; +} + +/* ctable_walk - iterate over all cache entries */ + +void ctable_walk(CTABLE *cache, void (*action) (const char *, const void *)) +{ + RING *entry = RING_PTR_OF(cache); + + /* Walking down the MRU chain is less work than using ht_walk(). */ + + while ((entry = ring_succ(entry)) != RING_PTR_OF(cache)) + action((RING_TO_CTABLE_ENTRY(entry)->key), + (RING_TO_CTABLE_ENTRY(entry)->value)); +} + +#ifdef TEST + + /* + * Proof-of-concept test program. Read keys from stdin, ask for values not + * in cache. + */ +#include +#include +#include +#include +#include + +#define STR(x) vstring_str(x) + +static void *ask(const char *key, void *context) +{ + VSTRING *data_buf = (VSTRING *) context; + + vstream_printf("ask: %s = ", key); + vstream_fflush(VSTREAM_OUT); + if (vstring_get_nonl(data_buf, VSTREAM_IN) == VSTREAM_EOF) + vstream_longjmp(VSTREAM_IN, 1); + if (!isatty(0)) { + vstream_printf("%s\n", STR(data_buf)); + vstream_fflush(VSTREAM_OUT); + } + return (mystrdup(STR(data_buf))); +} + +static void drop(void *data, void *unused_context) +{ + myfree(data); +} + +int main(int unused_argc, char **argv) +{ + VSTRING *key_buf; + VSTRING *data_buf; + CTABLE *cache; + const char *value; + + msg_vstream_init(argv[0], VSTREAM_ERR); + key_buf = vstring_alloc(100); + data_buf = vstring_alloc(100); + cache = ctable_create(1, ask, drop, (void *) data_buf); + msg_verbose = 1; + vstream_control(VSTREAM_IN, VSTREAM_CTL_EXCEPT, VSTREAM_CTL_END); + + if (vstream_setjmp(VSTREAM_IN) == 0) { + for (;;) { + vstream_printf("key = "); + vstream_fflush(VSTREAM_OUT); + if (vstring_get_nonl(key_buf, VSTREAM_IN) == VSTREAM_EOF) + vstream_longjmp(VSTREAM_IN, 1); + if (!isatty(0)) { + vstream_printf("%s\n", STR(key_buf)); + vstream_fflush(VSTREAM_OUT); + } + value = ctable_locate(cache, STR(key_buf)); + vstream_printf("result: %s\n", value); + } + } + ctable_free(cache); + vstring_free(key_buf); + vstring_free(data_buf); + return (0); +} + +#endif diff --git a/postfix/src/util/ctable.h b/postfix/src/util/ctable.h new file mode 100644 index 000000000..7e3d899b3 --- /dev/null +++ b/postfix/src/util/ctable.h @@ -0,0 +1,39 @@ +#ifndef _CTABLE_H_INCLUDED_ +#define _CTABLE_H_INCLUDED_ + +/*++ +/* NAME +/* ctable 5 +/* SUMMARY +/* cache manager +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Interface of the cache manager. The structure of a cache is not visible + * to the caller. + */ + +#define CTABLE struct ctable +typedef void *(*CTABLE_CREATE_FN) (const char *, void *); +typedef void (*CTABLE_DELETE_FN) (void *, void *); + +extern CTABLE *ctable_create(int, CTABLE_CREATE_FN, CTABLE_DELETE_FN, void *); +extern void ctable_free(CTABLE *); +extern void ctable_walk(CTABLE *, void (*) (const char *, const void *)); +extern const void *ctable_locate(CTABLE *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/postfix/src/util/ctable.in b/postfix/src/util/ctable.in new file mode 100644 index 000000000..78763cfaf --- /dev/null +++ b/postfix/src/util/ctable.in @@ -0,0 +1,39 @@ +a +1 +b +2 +c +3 +d +4 +e +5 +f +6 +f +a +1 +b +2 +c +3 +d +4 +e +5 +f +6 +f +e +d +c +b +a +1 +b +c +d +e +f +6 +f diff --git a/postfix/src/util/ctable.ref b/postfix/src/util/ctable.ref new file mode 100644 index 000000000..34e8a951a --- /dev/null +++ b/postfix/src/util/ctable.ref @@ -0,0 +1,99 @@ +key = a +ask: a = 1 +./ctable: ctable_locate: install entry key a +result: 1 +key = b +ask: b = 2 +./ctable: ctable_locate: install entry key b +result: 2 +key = c +ask: c = 3 +./ctable: ctable_locate: install entry key c +result: 3 +key = d +ask: d = 4 +./ctable: ctable_locate: install entry key d +result: 4 +key = e +ask: e = 5 +./ctable: ctable_locate: install entry key e +result: 5 +key = f +./ctable: ctable_locate: purge entry key a +ask: f = 6 +./ctable: ctable_locate: install entry key f +result: 6 +key = f +./ctable: ctable_locate: leave existing entry key f +result: 6 +key = a +./ctable: ctable_locate: purge entry key b +ask: a = 1 +./ctable: ctable_locate: install entry key a +result: 1 +key = b +./ctable: ctable_locate: purge entry key c +ask: b = 2 +./ctable: ctable_locate: install entry key b +result: 2 +key = c +./ctable: ctable_locate: purge entry key d +ask: c = 3 +./ctable: ctable_locate: install entry key c +result: 3 +key = d +./ctable: ctable_locate: purge entry key e +ask: d = 4 +./ctable: ctable_locate: install entry key d +result: 4 +key = e +./ctable: ctable_locate: purge entry key f +ask: e = 5 +./ctable: ctable_locate: install entry key e +result: 5 +key = f +./ctable: ctable_locate: purge entry key a +ask: f = 6 +./ctable: ctable_locate: install entry key f +result: 6 +key = f +./ctable: ctable_locate: leave existing entry key f +result: 6 +key = e +./ctable: ctable_locate: move existing entry key e +result: 5 +key = d +./ctable: ctable_locate: move existing entry key d +result: 4 +key = c +./ctable: ctable_locate: move existing entry key c +result: 3 +key = b +./ctable: ctable_locate: move existing entry key b +result: 2 +key = a +./ctable: ctable_locate: purge entry key f +ask: a = 1 +./ctable: ctable_locate: install entry key a +result: 1 +key = b +./ctable: ctable_locate: move existing entry key b +result: 2 +key = c +./ctable: ctable_locate: move existing entry key c +result: 3 +key = d +./ctable: ctable_locate: move existing entry key d +result: 4 +key = e +./ctable: ctable_locate: move existing entry key e +result: 5 +key = f +./ctable: ctable_locate: purge entry key a +ask: f = 6 +./ctable: ctable_locate: install entry key f +result: 6 +key = f +./ctable: ctable_locate: leave existing entry key f +result: 6 +key = \ No newline at end of file diff --git a/postfix/src/util/dict_ldap.c b/postfix/src/util/dict_ldap.c index 776a437db..528769248 100644 --- a/postfix/src/util/dict_ldap.c +++ b/postfix/src/util/dict_ldap.c @@ -151,6 +151,10 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) void (*saved_alarm) (int); int rc = 0; +#ifdef LDAP_API_FEATURE_X_MEMCACHE + LDAPMemCache *dircache; +#endif + #ifdef LDAP_OPT_NETWORK_TIMEOUT struct timeval mytimeval; @@ -162,7 +166,7 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) msg_info("%s: Connecting to server %s", myname, dict_ldap->server_host); -#ifdef UNTESTED_LDAP_OPT_NETWORK_TIMEOUT +#ifdef LDAP_OPT_NETWORK_TIMEOUT dict_ldap->ld = ldap_init(dict_ldap->server_host, (int) dict_ldap->server_port); if (dict_ldap->ld == NULL) { @@ -247,6 +251,27 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) myname, dict_ldap->cache_size, dict_ldap->ldapsource, dict_ldap->cache_expiry); +#ifdef LDAP_API_FEATURE_X_MEMCACHE + rc = ldap_memcache_init(dict_ldap->cache_expiry, dict_ldap->cache_size, + NULL, NULL, &dircache); + if (rc != LDAP_SUCCESS) { + msg_warn + ("%s: Unable to configure cache for %s: %d (%s) -- continuing", + myname, dict_ldap->ldapsource, rc, ldap_err2string(rc)); + } else { + rc = ldap_memcache_set(dict_ldap->ld, dircache); + if (rc != LDAP_SUCCESS) { + msg_warn + ("%s: Unable to configure cache for %s: %d (%s) -- continuing", + myname, dict_ldap->ldapsource, rc, ldap_err2string(rc)); + } else { + if (msg_verbose) + msg_info("%s: Caching enabled for %s", + myname, dict_ldap->ldapsource); + } + } +#else + rc = ldap_enable_cache(dict_ldap->ld, dict_ldap->cache_expiry, dict_ldap->cache_size); if (rc != LDAP_SUCCESS) { @@ -258,6 +283,8 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) msg_info("%s: Caching enabled for %s", myname, dict_ldap->ldapsource); } + +#endif } if (msg_verbose) msg_info("%s: Cached connection handle for LDAP source %s", @@ -315,7 +342,7 @@ static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res, if (strcasecmp(dict_ldap->result_attributes->argv[i], attr) == 0) { if (msg_verbose) - msg_info("%s: search returned value(s) for requested result attribute %s", myname, attr); + msg_info("%s: search returned %d value(s) for requested result attribute %s", myname, i, attr); break; } } @@ -390,7 +417,7 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) * load on the LDAP server. */ if (dict_ldap->domain) { - char *p=strrchr(name,'@'); + const char *p=strrchr(name,'@'); if (p != 0) p=p+1; else @@ -482,7 +509,7 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) /* * Does the supplied query_filter even include a substitution? */ - if ((char *) strstr(dict_ldap->query_filter, "%s") == NULL) { + if ((char *) strchr(dict_ldap->query_filter, '%') == NULL) { /* * No, log the fact and continue. @@ -494,21 +521,40 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) /* * Yes, replace all instances of %s with the address to look up. + * Replace %u with the user portion, and %d with the domain portion. */ sub = dict_ldap->query_filter; end = sub + strlen(dict_ldap->query_filter); while (sub < end) { /* - * Make sure it's %s and not something else, though it wouldn't - * really matter; the token could be any single character. + * Make sure it's %[sud] and not something else. For backward + * compatibilty, treat anything other than %u or %d as %s, with + * a warning. */ if (*(sub) == '%') { - if ((sub + 1) != end && *(sub + 1) != 's') - msg_warn - ("%s: Invalid lookup substitution format '%%%c'!", - myname, *(sub + 1)); - vstring_strcat(filter_buf, vstring_str(escaped_name)); + char *u=vstring_str(escaped_name); + char *p=strchr(u,'@'); + switch (*(sub+1)) { + case 'd': + if (p) + vstring_strcat(filter_buf, p+1); + break; + case 'u': + if (p) + vstring_strncat(filter_buf, u, p-u); + else + vstring_strcat(filter_buf, u); + break; + default: + msg_warn + ("%s: Invalid lookup substitution format '%%%c'!", + myname, *(sub + 1)); + /* fall through */ + case 's': + vstring_strcat(filter_buf, u); + break; + } sub++; } else vstring_strncat(filter_buf, sub, 1); @@ -607,7 +653,8 @@ static void dict_ldap_close(DICT *dict) myfree(dict_ldap->ldapsource); myfree(dict_ldap->server_host); myfree(dict_ldap->search_base); - match_list_free(dict_ldap->domain); + if (dict_ldap->domain) + match_list_free(dict_ldap->domain); myfree(dict_ldap->query_filter); argv_free(dict_ldap->result_attributes); myfree(dict_ldap->bind_dn); @@ -626,15 +673,15 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) char *scope; char *attr; + if (msg_verbose) + msg_info("%s: Using LDAP source %s", myname, ldapsource); + dict_ldap = (DICT_LDAP *) dict_alloc(DICT_TYPE_LDAP, ldapsource, sizeof(*dict_ldap)); dict_ldap->dict.lookup = dict_ldap_lookup; dict_ldap->dict.close = dict_ldap_close; dict_ldap->dict.flags = dict_flags | DICT_FLAG_FIXED; - if (msg_verbose) - msg_info("%s: Using LDAP source %s", myname, ldapsource); - dict_ldap->ldapsource = mystrdup(ldapsource); config_param = vstring_alloc(15); diff --git a/postfix/src/util/dict_pcre.c b/postfix/src/util/dict_pcre.c index 1e8e84b6b..6d3c6f523 100644 --- a/postfix/src/util/dict_pcre.c +++ b/postfix/src/util/dict_pcre.c @@ -266,6 +266,7 @@ DICT *dict_pcre_open(const char *map, int unused_flags, int dict_flags) continue; p = vstring_str(line_buffer); + trimblanks(p, 0)[0] = 0; /* Trim space at end */ re_delimiter = *p++; regexp = p; diff --git a/postfix/src/util/dict_regexp.c b/postfix/src/util/dict_regexp.c index 0d4eaf852..1debbe96f 100644 --- a/postfix/src/util/dict_regexp.c +++ b/postfix/src/util/dict_regexp.c @@ -365,6 +365,8 @@ DICT *dict_regexp_open(const char *map, int unused_flags, int dict_flags) if (*p == 0) /* Skip blank lines */ continue; + trimblanks(p, 0)[0] = 0; /* Trim space at end */ + rule = dict_regexp_parseline(lineno, p, &nsub, map_fp); if (rule) { if (nsub > max_nsub) diff --git a/postfix/src/util/inet_addr_list.c b/postfix/src/util/inet_addr_list.c index d449e76c7..fa1cbd399 100644 --- a/postfix/src/util/inet_addr_list.c +++ b/postfix/src/util/inet_addr_list.c @@ -13,6 +13,9 @@ /* INET_ADDR_LIST *list; /* struct in_addr *addr; /* +/* void inet_addr_list_uniq(list) +/* INET_ADDR_LIST *list; +/* /* void inet_addr_list_free(list) /* INET_ADDR_LIST *list; /* DESCRIPTION @@ -25,6 +28,9 @@ /* inet_addr_list_append() appends the specified address to /* the specified list, extending the list on the fly. /* +/* inet_addr_list_uniq() sorts the specified address list and +/* eliminates duplicates. +/* /* inet_addr_list_free() reclaims memory used for the /* specified address list. /* LICENSE @@ -43,6 +49,7 @@ #include #include #include +#include /* Utility library. */ @@ -77,9 +84,81 @@ void inet_addr_list_append(INET_ADDR_LIST *list, struct in_addr * addr) list->addrs[list->used++] = *addr; } +/* inet_addr_list_comp - compare addresses */ + +static int inet_addr_list_comp(const void *a, const void *b) +{ + const struct in_addr *a_addr = (const struct in_addr *) a; + const struct in_addr *b_addr = (const struct in_addr *) b; + + return (a_addr->s_addr - b_addr->s_addr); +} + +/* inet_addr_list_uniq - weed out duplicates */ + +void inet_addr_list_uniq(INET_ADDR_LIST *list) +{ + int n; + int m; + + /* + * Put the identical members right next to each other. + */ + qsort((void *) list->addrs, list->used, + sizeof(list->addrs[0]), inet_addr_list_comp); + + /* + * Nuke the duplicates. Postcondition after while loop: m is the largest + * index for which list->addrs[n] == list->addrs[m]. + */ + for (m = n = 0; m < list->used; m++, n++) { + if (m != n) + list->addrs[n] = list->addrs[m]; + while (m + 1 < list->used + && inet_addr_list_comp((void *) &(list->addrs[n]), + (void *) &(list->addrs[m + 1])) == 0) + m += 1; + } + list->used = n; +} + /* inet_addr_list_free - destroy internet address list */ void inet_addr_list_free(INET_ADDR_LIST *list) { myfree((char *) list->addrs); } + +#ifdef TEST + + /* + * Duplicate elimination needs to be tested. + */ +#include + +static void inet_addr_list_print(INET_ADDR_LIST *list) +{ + int n; + + for (n = 0; n < list->used; n++) + msg_info("%s", inet_ntoa(list->addrs[n])); +} + +int main(int argc, char **argv) +{ + INET_ADDR_LIST list; + + inet_addr_list_init(&list); + while (--argc && *++argv) + if (inet_addr_host(&list, *argv) == 0) + msg_fatal("host not found: %s", *argv); + msg_info("list before sort/uniq"); + inet_addr_list_print(&list); + inet_addr_list_uniq(&list); + msg_info("list after sort/uniq"); + inet_addr_list_print(&list); + inet_addr_list_free(&list); + return (0); +} + +#endif diff --git a/postfix/src/util/inet_addr_list.h b/postfix/src/util/inet_addr_list.h index 48a4c9712..372b3ccb7 100644 --- a/postfix/src/util/inet_addr_list.h +++ b/postfix/src/util/inet_addr_list.h @@ -27,6 +27,7 @@ typedef struct INET_ADDR_LIST { extern void inet_addr_list_init(INET_ADDR_LIST *); extern void inet_addr_list_free(INET_ADDR_LIST *); +extern void inet_addr_list_uniq(INET_ADDR_LIST *); extern void inet_addr_list_append(INET_ADDR_LIST *, struct in_addr *); /* LICENSE diff --git a/postfix/src/util/inet_addr_list.in b/postfix/src/util/inet_addr_list.in new file mode 100644 index 000000000..742281fae --- /dev/null +++ b/postfix/src/util/inet_addr_list.in @@ -0,0 +1,9 @@ +168.100.189.2 +168.100.189.2 +168.100.189.1 +168.100.189.3 +168.100.189.3 +168.100.189.3 +168.100.189.4 +168.100.189.1 +168.100.189.4 diff --git a/postfix/src/util/inet_addr_list.ref b/postfix/src/util/inet_addr_list.ref new file mode 100644 index 000000000..0a8a56e1a --- /dev/null +++ b/postfix/src/util/inet_addr_list.ref @@ -0,0 +1,15 @@ +unknown: list before sort/uniq +unknown: 168.100.189.2 +unknown: 168.100.189.2 +unknown: 168.100.189.1 +unknown: 168.100.189.3 +unknown: 168.100.189.3 +unknown: 168.100.189.3 +unknown: 168.100.189.4 +unknown: 168.100.189.1 +unknown: 168.100.189.4 +unknown: list after sort/uniq +unknown: 168.100.189.1 +unknown: 168.100.189.2 +unknown: 168.100.189.3 +unknown: 168.100.189.4 diff --git a/postfix/src/util/ring.h b/postfix/src/util/ring.h index a96fe2696..d0219cf80 100644 --- a/postfix/src/util/ring.h +++ b/postfix/src/util/ring.h @@ -29,6 +29,17 @@ extern void ring_detach(RING *); #define ring_succ(c) ((c)->succ) #define ring_pred(c) ((c)->pred) + /* + * Typically, an application will embed a RING structure into a larger + * structure that also contains application-specific members. This approach + * gives us the best of both worlds. The application can still use the + * generic RING primitives for manipulating RING structures. The macro below + * transforms a pointer from RING structure to the structure that contains + * it. + */ +#define RING_TO_APPL(ring_ptr,app_type,ring_member) \ + ((app_type *) (((char *) (ring_ptr)) - offsetof(app_type,ring_member))) + /* LICENSE /* .ad /* .fi