# The following expansions are done on mailbox_command: $user (recipient
# username), $shell (recipient shell), $home (recipient home directory),
# $recipient (full recipient address), $extension (recipient address
-# extension), $domain (recipient domain), $recipient_delimiter. Specify
-# ${name?value} or ${name:value} to expand value only when $name does
-# (does not) exist.
+# extension), $domain (recipient domain), $mailbox (entire recipient
+# localpart), $recipient_delimiter. Specify ${name?value} or
+# ${name:value} to expand value only when $name does (does not) exist.
#
# Avoid shell meta characters because they will force Postfix to run
# an expensive shell process. Procmail alone is expensive enough.
#
#fallback_transport =
-# The luser_relay parameter specifies an optional destination
-# (@domain, address, "|command", /file/name) for unknown recipients.
-# By default, mail for unknown local recipients is bounced.
+# The luser_relay parameter specifies an optional destination (@domain,
+# address) for unknown recipients. By default, mail for unknown
+# local recipients is bounced.
#
# Specify @domain in order to keep the original recipient name.
-# If an address is specified, and if a recipient delimiter is
-# specified, the original recipient name is appended to the addres
-# localpart.
+# The following expansions are done on luser_relay: $user (recipient
+# username), $shell (recipient shell), $home (recipient home directory),
+# $recipient (full recipient address), $extension (recipient address
+# extension), $domain (recipient domain), $mailbox (entire recipient
+# localpart), $recipient_delimiter. Specify ${name?value} or
+# ${name:value} to expand value only when $name does (does not) exist.
#
-# luser_relay =
+# luser_relay = @other.host
+# luser_relay = admin+$mailbox
# JUNK MAIL CONTROLS
#
mail_name = Postfix
mail_owner = postfix
mail_spool_directory = /var/mail
-mail_version = Snapshot-19990505
+mail_version = Snapshot-19990507
mailbox_command =
mailbox_transport =
maps_rbl_domains = rbl.maps.vix.com
# The following expansions are done on forward_path: $user (recipient
# username), $shell (recipient shell), $home (recipient home directory),
# $recipient (full recipient address), $extension (recipient address
-# extension), $domain (recipient domain), $recipient_delimiter. Specify
-# ${name?value} or ${name:value} to expand value only when $name does
-# (does not) exist.
+# extension), $domain (recipient domain), $mailbox (entire recipient
+# localpart), $recipient_delimiter. Specify ${name?value} or
+# ${name:value} to expand value only when $name does (does not) exist.
#
#forward_path = /var/forward/$user
forward_path = $home/.forward$recipient_delimiter$extension,$home/.forward
# By default, mail for unknown local recipients is bounced.
#
# Specify @domain in order to keep the original recipient name.
-# If an address is specified, and if a recipient delimiter is
-# specified, the original recipient name is appended to the addres
-# localpart.
+# The following expansions are done on luser_relay: $user (recipient
+# username), $shell (recipient shell), $home (recipient home directory),
+# $recipient (full recipient address), $extension (recipient address
+# extension), $domain (recipient domain), $mailbox (entire recipient
+# localpart), $recipient_delimiter. Specify ${name?value} or
+# ${name:value} to expand value only when $name does (does not) exist.
#
-# luser_relay =
+# luser_relay = @other.host
+# luser_relay = admin+$mailbox
# The mail_spool_directory parameter specifies the directory where
# UNIX-style mailboxes are kept. The default setting depends on the
# The following expansions are done on mailbox_command: $user (recipient
# username), $shell (recipient shell), $home (recipient home directory),
# $recipient (full recipient address), $extension (recipient address
-# extension), $domain (recipient domain), $recipient_delimiter. Specify
-# ${name?value} or ${name:value} to expand value only when $name does
-# (does not) exist.
+# extension), $domain (recipient domain), $mailbox (entire recipient
+# localpart), $recipient_delimiter. Specify ${name?value} or
+# ${name:value} to expand value only when $name does (does not) exist.
#
# Avoid shell meta characters because they will force Postfix to run
# an expensive shell process. Procmail alone is expensive enough.
* Version of this program.
*/
#define VAR_MAIL_VERSION "mail_version"
-#define DEF_MAIL_VERSION "Snapshot-19990505"
+#define DEF_MAIL_VERSION "Snapshot-19990507"
extern char *var_mail_version;
/* LICENSE
interpolation of <b>$user</b> (recipient username), <b>$home</b> (recip-
ient home directory), <b>$shell</b> (recipient shell), <b>$recipient</b>
(complete recipient address), <b>$extension</b> (recipient
- address extension), <b>$domain</b> (recipient domain) and <b>$recip-</b>
- <b>ient</b><i>_</i><b>delimiter.</b> The forms <i>${name?value}</i> and <i>${name:value}</i>
- expand conditionally to <i>value</i> when <i>$name</i> is (is not)
- defined.
+ address extension), <b>$domain</b> (recipient domain), <b>mailbox</b>
+ (entire recipient address localpart) and <b>$recipient</b><i>_</i><b>delim-</b>
+ <b>iter.</b> The forms <i>${name?value}</i> and <i>${name:value}</i> expand
+ conditionally to <i>value</i> when <i>$name</i> is (is not) defined.
An alias or ~/.<b>forward</b> file may list any combination of
external commands, destination file names, <b>:include:</b>
ent username), <b>$home</b> (recipient home directory), <b>$shell</b>
(recipient shell), <b>$recipient</b> (complete recipient
address), <b>$extension</b> (recipient address extension),
- <b>$domain</b> (recipient domain) and <b>$recipient</b><i>_</i><b>delimiter.</b> The
- forms <i>${name?value}</i> and <i>${name:value}</i> expand conditionally
- to <i>value</i> when <i>$name</i> is (is not) defined. In the result of
- <i>name</i> expansion, characters that have special meaning to
+ <b>$domain</b> (recipient domain), <b>mailbox</b> (entire recipient
+ address localpart) and <b>$recipient</b><i>_</i><b>delimiter.</b> The forms
+ <i>${name?value}</i> and <i>${name:value}</i> expand conditionally to
+ <i>value</i> when <i>$name</i> is (is not) defined. In the result of
+ <i>name</i> expansion, characters that have special meaning to
the shell are censored and replaced by underscores.
- Mailbox delivery can be delegated to alternative message
- transports specified in the <b>master.cf</b> file. The <b>mail-</b>
- <b>box</b><i>_</i><b>transport</b> configuration parameter specifies a message
- transport that is to be used for all local recipients,
- regardless of whether they are found in the UNIX passwd
- database. The <b>fallback</b><i>_</i><b>transport</b> parameter specifies a
+ Mailbox delivery can be delegated to alternative message
+ transports specified in the <b>master.cf</b> file. The <b>mail-</b>
+ <b>box</b><i>_</i><b>transport</b> configuration parameter specifies a message
+ transport that is to be used for all local recipients,
+ regardless of whether they are found in the UNIX passwd
+ database. The <b>fallback</b><i>_</i><b>transport</b> parameter specifies a
message transport for recipients that are not found in the
- UNIX passwd database.
LOCAL(8) LOCAL(8)
+ UNIX passwd database.
+
In the case of UNIX-style mailbox delivery, the <b>local</b> dae-
mon prepends a "<b>From</b> <i>sender</i> <i>time_stamp</i>" envelope header to
- each message, prepends a <b>Delivered-To:</b> header with the
+ each message, prepends a <b>Delivered-To:</b> header with the
envelope recipient address, prepends a <b>Return-Path:</b> header
- with the envelope sender address, prepends a > character
- to lines beginning with "<b>From</b> ", and appends an empty
- line. The mailbox is locked for exclusive access while
- delivery is in progress. In case of problems, an attempt
+ with the envelope sender address, prepends a > character
+ to lines beginning with "<b>From</b> ", and appends an empty
+ line. The mailbox is locked for exclusive access while
+ delivery is in progress. In case of problems, an attempt
is made to truncate the mailbox to its original length.
In the case of <b>maildir</b> delivery, the local daemon prepends
a <b>Delivered-To:</b> header with the envelope recipient address
- and prepends a <b>Return-Path:</b> header with the envelope
+ and prepends a <b>Return-Path:</b> header with the envelope
sender address.
<b>EXTERNAL</b> <b>COMMAND</b> <b>DELIVERY</b>
- The <b>allow</b><i>_</i><b>mail</b><i>_</i><b>to</b><i>_</i><b>commands</b> configuration parameter
- restricts delivery to external commands. The default set-
- ting (<b>alias,</b> <b>forward</b>) forbids command destinations in
+ The <b>allow</b><i>_</i><b>mail</b><i>_</i><b>to</b><i>_</i><b>commands</b> configuration parameter
+ restricts delivery to external commands. The default set-
+ ting (<b>alias,</b> <b>forward</b>) forbids command destinations in
<b>:include:</b> files.
- The command is executed directly where possible. Assis-
- tance by the shell (<b>/bin/sh</b> on UNIX systems) is used only
- when the command contains shell magic characters, or when
+ The command is executed directly where possible. Assis-
+ tance by the shell (<b>/bin/sh</b> on UNIX systems) is used only
+ when the command contains shell magic characters, or when
the command invokes a shell built-in command.
- A limited amount of command output (standard output and
- standard error) is captured for inclusion with non-deliv-
- ery status reports. A command is forcibly terminated if
- it does not complete within <b>command</b><i>_</i><b>time</b><i>_</i><b>limit</b> seconds.
- Command exit status codes are expected to follow the con-
+ A limited amount of command output (standard output and
+ standard error) is captured for inclusion with non-deliv-
+ ery status reports. A command is forcibly terminated if
+ it does not complete within <b>command</b><i>_</i><b>time</b><i>_</i><b>limit</b> seconds.
+ Command exit status codes are expected to follow the con-
ventions defined in <<b>sysexits.h</b>>.
When mail is delivered on behalf of a user, the <b>HOME</b>, <b>LOG-</b>
The current working directory is the mail queue directory.
The <b>local</b> daemon prepends a "<b>From</b> <i>sender</i> <i>time_stamp</i>" enve-
- lope header to each message, prepends a <b>Delivered-To:</b>
- header with the recipient envelope address, prepends a
- <b>Return-Path:</b> header with the sender envelope address, and
+ lope header to each message, prepends a <b>Delivered-To:</b>
+ header with the recipient envelope address, prepends a
+ <b>Return-Path:</b> header with the sender envelope address, and
appends an empty line.
<b>EXTERNAL</b> <b>FILE</b> <b>DELIVERY</b>
- The <b>allow</b><i>_</i><b>mail</b><i>_</i><b>to</b><i>_</i><b>files</b> configuration parameter restricts
- delivery to external files. The default setting (<b>alias,</b>
- <b>forward</b>) forbids file destinations in <b>:include:</b> files.
+ The <b>allow</b><i>_</i><b>mail</b><i>_</i><b>to</b><i>_</i><b>files</b> configuration parameter restricts
+ delivery to external files. The default setting (<b>alias,</b>
+ <b>forward</b>) forbids file destinations in <b>:include:</b> files.
Specify a pathname ending in <b>/</b> for <b>qmail</b>-compatible
- <b>maildir</b> delivery.
-
LOCAL(8) LOCAL(8)
+ <b>maildir</b> delivery.
+
The <b>local</b> daemon prepends a "<b>From</b> <i>sender</i> <i>time_stamp</i>" enve-
- lope header to each message, prepends a <b>Delivered-To:</b>
- header with the recipient envelope address, prepends a >
- character to lines beginning with "<b>From</b> ", and appends an
- empty line. The envelope sender address is available in
- the <b>Return-Path:</b> header. When the destination is a regu-
+ lope header to each message, prepends a <b>Delivered-To:</b>
+ header with the recipient envelope address, prepends a >
+ character to lines beginning with "<b>From</b> ", and appends an
+ empty line. The envelope sender address is available in
+ the <b>Return-Path:</b> header. When the destination is a regu-
lar file, it is locked for exclusive access while delivery
is in progress. In case of problems, an attempt is made to
truncate a regular file to its original length.
In the case of <b>maildir</b> delivery, the local daemon prepends
- a <b>Delivered-To:</b> header with the envelope recipient
- address. The envelope sender address is available in the
+ a <b>Delivered-To:</b> header with the envelope recipient
+ address. The envelope sender address is available in the
<b>Return-Path:</b> header.
<b>ADDRESS</b> <b>EXTENSION</b>
- The optional <b>recipient</b><i>_</i><b>delimiter</b> configuration parameter
- specifies how to separate address extensions from local
+ The optional <b>recipient</b><i>_</i><b>delimiter</b> configuration parameter
+ specifies how to separate address extensions from local
recipient names.
- For example, with "<b>recipient</b><i>_</i><b>delimiter</b> <b>=</b> <b>+</b>", mail for
- <i>name</i>+<i>foo</i> is delivered to the alias <i>name</i>+<i>foo</i> or to the
- alias <i>name</i>, to the destinations listed in ~<i>name</i>/.<b>for-</b>
+ For example, with "<b>recipient</b><i>_</i><b>delimiter</b> <b>=</b> <b>+</b>", mail for
+ <i>name</i>+<i>foo</i> is delivered to the alias <i>name</i>+<i>foo</i> or to the
+ alias <i>name</i>, to the destinations listed in ~<i>name</i>/.<b>for-</b>
<b>ward</b>+<i>foo</i> or in ~<i>name</i>/.<b>forward</b>, to the mailbox owned by the
user <i>name</i>, or it is sent back as undeliverable.
- In all cases the <b>local</b> daemon prepends a `<b>Delivered-To:</b>
+ In all cases the <b>local</b> daemon prepends a `<b>Delivered-To:</b>
<i>name</i>+<i>foo</i>' header line.
<b>DELIVERY</b> <b>RIGHTS</b>
- Deliveries to external files and external commands are
+ Deliveries to external files and external commands are
made with the rights of the receiving user on whose behalf
- the delivery is made. In the absence of a user context,
- the <b>local</b> daemon uses the owner rights of the <b>:include:</b>
+ the delivery is made. In the absence of a user context,
+ the <b>local</b> daemon uses the owner rights of the <b>:include:</b>
file or alias database. When those files are owned by the
superuser, delivery is made with the rights specified with
the <b>default</b><i>_</i><b>privs</b> configuration parameter.
RFC 822 (ARPA Internet Text Messages)
<b>DIAGNOSTICS</b>
- Problems and transactions are logged to <b>syslogd</b>(8). Cor-
- rupted message files are marked so that the queue manager
+ Problems and transactions are logged to <b>syslogd</b>(8). Cor-
+ rupted message files are marked so that the queue manager
can move them to the <b>corrupt</b> queue afterwards.
- Depending on the setting of the <b>notify</b><i>_</i><b>classes</b> parameter,
- the postmaster is notified of bounces and of other trou-
+ Depending on the setting of the <b>notify</b><i>_</i><b>classes</b> parameter,
+ the postmaster is notified of bounces and of other trou-
ble.
<b>BUGS</b>
- For security reasons, the message delivery status of
- external commands or of external files is never check-
- pointed to file. As a result, the program may occasionally
+ For security reasons, the message delivery status of
LOCAL(8) LOCAL(8)
+ external commands or of external files is never check-
+ pointed to file. As a result, the program may occasionally
deliver more than once to a command or external file. Bet-
ter safe than sorry.
- Mutually-recursive aliases or ~/.<b>forward</b> files are not
- detected early. The resulting mail forwarding loop is
+ Mutually-recursive aliases or ~/.<b>forward</b> files are not
+ detected early. The resulting mail forwarding loop is
broken by the use of the <b>Delivered-To:</b> message header.
<b>CONFIGURATION</b> <b>PARAMETERS</b>
- The following <b>main.cf</b> parameters are especially relevant
- to this program. See the Postfix <b>main.cf</b> file for syntax
- details and for default values. Use the <b>postfix</b> <b>reload</b>
+ The following <b>main.cf</b> parameters are especially relevant
+ to this program. See the Postfix <b>main.cf</b> file for syntax
+ details and for default values. Use the <b>postfix</b> <b>reload</b>
command after a configuration change.
<b>Miscellaneous</b>
List of alias databases.
<b>forward</b><i>_</i><b>path</b>
- Search list for .forward files. The following
- macros are recognized: <b>$home</b> (home directory),
- <b>$user</b> (login name), <b>$extension</b> (address extension),
- <b>$recipient</b><i>_</i><b>delimiter</b> (address extension delimiter).
+ Search list for .forward files. The names are sub-
+ ject to <i>$name</i> expansion.
<b>local</b><i>_</i><b>command</b><i>_</i><b>shell</b>
Shell to use for external command execution (for
<b>luser</b><i>_</i><b>relay</b>
Destination (<i>@domain</i> or <i>address</i>) for non-existent
- users. The <i>address</i> can be any destination that is
- valid in an alias file.
+ users. The <i>address</i> is subjected to <i>$name</i> expan-
+ sion.
command.o: ../include/vbuf.h
command.o: ../include/vstream.h
command.o: ../include/argv.h
+command.o: ../include/mac_expand.h
command.o: ../include/defer.h
command.o: ../include/bounce.h
command.o: ../include/sent.h
dotforward.o: ../include/iostuff.h
dotforward.o: ../include/stringops.h
dotforward.o: ../include/mymalloc.h
-dotforward.o: ../include/mac_parse.h
+dotforward.o: ../include/mac_expand.h
dotforward.o: ../include/mypwd.h
dotforward.o: ../include/bounce.h
dotforward.o: ../include/been_here.h
local.o: ../include/resolve_clnt.h
local_expand.o: local_expand.c
local_expand.o: ../include/sys_defs.h
-local_expand.o: ../include/vstring.h
-local_expand.o: ../include/vbuf.h
-local_expand.o: ../include/mac_expand.h
-local_expand.o: ../include/mail_params.h
-local_expand.o: local.h
local_expand.o: ../include/htable.h
+local_expand.o: local.h
local_expand.o: ../include/vstream.h
+local_expand.o: ../include/vbuf.h
+local_expand.o: ../include/vstring.h
local_expand.o: ../include/been_here.h
local_expand.o: ../include/tok822.h
local_expand.o: ../include/resolve_clnt.h
/* SYNOPSIS
/* #include "local.h"
/*
-/* int deliver_alias(state, usr_attr, statusp)
+/* int deliver_alias(state, usr_attr, name, statusp)
/* LOCAL_STATE state;
/* USER_ATTR usr_attr;
+/* char *name;
/* int *statusp;
/* DESCRIPTION
/* deliver_alias() looks up the expansion of the recipient in
/* A table with delivered-to: addresses taken from the message.
/* .IP usr_attr
/* User attributes (rights, environment).
+/* .IP name
+/* The alias to be looked up.
/* .IP statusp
/* Delivery status. See below.
/* DIAGNOSTICS
/* deliver_alias - expand alias file entry */
-int deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp)
+int deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr,
+ char *name, int *statusp)
{
char *myname = "deliver_alias";
const char *alias_result;
* a possible alias loop.
*/
if (state.msg_attr.exp_from != 0
- && strcasecmp(state.msg_attr.exp_from, state.msg_attr.local) == 0)
+ && strcasecmp(state.msg_attr.exp_from, name) == 0)
return (NO);
if (state.level > 100) {
- msg_warn("possible alias database loop for %s", state.msg_attr.local);
+ msg_warn("possible alias database loop for %s", name);
*statusp = bounce_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr),
- "possible alias database loop for %s", state.msg_attr.local);
+ "possible alias database loop for %s", name);
return (YES);
}
- state.msg_attr.exp_from = state.msg_attr.local;
+ state.msg_attr.exp_from = name;
/*
* There are a bunch of roles that we're trying to keep track of.
msg_warn("invalid alias map type: %s", *cpp);
continue;
}
- if ((alias_result = dict_get(dict, state.msg_attr.local)) != 0) {
+ if ((alias_result = dict_get(dict, name)) != 0) {
if (msg_verbose)
- msg_info("%s: %s: %s = %s", myname, *cpp,
- state.msg_attr.local, alias_result);
+ msg_info("%s: %s: %s = %s", myname, *cpp, name, alias_result);
/*
* DELIVERY POLICY
#define STR(x) vstring_str(x)
#define OWNER_ASSIGN(own) \
(own = (var_ownreq_special == 0 ? 0 : \
- concatenate("owner-", state.msg_attr.local, (char *) 0)))
+ concatenate("owner-", name, (char *) 0)))
expansion = mystrdup(alias_result);
if (OWNER_ASSIGN(owner) != 0 && maps_find(maps, owner,
return (YES);
} else {
if (msg_verbose)
- msg_info("%s: %s: %s not found", myname, *cpp,
- state.msg_attr.local);
+ msg_info("%s: %s: %s not found", myname, *cpp, name);
}
}
*/
#define STREQ(x,y) (strcasecmp(x,y) == 0)
- if (STREQ(state.msg_attr.local, MAIL_ADDR_MAIL_DAEMON)
- || STREQ(state.msg_attr.local, MAIL_ADDR_POSTMASTER)) {
- msg_warn("required alias not found: %s", state.msg_attr.local);
+ if (STREQ(name, MAIL_ADDR_MAIL_DAEMON)
+ || STREQ(name, MAIL_ADDR_POSTMASTER)) {
+ msg_warn("required alias not found: %s", name);
*statusp = sent(SENT_ATTR(state.msg_attr), "discarded");
return (YES);
}
#include <vstring.h>
#include <vstream.h>
#include <argv.h>
+#include <mac_expand.h>
/* Global library. */
abcdefghijklmnopqrstuvwxyz\
ABCDEFGHIJKLMNOPQRSTUVWXYZ";
VSTRING *expanded_cmd;
+ HTABLE *expand_attr;
/*
* Make verbose logging easier to understand.
argv_terminate(env);
expanded_cmd = vstring_alloc(10);
- if (command == var_mailbox_command)
- local_expand(expanded_cmd, command, state, usr_attr, ok_chars);
- else
+ if (command == var_mailbox_command) {
+ expand_attr = local_expand(state, usr_attr);
+ mac_expand(expanded_cmd, command, MAC_EXP_FLAG_NONE,
+ MAC_EXP_ARG_FILTER, ok_chars,
+ MAC_EXP_ARG_TABLE, expand_attr,
+ 0);
+ htable_free(expand_attr, (void (*) (char *)) 0);
+ } else
vstring_strcpy(expanded_cmd, command);
cmd_status = pipe_command(state.msg_attr.fp, why,
attrp->offset = 0;
attrp->sender = 0;
attrp->recipient = 0;
+ attrp->domain = 0;
attrp->local = 0;
+ attrp->user = 0;
attrp->extension = 0;
+ attrp->unmatched = 0;
attrp->owner = 0;
attrp->delivered = 0;
attrp->relay = 0;
msg_info("offset: %ld", attrp->offset);
msg_info("sender: %s", attrp->sender ? attrp->sender : "null");
msg_info("recipient: %s", attrp->recipient ? attrp->recipient : "null");
+ msg_info("domain: %s", attrp->domain ? attrp->domain : "null");
msg_info("local: %s", attrp->local ? attrp->local : "null");
+ msg_info("user: %s", attrp->user ? attrp->user : "null");
msg_info("extension: %s", attrp->extension ? attrp->extension : "null");
+ msg_info("unmatched: %s", attrp->unmatched ? attrp->unmatched : "null");
msg_info("owner: %s", attrp->owner ? attrp->owner : "null");
msg_info("delivered: %s", attrp->delivered ? attrp->delivered : "null");
msg_info("relay: %s", attrp->relay ? attrp->relay : "null");
#include <iostuff.h>
#include <stringops.h>
#include <mymalloc.h>
-#include <mac_parse.h>
+#include <mac_expand.h>
/* Global library. */
char *lhs;
char *next;
const char *forward_path;
+ HTABLE *expand_attr;
+ HTABLE *record_attr;
+ HTABLE_INFO *extension_record;
/*
* Make verbose logging easier to understand.
/*
* Skip this module if per-user forwarding is disabled. XXX We need to
- * extend the mail_conf_XXX() interface to request no expansion of $names in
- * the given value or in the default value.
+ * extend the mail_conf_XXX() interface to request no expansion of $names
+ * in the given value or in the default value.
*/
if ((forward_path = mail_conf_lookup(VAR_FORWARD_PATH)) == 0)
forward_path = DEF_FORWARD_PATH;
* Skip non-existing users. The mailbox delivery routine will catch the
* error.
*/
- if ((mypwd = mypwnam(state.msg_attr.local)) == 0)
+ if ((mypwd = mypwnam(state.msg_attr.user)) == 0)
return (NO);
/*
next = saved_forward_path;
lookup_status = -1;
+ expand_attr = local_expand(state, usr_attr);
+ record_attr = htable_create(0);
+ extension_record = htable_enter(record_attr, "extension", (char *) 0);
+
while ((lhs = mystrtok(&next, ", \t\r\n")) != 0) {
VSTRING_RESET(path);
- if (local_expand(path, lhs, state, usr_attr, (char *) 0) == 0) {
+ extension_record->value = 0;
+ if (mac_expand(path, lhs, MAC_EXP_FLAG_NONE,
+ MAC_EXP_ARG_TABLE, expand_attr,
+ MAC_EXP_ARG_RECORD, record_attr,
+ 0) == 0) {
lookup_status =
lstat_as(STR(path), &st, usr_attr.uid, usr_attr.gid);
if (msg_verbose)
msg_info("%s: path %s status %d", myname,
STR(path), lookup_status);
- if (lookup_status >= 0)
+ if (lookup_status >= 0) {
+ if (extension_record->value != 0)
+ state.msg_attr.unmatched = 0;
break;
+ }
}
}
+ htable_free(expand_attr, (void (*) (char *)) 0);
+ htable_free(record_attr, (void (*) (char *)) 0);
+
if (lookup_status >= 0) {
if (S_ISREG(st.st_mode) == 0) {
msg_warn("file %s is not a regular file", STR(path));
/* \fB$user\fR (recipient username), \fB$home\fR (recipient home
/* directory), \fB$shell\fR (recipient shell), \fB$recipient\fR
/* (complete recipient address), \fB$extension\fR (recipient address
-/* extension), \fB$domain\fR (recipient domain) and
+/* extension), \fB$domain\fR (recipient domain), \fBmailbox\fR
+/* (entire recipient address localpart) and
/* \fB$recipient_delimiter.\fR The forms \fI${name?value}\fR and
/* \fI${name:value}\fR expand conditionally to \fIvalue\fR when
/* \fI$name\fR is (is not) defined.
/* username), \fB$home\fR (recipient home directory), \fB$shell\fR
/* (recipient shell), \fB$recipient\fR (complete recipient address),
/* \fB$extension\fR (recipient address extension), \fB$domain\fR
-/* (recipient domain) and \fB$recipient_delimiter.\fR The forms
+/* (recipient domain), \fBmailbox\fR (entire recipient address
+/* localpart) and \fB$recipient_delimiter.\fR The forms
/* \fI${name?value}\fR and \fI${name:value}\fR expand conditionally to
/* \fIvalue\fR when \fI$name\fR is (is not) defined. In the result
/* of \fIname\fR expansion, characters that have special meaning to
/* .IP \fBalias_maps\fR
/* List of alias databases.
/* .IP \fBforward_path\fR
-/* Search list for .forward files. The following macros are recognized:
-/* \fB$home\fR (home directory), \fB$user\fR (login name),
-/* \fB$extension\fR (address extension), \fB$recipient_delimiter\fR
-/* (address extension delimiter).
+/* Search list for .forward files. The names are subject to \fI$name\fR
+/* expansion.
/* .IP \fBlocal_command_shell\fR
/* Shell to use for external command execution (for example,
/* /some/where/smrsh -c).
/* Specify a path ending in \fB/\fR for maildir-style delivery.
/* .IP \fBluser_relay\fR
/* Destination (\fI@domain\fR or \fIaddress\fR) for non-existent users.
-/* The \fIaddress\fR can be any destination that is valid in an alias
-/* file.
+/* The \fIaddress\fR is subjected to \fI$name\fR expansion.
/* .IP \fBmail_spool_directory\fR
/* Directory with UNIX-style mailboxes. The default pathname is system
/* dependent.
static CONFIG_STR_TABLE raw_table[] = {
VAR_FORWARD_PATH, DEF_FORWARD_PATH, &var_forward_path, 0, 0,
VAR_MAILBOX_COMMAND, DEF_MAILBOX_COMMAND, &var_mailbox_command, 0, 0,
+ VAR_LUSER_RELAY, DEF_LUSER_RELAY, &var_luser_relay, 0, 0,
0,
};
long offset; /* data offset */
char *sender; /* taken from envelope */
char *recipient; /* taken from resolver */
- char *local; /* recipient localpart, base name */
+ char *domain; /* recipient domain */
+ char *local; /* recipient full localpart */
+ char *user; /* recipient localpart, base name */
char *extension; /* recipient localpart, extension */
+ char *unmatched; /* unmatched extension */
char *owner; /* null or list owner */
char *delivered; /* for loop detection */
char *relay; /* relay host */
* "inner" nodes of the delivery graph.
*/
extern int deliver_recipient(LOCAL_STATE, USER_ATTR);
-extern int deliver_alias(LOCAL_STATE, USER_ATTR, int *);
+extern int deliver_alias(LOCAL_STATE, USER_ATTR, char *, int *);
extern int deliver_dotforward(LOCAL_STATE, USER_ATTR, int *);
extern int deliver_include(LOCAL_STATE, USER_ATTR, char *);
extern int deliver_token(LOCAL_STATE, USER_ATTR, TOK822 *);
/*
* local_expand.c
*/
-int local_expand(VSTRING *, const char *, LOCAL_STATE, USER_ATTR, const char *);
+HTABLE *local_expand(LOCAL_STATE, USER_ATTR);
/* LICENSE
/* .ad
/* NAME
/* local_expand 3
/* SUMMARY
-/* expand $name based on delivery attributes
+/* set up attribute list for $name expansion
/* SYNOPSIS
/* #include "local.h"
/*
-/* int local_expand(result, pattern, state, usr_attr, filter)
-/* VSTRING *result;
-/* const char *pattern;
+/* HTABLE *local_expand(state, usr_attr)
/* LOCAL_STATE state;
/* USER_ATTR usr_attr;
-/* const char *filter;
/* DESCRIPTION
-/* local_expand() expands $name instances on the basis of message
-/* delivery attributes.
+/* local_expand() instantiates an attribute table for $name
+/* expansion.
/*
-/* Macros:
-/* .IP $domain
+/* Attributes:
+/* .IP domain
/* The recipient address domain.
-/* .IP $extension
+/* .IP extension
/* The recipient address extension.
-/* .IP $home
+/* .IP home
/* The recipient home directory.
-/* .IP $recipient
+/* .IP mailbox
+/* The full recipient address localpart.
+/* .IP recipient
/* The full recipient address.
-/* .IP $recipient_delimiter
+/* .IP recipient_delimiter
/* The recipient delimiter.
-/* .IP $shell
+/* .IP shell
/* The recipient shell program.
-/* .IP $user
+/* .IP user
/* The recipient user name.
/* .PP
/* Arguments:
-/* .IP result
-/* Storage for the result. The result is truncated upon entry.
-/* .IP pattern
-/* The input with zero or more $name references.
/* .IP state
/* Message delivery attributes (sender, recipient etc.).
/* Attributes describing alias, include or forward expansion.
/* A table with delivered-to: addresses taken from the message.
/* .IP usr_attr
/* Attributes describing user rights and environment.
-/* .IP filter
-/* A null pointer, or a null-terminated list of characters that
-/* are allowed to appear in the result if a $name expansion.
/* DIAGNOSTICS
/* Fatal errors: out of memory.
/* SEE ALSO
/* Utility library. */
-#include <vstring.h>
-#include <mac_expand.h>
+#include <htable.h>
-/* Global library. */
+/* Global library */
#include <mail_params.h>
#include "local.h"
-/* local_expand - expand contents of .forward file */
+/* local_expand - set up macro expansion attributes */
-int local_expand(VSTRING *result, const char *pattern,
- LOCAL_STATE state, USER_ATTR usr_attr, const char *filter)
+HTABLE *local_expand(LOCAL_STATE state, USER_ATTR usr_attr)
{
- char *domain;
+ HTABLE *expand_attr;
/*
* Impedance matching between the local delivery agent data structures
* and the mac_expand() interface. The CPU cycles wasted will be
* negligible.
*/
- if ((domain = strrchr(state.msg_attr.recipient, '@')) != 0)
- domain++;
-
- return (mac_expand(result, pattern, MAC_EXP_FLAG_NONE,
- MAC_EXP_ARG_FILTER, filter,
- MAC_EXP_ARG_ATTR, "user", usr_attr.logname,
- MAC_EXP_ARG_ATTR, "home", usr_attr.home,
- MAC_EXP_ARG_ATTR, "shell", usr_attr.shell,
- MAC_EXP_ARG_ATTR, "domain", domain,
- MAC_EXP_ARG_ATTR, "recipient", state.msg_attr.recipient,
- MAC_EXP_ARG_ATTR, "extension", state.msg_attr.extension,
- MAC_EXP_ARG_ATTR, "recipient_delimiter",
- *var_rcpt_delim ? var_rcpt_delim : 0,
- 0));
+ expand_attr = htable_create(0);
+ htable_enter(expand_attr, "user", usr_attr.logname);
+ htable_enter(expand_attr, "home", usr_attr.home);
+ htable_enter(expand_attr, "shell", usr_attr.shell);
+ htable_enter(expand_attr, "domain", state.msg_attr.domain);
+ htable_enter(expand_attr, "mailbox", state.msg_attr.local);
+ htable_enter(expand_attr, "recipient", state.msg_attr.recipient);
+ htable_enter(expand_attr, "extension", state.msg_attr.extension);
+ htable_enter(expand_attr, "recipient_delimiter", var_rcpt_delim);
+ return (expand_attr);
}
mailbox = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0);
} else {
spool_dir = var_mail_spool_dir;
- mailbox = concatenate(spool_dir, "/", state.msg_attr.local, (char *) 0);
+ mailbox = concatenate(spool_dir, "/", state.msg_attr.user, (char *) 0);
}
/*
*
* Don't come here more than once, whether or not the recipient exists.
*/
- if (been_here(state.dup_filter, "mailbox %s", state.msg_attr.local))
+ if (been_here(state.dup_filter, "mailbox %s", state.msg_attr.user))
return (YES);
/*
/*
* Skip delivery when this recipient does not exist.
*/
- if ((mbox_pwd = mypwnam(state.msg_attr.local)) == 0)
+ if ((mbox_pwd = mypwnam(state.msg_attr.user)) == 0)
return (NO);
/*
* \user is special: it means don't do any alias or forward expansion.
*/
if (state.msg_attr.recipient[0] == '\\') {
- state.msg_attr.recipient++, state.msg_attr.local++;
- if (*var_rcpt_delim)
- state.msg_attr.extension =
- split_addr(state.msg_attr.local, *var_rcpt_delim);
+ state.msg_attr.recipient++, state.msg_attr.local++, state.msg_attr.user++;
if (deliver_mailbox(state, usr_attr, &status) == 0)
status = deliver_unknown(state, usr_attr);
return (status);
}
/*
- * Otherwise, alias expansion has highest precedence.
+ * Otherwise, alias expansion has highest precedence. First look up the
+ * full localpart, then the bare user.
*/
- if (deliver_alias(state, usr_attr, &status))
- return (status);
-
- /*
- * Don't apply the recipient delimiter to reserved addresses. After
- * stripping the recipient extension, try aliases again.
- */
- if (*var_rcpt_delim)
- state.msg_attr.extension =
- split_addr(state.msg_attr.local, *var_rcpt_delim);
- if (state.msg_attr.extension && strchr(state.msg_attr.extension, '/')) {
- msg_warn("%s: address with illegal extension: %s",
- state.msg_attr.queue_id, state.msg_attr.recipient);
- state.msg_attr.extension = 0;
- }
- if (state.msg_attr.extension && deliver_alias(state, usr_attr, &status))
+ state.msg_attr.unmatched = 0;
+ if (deliver_alias(state, usr_attr, state.msg_attr.local, &status))
return (status);
+ state.msg_attr.unmatched = state.msg_attr.extension;
+ if (state.msg_attr.extension != 0)
+ if (deliver_alias(state, usr_attr, state.msg_attr.user, &status))
+ return (status);
/*
* Special case for mail locally forwarded or aliased to a different
if (state.msg_attr.delivered == 0)
state.msg_attr.delivered = state.msg_attr.recipient;
state.msg_attr.local = mystrdup(state.msg_attr.recipient);
- if (split_at_right(state.msg_attr.local, '@') == 0)
- msg_warn("no @ in recipient address: %s", state.msg_attr.local);
lowercase(state.msg_attr.local);
+ if ((state.msg_attr.domain = split_at_right(state.msg_attr.local, '@')) == 0)
+ msg_warn("no @ in recipient address: %s", state.msg_attr.local);
state.msg_attr.features = feature_control(state.msg_attr.local);
- state.msg_attr.extension = 0;
+
+ /*
+ * Address extension management.
+ */
+ state.msg_attr.user = mystrdup(state.msg_attr.local);
+ if (*var_rcpt_delim) {
+ state.msg_attr.extension =
+ split_addr(state.msg_attr.local, *var_rcpt_delim);
+ if (strchr(state.msg_attr.extension, '/')) {
+ msg_warn("%s: address with illegal extension: %s",
+ state.msg_attr.queue_id, state.msg_attr.local);
+ state.msg_attr.extension = 0;
+ }
+ } else
+ state.msg_attr.extension = 0;
+ state.msg_attr.unmatched = state.msg_attr.extension;
/*
* Run the recipient through the delivery switch.
tok822_resolve(addr, &reply);
state.msg_attr.recipient = STR(reply.recipient);
-#if 0
/*
* Splice in the optional unmatched address extension.
*/
- if (state.msg_attr.extension) {
+ if (state.msg_attr.unmatched) {
if ((ratsign = strrchr(STR(reply.recipient), '@')) == 0) {
VSTRING_ADDCH(reply.recipient, *var_rcpt_delim);
- vstring_strcat(reply.recipient, state.msg_attr.extension);
+ vstring_strcat(reply.recipient, state.msg_attr.unmatched);
} else {
- ext_len = strlen(state.msg_attr.extension);
+ ext_len = strlen(state.msg_attr.unmatched);
VSTRING_SPACE(reply.recipient, ext_len + 2);
memmove(ratsign + ext_len + 1, ratsign, strlen(ratsign) + 1);
*ratsign = *var_rcpt_delim;
- memcpy(ratsign + 1, state.msg_attr.extension, ext_len);
+ memcpy(ratsign + 1, state.msg_attr.unmatched, ext_len);
VSTRING_SKIP(reply.recipient);
}
}
-#endif
/*
* Delivery to a local or non-local address. For a while there was some
fi
;;
HP-UX.B.10.*) SYSTYPE=HPUX10
+ CCARGS="$CCARGS `nm /usr/lib/libc.a 2>/dev/null |
+ (grep usleep >/dev/null || echo '-Dusleep=doze')`"
if [ -f /usr/lib/libdb.a ]; then
CCARGS="$CCARGS -DHAS_DB"
SYSLIBS=-ldb
\fB$user\fR (recipient username), \fB$home\fR (recipient home
directory), \fB$shell\fR (recipient shell), \fB$recipient\fR
(complete recipient address), \fB$extension\fR (recipient address
-extension), \fB$domain\fR (recipient domain) and
+extension), \fB$domain\fR (recipient domain), \fBmailbox\fR
+(entire recipient address localpart) and
\fB$recipient_delimiter.\fR The forms \fI${name?value}\fR and
\fI${name:value}\fR expand conditionally to \fIvalue\fR when
\fI$name\fR is (is not) defined.
username), \fB$home\fR (recipient home directory), \fB$shell\fR
(recipient shell), \fB$recipient\fR (complete recipient address),
\fB$extension\fR (recipient address extension), \fB$domain\fR
-(recipient domain) and \fB$recipient_delimiter.\fR The forms
+(recipient domain), \fBmailbox\fR (entire recipient address
+localpart) and \fB$recipient_delimiter.\fR The forms
\fI${name?value}\fR and \fI${name:value}\fR expand conditionally to
\fIvalue\fR when \fI$name\fR is (is not) defined. In the result
of \fIname\fR expansion, characters that have special meaning to
.IP \fBalias_maps\fR
List of alias databases.
.IP \fBforward_path\fR
-Search list for .forward files. The following macros are recognized:
-\fB$home\fR (home directory), \fB$user\fR (login name),
-\fB$extension\fR (address extension), \fB$recipient_delimiter\fR
-(address extension delimiter).
+Search list for .forward files. The names are subject to \fI$name\fR
+expansion.
.IP \fBlocal_command_shell\fR
Shell to use for external command execution (for example,
/some/where/smrsh -c).
Specify a path ending in \fB/\fR for maildir-style delivery.
.IP \fBluser_relay\fR
Destination (\fI@domain\fR or \fIaddress\fR) for non-existent users.
-The \fIaddress\fR can be any destination that is valid in an alias
-file.
+The \fIaddress\fR is subjected to \fI$name\fR expansion.
.IP \fBmail_spool_directory\fR
Directory with UNIX-style mailboxes. The default pathname is system
dependent.
$(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
../conf/main.cf.default: $(PROG) Makefile
+ rm -f $@
./$(PROG) -d |egrep -v '^(myhostname|mydomain|mynetworks) ' >$@
Makefile: Makefile.in
va_start(ap, format);
vstring_vsprintf(error_text, format, ap);
va_end(ap);
- printable(STR(error_text), ' ');
/*
* Validate the response, that is, the response must begin with a
vstring_strcpy(error_text, "450 Service unavailable");
}
+ /*
+ * Give everyone involved a clue.
+ */
+ if (state->sender) {
+ vstring_sprintf_append(error_text, " (from=<%s>", state->sender);
+ if (state->recipient)
+ vstring_sprintf_append(error_text, " to=<%s>", state->recipient);
+ VSTRING_ADDCH(error_text, ')');
+ }
+ printable(STR(error_text), ' ');
+
/*
* Log what is happening. When the sysadmin discards policy violation
* postmaster notices, this may be the only trace left that service was
* rejected. Print the request, client name/address, and response.
*/
- msg_info("reject: %s from %s[%s]: %s", state->where, state->name,
- state->addr, STR(error_text));
+ msg_info("%s: reject: %s from %s[%s]: %s", state->queue_id, state->where,
+ state->name, state->addr, STR(error_text));
return (SMTPD_CHECK_REJECT);
}
char **cpp;
char *name;
int status;
+ char *saved_recipient = state->recipient;
/*
* Initialize.
return (0);
/*
- * Apply restrictions in the order as specified.
+ * Apply restrictions in the order as specified. Minor kluge so that we
+ * can delegate work to the generic routine.
*/
+ state->recipient = mystrdup(recipient);
for (cpp = rcpt_restrctions->argv; (name = *cpp) != 0; cpp++) {
if (strchr(name, ':') != 0) {
status = check_mail_access(state, name, recipient);
if (status != 0)
break;
}
+ myfree(state->recipient);
+ state->recipient = saved_recipient;
return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
}
--- /dev/null
+/* mac_expand_update_va - update engine */
+
+static MAC_EXP *mac_expand_update_va(MAC_EXP *mc, int key, va_list ap)
+{
+ HTABLE_INFO **ht_info;
+ HTABLE_INFO **ht;
+ HTABLE *table;
+ char *name;
+ char *value;
+
+#define HTABLE_CLOBBER(t, n, v) do { \
+ HTABLE_INFO *_ht; \
+ if ((_ht = htable_locate(t, n)) != 0) \
+ _ht->value = v; \
+ else \
+ htable_enter(t, n, v); \
+ } while(0);
+
+ /*
+ * Optionally create expansion context.
+ */
+ if (mc == 0) {
+ mc = (MAC_EXP *) mymalloc(sizeof(*mc));
+ mc->table = htable_create(0);
+ mc->result = 0;
+ mc->flags = 0;
+ mc->filter = 0;
+ mc->clobber = '_';
+ mc->level = 0;
+ }
+
+ /*
+ * Stash away the attributes.
+ */
+ for ( /* void */ ; key != 0; key = va_arg(ap, int)) {
+ switch (key) {
+ case MAC_EXP_ARG_ATTR:
+ name = va_arg(ap, char *);
+ value = va_arg(ap, char *);
+ HTABLE_CLOBBER(mc->table, name, value);
+ break;
+ case MAC_EXP_ARG_TABLE:
+ table = va_arg(ap, HTABLE *);
+ ht_info = htable_list(table);
+ for (ht = ht_info; *ht; ht++)
+ HTABLE_CLOBBER(mc->table, ht[0]->key, ht[0]->value);
+ myfree((char *) ht_info);
+ break;
+ case MAC_EXP_ARG_FILTER:
+ mc->filter = va_arg(ap, char *);
+ break;
+ case MAC_EXP_ARG_CLOBBER:
+ mc->clobber = va_arg(ap, int);
+ break;
+ }
+ }
+ return (mc);
+}
+
+/* mac_expand_update - update or create macro expansion context */
+
+MAC_EXP *mac_expand_update(MAC_EXP *mc, int key,...)
+{
+ va_list ap;
+
+ va_start(ap, key);
+ mc = mac_expand_update(mc, key, ap);
+ va_end(ap);
+ return (mc);
+}
+
+/* .IP key
+/* The attribute information is specified as a null-terminated list.
+/* Attributes are defined left to right; only the last definition
+/* of an attribute is remembered.
+/* The following keys are understood (types of arguments indicated
+/* in parentheses):
+/* .RS
+/* .IP "MAC_EXP_ARG_ATTR (char *, char *)"
+/* The next two arguments specify an attribute name and its attribute
+/* string value. Specify a null string value for an attribute that is
+/* known but unset. Attribute string values are not copied.
+/* .IP "MAC_EXP_ARG_TABLE (HTABLE *)"
+/* The next argument is a hash table with attribute names and values.
+/* Specify a null string value for an attribute that is known but unset.
+/* Attribute string values are not copied.
+/* .RE
+/* .IP MAC_EXP_ARG_END
+/* A manifest constant that indicates the end of the argument list.
/*
/* htable_delete() removes one entry that was stored under the given key.
/* If the free_fn argument is not a null pointer, the corresponding
-/* function is called with as argument the value that was stored under
+/* function is called with as argument the non-zero value stored under
/* the key.
/*
/* htable_free() destroys a hash table, including contents. If the free_fn
/* argument is not a null pointer, the corresponding function is called
-/* for each table entry, with as argument the value that was stored
+/* for each table entry, with as argument the non-zero value stored
/* with the entry.
/*
/* htable_walk() invokes the action function for each table entry, with
*h = ht->next;
table->used--;
myfree(ht->key);
- if (free_fn)
+ if (free_fn && ht->value)
(*free_fn) (ht->value);
myfree((char *) ht);
return;
for (ht = *h++; ht; ht = next) {
next = ht->next;
myfree(ht->key);
- if (free_fn)
+ if (free_fn && ht->value)
(*free_fn) (ht->value);
myfree((char *) ht);
}
/* SYNOPSIS
/* #include <mac_expand.h>
/*
-/* MAC_EXP *mac_exp_update(handle, key, ...)
-/* MAC_EXP *handle;
-/* int key;
-/*
-/* int mac_expand_use(handle, result, pattern, flags)
-/* MAC_EXP *handle;
-/* VSTRING *result;
-/* const char *pattern;
-/* int flags;
-/*
-/* void mac_expand_free(handle)
-/* MAC_EXP *handle;
-/*
/* int mac_expand(result, pattern, flags, key, ...)
/* VSTRING *result;
/* const char *pattern;
/* int flags;
/* int key;
/* DESCRIPTION
-/* This module maintains a private attribute-value list and implements
-/* the following expansions:
+/* This module implements parameter-less macro expansions, both
+/* conditional and unconditional, and both recursive and non-recursive.
+/* The algorithm can search multiple user-specified symbol tables.
+/* In the text below, an attribute is "defined" when its value is a
+/* string of non-zero length. In all other cases the attribute is
+/* considered "undefined".
+/*
+/* The following expansions are implemented:
/* .IP "$name, ${name}, $(name)"
-/* The result is the value of the named attribute. Optionally, the
-/* result is subjected to further expansions.
+/* Unconditional expansion. If the named attribute is defined, the
+/* expansion is the value of the named attribute, optionally subjected
+/* to further $name expansions. Otherwise, the expansion is empty.
/* .IP "${name?text}, $(name?text)"
-/* If the named attribute is defined, the result is the given text,
-/* after another iteration of $name expansion. Otherwise, the result is
-/* empty.
+/* Conditional expansion. If the named attribute is defined, the
+/* expansion is the given text, subjected to another iteration of
+/* $name expansion. Otherwise, the expansion is empty.
/* .IP "${name:text}, $(name:text)"
-/* If the named attribute is undefined, the result is the given text,
-/* after another iteration of $name expansion. Otherwise, the result is
-/* empty.
+/* Conditional expansion. If the named attribute is undefined, the
+/* the expansion is the given text, after another iteration of $name
+/* expansion. Otherwise, the expansion is empty.
/* .PP
-/* max_expand_update() updates an existing macro expansion context
-/* or instantiates a new one when given a null handle. The result
-/* is a handle that can be used by other mac_expand_xxx() routines.
-/*
-/* mac_expand_use() uses a macro expansion context to replace $name etc.
-/* instances in \fBpattern\fR and stores the result into \fBresult\fR.
-/*
-/* mac_expand_free() destroys a macro expansion context.
-/*
-/* mac_expand() is a convenience routine that combines all of the
-/* above in one function call.
+/* mac_expand() replaces $name etc. instances in \fBpattern\fR
+/* and stores the result into \fBresult\fR.
/*
/* Arguments:
-/* .IP mc
-/* Macro expansion context created or update with mac_expand_update().
/* .IP result
/* Storage for the result of expansion. The result is truncated
/* upon entry.
/* The constant MAC_EXP_FLAG_NONE specifies a manifest null value.
/* .IP key
/* The attribute information is specified as a null-terminated list.
-/* Attributes are defined left to right; only the last definition
-/* of an attribute is remembered.
+/* Attributes may appear multiple times; the right-most definition
+/* of an attribute determines the result of attribute lookup.
+/* .sp
/* The following keys are understood (types of arguments indicated
/* in parentheses):
/* .RS
/* .IP "MAC_EXP_ARG_ATTR (char *, char *)"
/* The next two arguments specify an attribute name and its attribute
-/* string value. Specify a null string value for an attribute that is
-/* known but unset. Attribute string values are not copied.
+/* string value. Specify a null pointer or empty string for an
+/* attribute value that is unset. Attribute keys and string values
+/* are copied.
/* .IP "MAC_EXP_ARG_TABLE (HTABLE *)"
/* The next argument is a hash table with attribute names and values.
-/* Specify a null string value for an attribute that is known but unset.
-/* Attribute string values are not copied.
-/* .IP "MAC_EXP_ARG_FILTER (char *)"
-/* The next argument specifies a null-terminated list of characters
-/* that are allowed to appear in $name expansions. By default, illegal
-/* characters are replaced by underscore. Only the last specified
-/* filter takes effect. Specify a null pointer to disable filtering.
-/* .IP "MAC_EXP_ARG_CLOBBER (int)"
-/* Character value to be used when the result of expansion is not
-/* allowed according to the MAC_EXP_ARG_FILTER argument. Only the
-/* last specified replacement value takes effect.
+/* Specify a null pointer or empty string for an attribute value that
+/* is unset. Hash tables are not copied.
+/* .IP "MAC_EXP_ARG_RECORD (HTABLE *)"
+/* Record in the specified table how many times an attribute was
+/* referenced.
/* .RE
/* .IP MAC_EXP_ARG_END
/* A manifest constant that indicates the end of the argument list.
/* macro nesting.
/*
/* The result value is the binary OR of zero or more of the following:
+/* .IP MAC_EXP_FLAG_ERROR
+/* A syntax error was foud in the \fBpattern\fR, or some macro had
+/* an unreasonable nesting depth.
/* .IP MAC_EXP_FLAG_UNDEF
-/* The pattern contains a reference to an unknown parameter or to
-/* a parameter whose value is not defined.
-/* A zero-length string was used as replacement.
+/* The pattern contains a reference to an undefined attribute.
/* SEE ALSO
/* mac_parse(3) locate macro references in string.
/* LICENSE
/* System library. */
#include <sys_defs.h>
-#include <setjmp.h>
#include <ctype.h>
#include <string.h>
#include <mac_expand.h>
/*
- * Little helper structure.
+ * Little helper structure. Name-value pairs given as explicit arguments are
+ * stored into private hash tables. Hash tables provided by the caller are
+ * simply referenced. For now we do only hash tables. The structure can be
+ * generalized when needed.
*/
+struct table_info {
+ int status; /* who owns this table */
+ HTABLE *table; /* table reference */
+};
+
+#define MAC_EXP_STAT_UNUSED 0 /* this slot is unused */
+#define MAC_EXP_STAT_PRIVATE 1 /* we own this table */
+#define MAC_EXP_STAT_EXTERN 2 /* caller owns this table */
+
+#define MAC_EXP_MIN_LEN 2 /* min number of table slots */
+
struct MAC_EXP {
- HTABLE *table; /* private symbol table */
VSTRING *result; /* result buffer */
const char *filter; /* safe character list */
int clobber; /* safe replacement */
int flags; /* findings, features */
int level; /* nesting level */
- jmp_buf jbuf; /* escape */
+ HTABLE *record; /* record of substitutions */
+ int len; /* table list length */
+ int last; /* last element used */
+ struct table_info table_info[MAC_EXP_MIN_LEN];
};
/* mac_expand_callback - callback for mac_parse */
char *text;
char *cp;
int ch;
+ int n;
+ /*
+ * Sanity check.
+ */
if (mc->level++ > 100) {
msg_warn("unreasonable macro call nesting: \"%s\"", vstring_str(buf));
- longjmp(mc->jbuf, 1);
+ mc->flags |= MAC_EXP_FLAG_ERROR;
+ return;
}
/*
}
if (!ISALNUM(ch) && ch != '_') {
msg_warn("macro name syntax error: \"%s\"", vstring_str(buf));
- longjmp(mc->jbuf, 1);
+ mc->flags |= MAC_EXP_FLAG_ERROR;
+ return;
}
}
/*
* Look up the named parameter.
*/
- text = (ht = htable_locate(mc->table, vstring_str(buf))) == 0 ?
- 0 : ht->value;
+ for (text = 0, n = mc->last; n >= 0; n--) {
+ ht = htable_locate(mc->table_info[n].table, vstring_str(buf));
+ if (ht != 0) {
+ text = ht->value;
+ break;
+ }
+ }
/*
* Perform the requested substitution.
if (mc->filter) {
vstring_strcpy(buf, text);
text = vstring_str(buf);
- for (cp = text; (cp += strspn(cp, mc->filter))[0];)
+ for (cp = text; (cp += strspn(cp, mc->filter))[0]; /* void */ )
*cp++ = mc->clobber;
}
if (mc->flags & MAC_EXP_FLAG_RECURSE)
vstring_strcat(mc->result, text);
break;
}
+
+ /*
+ * Record keeping...
+ */
+ if (mc->record) {
+ if ((ht = htable_locate(mc->record, vstring_str(buf))) == 0)
+ ht = htable_enter(mc->record, vstring_str(buf), (char *) 0);
+ ht->value++;
+ }
}
/*
mc->level--;
}
-/* mac_expand_update_va - update engine */
+/* mac_expand_addtable - add table to expansion context */
-static MAC_EXP *mac_expand_update_va(MAC_EXP *mc, int key, va_list ap)
+static MAC_EXP *mac_expand_addtable(MAC_EXP *mc, HTABLE *table, int status)
{
- HTABLE_INFO **ht_info;
- HTABLE_INFO **ht;
- HTABLE *table;
+ mc->last += 1;
+ if (mc->last >= mc->len) {
+ mc->len *= 2;
+ mc = (MAC_EXP *) myrealloc((char *) mc, sizeof(*mc) + sizeof(mc->table_info[0]) * (mc->len - MAC_EXP_MIN_LEN));
+ }
+ mc->table_info[mc->last].table = table;
+ mc->table_info[mc->last].status = status;
+ return (mc);
+}
+
+/* mac_expand - expand $name instances */
+
+int mac_expand(VSTRING *result, const char *pattern, int flags, int key,...)
+{
+ MAC_EXP *mc;
+ va_list ap;
char *name;
char *value;
-
-#define HTABLE_CLOBBER(t, n, v) do { \
- HTABLE_INFO *_ht; \
- if ((_ht = htable_locate(t, n)) != 0) \
- _ht->value = v; \
- else \
- htable_enter(t, n, v); \
- } while(0);
+ HTABLE *table;
+ HTABLE_INFO *ht;
+ int status;
/*
- * Optionally create expansion context.
+ * Initialize.
*/
- if (mc == 0) {
- mc = (MAC_EXP *) mymalloc(sizeof(*mc));
- mc->table = htable_create(0);
- mc->result = 0;
- mc->flags = 0;
- mc->filter = 0;
- mc->clobber = '_';
- mc->level = 0;
- }
+ mc = (MAC_EXP *) mymalloc(sizeof(*mc));
+ mc->result = result;
+ mc->flags = (flags & MAC_EXP_FLAG_INMASK);
+ mc->filter = 0;
+ mc->clobber = '_';
+ mc->record = 0;
+ mc->level = 0;
+ mc->len = MAC_EXP_MIN_LEN;
+ mc->last = -1;
/*
* Stash away the attributes.
*/
- for ( /* void */ ; key != 0; key = va_arg(ap, int)) {
+ for (va_start(ap, key); key != 0; key = va_arg(ap, int)) {
switch (key) {
case MAC_EXP_ARG_ATTR:
name = va_arg(ap, char *);
value = va_arg(ap, char *);
- HTABLE_CLOBBER(mc->table, name, value);
+ if (mc->last < 0
+ || mc->table_info[mc->last].status != MAC_EXP_STAT_PRIVATE) {
+ table = htable_create(0);
+ mc = mac_expand_addtable(mc, table, MAC_EXP_STAT_PRIVATE);
+ } else
+ table = mc->table_info[mc->last].table;
+ if ((ht = htable_locate(table, name)) == 0)
+ ht = htable_enter(table, name, (char *) 0);
+ if (ht->value != 0)
+ myfree(ht->value);
+ ht->value = (value ? mystrdup(value) : 0);
break;
case MAC_EXP_ARG_TABLE:
table = va_arg(ap, HTABLE *);
- ht_info = htable_list(table);
- for (ht = ht_info; *ht; ht++)
- HTABLE_CLOBBER(mc->table, ht[0]->key, ht[0]->value);
- myfree((char *) ht_info);
+ mc = mac_expand_addtable(mc, table, MAC_EXP_STAT_EXTERN);
break;
case MAC_EXP_ARG_FILTER:
mc->filter = va_arg(ap, char *);
case MAC_EXP_ARG_CLOBBER:
mc->clobber = va_arg(ap, int);
break;
+ case MAC_EXP_ARG_RECORD:
+ mc->record = va_arg(ap, HTABLE *);
+ break;
}
}
- return (mc);
-}
-
-/* mac_expand_update - update or create macro expansion context */
-
-MAC_EXP *mac_expand_update(MAC_EXP *mc, int key,...)
-{
- va_list ap;
-
- va_start(ap, key);
- mc = mac_expand_update(mc, key, ap);
- va_end(ap);
- return (mc);
-}
-
-/* mac_expand_use - string expansion */
-
-int mac_expand_use(MAC_EXP *mc, VSTRING *result, const char *pattern, int flags)
-{
- VSTRING_RESET(result);
- mc->result = result;
- mc->level = 0;
- mc->flags = flags;
- if (setjmp(mc->jbuf) == 0)
- mac_parse(pattern, mac_expand_callback, (char *) mc);
- VSTRING_TERMINATE(result);
- return (mc->flags & MAC_EXP_FLAG_UNDEF);
-}
-
-/* mac_expand_free - destroy macro expansion context */
-
-void mac_expand_free(MAC_EXP *mc)
-{
- htable_free(mc->table, (void (*) (char *)) 0);
- myfree((char *) mc);
-}
-
-/* mac_expand - expand $name instances */
-
-int mac_expand(VSTRING *result, const char *pattern, int flags, int key,...)
-{
- MAC_EXP *mc = 0;
- va_list ap;
- int status;
-
- /*
- * Stash away the attributes.
- */
- va_start(ap, key);
- mc = mac_expand_update_va(mc, key, ap);
va_end(ap);
/*
* Do the substitutions.
*/
- status = mac_expand_use(mc, result, pattern, flags);
+ VSTRING_RESET(result);
+ mac_parse(pattern, mac_expand_callback, (char *) mc);
+ VSTRING_TERMINATE(result);
+ status = (mc->flags & MAC_EXP_FLAG_OUTMASK);
/*
* Clean up.
*/
- mac_expand_free(mc);
+ while (mc->last >= 0) {
+ if (mc->table_info[mc->last].status == MAC_EXP_STAT_PRIVATE)
+ htable_free(mc->table_info[mc->last].table, myfree);
+ mc->last--;
+ }
+ myfree((char *) mc);
+
return (status);
}
#include <vstream.h>
#include <vstring_vstream.h>
-static void nfree(char *ptr)
-{
- if (ptr)
- myfree(ptr);
-}
-
int main(int unused_argc, char **unused_argv)
{
VSTRING *buf = vstring_alloc(100);
vstream_printf("stat=%d result=%s\n", stat, vstring_str(result));
vstream_fflush(VSTREAM_OUT);
}
- htable_free(table, nfree);
+ htable_free(table, myfree);
vstream_printf("\n");
}
#define MAC_EXP_FLAG_NONE (0)
#define MAC_EXP_FLAG_UNDEF (1<<0)
#define MAC_EXP_FLAG_RECURSE (1<<1)
+#define MAC_EXP_FLAG_ERROR (1<<2)
+
+#define MAC_EXP_FLAG_INMASK MAC_EXP_FLAG_RECURSE
+#define MAC_EXP_FLAG_OUTMASK (MAC_EXP_FLAG_UNDEF | MAC_EXP_FLAG_ERROR)
#define MAC_EXP_ARG_END 0
#define MAC_EXP_ARG_ATTR 1
#define MAC_EXP_ARG_TABLE 2
#define MAC_EXP_ARG_FILTER 3
#define MAC_EXP_ARG_CLOBBER 4
+#define MAC_EXP_ARG_RECORD 5
extern MAC_EXP *mac_expand_update(MAC_EXP *, int,...);
extern int mac_expand_use(MAC_EXP *, VSTRING *, const char *, int);
stat=1 result=name 2 undefined, |name1-value||
stat=1 result=|name1-value||
stat=0 result=name1-value
-stat=0 result=
+stat=4 result=
stat=0 result=
stat=1 result=name 1 undefined, ||name2-value|