In addition to doing a fresh install, this script can change an
existing installation from using a world-writable maildrop to a
group-writable one. It cannot be used to change Postfix queue
-ownership.
+file/directory ownership.
Before installing files, this script prompts you for some definitions.
-You can either edit this script ahead of time, or you can specify
-your changes interactively.
+Most definitions will be remembered, so you have to specify them
+only once. All definitions have a reasonable default value.
- install_root - prepend to installed file names (for package building)
+ install_root - prefix for installed file names (for package building)
config_directory - directory with Postfix configuration files.
daemon_directory - directory with Postfix daemon programs.
mail_owner - owner of Postfix queue files.
setgid - groupname, e.g., postdrop (default: no). See INSTALL section 12.
- manpages - path to man tree (default: no). Example: /usr/local/man.
+ manpages - "no" or path to man tree. Example: /usr/local/man.
EOF
# By now, shells must have functions. Ultrix users must use sh5 or lose.
+# Apparently, some broken LINUX file utilities won't move symlinks across
+# file systems. Upgrade to a better system. Don't waste my time.
+
compare_or_replace() {
cmp $2 $3 >/dev/null 2>&1 || {
rm -f junk || exit 1
*) n=; c='\c';;
esac
-# Default settings, edit to taste or change interactively. Once this
-# script has run it saves settings to $config_directory/install.cf.
+# Default settings. These are clobbered by remembered settings.
+install_root=/
config_directory=/etc/postfix
daemon_directory=/usr/libexec/postfix
command_directory=/usr/sbin
mailq_path=/usr/bin/mailq
mail_owner=postfix
setgid=no
-manpages=no
+manpages=/usr/local/man
+
+# Find out the location of configuration files.
+
+for name in install_root config_directory
+do
+ while :
+ do
+ eval echo \$n "$name: [\$$name]\ \$c"
+ read ans
+ case $ans in
+ "") break;;
+ *) eval $name=\$ans; break;;
+ esac
+ done
+done
+
+# Sanity checks
-while :
+for path in $install_root $config_directory
do
- echo $n "install_root: [/] $c"
- read ans
- case $ans in
-""|/|no) install_root=; break;;
- /*) install_root=$ans; break;;
- *) echo "install_root should be an absolute path name" 1>&2; exit 1;;
- esac
+ case $path in
+ /*) ;;
+ *) echo "$path should be an absolute path name" 1>&2; exit 1;;
+ esac
done
+# In case some systems special-case pathnames beginning with //.
+
+case $install_root in
+/) install_root=
+esac
+
# Load defaults from existing installation.
CONFIG_DIRECTORY=$install_root$config_directory
test -f $CONFIG_DIRECTORY/main.cf && {
for name in daemon_directory command_directory queue_directory mail_owner
do
- eval "$name=\"\`bin/postconf -c $CONFIG_DIRECTORY -h $name || kill \$\$\`\""
+ eval $name='"`bin/postconf -c $CONFIG_DIRECTORY -h $name`"' || kill $$
done
}
test -f $CONFIG_DIRECTORY/install.cf && . $CONFIG_DIRECTORY/install.cf
-for name in config_directory daemon_directory command_directory \
- queue_directory sendmail_path newaliases_path mailq_path mail_owner \
+# Override default settings.
+
+for name in daemon_directory command_directory \
+ queue_directory sendmail_path newaliases_path mailq_path mail_owner\
setgid manpages
do
while :
# Sanity checks
-rm -f foobar-
-touch foobar-
+for path in $daemon_directory $command_directory \
+ $queue_directory $sendmail_path $newaliases_path $mailq_path $manpages
+do
+ case $path in
+ /*) ;;
+ no) ;;
+ *) echo "$path should be an absolute path name" 1>&2; exit 1;;
+ esac
+done
-chown $mail_owner foobar- >/dev/null 2>&1 || {
+rm -f junk || exit 1
+touch junk
+
+chown "$mail_owner" junk >/dev/null 2>&1 || {
echo "Error: $mail_owner needs an entry in the passwd file" 1>&2
echo "Remember, $mail_owner must have a dedicated user id and group id." 1>&2
exit 1
case $setgid in
no) ;;
- *) chgrp "$setgid" foobar- >/dev/null 2>&1 || {
+ *) chgrp "$setgid" junk >/dev/null 2>&1 || {
echo "Error: $setgid needs an entry in the group file" 1>&2
echo "Remember, $setgid must have a dedicated group id." 1>&2
exit 1
}
esac
-rm -f foobar-
-
-for path in $config_directory $daemon_directory $command_directory \
- $queue_directory $sendmail_path $newaliases_path $mailq_path $manpages
-do
- case $path in
- /*) ;;
- no) ;;
- *) echo "$path should be an absolute path name" 1>&2; exit 1;;
- esac
-done
+rm -f junk
-# Create any missing directories.
+# Avoid clumsiness.
-CONFIG_DIRECTORY=$install_root$config_directory
DAEMON_DIRECTORY=$install_root$daemon_directory
COMMAND_DIRECTORY=$install_root$command_directory
QUEUE_DIRECTORY=$install_root$queue_directory
MAILQ_PATH=$install_root$mailq_path
MANPAGES=$install_root$manpages
-case $install_root in
- /?*) test -d $install_root || mkdir -p $install_root || exit 1
-esac
+# Create any missing directories.
test -d $CONFIG_DIRECTORY || mkdir -p $CONFIG_DIRECTORY || exit 1
test -d $DAEMON_DIRECTORY || mkdir -p $DAEMON_DIRECTORY || exit 1
# Install files. Be careful to not copy over running programs.
-for file in `ls libexec`
+for file in `ls libexec | grep -v '^\.'`
do
compare_or_replace a+x,go-w libexec/$file $DAEMON_DIRECTORY/$file || exit 1
done
cp conf/* $CONFIG_DIRECTORY || exit 1
chmod a+r,go-w $CONFIG_DIRECTORY/* || exit 1
- echo "Warning: you still need to edit myorigin/mydestination in" 1>&2
- echo "$CONFIG_DIRECTORY/main.cf. See also html/faq.html for dialup" 1>&2
- echo "sites or for sites inside a firewalled network." 1>&2
- echo "" 1>&2
- echo "BTW, Edit your alias database and be sure to set up aliases" 1>&2
- echo "for root and postmaster, then run the newaliases command." 1>&2
+ test -z "$install_root" && {
+ echo "Warning: you still need to edit myorigin/mydestination in" 1>&2
+ echo "$CONFIG_DIRECTORY/main.cf. See also html/faq.html for dialup" 1>&2
+ echo "sites or for sites inside a firewalled network." 1>&2
+ echo "" 1>&2
+ echo "BTW: Edit your alias database and be sure to set up aliases" 1>&2
+ echo "for root and postmaster, then run $NEWALIASES_PATH." 1>&2
+ }
}
# Save settings.
|| exit 1
(echo "# This file was generated by $0"
-for name in config_directory sendmail_path newaliases_path mailq_path \
- setgid manpages
+for name in sendmail_path newaliases_path mailq_path setgid manpages
do
eval echo $name=\$$name
done) >junk || exit 1
compare_or_replace a+x,go-w $postfix_script $CONFIG_DIRECTORY/postfix-script ||
exit 1
-# Install manual pages (optional). We just clobber whatever is there.
+# Install manual pages (optional).
case $manpages in
no) ;;
done
for file in man?/*
do
- rm -f $MANPAGES/$file
- cp $file $MANPAGES/$file || exit 1
- chmod 644 $MANPAGES/$file || exit 1
+ cmp -s $file $MANPAGES/$file || {
+ rm -f $MANPAGES/$file
+ cp $file $MANPAGES/$file || exit 1
+ chmod 644 $MANPAGES/$file || exit 1
+ }
done
)
esac
-Incompatible changes with snapshot 19991120
+Incompatible changes with snapshot 19991122
===========================================
- In an SMTPD access map, an all-numeric right-hand side now means
SMTPD access control tables. Use the permit_recipient_map feature
instead. The loss is compensated for (see below).
-Major changes with snapshot 19991120
+Major changes with snapshot 19991122
====================================
+- It is now relatively safe to configure 550 status codes for the
+main.cf unknown_address_reject_code or unknown_client_reject_code
+parameters. The SMTP server now always sends a 450 (try again)
+reply code when an UCE restriction fails due to a soft DNS error,
+regardless of what main.cf specifies.
+
+- The RBL checks now show the content of TXT records (Simon J Mudd).
+
- The Postfix SMTP server now understands a wider range of illegal
-address formats in MAIL FROM and RCPT TO commands. In order to
-disable those forms, specify "strict_rfc821_envelopes = yes".
+address forms in MAIL FROM and RCPT TO commands. In order to disable
+those forms, specify "strict_rfc821_envelopes = yes".
- Per-client/helo/sender/recipient UCE restrictions (fully-recursive
UCE restriction parser). See the RESTRICTION_CLASS file for details.
-- Block mail for non-existent users at the SMTP port. On a non-relay
-host, use the following to reject mail for non-existent users and
-for non-local destinations.
+- Block mail for most non-existent users at the SMTP port. Example:
+a non-relaying host could use the following to reject mail for
+non-existent local users and for all non-local destinations.
smtpd_recipient_restrictions =
- permit_recipient_map unix:passwd .byname
+ reject_unknown_sender
+ permit_recipient_map unix:passwd.byname
permit_recipient_map hash:/etc/postfix/canonical
permit_recipient_map hash:/etc/postfix/virtual
permit_recipient_map hash:/etc/aliases
reject
-- "postconf -e name=value..." edits the main.cf file. This is
-easier and safer than editing the main.cf file by hand. The edits
-are done on a temporary copy that is renamed into place.
+I haven't figured out yet how to use this easily on hosts that must
+relay mail for other systems.
-- "postconf -m" displays all supported lookup table types (Scott
-Cotton).
+- Use "postmap -q key" or "postalias -q key" for testing Postfix
+lookup tables or alias files.
-- It is now relatively safe to configure 550 status codes for the
-main.cf unknown_address_reject_code or unknown_client_reject_code
-parameters. The SMTP server now always sends a 450 (try again)
-reply code when an UCE restriction fails due to a soft DNS error.
+- Use "postconf -e name=value..." edits the main.cf file. This is
+easier and safer than editing the main.cf file by hand. The edits
+are done on a temporary copy that is renamed into place.
-- The RBL checks now show the content of TXT records (Simon J Mudd).
+- Use "postconf -m" to display all supported lookup table types
+(Scott Cotton).
- New "permit_auth_destination" UCE restriction for finer-grained
-control (Jesper Skriver).
+access control (Jesper Skriver).
Incompatible changes with postfix-19990906
==========================================
/* .IP \fIldapsource_\fRquery_filter
/* The filter used to search for directory entries, for example
/* \fI(mailacceptinggeneralid=%s)\fR.
-/* .IP \fIldapsource_\fRlookup_wildcards
-/* Whether to allow '*' in addresses to be looked up.
/* .IP \fIldapsource_\fRresult_attribute
-/* The attribute returned by the search, in which we expect to find
+/* The attribute returned by the search, in which to find
/* RFC822 addresses, for example \fImaildrop\fR.
/* .IP \fIldapsource_\fRbind
/* Whether or not to bind to the server -- LDAP v3 implementations don't
/* .IP \fIldapsource_\fRbind_pw
/* \&... and this password.
/* BUGS
-/* Of course not! :)
+/* Thrice a year, needed or not.
/* SEE ALSO
/* dict(3) generic dictionary manager
/* DIAGNOSTICS
/* Yorktown Heights, NY 10532, USA
/*
/* John Hensley
-/* Merit Network, Inc.
-/* hensley@merit.edu
+/* stormroll@yahoo.com
/*
/*--*/
#include <sys/time.h>
#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <stdlib.h>
#include "dict.h"
#include "dict_ldap.h"
- /*
- * Grr.. this module should sit in the global library, because it interacts
- * with application-specific configuration parameters. I will have to
- * generalize the manner in which new dictionary types can register
- * themselves, including their configuration file parameters.
- */
+/* Global library. */
+
+#include "../global/mail_conf.h" /* XXX Fixme. */
/*
* structure containing all the configuration parameters for a given
int server_port;
char *search_base;
char *query_filter;
- int lookup_wildcards;
char *result_attribute;
int bind;
char *bind_dn;
LDAPMessage *res = 0;
LDAPMessage *entry = 0;
struct timeval tv;
- VSTRING *filter_buf = 0;
+ VSTRING *escaped_name = 0,
+ *filter_buf = 0;
char **attr_values;
long i = 0;
int rc = 0;
dict_errno = 0;
- /*
- * Unless configured to allow them, refuse to search for a name
- * containing wildcards.
- */
- if (!dict_ldap->lookup_wildcards) {
- if (strstr(name, "*") != NULL) {
- msg_warn("%s: Address (%s) contains a wildcard; refusing to search. See the lookup_wildcards attribute in LDAP_README for more information.", myname, name);
- return (0);
- }
- }
-
/*
* Initialize.
*/
if (msg_verbose)
msg_info("%s: In dict_ldap_lookup", myname);
- if (dict_ldap->ld == 0) {
- msg_warn("%s: no existing connection for ldapsource %s, reopening",
- myname, dict_ldap->ldapsource);
+ if (dict_ldap->ld == NULL) {
+ if (msg_verbose)
+ msg_info("%s: no existing connection for ldapsource %s, reopening",
+ myname, dict_ldap->ldapsource);
+
+ if ((saved_alarm = signal(SIGALRM, dict_ldap_timeout)) == SIG_ERR) {
+ msg_warn("%s: error setting signal handler for open timeout: %m", myname);
+ dict_errno = DICT_ERR_RETRY;
+ return (0);
+ }
if (msg_verbose)
msg_info("%s: connecting to server %s", myname,
dict_ldap->server_host);
- if ((saved_alarm = signal(SIGALRM, dict_ldap_timeout)) == SIG_ERR)
- msg_fatal("%s: signal: %m", myname);
-
alarm(dict_ldap->timeout);
if (setjmp(env) == 0)
dict_ldap->ld = ldap_open(dict_ldap->server_host,
(int) dict_ldap->server_port);
alarm(0);
- if (signal(SIGALRM, saved_alarm) == SIG_ERR)
- msg_fatal("%s: signal: %m", myname);
-
+ if (signal(SIGALRM, saved_alarm) == SIG_ERR) {
+ msg_warn("%s: error resetting signal handler after open: %m", myname);
+ dict_errno = DICT_ERR_RETRY;
+ return (0);
+ }
if (msg_verbose)
msg_info("%s: after ldap_open", myname);
- if (dict_ldap->ld == 0) {
- msg_fatal("%s: Unable to contact LDAP server %s",
- myname, dict_ldap->server_host);
+ if (dict_ldap->ld == NULL) {
+ msg_warn("%s: Unable to contact LDAP server %s",
+ myname, dict_ldap->server_host);
+ dict_errno = DICT_ERR_RETRY;
+ return (0);
} else {
/*
- * If this server requires us to bind, do so.
+ * If this server requires a bind, do so.
*/
if (dict_ldap->bind) {
if (msg_verbose)
rc = ldap_bind_s(dict_ldap->ld, dict_ldap->bind_dn,
dict_ldap->bind_pw, LDAP_AUTH_SIMPLE);
if (rc != LDAP_SUCCESS) {
- msg_fatal("%s: Unable to bind to server %s as %s (%d -- %s): ", myname, dict_ldap->server_host, dict_ldap->bind_dn, rc, ldap_err2string(rc));
+ msg_warn("%s: Unable to bind to server %s as %s (%d -- %s): ", myname, dict_ldap->server_host, dict_ldap->bind_dn, rc, ldap_err2string(rc));
+ dict_errno = DICT_ERR_RETRY;
+ return (0);
} else {
if (msg_verbose)
msg_info("%s: Successful bind to server %s as %s (%d -- %s): ", myname, dict_ldap->server_host, dict_ldap->bind_dn, rc, ldap_err2string(rc));
myname, dict_ldap->ldapsource);
}
}
-
/*
- * Look for entries matching query_filter.
+ * Prepare the query.
*/
tv.tv_sec = dict_ldap->timeout;
tv.tv_usec = 0;
+ escaped_name = vstring_alloc(20);
filter_buf = vstring_alloc(30);
+ /* Any wildcards and escapes in the supplied address should be escaped. */
+ if (strchr(name, '*') || strchr(name, '\\')) {
+ if (msg_verbose)
+ msg_info("%s: found wildcard in %s", myname, name);
+ for (sub = (char *) name; *sub != '\0'; sub++) {
+ if (*sub == '*' || *sub == '\\') {
+ vstring_strncat(escaped_name, "\\", 1);
+ vstring_strncat(escaped_name, sub, 1);
+ } else {
+ vstring_strncat(escaped_name, sub, 1);
+ }
+ }
+ if (msg_verbose)
+ msg_info("%s: with wildcards escaped, it's %s", myname, vstring_str(escaped_name));
+ } else
+ vstring_strcpy(escaped_name, (char *) name);
+
/* Does the supplied query_filter even include a substitution? */
if (strstr(dict_ldap->query_filter, "%s") == NULL) {
+ /* No, log the fact and continue. */
msg_warn("%s: fixed query_filter %s is probably useless", myname,
dict_ldap->query_filter);
vstring_strcpy(filter_buf, dict_ldap->query_filter);
} else {
- /*
- * OK, let's replace all the instances of %s with the address to look
- * up.
- */
+ /* Yes, replace all instances of %s with the address to look up. */
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; we could skip any single character.
+ * really matter; the token could be any single character.
*/
if (*(sub) == '%') {
if ((sub + 1) != end && *(sub + 1) != 's')
- msg_fatal("%s: invalid lookup substitution format '%%%c'!", myname, *(sub + 1));
- vstring_strcat(filter_buf, name);
+ msg_warn("%s: invalid lookup substitution format '%%%c'!", myname, *(sub + 1));
+ vstring_strcat(filter_buf, vstring_str(escaped_name));
sub++;
} else
vstring_strncat(filter_buf, sub, 1);
}
}
+ /* On to the search. */
if (msg_verbose)
msg_info("%s: searching with filter %s", myname,
vstring_str(filter_buf));
if ((rc = ldap_search_st(dict_ldap->ld, dict_ldap->search_base,
LDAP_SCOPE_SUBTREE,
vstring_str(filter_buf),
- 0, 0, &tv, &res)) != LDAP_SUCCESS) {
-
- ldap_unbind(dict_ldap->ld);
- dict_ldap->ld = 0;
- if (msg_verbose)
- msg_info("%s: freed connection handle for LDAP source %s", myname, dict_ldap->ldapsource);
- msg_fatal("%s: Unable to search base %s at server %s (%d -- %s): ",
- myname, dict_ldap->search_base, dict_ldap->server_host, rc,
- ldap_err2string(rc));
-
- } else {
-
+ 0, 0, &tv, &res)) == LDAP_SUCCESS) {
/*
- * Extract the requested result_attribute.
+ * Search worked; extract the requested result_attribute.
*/
if (msg_verbose)
- msg_info("%s: search found %d", myname,
+ msg_info("%s: search found %d matches", myname,
ldap_count_entries(dict_ldap->ld, res));
+ /* There could have been lots of hits. */
for (entry = ldap_first_entry(dict_ldap->ld, res); entry != NULL; entry = ldap_next_entry(dict_ldap->ld, entry)) {
+
+ /* And each entry could have multiple attributes. */
attr_values = ldap_get_values(dict_ldap->ld, entry,
dict_ldap->result_attribute);
if (attr_values == NULL) {
msg_warn("%s: entry doesn't have any values for %s", myname, dict_ldap->result_attribute);
continue;
}
-
/*
* Append each returned address to the result list.
*/
}
if (msg_verbose)
msg_info("%s: search returned: %s", myname, vstring_str(result));
+ } else {
+ /* Rats. That didn't work. */
+ msg_warn("%s: search error %d: %s ", myname, rc,
+ ldap_err2string(rc));
+
+ /*
+ * Tear down the connection so it gets set up from scratch on the
+ * next lookup.
+ */
+ ldap_unbind(dict_ldap->ld);
+ dict_ldap->ld = NULL;
+
+ /* And tell the caller to try again later. */
+ dict_errno = DICT_ERR_RETRY;
}
- /*
- * Cleanup. Always return with dict_errno set when we were unable to
- * perform the query.
- */
+ /* Cleanup. */
if (res != 0)
ldap_msgfree(res);
- else
- dict_errno = 1;
if (filter_buf != 0)
vstring_free(filter_buf);
+
return (VSTRING_LEN(result) > 0 ? vstring_str(result) : 0);
}
msg_info("%s: %s is %s", myname, vstring_str(config_param),
dict_ldap->query_filter);
- /*
- * get configured value of "ldapsource_lookup_wildcards"; default to
- * false
- */
- vstring_sprintf(config_param, "%s_lookup_wildcards", ldapsource);
- dict_ldap->lookup_wildcards =
- get_mail_conf_bool(vstring_str(config_param), 0);
- if (msg_verbose)
- msg_info("%s: %s is %d", myname, vstring_str(config_param),
- dict_ldap->lookup_wildcards);
-
vstring_sprintf(config_param, "%s_result_attribute", ldapsource);
dict_ldap->result_attribute =
mystrdup((char *) get_mail_conf_str(vstring_str(config_param),
msg_info("%s: connecting to server %s", myname,
dict_ldap->server_host);
- if ((saved_alarm = signal(SIGALRM, dict_ldap_timeout)) == SIG_ERR)
- msg_fatal("%s: signal: %m", myname);
-
+ if ((saved_alarm = signal(SIGALRM, dict_ldap_timeout)) == SIG_ERR) {
+ msg_warn("%s: error setting signal handler for open timeout: %m", myname);
+ dict_errno = DICT_ERR_RETRY;
+ return (0);
+ }
alarm(dict_ldap->timeout);
if (setjmp(env) == 0)
dict_ldap->ld = ldap_open(dict_ldap->server_host,
(int) dict_ldap->server_port);
alarm(0);
- if (signal(SIGALRM, saved_alarm) == SIG_ERR)
- msg_fatal("%s: signal: %m", myname);
-
- if (msg_verbose)
- msg_info("%s: after ldap_open", myname);
-
- if (dict_ldap->ld == 0) {
- msg_fatal("%s: Unable to contact LDAP server %s",
- myname, dict_ldap->server_host);
+ if (signal(SIGALRM, saved_alarm) == SIG_ERR) {
+ msg_warn("%s: error resetting signal handler after open: %m", myname);
+ dict_errno = DICT_ERR_RETRY;
+ return (0);
+ }
+ if (dict_ldap->ld == NULL) {
+ msg_warn("%s: Unable to contact LDAP server %s",
+ myname, dict_ldap->server_host);
+ dict_errno = DICT_ERR_RETRY;
+ return (0);
} else {
+ if (msg_verbose)
+ msg_info("%s: after ldap_open", myname);
/*
- * If this server requires us to bind, do so.
+ * If this server requires a bind, do so.
*/
if (dict_ldap->bind) {
if (msg_verbose)
rc = ldap_bind_s(dict_ldap->ld, dict_ldap->bind_dn,
dict_ldap->bind_pw, LDAP_AUTH_SIMPLE);
if (rc != LDAP_SUCCESS) {
- msg_fatal("%s: Unable to bind to server %s as %s (%d -- %s): ", myname, dict_ldap->server_host, dict_ldap->bind_dn, rc, ldap_err2string(rc));
+ msg_warn("%s: Unable to bind to server %s as %s (%d -- %s): ", myname, dict_ldap->server_host, dict_ldap->bind_dn, rc, ldap_err2string(rc));
+ dict_errno = DICT_ERR_RETRY;
+ return (0);
} else {
if (msg_verbose)
msg_info("%s: Successful bind to server %s as %s (%d -- %s): ", myname, dict_ldap->server_host, dict_ldap->bind_dn, rc, ldap_err2string(rc));