due to expensive crypto operations. Files: global/anvil_clnt.c,
anvil/anvil.c, smtpd/smtpd.c.
- Cleanup: eliminated massive code duplicatiom in the anvil
+ Cleanup: eliminated massive code duplication in the anvil
server that resulted from adding similar features one at a
time. File: anvil/anvil.c.
many MAIL FROM or RCPT TO commands) when we aren't closing
the connection. File: smtpd/smtpd.c.
+20051012
+
+ Polishing: content of comments and sequence of code blocks
+ in the anvil server, TLS request rate error message in the
+ smtp server, and documentation, but no changes in code.
+ Files: anvil/anvil.c, smtpd/smtpd.c.
+
+20051013
+
+ Horror: some systems have basename() and dirname() and some
+ don't; some implementations modify their input and some
+ don't; and some implementations use a private buffer that
+ is overwritten upon the next call. Postfix will use its own
+ safer versions called sane_basename() and sane_dirname().
+ These never modify the input, and allow the caller to control
+ how memory is allocated for the result. File:
+ util/sane_basename.c.
+
+ Feature: "sendmail -C path-to-main.cf" and "sendmail -C
+ config_directory" now do what one would expect. File:
+ sendmail/sendmail.c.
+
+ Bugfix: don't do smtpd_end_of_data_restrictions after the
+ transaction failed due to, e.g., a write error. File:
+ smtpd/smtpd.c.
+
+ Cleanup: the SMTP server now enforces the message_size_limit
+ even when the client did not send SIZE information with the
+ MAIL FROM command. This protects before-queue content
+ filters against over-size messages. File: smtpd/smtpd.c.
+
Open problems:
Try to recognize that Resent- headers appear in blocks,
Hard limits on cache sizes (anvil, specifically).
- Look for systems with XPG basename() declared in <libgen.h>,
- and prepare for phasing out the Postfix-supplied one.
- Beware, however, that XPG basename() takes (char *), and
- not (const char *) because it may change its argument.
-
Laptop friendliness: make the qmgr remember when the next
deferred queue scan needs to be done, and have the pickup
server stat() the maildrop directory before searching it.
Or do we punt the issue and issue X-Postfix for all errors
except SMTP?
- Implement smtp_greet() routine to distinguish between reject
- before versus after sending HELO/EHLO; this is needed to
- eliminate the hack that uses one character lookahead to
- find out if the server wants to talk to us.
-
Low: replace_sender/replace_recipient actions in access
maps?
reduce the number of queue files and cleanup process instances. To
get the earlier behavior, specify "frozen_delivered_to = no".
-The frozen_delivered_to feature also fixes a long-standing problem
-with multiple deliveries to recipients that are listed in multiple
-nested aliases, but does so only when only the top-level alias has
-an owner- alias, and none of the subordinate aliases.
+The frozen_delivered_to feature can help to alleviate a long-standing
+problem with multiple deliveries to recipients that are listed
+multiple times in a hierarchy of nested aliases. For this to work,
+only the top-level alias should have an owner- alias, and none of
+the subordinate aliases.
Major changes with snapshot 20051011
====================================
This feature is available in Postfix version 2.1
and later.
- <b>-C</b> <i>config</i><b>_</b><i>file</i> (ignored)
- The path name of the <b>sendmail.cf</b> file. Postfix con-
- figuration files are kept in the <b>/etc/postfix</b>
- directory.
+ <b>-C</b> <i>config</i><b>_</b><i>file</i>
+
+ <b>-C</b> <i>config</i><b>_</b><i>dir</i>
+ The path name of the Postfix <b>main.cf</b> file, or of
+ its parent directory. This information is ignored
+ with Postfix versions before 2.3.
+
+ With older Postfix versions, specify a directory
+ pathname with the MAIL_CONFIG environment variable
+ to override the location of configuration files.
<b>-F</b> <i>full</i><b>_</b><i>name</i>
Set the sender full name. This is used only with
for testing address rewriting and routing configurations.
.sp
This feature is available in Postfix version 2.1 and later.
-.IP "\fB-C \fIconfig_file\fR (ignored)"
-The path name of the \fBsendmail.cf\fR file. Postfix configuration
-files are kept in the \fB/etc/postfix\fR directory.
+.IP "\fB-C \fIconfig_file\fR"
+.IP "\fB-C \fIconfig_dir\fR"
+The path name of the Postfix \fBmain.cf\fR file, or of its
+parent directory. This information is ignored with Postfix
+versions before 2.3.
+
+With older Postfix versions, specify a directory pathname
+with the MAIL_CONFIG environment variable to override the
+location of configuration files.
.IP "\fB-F \fIfull_name\fR
Set the sender full name. This is used only with messages that
have no \fBFrom:\fR message header.
/* Application-specific. */
+ /*
+ * Configuration parameters.
+ */
int var_anvil_time_unit;
int var_anvil_stat_time;
/*
- * State.
+ * Global dynamic state.
*/
static HTABLE *anvil_remote_map; /* indexed by service+ remote client */
- /*
- * Absent a real-time query interface, these are logged at process exit time
- * and at regular intervals.
- */
-typedef struct {
- int value; /* peak value */
- char *ident; /* lookup key */
- time_t when; /* time of peak value */
-} ANVIL_MAX;
-
-static ANVIL_MAX max_conn_count; /* peak connection count */
-static ANVIL_MAX max_conn_rate; /* peak connection rate */
-static ANVIL_MAX max_mail_rate; /* peak message rate */
-static ANVIL_MAX max_rcpt_rate; /* peak recipient rate */
-static ANVIL_MAX max_ntls_rate; /* peak new TLS session rate */
-
-static int max_cache_size; /* peak cache size */
-static time_t max_cache_time; /* time of peak size */
-
-/* Update/report peak usage. */
-
-#define ANVIL_MAX_UPDATE(_max, _value, _ident) \
- do { \
- _max.value = _value; \
- if (_max.ident == 0) { \
- _max.ident = mystrdup(_ident); \
- } else if (!STREQ(_max.ident, _ident)) { \
- myfree(_max.ident); \
- _max.ident = mystrdup(_ident); \
- } \
- _max.when = event_time(); \
- } while (0)
-
-#define ANVIL_MAX_RATE_REPORT(_max, _name) \
- do { \
- if (_max.value > 0) { \
- msg_info("statistics: max " _name " rate %d/%ds for (%s) at %.15s", \
- _max.value, var_anvil_time_unit, \
- _max.ident, ctime(&_max.when) + 4); \
- _max.value = 0; \
- } \
- } while (0);
-
-#define ANVIL_MAX_COUNT_REPORT(_max, _name) \
- do { \
- if (_max.value > 0) { \
- msg_info("statistics: max " _name " count %d for (%s) at %.15s", \
- _max.value, _max.ident, ctime(&_max.when) + 4); \
- _max.value = 0; \
- } \
- } while (0);
-
/*
* Remote connection state, one instance for each (service, client) pair.
*/
} ANVIL_REMOTE;
/*
- * Local server state, one per server instance. This allows us to clean up
- * connection state when a local server goes away without cleaning up.
+ * Local server state, one instance per anvil client connection. This allows
+ * us to clean up remote connection state when a local server goes away
+ * without cleaning up.
*/
typedef struct {
ANVIL_REMOTE *anvil_remote; /* XXX should be list */
myfree((char *) (remote)); \
} while(0)
-/* Reset event rate counters and start of data collection interval. */
+/* Reset or update rate information for existing (service, client) state. */
#define ANVIL_REMOTE_RSET_RATE(remote, _start) \
do { \
(remote)->start = _start; \
} while(0)
-/* Add connection to (service, client) state. */
-
#define ANVIL_REMOTE_INCR_RATE(remote, _what) \
do { \
time_t _now = event_time(); \
(remote)->_what += 1; \
} while(0)
+/* Update existing (service, client) state. */
+
#define ANVIL_REMOTE_NEXT_CONN(remote) \
do { \
ANVIL_REMOTE_INCR_RATE((remote), rate); \
(local)->anvil_remote = 0; \
} while(0)
-/* Add connection to local server. */
+/* Add remote connection to local server. */
#define ANVIL_LOCAL_ADD_ONE(local, remote) \
do { \
(local)->anvil_remote = (remote); \
} while(0)
-/* Test if this remote site is listed for this local client. */
+/* Test if this remote connection is listed for this local server. */
#define ANVIL_LOCAL_REMOTE_LINKED(local, remote) \
((local)->anvil_remote == (remote))
-/* Drop connection from local server. */
+/* Drop specific remote connection from local server. */
#define ANVIL_LOCAL_DROP_ONE(local, remote) \
do { \
(local)->anvil_remote = 0; \
} while(0)
-/* Drop all connections from local server. */
+/* Drop all remote connections from local server. */
#define ANVIL_LOCAL_DROP_ALL(stream, local) \
do { \
void (*action) (VSTREAM *, const char *);
} ANVIL_REQ_TABLE;
+ /*
+ * Run-time statistics for maximal connection counts and event rates. These
+ * store the peak resource usage, remote connection, and time. Absent a
+ * query interface, this information is logged at process exit time and at
+ * configurable intervals.
+ */
+typedef struct {
+ int value; /* peak value */
+ char *ident; /* lookup key */
+ time_t when; /* time of peak value */
+} ANVIL_MAX;
+
+static ANVIL_MAX max_conn_count; /* peak connection count */
+static ANVIL_MAX max_conn_rate; /* peak connection rate */
+static ANVIL_MAX max_mail_rate; /* peak message rate */
+static ANVIL_MAX max_rcpt_rate; /* peak recipient rate */
+static ANVIL_MAX max_ntls_rate; /* peak new TLS session rate */
+
+static int max_cache_size; /* peak cache size */
+static time_t max_cache_time; /* time of peak size */
+
+/* Update/report peak usage. */
+
+#define ANVIL_MAX_UPDATE(_max, _value, _ident) \
+ do { \
+ _max.value = _value; \
+ if (_max.ident == 0) { \
+ _max.ident = mystrdup(_ident); \
+ } else if (!STREQ(_max.ident, _ident)) { \
+ myfree(_max.ident); \
+ _max.ident = mystrdup(_ident); \
+ } \
+ _max.when = event_time(); \
+ } while (0)
+
+#define ANVIL_MAX_RATE_REPORT(_max, _name) \
+ do { \
+ if (_max.value > 0) { \
+ msg_info("statistics: max " _name " rate %d/%ds for (%s) at %.15s", \
+ _max.value, var_anvil_time_unit, \
+ _max.ident, ctime(&_max.when) + 4); \
+ _max.value = 0; \
+ } \
+ } while (0);
+
+#define ANVIL_MAX_COUNT_REPORT(_max, _name) \
+ do { \
+ if (_max.value > 0) { \
+ msg_info("statistics: max " _name " count %d for (%s) at %.15s", \
+ _max.value, _max.ident, ctime(&_max.when) + 4); \
+ _max.value = 0; \
+ } \
+ } while (0);
+
/*
* Silly little macros.
*/
}
/*
- * Record this connection under the local client information, so that we
- * can clean up all its connection state when the local client goes away.
+ * Record this connection under the local server information, so that we
+ * can clean up all its connection state when the local server goes away.
*/
if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) == 0) {
anvil_local = (ANVIL_LOCAL *) mymalloc(sizeof(*anvil_local));
anvil_remote = anvil_remote_conn_update(client_stream, ident);
/*
- * Respond to the local client.
+ * Respond to the local server.
*/
attr_print_plain(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_NUM, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
anvil_remote = anvil_remote_conn_update(client_stream, ident);
/*
- * Update message delivery request rate and respond to local client.
+ * Update message delivery request rate and respond to local server.
*/
ANVIL_REMOTE_INCR_MAIL(anvil_remote);
attr_print_plain(client_stream, ATTR_FLAG_NONE,
anvil_remote = anvil_remote_conn_update(client_stream, ident);
/*
- * Update recipient address rate and respond to local client.
+ * Update recipient address rate and respond to local server.
*/
ANVIL_REMOTE_INCR_RCPT(anvil_remote);
attr_print_plain(client_stream, ATTR_FLAG_NONE,
anvil_remote = anvil_remote_conn_update(client_stream, ident);
/*
- * Update newtls rate and respond to local client.
+ * Update newtls rate and respond to local server.
*/
ANVIL_REMOTE_INCR_NTLS(anvil_remote);
attr_print_plain(client_stream, ATTR_FLAG_NONE,
}
/*
- * Respond to local client.
+ * Respond to local server.
*/
attr_print_plain(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_NUM, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
(unsigned long) client_stream, ident);
/*
- * Update local and remote info if this remote site is listed for this
- * local client.
+ * Update local and remote info if this remote connection is listed for
+ * this local server.
*/
if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) != 0
&& (anvil_remote =
myname, (unsigned long) anvil_local);
/*
- * Respond to the local client.
+ * Respond to the local server.
*/
attr_print_plain(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_NUM, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
(unsigned long) client_stream);
/*
- * Look up the local client, and get rid of open remote connection state
- * that we still have for this local client. Do not destroy remote client
+ * Look up the local server, and get rid of any remote connection state
+ * that we still have for this local server. Do not destroy remote client
* status information before it expires.
*/
if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) != 0) {
vstream_fileno(client_stream));
}
+/* anvil_status_dump - log and reset extreme usage */
+
+static void anvil_status_dump(char *unused_name, char **unused_argv)
+{
+ ANVIL_MAX_RATE_REPORT(max_conn_rate, "connection");
+ ANVIL_MAX_COUNT_REPORT(max_conn_count, "connection");
+ ANVIL_MAX_RATE_REPORT(max_mail_rate, "message");
+ ANVIL_MAX_RATE_REPORT(max_rcpt_rate, "recipient");
+ ANVIL_MAX_RATE_REPORT(max_ntls_rate, "newtls");
+
+ if (max_cache_size > 0) {
+ msg_info("statistics: max cache size %d at %.15s",
+ max_cache_size, ctime(&max_cache_time) + 4);
+ max_cache_size = 0;
+ }
+}
+
+/* anvil_status_update - log and reset extreme usage periodically */
+
+static void anvil_status_update(int unused_event, char *context)
+{
+ anvil_status_dump((char *) 0, (char **) 0);
+ event_request_timer(anvil_status_update, context, var_anvil_stat_time);
+}
+
/* anvil_service - perform service for client */
static void anvil_service(VSTREAM *client_stream, char *unused_service, char **argv)
/* post_jail_init - post-jail initialization */
-static void anvil_status_update(int, char *);
-
static void post_jail_init(char *unused_name, char **unused_argv)
{
var_use_limit = 0;
}
-/* anvil_status_dump - log and reset extreme usage */
-
-static void anvil_status_dump(char *unused_name, char **unused_argv)
-{
- ANVIL_MAX_RATE_REPORT(max_conn_rate, "connection");
- ANVIL_MAX_COUNT_REPORT(max_conn_count, "connection");
- ANVIL_MAX_RATE_REPORT(max_mail_rate, "message");
- ANVIL_MAX_RATE_REPORT(max_rcpt_rate, "recipient");
- ANVIL_MAX_RATE_REPORT(max_ntls_rate, "newtls");
-
- if (max_cache_size > 0) {
- msg_info("statistics: max cache size %d at %.15s",
- max_cache_size, ctime(&max_cache_time) + 4);
- max_cache_size = 0;
- }
-}
-
-/* anvil_status_update - log and reset extreme usage periodically */
-
-static void anvil_status_update(int unused_event, char *context)
-{
- anvil_status_dump((char *) 0, (char **) 0);
- event_request_timer(anvil_status_update, context, var_anvil_stat_time);
-}
-
/* main - pass control to the multi-threaded skeleton */
int main(int argc, char **argv)
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20051011"
+#define MAIL_RELEASE_DATE "20051014"
#define MAIL_VERSION_NUMBER "2.3"
#ifdef SNAPSHOT
*/
argv[0] = "postdrop";
msg_vstream_init(argv[0], VSTREAM_ERR);
- msg_syslog_init(mail_task(argv[0]), LOG_PID, LOG_FACILITY);
+ msg_syslog_init(mail_task("postdrop"), LOG_PID, LOG_FACILITY);
set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0]));
/*
postqueue.o: ../../include/attr.h
postqueue.o: ../../include/clean_env.h
postqueue.o: ../../include/connect.h
-postqueue.o: ../../include/debug_process.h
postqueue.o: ../../include/flush_clnt.h
postqueue.o: ../../include/iostuff.h
postqueue.o: ../../include/mail_conf.h
#include <mail_params.h>
#include <mail_conf.h>
#include <mail_task.h>
-#include <debug_process.h>
#include <mail_run.h>
#include <mail_flush.h>
#include <flush_clnt.h>
/* for testing address rewriting and routing configurations.
/* .sp
/* This feature is available in Postfix version 2.1 and later.
-/* .IP "\fB-C \fIconfig_file\fR (ignored)"
-/* The path name of the \fBsendmail.cf\fR file. Postfix configuration
-/* files are kept in the \fB/etc/postfix\fR directory.
+/* .IP "\fB-C \fIconfig_file\fR"
+/* .IP "\fB-C \fIconfig_dir\fR"
+/* The path name of the Postfix \fBmain.cf\fR file, or of its
+/* parent directory. This information is ignored with Postfix
+/* versions before 2.3.
+/*
+/* With older Postfix versions, specify a directory pathname
+/* with the MAIL_CONFIG environment variable to override the
+/* location of configuration files.
/* .IP "\fB-F \fIfull_name\fR
/* Set the sender full name. This is used only with messages that
/* have no \fBFrom:\fR message header.
char *rewrite_context = MAIL_ATTR_RWR_LOCAL;
int dsn_notify = 0;
const char *dsn_envid = 0;
+ int saved_optind;
/*
* Be consistent with file permissions.
}
/*
- * Further initialization...
+ * Further initialization. Load main.cf first, so that command-line
+ * options can override main.cf settings. Pre-scan the argument list so
+ * that we load the right main.cf file.
*/
+#define GETOPT_LIST "A:B:C:F:GIL:N:O:R:UV:X:b:ce:f:h:imno:p:r:q:tvx"
+
+ saved_optind = optind;
+ while ((c = GETOPT(argc, argv, GETOPT_LIST)) > 0) {
+ VSTRING *buf = vstring_alloc(1);
+
+ if (c == 'C'
+ && setenv(CONF_ENV_PATH,
+ strcmp(sane_basename(buf, optarg), MAIN_CONF_FILE) == 0 ?
+ sane_dirname(buf, optarg) : optarg, 1) < 0)
+ msg_fatal_status(EX_UNAVAILABLE, "out of memory");
+ vstring_free(buf);
+ }
+ optind = saved_optind;
mail_conf_read();
get_mail_conf_str_table(str_table);
optind++;
continue;
}
- if ((c = GETOPT(argc, argv, "A:B:C:F:GIL:N:O:R:UV:X:b:ce:f:h:imno:p:r:q:tvx")) <= 0)
+ if ((c = GETOPT(argc, argv, GETOPT_LIST)) <= 0)
break;
switch (c) {
default:
if (prev_rec_type != REC_TYPE_CONT && *start == '.'
&& (state->proxy == 0 ? (++start, --len) == 0 : len == 1))
break;
- state->act_size += len + 2;
- if (state->err == CLEANUP_STAT_OK
- && out_record(out_stream, curr_rec_type, start, len) < 0)
- state->err = out_error;
+ if (state->err == CLEANUP_STAT_OK) {
+ state->act_size += len + 2;
+ if (var_message_limit > 0 && state->act_size > var_message_limit)
+ state->err = CLEANUP_STAT_SIZE;
+ else if (out_record(out_stream, curr_rec_type, start, len) < 0)
+ state->err = out_error;
+ }
}
state->where = SMTPD_AFTER_DOT;
- if (SMTPD_STAND_ALONE(state) == 0 && (err = smtpd_check_eod(state)) != 0) {
+ if (state->err == CLEANUP_STAT_OK
+ && SMTPD_STAND_ALONE(state) == 0
+ && (err = smtpd_check_eod(state)) != 0) {
smtpd_chat_reply(state, "%s", err);
if (state->proxy) {
smtpd_proxy_close(state);
if (state->err == CLEANUP_STAT_OK &&
*STR(state->proxy_buffer) != '2')
state->err = CLEANUP_STAT_CONT;
- } else {
- state->error_mask |= MAIL_ERROR_SOFTWARE;
+ } else if (state->err != CLEANUP_STAT_SIZE) {
state->err |= CLEANUP_STAT_PROXY;
detail = cleanup_stat_detail(CLEANUP_STAT_PROXY);
vstring_sprintf(state->proxy_buffer,
smtpd_chat_reply(state,
"421 4.7.0 %s Error: too many new TLS sessions from %s",
var_myhostname, state->namaddr);
- msg_warn("Too many new TLS sessions: %d from %s for service %s",
+ msg_warn("New TLS session rate limit exceeded: %d from %s for service %s",
rate, state->namaddr, state->service);
/* XXX Use regular return to signal end of session. */
vstream_longjmp(state->client, SMTP_ERR_QUIET);
unix_recv_fd.c unix_send_fd.c unix_trigger.c unsafe.c uppercase.c \
username.c valid_hostname.c vbuf.c vbuf_print.c vstream.c \
vstream_popen.c vstring.c vstring_vstream.c watchdog.c writable.c \
- write_buf.c write_wait.c
+ write_buf.c write_wait.c sane_basename.c
OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \
attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \
unix_recv_fd.o unix_send_fd.o unix_trigger.o unsafe.o uppercase.o \
username.o valid_hostname.o vbuf.o vbuf_print.o vstream.o \
vstream_popen.o vstring.o vstring_vstream.o watchdog.o writable.o \
- write_buf.o write_wait.o
+ write_buf.o write_wait.o sane_basename.o
HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \
chroot_uid.h cidr_match.h clean_env.h connect.h ctable.h dict.h \
dict_cdb.h dict_cidr.h dict_db.h dict_dbm.h dict_env.h dict_ht.h \
inet_addr_list attr_print64 attr_scan64 base64_code attr_print0 \
attr_scan0 host_port attr_scan_plain attr_print_plain htable \
unix_recv_fd unix_send_fd stream_recv_fd stream_send_fd hex_code \
- myaddrinfo myaddrinfo4 inet_proto
+ myaddrinfo myaddrinfo4 inet_proto sane_basename
LIB_DIR = ../../lib
INC_DIR = ../../include
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
mv junk $@.o
+sane_basename: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
stream_test: stream_test.c $(LIB)
$(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS)
diff inet_addr_list.ref inet_addr_list.tmp
rm -f inet_addr_list.tmp
+sane_basename_test: sane_basename
+ ./sane_basename <sane_basename.in >sane_basename.tmp 2>&1
+ diff sane_basename.ref sane_basename.tmp
+ rm -f sane_basename.tmp
+
base64_code_test: base64_code
./base64_code
sane_accept.o: sane_accept.c
sane_accept.o: sane_accept.h
sane_accept.o: sys_defs.h
+sane_basename.o: msg.h
+sane_basename.o: mymalloc.c
+sane_basename.o: mymalloc.h
+sane_basename.o: sane_basename.c
+sane_basename.o: stringops.h
+sane_basename.o: sys_defs.h
+sane_basename.o: vbuf.h
+sane_basename.o: vstring.h
sane_connect.o: msg.h
sane_connect.o: sane_connect.c
sane_connect.o: sane_connect.h
--- /dev/null
+/*++
+/* NAME
+/* sane_basename 3
+/* SUMMARY
+/* split pathname into last component and parent directory
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *sane_basename(buf, path)
+/* VSTRING *buf;
+/* const char *path;
+/*
+/* char *sane_dirname(buf, path)
+/* VSTRING *buf;
+/* const char *path;
+/* DESCRIPTION
+/* These functions split a pathname into its last component
+/* and its parent directory, excluding any trailing "/"
+/* characters from the input. The result is a pointer to "/"
+/* when the input is all "/" characters, or a pointer to "."
+/* when the input is a null pointer or zero-length string.
+/*
+/* sane_basename() and sane_dirname() differ as follows
+/* from standard basename() and dirname() implementations:
+/* .IP \(bu
+/* They can use caller-provided storage or private storage.
+/* .IP \(bu
+/* They never modify their input.
+/* .PP
+/* sane_basename() returns a pointer to string with the last
+/* pathname component.
+/*
+/* sane_dirname() returns a pointer to string with the parent
+/* directory. The result is a pointer to "." when the input
+/* contains no '/' character.
+/*
+/* Arguments:
+/* .IP buf
+/* Result storage. If a null pointer is specified, each function
+/* uses its own private memory that is overwritten upon each call.
+/* .IP path
+/* The input pathname.
+/* 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 <sys_defs.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <stringops.h>
+
+#define STR(x) vstring_str(x)
+
+/* sane_basename - skip directory prefix */
+
+char *sane_basename(VSTRING *bp, const char *path)
+{
+ static VSTRING *buf;
+ const char *first;
+ const char *last;
+
+ /*
+ * Your buffer or mine?
+ */
+ if (bp == 0) {
+ bp = buf;
+ if (bp == 0)
+ bp = buf = vstring_alloc(10);
+ }
+
+ /*
+ * Special case: return "." for null or zero-length input.
+ */
+ if (path == 0 || *path == 0)
+ return (STR(vstring_strcpy(bp, ".")));
+
+ /*
+ * Remove trailing '/' characters from input. Return "/" if input is all
+ * '/' characters.
+ */
+ last = path + strlen(path) - 1;
+ while (*last == '/') {
+ if (last == path)
+ return (STR(vstring_strcpy(bp, "/")));
+ last--;
+ }
+
+ /*
+ * The pathname does not end in '/'. Skip to last '/' character if any.
+ */
+ first = last - 1;
+ while (first >= path && *first != '/')
+ first--;
+
+ return (STR(vstring_strncpy(bp, first + 1, last - first)));
+}
+
+/* sane_dirname - keep directory prefix */
+
+char *sane_dirname(VSTRING *bp, const char *path)
+{
+ static VSTRING *buf;
+ const char *last;
+
+ /*
+ * Your buffer or mine?
+ */
+ if (bp == 0) {
+ bp = buf;
+ if (bp == 0)
+ bp = buf = vstring_alloc(10);
+ }
+
+ /*
+ * Special case: return "." for null or zero-length input.
+ */
+ if (path == 0 || *path == 0)
+ return (STR(vstring_strcpy(bp, ".")));
+
+ /*
+ * Remove trailing '/' characters from input. Return "/" if input is all
+ * '/' characters.
+ */
+ last = path + strlen(path) - 1;
+ while (*last == '/') {
+ if (last == path)
+ return (STR(vstring_strcpy(bp, "/")));
+ last--;
+ }
+
+ /*
+ * This pathname does not end in '/'. Skip to last '/' character if any.
+ */
+ while (last >= path && *last != '/')
+ last--;
+ if (last < path) /* no '/' */
+ return (STR(vstring_strcpy(bp, ".")));
+
+ /*
+ * Strip trailing '/' characters from dirname (not strictly needed).
+ */
+ while (last > path && *last == '/')
+ last--;
+
+ return (STR(vstring_strncpy(bp, path, last - path + 1)));
+}
+
+#ifdef TEST
+#include <vstring_vstream.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *buf = vstring_alloc(10);
+ char *dir;
+ char *base;
+
+ while (vstring_get_nonl(buf, VSTREAM_IN) > 0) {
+ dir = sane_dirname((VSTRING *) 0, STR(buf));
+ base = sane_basename((VSTRING *) 0, STR(buf));
+ vstream_printf("input=\"%s\" dir=\"%s\" base=\"%s\"\n",
+ STR(buf), dir, base);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
--- /dev/null
+///
+/
+fo2///
+fo2/
+fo2
+///fo2
+/fo2
+///fo2///bar///
+fo2///bar///
+fo2///bar/
+fo2/bar
+
+/usr/lib
+/usr/
+usr
+/
+.
+..
--- /dev/null
+input="///" dir="/" base="/"
+input="/" dir="/" base="/"
+input="fo2///" dir="." base="fo2"
+input="fo2/" dir="." base="fo2"
+input="fo2" dir="." base="fo2"
+input="///fo2" dir="/" base="fo2"
+input="/fo2" dir="/" base="fo2"
+input="///fo2///bar///" dir="///fo2" base="bar"
+input="fo2///bar///" dir="fo2" base="bar"
+input="fo2///bar/" dir="fo2" base="bar"
+input="fo2/bar" dir="fo2" base="bar"
+input="" dir="." base="."
+input="/usr/lib" dir="/usr" base="lib"
+input="/usr/" dir="/" base="usr"
+input="usr" dir="." base="usr"
+input="/" dir="/" base="/"
+input="." dir="." base="."
+input=".." dir="." base=".."
extern char *mystrtok(char **, const char *);
extern char *translit(char *, const char *, const char *);
#ifndef HAVE_BASENAME
+#define basename postfix_basename
extern char *basename(const char *);
#endif
+extern char *sane_basename(VSTRING *, const char *);
+extern char *sane_dirname(VSTRING *, const char *);
extern VSTRING *unescape(VSTRING *, const char *);
extern int alldig(const char *);
extern int allprint(const char *);