mirror of
https://github.com/vdukhovni/postfix
synced 2025-08-30 21:55:20 +00:00
postfix-2.3-20051014
This commit is contained in:
committed by
Viktor Dukhovni
parent
9dba3caad0
commit
3bb375dbd2
@@ -11177,7 +11177,7 @@ Apologies for any names omitted.
|
||||
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.
|
||||
|
||||
@@ -11191,6 +11191,37 @@ Apologies for any names omitted.
|
||||
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,
|
||||
@@ -11199,11 +11230,6 @@ Open problems:
|
||||
|
||||
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.
|
||||
@@ -11213,11 +11239,6 @@ Open problems:
|
||||
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?
|
||||
|
||||
|
@@ -26,10 +26,11 @@ files. With deeply nested aliases or .forward files, this can greatly
|
||||
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
|
||||
====================================
|
||||
|
@@ -107,10 +107,16 @@ SENDMAIL(1) SENDMAIL(1)
|
||||
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
|
||||
|
@@ -91,9 +91,15 @@ report after verifying each recipient address. This is useful
|
||||
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.
|
||||
|
@@ -276,17 +276,168 @@
|
||||
|
||||
/* 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.
|
||||
* Remote connection state, one instance for each (service, client) pair.
|
||||
*/
|
||||
typedef struct {
|
||||
char *ident; /* lookup key */
|
||||
int count; /* connection count */
|
||||
int rate; /* connection rate */
|
||||
int mail; /* message rate */
|
||||
int rcpt; /* recipient rate */
|
||||
int ntls; /* new TLS session rate */
|
||||
time_t start; /* time of first rate sample */
|
||||
} ANVIL_REMOTE;
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
} ANVIL_LOCAL;
|
||||
|
||||
/*
|
||||
* The following operations are implemented as macros with recognizable
|
||||
* names so that we don't lose sight of what the code is trying to do.
|
||||
*
|
||||
* Related operations are defined side by side so that the code implementing
|
||||
* them isn't pages apart.
|
||||
*/
|
||||
|
||||
/* Create new (service, client) state. */
|
||||
|
||||
#define ANVIL_REMOTE_FIRST_CONN(remote, id) \
|
||||
do { \
|
||||
(remote)->ident = mystrdup(id); \
|
||||
(remote)->count = 1; \
|
||||
(remote)->rate = 1; \
|
||||
(remote)->mail = 0; \
|
||||
(remote)->rcpt = 0; \
|
||||
(remote)->ntls = 0; \
|
||||
(remote)->start = event_time(); \
|
||||
} while(0)
|
||||
|
||||
/* Destroy unused (service, client) state. */
|
||||
|
||||
#define ANVIL_REMOTE_FREE(remote) \
|
||||
do { \
|
||||
myfree((remote)->ident); \
|
||||
myfree((char *) (remote)); \
|
||||
} while(0)
|
||||
|
||||
/* Reset or update rate information for existing (service, client) state. */
|
||||
|
||||
#define ANVIL_REMOTE_RSET_RATE(remote, _start) \
|
||||
do { \
|
||||
(remote)->rate = 0; \
|
||||
(remote)->mail = 0; \
|
||||
(remote)->rcpt = 0; \
|
||||
(remote)->ntls = 0; \
|
||||
(remote)->start = _start; \
|
||||
} while(0)
|
||||
|
||||
#define ANVIL_REMOTE_INCR_RATE(remote, _what) \
|
||||
do { \
|
||||
time_t _now = event_time(); \
|
||||
if ((remote)->start + var_anvil_time_unit < _now) \
|
||||
ANVIL_REMOTE_RSET_RATE((remote), _now); \
|
||||
if ((remote)->_what < INT_MAX) \
|
||||
(remote)->_what += 1; \
|
||||
} while(0)
|
||||
|
||||
/* Update existing (service, client) state. */
|
||||
|
||||
#define ANVIL_REMOTE_NEXT_CONN(remote) \
|
||||
do { \
|
||||
ANVIL_REMOTE_INCR_RATE((remote), rate); \
|
||||
if ((remote)->count == 0) \
|
||||
event_cancel_timer(anvil_remote_expire, (char *) remote); \
|
||||
(remote)->count++; \
|
||||
} while(0)
|
||||
|
||||
#define ANVIL_REMOTE_INCR_MAIL(remote) ANVIL_REMOTE_INCR_RATE((remote), mail)
|
||||
|
||||
#define ANVIL_REMOTE_INCR_RCPT(remote) ANVIL_REMOTE_INCR_RATE((remote), rcpt)
|
||||
|
||||
#define ANVIL_REMOTE_INCR_NTLS(remote) ANVIL_REMOTE_INCR_RATE((remote), ntls)
|
||||
|
||||
/* Drop connection from (service, client) state. */
|
||||
|
||||
#define ANVIL_REMOTE_DROP_ONE(remote) \
|
||||
do { \
|
||||
if ((remote) && (remote)->count > 0) { \
|
||||
if (--(remote)->count == 0) \
|
||||
event_request_timer(anvil_remote_expire, (char *) remote, \
|
||||
var_anvil_time_unit); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/* Create local server state. */
|
||||
|
||||
#define ANVIL_LOCAL_INIT(local) \
|
||||
do { \
|
||||
(local)->anvil_remote = 0; \
|
||||
} while(0)
|
||||
|
||||
/* Add remote connection to local server. */
|
||||
|
||||
#define ANVIL_LOCAL_ADD_ONE(local, remote) \
|
||||
do { \
|
||||
/* XXX allow multiple remote clients per local server. */ \
|
||||
if ((local)->anvil_remote) \
|
||||
ANVIL_REMOTE_DROP_ONE((local)->anvil_remote); \
|
||||
(local)->anvil_remote = (remote); \
|
||||
} while(0)
|
||||
|
||||
/* Test if this remote connection is listed for this local server. */
|
||||
|
||||
#define ANVIL_LOCAL_REMOTE_LINKED(local, remote) \
|
||||
((local)->anvil_remote == (remote))
|
||||
|
||||
/* Drop specific remote connection from local server. */
|
||||
|
||||
#define ANVIL_LOCAL_DROP_ONE(local, remote) \
|
||||
do { \
|
||||
/* XXX allow multiple remote clients per local server. */ \
|
||||
if ((local)->anvil_remote == (remote)) \
|
||||
(local)->anvil_remote = 0; \
|
||||
} while(0)
|
||||
|
||||
/* Drop all remote connections from local server. */
|
||||
|
||||
#define ANVIL_LOCAL_DROP_ALL(stream, local) \
|
||||
do { \
|
||||
/* XXX allow multiple remote clients per local server. */ \
|
||||
if ((local)->anvil_remote) \
|
||||
anvil_remote_disconnect((stream), (local)->anvil_remote->ident); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Lookup table to map request names to action routines.
|
||||
*/
|
||||
typedef struct {
|
||||
const char *name;
|
||||
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 */
|
||||
@@ -336,151 +487,6 @@ static time_t max_cache_time; /* time of peak size */
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
/*
|
||||
* Remote connection state, one instance for each (service, client) pair.
|
||||
*/
|
||||
typedef struct {
|
||||
char *ident; /* lookup key */
|
||||
int count; /* connection count */
|
||||
int rate; /* connection rate */
|
||||
int mail; /* message rate */
|
||||
int rcpt; /* recipient rate */
|
||||
int ntls; /* new TLS session rate */
|
||||
time_t start; /* time of first rate sample */
|
||||
} 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.
|
||||
*/
|
||||
typedef struct {
|
||||
ANVIL_REMOTE *anvil_remote; /* XXX should be list */
|
||||
} ANVIL_LOCAL;
|
||||
|
||||
/*
|
||||
* The following operations are implemented as macros with recognizable
|
||||
* names so that we don't lose sight of what the code is trying to do.
|
||||
*
|
||||
* Related operations are defined side by side so that the code implementing
|
||||
* them isn't pages apart.
|
||||
*/
|
||||
|
||||
/* Create new (service, client) state. */
|
||||
|
||||
#define ANVIL_REMOTE_FIRST_CONN(remote, id) \
|
||||
do { \
|
||||
(remote)->ident = mystrdup(id); \
|
||||
(remote)->count = 1; \
|
||||
(remote)->rate = 1; \
|
||||
(remote)->mail = 0; \
|
||||
(remote)->rcpt = 0; \
|
||||
(remote)->ntls = 0; \
|
||||
(remote)->start = event_time(); \
|
||||
} while(0)
|
||||
|
||||
/* Destroy unused (service, client) state. */
|
||||
|
||||
#define ANVIL_REMOTE_FREE(remote) \
|
||||
do { \
|
||||
myfree((remote)->ident); \
|
||||
myfree((char *) (remote)); \
|
||||
} while(0)
|
||||
|
||||
/* Reset event rate counters and start of data collection interval. */
|
||||
|
||||
#define ANVIL_REMOTE_RSET_RATE(remote, _start) \
|
||||
do { \
|
||||
(remote)->rate = 0; \
|
||||
(remote)->mail = 0; \
|
||||
(remote)->rcpt = 0; \
|
||||
(remote)->ntls = 0; \
|
||||
(remote)->start = _start; \
|
||||
} while(0)
|
||||
|
||||
/* Add connection to (service, client) state. */
|
||||
|
||||
#define ANVIL_REMOTE_INCR_RATE(remote, _what) \
|
||||
do { \
|
||||
time_t _now = event_time(); \
|
||||
if ((remote)->start + var_anvil_time_unit < _now) \
|
||||
ANVIL_REMOTE_RSET_RATE((remote), _now); \
|
||||
if ((remote)->_what < INT_MAX) \
|
||||
(remote)->_what += 1; \
|
||||
} while(0)
|
||||
|
||||
#define ANVIL_REMOTE_NEXT_CONN(remote) \
|
||||
do { \
|
||||
ANVIL_REMOTE_INCR_RATE((remote), rate); \
|
||||
if ((remote)->count == 0) \
|
||||
event_cancel_timer(anvil_remote_expire, (char *) remote); \
|
||||
(remote)->count++; \
|
||||
} while(0)
|
||||
|
||||
#define ANVIL_REMOTE_INCR_MAIL(remote) ANVIL_REMOTE_INCR_RATE((remote), mail)
|
||||
|
||||
#define ANVIL_REMOTE_INCR_RCPT(remote) ANVIL_REMOTE_INCR_RATE((remote), rcpt)
|
||||
|
||||
#define ANVIL_REMOTE_INCR_NTLS(remote) ANVIL_REMOTE_INCR_RATE((remote), ntls)
|
||||
|
||||
/* Drop connection from (service, client) state. */
|
||||
|
||||
#define ANVIL_REMOTE_DROP_ONE(remote) \
|
||||
do { \
|
||||
if ((remote) && (remote)->count > 0) { \
|
||||
if (--(remote)->count == 0) \
|
||||
event_request_timer(anvil_remote_expire, (char *) remote, \
|
||||
var_anvil_time_unit); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/* Create local server state. */
|
||||
|
||||
#define ANVIL_LOCAL_INIT(local) \
|
||||
do { \
|
||||
(local)->anvil_remote = 0; \
|
||||
} while(0)
|
||||
|
||||
/* Add connection to local server. */
|
||||
|
||||
#define ANVIL_LOCAL_ADD_ONE(local, remote) \
|
||||
do { \
|
||||
/* XXX allow multiple remote clients per local server. */ \
|
||||
if ((local)->anvil_remote) \
|
||||
ANVIL_REMOTE_DROP_ONE((local)->anvil_remote); \
|
||||
(local)->anvil_remote = (remote); \
|
||||
} while(0)
|
||||
|
||||
/* Test if this remote site is listed for this local client. */
|
||||
|
||||
#define ANVIL_LOCAL_REMOTE_LINKED(local, remote) \
|
||||
((local)->anvil_remote == (remote))
|
||||
|
||||
/* Drop connection from local server. */
|
||||
|
||||
#define ANVIL_LOCAL_DROP_ONE(local, remote) \
|
||||
do { \
|
||||
/* XXX allow multiple remote clients per local server. */ \
|
||||
if ((local)->anvil_remote == (remote)) \
|
||||
(local)->anvil_remote = 0; \
|
||||
} while(0)
|
||||
|
||||
/* Drop all connections from local server. */
|
||||
|
||||
#define ANVIL_LOCAL_DROP_ALL(stream, local) \
|
||||
do { \
|
||||
/* XXX allow multiple remote clients per local server. */ \
|
||||
if ((local)->anvil_remote) \
|
||||
anvil_remote_disconnect((stream), (local)->anvil_remote->ident); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Lookup table to map request names to action routines.
|
||||
*/
|
||||
typedef struct {
|
||||
const char *name;
|
||||
void (*action) (VSTREAM *, const char *);
|
||||
} ANVIL_REQ_TABLE;
|
||||
|
||||
/*
|
||||
* Silly little macros.
|
||||
*/
|
||||
@@ -587,8 +593,8 @@ static ANVIL_REMOTE *anvil_remote_conn_update(VSTREAM *client_stream, const char
|
||||
}
|
||||
|
||||
/*
|
||||
* 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));
|
||||
@@ -617,7 +623,7 @@ static void anvil_remote_connect(VSTREAM *client_stream, const char *ident)
|
||||
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,
|
||||
@@ -648,7 +654,7 @@ static void anvil_remote_mail(VSTREAM *client_stream, const char *ident)
|
||||
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,
|
||||
@@ -677,7 +683,7 @@ static void anvil_remote_rcpt(VSTREAM *client_stream, const char *ident)
|
||||
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,
|
||||
@@ -706,7 +712,7 @@ static void anvil_remote_newtls(VSTREAM *client_stream, const char *ident)
|
||||
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,
|
||||
@@ -747,7 +753,7 @@ static void anvil_remote_newtls_stat(VSTREAM *client_stream, const char *ident)
|
||||
}
|
||||
|
||||
/*
|
||||
* 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,
|
||||
@@ -769,8 +775,8 @@ static void anvil_remote_disconnect(VSTREAM *client_stream, const char *ident)
|
||||
(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 =
|
||||
@@ -784,7 +790,7 @@ static void anvil_remote_disconnect(VSTREAM *client_stream, const char *ident)
|
||||
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,
|
||||
@@ -805,8 +811,8 @@ static void anvil_service_done(VSTREAM *client_stream, char *unused_service,
|
||||
(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) {
|
||||
@@ -820,6 +826,31 @@ static void anvil_service_done(VSTREAM *client_stream, char *unused_service,
|
||||
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)
|
||||
@@ -889,8 +920,6 @@ static void anvil_service(VSTREAM *client_stream, char *unused_service, char **a
|
||||
|
||||
/* post_jail_init - post-jail initialization */
|
||||
|
||||
static void anvil_status_update(int, char *);
|
||||
|
||||
static void post_jail_init(char *unused_name, char **unused_argv)
|
||||
{
|
||||
|
||||
@@ -910,31 +939,6 @@ 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)
|
||||
|
@@ -20,7 +20,7 @@
|
||||
* 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
|
||||
|
@@ -249,7 +249,7 @@ int main(int argc, char **argv)
|
||||
*/
|
||||
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]));
|
||||
|
||||
/*
|
||||
|
@@ -59,7 +59,6 @@ postqueue.o: ../../include/argv.h
|
||||
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
|
||||
|
@@ -184,7 +184,6 @@
|
||||
#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>
|
||||
|
@@ -85,9 +85,15 @@
|
||||
/* 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.
|
||||
@@ -901,6 +907,7 @@ int main(int argc, char **argv)
|
||||
char *rewrite_context = MAIL_ATTR_RWR_LOCAL;
|
||||
int dsn_notify = 0;
|
||||
const char *dsn_envid = 0;
|
||||
int saved_optind;
|
||||
|
||||
/*
|
||||
* Be consistent with file permissions.
|
||||
@@ -959,8 +966,24 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
@@ -1024,7 +1047,7 @@ int main(int argc, char **argv)
|
||||
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:
|
||||
|
@@ -2253,13 +2253,18 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
|
||||
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);
|
||||
@@ -2285,8 +2290,7 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
|
||||
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,
|
||||
@@ -3077,7 +3081,7 @@ static void smtpd_start_tls(SMTPD_STATE *state)
|
||||
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);
|
||||
|
@@ -29,7 +29,7 @@ SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \
|
||||
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 \
|
||||
@@ -60,7 +60,7 @@ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.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 \
|
||||
@@ -96,7 +96,7 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \
|
||||
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
|
||||
@@ -387,6 +387,11 @@ inet_proto: $(LIB)
|
||||
$(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)
|
||||
|
||||
@@ -433,6 +438,11 @@ inet_addr_list_test: inet_addr_list
|
||||
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
|
||||
|
||||
@@ -1299,6 +1309,14 @@ sane_accept.o: msg.h
|
||||
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
|
||||
|
180
postfix/src/util/sane_basename.c
Normal file
180
postfix/src/util/sane_basename.c
Normal file
@@ -0,0 +1,180 @@
|
||||
/*++
|
||||
/* 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
|
18
postfix/src/util/sane_basename.in
Normal file
18
postfix/src/util/sane_basename.in
Normal file
@@ -0,0 +1,18 @@
|
||||
///
|
||||
/
|
||||
fo2///
|
||||
fo2/
|
||||
fo2
|
||||
///fo2
|
||||
/fo2
|
||||
///fo2///bar///
|
||||
fo2///bar///
|
||||
fo2///bar/
|
||||
fo2/bar
|
||||
|
||||
/usr/lib
|
||||
/usr/
|
||||
usr
|
||||
/
|
||||
.
|
||||
..
|
18
postfix/src/util/sane_basename.ref
Normal file
18
postfix/src/util/sane_basename.ref
Normal file
@@ -0,0 +1,18 @@
|
||||
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=".."
|
@@ -29,8 +29,11 @@ extern char *concatenate(const char *,...);
|
||||
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 *);
|
||||
|
Reference in New Issue
Block a user