reuse a server SASL handle after authentication failure.
Problem reported by Thomas Jarosch of Intra2net AG. File:
smtpd/smtpd_proto.c.
+
+20110420
+
+ Performance: a high load of DSN success notification requests
+ could slow down the queue manager. Solution: make the trace
+ client asynchronous, just like the bounce and defer clients.
+ Problem reported by Eduardo M. Stelmaszczyk of terra.com.br.
+ Files: global/abounce.[hc], *qmgr/qmgr_active.c (the
+ qmgr_active.c files are identical).
+
+20110418
+
+ Bugfix (introduced Postfix 2.3 and Postfix 2.7): the Milter
+ client reported some "file too large" errors as temporary
+ errors. Problem reported by Michael Tokarev. Files:
+ milter/milter8.c, cleanup/cleanup_milter.c.
+
+20110426
+
+ Bugfix (introduced in Postfix 1.1, duplicated in Postfix
+ 2.3, unrelated mistake in Postfix 2.7): the local(8) delivery
+ agent ignored table lookup errors in mailbox_command_maps,
+ mailbox_transport_maps, fallback_transport_maps and (while
+ bouncing mail to alias) alias owner lookup. Problem reported
+ by William Ono. Files: local/command.c, local/mailbox.c,
+ local/unknown.c, local/bounce_workaround.c.
+
+20110601
+
+ Bugfix (introduced Postfix 2.6 with master_service_disable)
+ loop control error when parsing a malformed master.cf file.
+ Found by Coverity. File: master/master_ent.c.
+
+20110602
+
+ Bugfix (introduced: Postfix 2.7): "sendmail -t" reported
+ "protocol error" after queue file write error. File:
+ postdrop/postdrop.c.
+
+20110614
+
+ Linux kernel version 3 support. Linus Torvalds has reset
+ the counters for reasons not related to changes in code.
+ Files: makedefs, util/sys_defs.h.
+
+20110615
+
+ Workaround: some Spamhaus RHSBL rejects lookups with "No
+ IP queries" even if the name has an alphanumerical prefix.
+ We play safe, and skip both RHSBL and RHSWL queries for
+ names ending in a numerical suffix. File: smtpd/smtpd_check.c.
(MAIL_CONFIG=/etc/postfix-out) of the output instance.
* Lines 1-2: With "authorized_submit_users = root", the superuser can test
- the postix-out instance with "postmulti -i postfix-out -x sendmail -bv
+ the postfix-out instance with "postmulti -i postfix-out -x sendmail -bv
recipient...", but otherwise local submission remains disabled.
* Lines 1-2: With "master_service_disable =", the "inet" listeners are re-
<ul>
<li> <p> Lines 1-2: With "<a href="postconf.5.html#authorized_submit_users">authorized_submit_users</a> = root", the
-superuser can test the postix-out instance with "postmulti -i
+superuser can test the postfix-out instance with "postmulti -i
postfix-out -x sendmail -bv recipient...", but otherwise local
submission remains disabled. </p>
<p> By default, the OpenSSL server selects the client's most preferred
cipher that the server supports. With SSLv3 and later, the server
may choose its own most preferred cipher that is supported (offered)
-by the client. Setting "<a href="postconf.5.html#tls_preempts_cipherlist">tls_preempt_cipherlist</a> = yes" enables server
+by the client. Setting "<a href="postconf.5.html#tls_preempt_cipherlist">tls_preempt_cipherlist</a> = yes" enables server
cipher preferences. The default OpenSSL behaviour applies with
-"<a href="postconf.5.html#tls_preempts_cipherlist">tls_preempt_cipherlist</a> = no". </p>
+"<a href="postconf.5.html#tls_preempt_cipherlist">tls_preempt_cipherlist</a> = no". </p>
<p> While server cipher selection may in some cases lead to a more secure
or performant cipher choice, there is some risk of interoperability
<p> By default, the OpenSSL server selects the client's most preferred
cipher that the server supports. With SSLv3 and later, the server may
choose its own most preferred cipher that is supported (offered) by
-the client. Setting "<a href="postconf.5.html#tls_preempts_cipherlist">tls_preempt_cipherlist</a> = yes" enables server cipher
+the client. Setting "<a href="postconf.5.html#tls_preempt_cipherlist">tls_preempt_cipherlist</a> = yes" enables server cipher
preferences. </p>
<p> While server cipher selection may in some cases lead to a more secure
Available in Postfix version 2.8 and later:
- <b><a href="postconf.5.html#tls_preempts_cipherlist">tls_preempt_cipherlist</a> (no)</b>
+ <b><a href="postconf.5.html#tls_preempt_cipherlist">tls_preempt_cipherlist</a> (no)</b>
With SSLv3 and later, use the server's cipher pref-
erence order instead of the client's cipher prefer-
ence order.
fi;;
esac
;;
+ Linux.3*) SYSTYPE=LINUX3
+ if [ -f /usr/include/db.h ]
+ then
+ : we are all set
+ elif [ -f /usr/include/db/db.h ]
+ then
+ CCARGS="$CCARGS -I/usr/include/db"
+ else
+ # On a properly installed system, Postfix builds
+ # by including <db.h> and by linking with -ldb
+ echo "No <db.h> include file found." 1>&2
+ echo "Install the appropriate db*-devel package first." 1>&2
+ echo "See the RELEASE_NOTES file for more information." 1>&2
+ exit 1
+ fi
+ SYSLIBS="-ldb"
+ for name in nsl resolv
+ do
+ for lib in /usr/lib64 /lib64 /usr/lib /lib
+ do
+ test -e $lib/lib$name.a -o -e $lib/lib$name.so && {
+ SYSLIBS="$SYSLIBS -l$name"
+ break
+ }
+ done
+ done
+ ;;
GNU.0*|GNU/kFreeBSD.[567]*)
SYSTYPE=GNU0
# Postfix no longer needs DB 1.85 compatibility
s;\btls_null_cipherlist\b;<a href="postconf.5.html#tls_null_cipherlist">$&</a>;g;
s;\btls_eecdh_strong_curve\b;<a href="postconf.5.html#tls_eecdh_strong_curve">$&</a>;g;
s;\btls_eecdh_ultra_curve\b;<a href="postconf.5.html#tls_eecdh_ultra_curve">$&</a>;g;
- s;\btls_preempt_cipherlist\b;<a href="postconf.5.html#tls_preempts_cipherlist">$&</a>;g;
+ s;\btls_preempt_cipherlist\b;<a href="postconf.5.html#tls_preempt_cipherlist">$&</a>;g;
s;\btls_disable_workarounds\b;<a href="postconf.5.html#tls_disable_workarounds">$&</a>;g;
s;\btls_append_default_CA\b;<a href="postconf.5.html#tls_append_default_CA">$&</a>;g;
<ul>
<li> <p> Lines 1-2: With "authorized_submit_users = root", the
-superuser can test the postix-out instance with "postmulti -i
+superuser can test the postfix-out instance with "postmulti -i
postfix-out -x sendmail -bv recipient...", but otherwise local
submission remains disabled. </p>
/*#define msg_verbose 2*/
+static void cleanup_milter_set_error(CLEANUP_STATE *, int);
+
#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)
* later.
*/
if ((new_meta_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
- msg_warn("%s: seek file %s: %m", myname, cleanup_path);
- state->errs |= CLEANUP_STAT_WRITE;
+ cleanup_milter_set_error(state, errno);
return;
}
if (state->filter != 0)
* value with the location of the new meta record.
*/
if (vstream_fseek(state->dst, state->append_meta_pt_offset, SEEK_SET) < 0) {
- msg_warn("%s: seek file %s: %m", myname, cleanup_path);
- state->errs |= CLEANUP_STAT_WRITE;
+ cleanup_milter_set_error(state, errno);
return;
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
/* int dsn_ret;
/* void (*callback)(int status, char *context);
/* char *context;
+/*
+/* void atrace_flush(flags, queue, id, encoding, sender,
+/* dsn_envid, dsn_ret, callback, context)
+/* int flags;
+/* const char *queue;
+/* const char *id;
+/* const char *encoding;
+/* const char *sender;
+/* const char *dsn_envid;
+/* int dsn_ret;
+/* void (*callback)(int status, char *context);
+/* char *context;
/* DESCRIPTION
/* This module implements an asynchronous interface to the
-/* bounce/defer service for submitting sender notifications
+/* bounce/defer/trace service for submitting sender notifications
/* without waiting for completion of the request.
/*
/* abounce_flush() bounces the specified message to
/* the specified sender, including the defer log that was
/* built with defer_append().
/*
+/* atrace_flush() returns the specified message to the specified
+/* sender, including the message delivery record log that was
+/* built with vtrace_append().
+/*
/* Arguments:
/* .IP flags
/* The bitwise OR of zero or more of the following (specify
flags, queue, id, encoding, sender, dsn_envid, dsn_ret,
callback, context);
}
+
+/* atrace_flush - asynchronous trace flush */
+
+void atrace_flush(int flags, const char *queue, const char *id,
+ const char *encoding, const char *sender,
+ const char *dsn_envid, int dsn_ret,
+ ABOUNCE_FN callback, char *context)
+{
+ abounce_request(MAIL_CLASS_PRIVATE, var_trace_service, BOUNCE_CMD_TRACE,
+ flags, queue, id, encoding, sender, dsn_envid, dsn_ret,
+ callback, context);
+}
extern void abounce_flush(int, const char *, const char *, const char *, const char *, const char *, int, ABOUNCE_FN, char *);
extern void adefer_flush(int, const char *, const char *, const char *, const char *, const char *, int, ABOUNCE_FN, char *);
extern void adefer_warn(int, const char *, const char *, const char *, const char *, const char *, int, ABOUNCE_FN, char *);
+extern void atrace_flush(int, const char *, const char *, const char *, const char *, const char *, int, ABOUNCE_FN, char *);
extern void abounce_flush_verp(int, const char *, const char *, const char *, const char *, const char *, int, const char *, ABOUNCE_FN, char *);
extern void adefer_flush_verp(int, const char *, const char *, const char *, const char *, const char *, int, const char *, ABOUNCE_FN, char *);
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20110509"
-#define MAIL_VERSION_NUMBER "2.8.3"
+#define MAIL_RELEASE_DATE "20110615"
+#define MAIL_VERSION_NUMBER "2.8.4-RC1"
#ifdef SNAPSHOT
# define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE
bounce_workaround.o: ../../include/been_here.h
bounce_workaround.o: ../../include/bounce.h
bounce_workaround.o: ../../include/canon_addr.h
+bounce_workaround.o: ../../include/defer.h
bounce_workaround.o: ../../include/deliver_request.h
bounce_workaround.o: ../../include/delivered_hdr.h
bounce_workaround.o: ../../include/dict.h
#include <strip_addr.h>
#include <stringops.h>
#include <bounce.h>
+#include <defer.h>
#include <split_addr.h>
#include <canon_addr.h>
char *stripped_recipient;
char *owner_alias;
const char *owner_expansion;
+ int saved_dict_errno;
#define FIND_OWNER(lhs, rhs, addr) { \
lhs = concatenate("owner-", addr, (char *) 0); \
rhs = maps_find(alias_maps, lhs, DICT_FLAG_NONE); \
}
+ dict_errno = 0;
FIND_OWNER(owner_alias, owner_expansion, state.msg_attr.rcpt.address);
- if (owner_expansion == 0
+ if ((saved_dict_errno = dict_errno) == 0 && owner_expansion == 0
&& (stripped_recipient = strip_addr(state.msg_attr.rcpt.address,
(char **) 0,
*var_rcpt_delim)) != 0) {
FIND_OWNER(owner_alias, owner_expansion, stripped_recipient);
myfree(stripped_recipient);
}
- if (owner_expansion != 0) {
+ if ((saved_dict_errno = dict_errno) == 0 && owner_expansion != 0) {
canon_owner = canon_addr_internal(vstring_alloc(10),
var_exp_own_alias ?
owner_expansion : owner_alias);
SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level);
}
myfree(owner_alias);
+ if (saved_dict_errno != 0)
+ /* At this point, canon_owner == 0. */
+ return (defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
}
/*
transp_maps = maps_create(VAR_MBOX_TRANSP_MAPS, var_mbox_transp_maps,
DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB);
/* The -1 is a hint for the down-stream deliver_completed() function. */
+ dict_errno = 0;
if (*var_mbox_transp_maps
&& (map_transport = maps_find(transp_maps, state.msg_attr.user,
DICT_FLAG_NONE)) != 0) {
*statusp = deliver_pass(MAIL_CLASS_PRIVATE, map_transport,
state.request, &state.msg_attr.rcpt);
return (YES);
+ } else if (dict_errno != 0) {
+ /* Details in the logfile. */
+ dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure");
+ *statusp = DEL_STAT_DEFER;
+ return (YES);
}
if (*var_mailbox_transport) {
state.msg_attr.rcpt.offset = -1L;
cmd_maps = maps_create(VAR_MAILBOX_CMD_MAPS, var_mailbox_cmd_maps,
DICT_FLAG_LOCK | DICT_FLAG_PARANOID);
+ dict_errno = 0;
if (*var_mailbox_cmd_maps
&& (map_command = maps_find(cmd_maps, state.msg_attr.user,
DICT_FLAG_NONE)) != 0) {
status = deliver_command(state, usr_attr, map_command);
+ } else if (dict_errno != 0) {
+ /* Details in the logfile. */
+ dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure");
+ status = DEL_STAT_DEFER;
} else if (*var_mailbox_command) {
status = deliver_command(state, usr_attr, var_mailbox_command);
} else if (*var_home_mailbox && LAST_CHAR(var_home_mailbox) == '/') {
transp_maps = maps_create(VAR_FBCK_TRANSP_MAPS, var_fbck_transp_maps,
DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB);
/* The -1 is a hint for the down-stream deliver_completed() function. */
+ dict_errno = 0;
if (*var_fbck_transp_maps
&& (map_transport = maps_find(transp_maps, state.msg_attr.user,
DICT_FLAG_NONE)) != 0) {
state.msg_attr.rcpt.offset = -1L;
return (deliver_pass(MAIL_CLASS_PRIVATE, map_transport,
state.request, &state.msg_attr.rcpt));
+ } else if (dict_errno != 0) {
+ /* Details in the logfile. */
+ dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure");
+ return (DEL_STAT_DEFER);
}
if (*var_fallback_transport) {
state.msg_attr.rcpt.offset = -1L;
/*
* Skip blank lines and comment lines.
*/
- do {
+ for (;;) {
if (readlline(buf, master_fp, &master_line) == 0) {
vstring_free(buf);
vstring_free(junk);
name = cp;
transport = get_str_ent(&bufp, "transport type", (char *) 0);
vstring_sprintf(junk, "%s.%s", name, transport);
- } while (match_service_match(master_disable, vstring_str(junk)) != 0);
+ if (match_service_match(master_disable, vstring_str(junk)) == 0)
+ break;
+ }
/*
* Parse one logical line from the configuration file. Initialize service
int mime_errs = 0;
MILTER_MSG_CONTEXT msg_ctx;
VSTRING *buf;
+ int saved_errno;
switch (milter->state) {
case MILTER8_STAT_ERROR:
if (msg_verbose)
msg_info("%s: message to milter %s", myname, milter->m.name);
if (vstream_fseek(qfile, data_offset, SEEK_SET) < 0) {
+ saved_errno = errno;
msg_warn("%s: vstream_fseek %s: %m", myname, VSTREAM_PATH(qfile));
- return ("450 4.3.0 Queue file write error");
+ /* XXX This should be available from cleanup_strerror.c. */
+ return (saved_errno == EFBIG ?
+ "552 5.3.4 Message file too big" :
+ "451 4.3.0 Queue file write error");
}
msg_ctx.milter = milter;
msg_ctx.eoh_macros = eoh_macros;
*/
static void qmgr_active_done_2_bounce_flush(int, char *);
static void qmgr_active_done_2_generic(QMGR_MESSAGE *);
+static void qmgr_active_done_25_trace_flush(int, char *);
+static void qmgr_active_done_25_generic(QMGR_MESSAGE *);
static void qmgr_active_done_3_defer_flush(int, char *);
static void qmgr_active_done_3_defer_warn(int, char *);
static void qmgr_active_done_3_generic(QMGR_MESSAGE *);
static void qmgr_active_done_2_generic(QMGR_MESSAGE *message)
{
- const char *myname = "qmgr_active_done_2_generic";
const char *path;
struct stat st;
- int status;
/*
* A delivery agent marks a queue file as corrupt by changing its
}
/*
- * As a temporary implementation, synchronously inform the sender of
- * trace information. This will block for 10 seconds when the qmgr FIFO
- * is full.
- *
* XXX With multi-recipient mail, some recipients may have NOTIFY=SUCCESS
* and others not. Depending on what subset of recipients are delivered,
* a trace file may or may not be created. Even when the last partial
*/
if ((message->tflags & (DEL_REQ_FLAG_USR_VRFY | DEL_REQ_FLAG_RECORD))
|| (message->rflags & QMGR_READ_FLAG_NOTIFY_SUCCESS)) {
- status = trace_flush(message->tflags,
- message->queue_name,
- message->queue_id,
- message->encoding,
- message->sender,
- message->dsn_envid,
- message->dsn_ret);
- if (status == 0 && message->tflags_offset)
- qmgr_message_kill_record(message, message->tflags_offset);
- message->flags |= status;
+ atrace_flush(message->tflags,
+ message->queue_name,
+ message->queue_id,
+ message->encoding,
+ message->sender,
+ message->dsn_envid,
+ message->dsn_ret,
+ qmgr_active_done_25_trace_flush,
+ (char *) message);
+ return;
}
+ /*
+ * Asynchronous processing does not reach this point.
+ */
+ qmgr_active_done_25_generic(message);
+}
+
+/* qmgr_active_done_25_trace_flush - continue after atrace_flush() completion */
+
+static void qmgr_active_done_25_trace_flush(int status, char *context)
+{
+ QMGR_MESSAGE *message = (QMGR_MESSAGE *) context;
+
+ /*
+ * Process atrace_flush() status and continue processing.
+ */
+ if (status == 0 && message->tflags_offset)
+ qmgr_message_kill_record(message, message->tflags_offset);
+ message->flags |= status;
+ qmgr_active_done_25_generic(message);
+}
+
+/* qmgr_active_done_25_generic - continue processing */
+
+static void qmgr_active_done_25_generic(QMGR_MESSAGE *message)
+{
+ const char *myname = "qmgr_active_done_25_generic";
+
/*
* If we get to this point we have tried all recipients for this message.
* If the message is too old, try to bounce it.
int saved_errno;
int from_count = 0;
int rcpt_count = 0;
+ int validate_input = 1;
/*
* Fingerprint executables and core dumps.
&& rec_type != REC_TYPE_EOF)
if (rec_type == REC_TYPE_ERROR)
msg_fatal("uid=%ld: malformed input", (long) uid);
+ validate_input = 0;
errno = saved_errno;
break;
}
* the segment terminator records, there aren't any other mandatory
* records in a Postfix submission queue file.
*/
- if (from_count == 0 || rcpt_count == 0) {
+ if (validate_input && (from_count == 0 || rcpt_count == 0)) {
status = CLEANUP_STAT_BAD;
mail_stream_cleanup(dst);
}
*/
static void qmgr_active_done_2_bounce_flush(int, char *);
static void qmgr_active_done_2_generic(QMGR_MESSAGE *);
+static void qmgr_active_done_25_trace_flush(int, char *);
+static void qmgr_active_done_25_generic(QMGR_MESSAGE *);
static void qmgr_active_done_3_defer_flush(int, char *);
static void qmgr_active_done_3_defer_warn(int, char *);
static void qmgr_active_done_3_generic(QMGR_MESSAGE *);
static void qmgr_active_done_2_generic(QMGR_MESSAGE *message)
{
- const char *myname = "qmgr_active_done_2_generic";
const char *path;
struct stat st;
- int status;
/*
* A delivery agent marks a queue file as corrupt by changing its
}
/*
- * As a temporary implementation, synchronously inform the sender of
- * trace information. This will block for 10 seconds when the qmgr FIFO
- * is full.
- *
* XXX With multi-recipient mail, some recipients may have NOTIFY=SUCCESS
* and others not. Depending on what subset of recipients are delivered,
* a trace file may or may not be created. Even when the last partial
*/
if ((message->tflags & (DEL_REQ_FLAG_USR_VRFY | DEL_REQ_FLAG_RECORD))
|| (message->rflags & QMGR_READ_FLAG_NOTIFY_SUCCESS)) {
- status = trace_flush(message->tflags,
- message->queue_name,
- message->queue_id,
- message->encoding,
- message->sender,
- message->dsn_envid,
- message->dsn_ret);
- if (status == 0 && message->tflags_offset)
- qmgr_message_kill_record(message, message->tflags_offset);
- message->flags |= status;
+ atrace_flush(message->tflags,
+ message->queue_name,
+ message->queue_id,
+ message->encoding,
+ message->sender,
+ message->dsn_envid,
+ message->dsn_ret,
+ qmgr_active_done_25_trace_flush,
+ (char *) message);
+ return;
}
+ /*
+ * Asynchronous processing does not reach this point.
+ */
+ qmgr_active_done_25_generic(message);
+}
+
+/* qmgr_active_done_25_trace_flush - continue after atrace_flush() completion */
+
+static void qmgr_active_done_25_trace_flush(int status, char *context)
+{
+ QMGR_MESSAGE *message = (QMGR_MESSAGE *) context;
+
+ /*
+ * Process atrace_flush() status and continue processing.
+ */
+ if (status == 0 && message->tflags_offset)
+ qmgr_message_kill_record(message, message->tflags_offset);
+ message->flags |= status;
+ qmgr_active_done_25_generic(message);
+}
+
+/* qmgr_active_done_25_generic - continue processing */
+
+static void qmgr_active_done_25_generic(QMGR_MESSAGE *message)
+{
+ const char *myname = "qmgr_active_done_25_generic";
+
/*
* If we get to this point we have tried all recipients for this message.
* If the message is too old, try to bounce it.
cp $(PROG) ../../libexec
SMTPD_CHECK_OBJ = smtpd_state.o smtpd_peer.o smtpd_xforward.o smtpd_dsn_fix.o \
- smtpd_resolve.o
+ smtpd_resolve.o smtpd_expand.o
smtpd_token: smtpd_token.c $(LIBS)
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS)
const char *domain;
const char *reply_addr;
const char *byte_codes;
+ const char *suffix;
/*
* Extract the domain, tack on the RBL domain name and query the DNS for
return (SMTPD_CHECK_DUNNO);
} else
domain = what;
- if (domain[0] == 0)
+
+ /*
+ * XXX Some Spamhaus RHSBL rejects lookups with "No IP queries" even if
+ * the name has an alphanumerical prefix. We play safe, and skip both
+ * RHSBL and RHSWL queries for names ending in a numerical suffix.
+ */
+ if (domain[0] == 0 || valid_hostname(domain, DONT_GRIPE) == 0)
+ return (SMTPD_CHECK_DUNNO);
+ suffix = strrchr(domain, '.');
+ if (alldig(suffix == 0 ? domain : suffix + 1))
return (SMTPD_CHECK_DUNNO);
query = vstring_alloc(100);
name);
else {
cpp += 1;
- if (state->helo_name
- && valid_hostname(state->helo_name, DONT_GRIPE))
+ if (state->helo_name)
status = reject_rbl_domain(state, *cpp, state->helo_name,
SMTPD_NAME_HELO);
}
string_init();
int_init();
smtpd_check_init();
+ smtpd_expand_init();
smtpd_state_init(&state, VSTREAM_IN, "smtpd");
state.queue_id = "<queue id>";
/*
* LINUX.
*/
-#ifdef LINUX2
+#if defined(LINUX2) || defined(LINUX3)
#define SUPPORTED
#include <sys/types.h>
#define UINT32_TYPE unsigned int