Cleanup: block non-printable ASCII text in UTF8 encoded TLS
peer and issuer common names. File: tls/tls_verify.c.
+
+20080602
+
+ Workaround: avoid watchdog timeout in the local pickup
+ daemon when the cleanup server expands a very large virtual
+ alias list. Files: master/trigger_server.c, pickup/pickup.c.
+
+20080603
+
+ Workaround: avoid "bad address pattern" errors with non-address
+ patterns in namadr_list_match() calls. File: util/match_ops.c.
+
+ Feature: print fsstone elapsed time with sub-second time
+ resolution. Kenji Kikuchi. File: fsstone/fsstone.c.
+
+20080606
+
+ Bitrot: "make test" was broken due to recent changes in
+ code and due to recent changes at mail-abuse.org.
WARN = -Wmissing-prototypes -Wformat
OPTS = 'CC=$(CC)'
DIRS = src/util src/global src/dns src/tls src/xsasl src/milter src/master \
- src/postfix src/smtpstone \
+ src/postfix src/fsstone src/smtpstone \
src/sendmail src/error src/pickup src/cleanup src/smtpd src/local \
src/trivial-rewrite src/qmgr src/oqmgr src/smtp src/bounce \
src/pipe src/showq src/postalias src/postcat src/postconf src/postdrop \
failure cache example (SASL_README). Then include them in
SOHO_README.
- See if "pickup -o content_filter=smtp:127.0.0.1" can be a
- viable alternative to the use of non_smtpd_milters.
+ Look for alternatives for the use of non_smtpd_milters.
+ This involves some way to force local submissions to go
+ through a local SMTP client and server, without triggering
+ "mail loops back to myself" false alarms. The advantage is
+ that it makes smtpd_mumble_restrictions available for local
+ and remote mail; the disadvantage is that it makes local
+ submissions more dependent on networking. One possibility
+ is to use "pickup -o content_filter=smtp:127.0.0.1". Another
+ is to have the pickup or cleanup server drive an SMTP client
+ directly; this would require extension of the mail_stream()
+ interface, plus a way to handle bounced/deferred recipients
+ intelligently.
Consolidate duplicated code in *_server_accept_{pass,inet}().
hints.ai_flags = AI_CANONNAME;
hints.ai_socktype = SOCK_STREAM;
if ((err = getaddrinfo(argv[1], NO_SERVICE, &hints, &res0)) != 0) {
- fprintf(stderr, "host %s not found\n", argv[1]);
+ fprintf(stderr, "host %s not found: %s\n", argv[1], gai_strerror(err));
exit(1);
}
printf("Hostname:\t%s\n", res0->ai_canonname);
@$(EXPORT) make -f Makefile.in Makefile 1>&2
# do not edit below this line - it is generated by 'make depend'
+fsstone.o: ../../include/mail_version.h
+fsstone.o: ../../include/msg.h
+fsstone.o: ../../include/msg_vstream.h
+fsstone.o: ../../include/sys_defs.h
+fsstone.o: ../../include/vbuf.h
+fsstone.o: ../../include/vstream.h
+fsstone.o: fsstone.c
/* SUMMARY
/* measure directory operation overhead
/* SYNOPSIS
+/* .fi
/* \fBfsstone\fR [\fB-cr\fR] [\fB-s \fIsize\fR]
/* \fImsg_count files_per_dir\fR
/* DESCRIPTION
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
-#include <time.h>
+#include <string.h>
+#include <sys/time.h>
/* Utility library. */
#include <msg.h>
#include <msg_vstream.h>
+/* Global directory. */
+
+#include <mail_version.h>
+
/* rename_file - rename a file */
static void rename_file(int old, int new)
{
int op_count;
int max_file;
- time_t start;
+ struct timeval start, end;
int do_rename = 0;
int do_create = 0;
int seq;
/*
* Simulate arrival and delivery of mail messages.
*/
- start = time((time_t *) 0);
+ GETTIMEOFDAY(&start);
while (op_count > 0) {
seq %= max_file;
if (do_create) {
seq++;
op_count--;
}
- printf("elapsed time: %ld\n", (long) time((time_t *) 0) - start);
+ GETTIMEOFDAY(&end);
+ if (end.tv_usec < start.tv_usec) {
+ end.tv_sec--;
+ end.tv_usec += 1000000;
+ }
+ printf("elapsed time: %ld.%06ld\n",
+ (long) (end.tv_sec - start.tv_sec),
+ (long) (end.tv_usec - start.tv_usec));
/*
* Clean up directory fillers.
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20080511"
+#define MAIL_RELEASE_DATE "20080606"
#define MAIL_VERSION_NUMBER "2.6"
#ifdef SNAPSHOT
./namadr_list `pwd`/junk bar 168.100.189.3
./namadr_list `pwd`/junk baz 168.100.189.3
rm -f junk
+./namadr_list 'be.be' x.x.x 127.0.0.1
+./namadr_list 'be/be' x.x.x 127.0.0.1
+./namadr_list '[be:be]' x.x.x 127.0.0.1
+./namadr_list '[be:be]' x.x.x ::1
env foo=x ./namadr_list environ:junk foo 168.100.189.3
env foo=x ./namadr_list environ:junk bar 168.100.189.3
env foo=x ./namadr_list !environ:junk foo 168.100.189.3
foo/168.100.189.3: YES
bar/168.100.189.3: NO
baz/168.100.189.3: YES
+x.x.x/127.0.0.1: NO
+./namadr_list: fatal: bad net/mask pattern: "be/be"
+x.x.x/127.0.0.1: NO
+./namadr_list: fatal: bad address pattern: "be:be"
foo/168.100.189.3: YES
bar/168.100.189.3: NO
foo/168.100.189.3: NO
#define MAIL_SERVER_UNLIMITED 16
#define MAIL_SERVER_PRE_DISCONN 17
#define MAIL_SERVER_PRIVILEGED 18
+#define MAIL_SERVER_WATCHDOG 19
#define MAIL_SERVER_IN_FLOW_DELAY 20
/* This service must be configured with process limit of 0.
/* .IP MAIL_SERVER_PRIVILEGED
/* This service must be configured as privileged.
+/* .IP "MAIL_SERVER_WATCHDOG (int *)"
+/* Override the default 1000s watchdog timeout. The value is
+/* used after command-line and main.cf file processing.
/* .PP
/* The var_use_limit variable limits the number of clients that
/* a server can service before it commits suicide.
static VSTREAM *trigger_server_lock;
static int trigger_server_in_flow_delay;
static unsigned trigger_server_generation;
+static int trigger_server_watchdog = 1000;
/* trigger_server_exit - normal termination */
msg_fatal("service %s requires privileged operation",
service_name);
break;
+ case MAIL_SERVER_WATCHDOG:
+ trigger_server_watchdog = *va_arg(ap, int *);
+ break;
default:
msg_panic("%s: unknown argument type: %d", myname, key);
}
close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC);
close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC);
close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC);
- watchdog = watchdog_create(1000, (WATCHDOG_FN) 0, (char *) 0);
+ watchdog = watchdog_create(trigger_server_watchdog,
+ (WATCHDOG_FN) 0, (char *) 0);
/*
* The event loop, at last.
/*
* Use the multi-threaded skeleton, because no-one else should be
* monitoring our service socket while this process runs.
+ *
+ * XXX The default watchdog timeout for trigger servers is 1000s, while the
+ * cleanup server watchdog timeout is $daemon_timeout (i.e. several
+ * hours). We override the default 1000s timeout to avoid problems with
+ * slow mail submission. The real problem is of course that the
+ * single-threaded pickup server is not a good solution for mail
+ * submissions.
*/
trigger_server_main(argc, argv, pickup_service,
MAIL_SERVER_STR_TABLE, str_table,
MAIL_SERVER_POST_INIT, post_jail_init,
MAIL_SERVER_SOLITARY,
+ MAIL_SERVER_WATCHDOG, &var_daemon_timeout,
0);
}
root_tests:
-# This requires that the DNS server can query mail-abuse.org.
+# This requires that the DNS server can query porcupine.org.
smtpd_check_test: smtpd_check smtpd_check.in smtpd_check.ref smtpd_check_access
../postmap/postmap hash:smtpd_check_access
diff smtpd_check.ref smtpd_check.tmp
rm -f smtpd_check.tmp smtpd_check_access.*
-# This requires that the DNS server can query mail-abuse.org.
+# This requires that the DNS server can query porcupine.org.
smtpd_check_test2: smtpd_check smtpd_check.in2 smtpd_check.ref2 smtpd_check_access
../postmap/postmap hash:smtpd_check_access
diff smtpd_acl.ref smtpd_check.tmp
rm -f smtpd_check.tmp smtpd_check_access.*
-# This requires that the DNS server can query mail-abuse.org.
+# This requires that the DNS server can query porcupine.org.
smtpd_exp_test: smtpd_check smtpd_exp.in smtpd_exp.ref
../postmap/postmap hash:smtpd_check_access
char *var_smtpd_sasl_opts;
char *var_local_rwr_clients;
char *var_smtpd_relay_ccerts;
+char *var_unv_from_why;
+char *var_unv_rcpt_why;
+char *var_stress;
typedef struct {
char *name;
VAR_SMTPD_SASL_OPTS, DEF_SMTPD_SASL_OPTS, &var_smtpd_sasl_opts,
VAR_LOC_RWR_CLIENTS, DEF_LOC_RWR_CLIENTS, &var_local_rwr_clients,
VAR_RELAY_CCERTS, DEF_RELAY_CCERTS, &var_smtpd_relay_ccerts,
+ VAR_UNV_FROM_WHY, DEF_UNV_FROM_WHY, &var_unv_from_why,
+ VAR_UNV_RCPT_WHY, DEF_UNV_RCPT_WHY, &var_unv_rcpt_why,
+ VAR_STRESS, DEF_STRESS, &var_stress,
0,
};
static void string_init(void)
{
- STRING_TABLE *sp;
+ const STRING_TABLE *sp;
for (sp = string_table; sp->name; sp++)
sp->target[0] = mystrdup(sp->defval);
static int string_update(char **argv)
{
- STRING_TABLE *sp;
+ const STRING_TABLE *sp;
for (sp = string_table; sp->name; sp++) {
if (strcasecmp(argv[0], sp->name) == 0) {
int var_smtpd_delay_reject;
int var_allow_untrust_route;
int var_mul_rcpt_code;
-int var_unv_from_code;
-int var_unv_rcpt_code;
+int var_unv_from_rcode;
+int var_unv_from_dcode;
+int var_unv_rcpt_rcode;
+int var_unv_rcpt_dcode;
int var_local_rcpt_code;
int var_relay_rcpt_code;
int var_virt_mailbox_code;
int var_smtpd_rej_unl_rcpt;
int var_plaintext_code;
bool var_smtpd_peername_lookup;
+bool var_smtpd_client_port_log;
static const INT_TABLE int_table[] = {
"msg_verbose", 0, &msg_verbose,
VAR_SMTPD_DELAY_REJECT, DEF_SMTPD_DELAY_REJECT, &var_smtpd_delay_reject,
VAR_ALLOW_UNTRUST_ROUTE, DEF_ALLOW_UNTRUST_ROUTE, &var_allow_untrust_route,
VAR_MUL_RCPT_CODE, DEF_MUL_RCPT_CODE, &var_mul_rcpt_code,
- VAR_UNV_FROM_CODE, DEF_UNV_FROM_CODE, &var_unv_from_code,
- VAR_UNV_RCPT_CODE, DEF_UNV_RCPT_CODE, &var_unv_rcpt_code,
+ VAR_UNV_FROM_RCODE, DEF_UNV_FROM_RCODE, &var_unv_from_rcode,
+ VAR_UNV_FROM_DCODE, DEF_UNV_FROM_DCODE, &var_unv_from_dcode,
+ VAR_UNV_RCPT_RCODE, DEF_UNV_RCPT_RCODE, &var_unv_rcpt_rcode,
+ VAR_UNV_RCPT_DCODE, DEF_UNV_RCPT_DCODE, &var_unv_rcpt_dcode,
VAR_LOCAL_RCPT_CODE, DEF_LOCAL_RCPT_CODE, &var_local_rcpt_code,
VAR_RELAY_RCPT_CODE, DEF_RELAY_RCPT_CODE, &var_relay_rcpt_code,
VAR_VIRT_ALIAS_CODE, DEF_VIRT_ALIAS_CODE, &var_virt_alias_code,
VAR_SMTPD_REJ_UNL_RCPT, DEF_SMTPD_REJ_UNL_RCPT, &var_smtpd_rej_unl_rcpt,
VAR_PLAINTEXT_CODE, DEF_PLAINTEXT_CODE, &var_plaintext_code,
VAR_SMTPD_PEERNAME_LOOKUP, DEF_SMTPD_PEERNAME_LOOKUP, &var_smtpd_peername_lookup,
+ VAR_SMTPD_CLIENT_PORT_LOG, DEF_SMTPD_CLIENT_PORT_LOG, &var_smtpd_client_port_log,
0,
};
static void int_init(void)
{
- INT_TABLE *sp;
+ const INT_TABLE *sp;
for (sp = int_table; sp->name; sp++)
sp->target[0] = sp->defval;
static int int_update(char **argv)
{
- INT_TABLE *ip;
+ const INT_TABLE *ip;
for (ip = int_table; ip->name; ip++) {
if (strcasecmp(argv[0], ip->name) == 0) {
static int rest_update(char **argv)
{
- REST_TABLE *rp;
+ const REST_TABLE *rp;
for (rp = rest_table; rp->name; rp++) {
if (strcasecmp(rp->name, argv[0]) == 0) {
smtpd_delay_reject 0
mynetworks 127.0.0.0/8,168.100.189.0/28
relay_domains porcupine.org
-maps_rbl_domains relays.mail-abuse.org
+maps_rbl_domains dnsbltest.porcupine.org
#
# Test the client restrictions.
#
smtpd_delay_reject 0
mynetworks 127.0.0.0/8,168.100.189.0/28
relay_domains porcupine.org
-maps_rbl_domains relays.mail-abuse.org
+maps_rbl_domains dnsbltest.porcupine.org
#
# Test the client restrictions.
#
OK
>>> relay_domains porcupine.org
OK
->>> maps_rbl_domains relays.mail-abuse.org
+>>> maps_rbl_domains dnsbltest.porcupine.org
OK
>>> #
>>> # Test the client restrictions.
./smtpd_check: warning: support for restriction "reject_maps_rbl" will be removed from Postfix; use "reject_rbl_client domain-name" instead
OK
>>> client foo 127.0.0.2
-./smtpd_check: <queue id>: reject: CONNECT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using relays.mail-abuse.org; from=<foo@friend.bad.domain> proto=SMTP helo=<123.123.123.123>
-554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using relays.mail-abuse.org
+./smtpd_check: <queue id>: reject: CONNECT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from=<foo@friend.bad.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org
>>> #
>>> # Hybrids
>>> #
OK
>>> relay_domains porcupine.org
OK
->>> maps_rbl_domains relays.mail-abuse.org
+>>> maps_rbl_domains dnsbltest.porcupine.org
OK
>>> #
>>> # Test the client restrictions.
./smtpd_check: warning: support for restriction "reject_maps_rbl" will be removed from Postfix; use "reject_rbl_client domain-name" instead
OK
>>> client foo 127.0.0.2
-./smtpd_check: <queue id>: reject: CONNECT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using relays.mail-abuse.org; from=<foo@friend.bad.domain> proto=SMTP helo=<friend.bad.domain>
-554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using relays.mail-abuse.org
+./smtpd_check: <queue id>: reject: CONNECT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from=<foo@friend.bad.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org
>>> #
>>> # unknown sender/recipient domain
>>> #
smtpd_delay_reject 0
mynetworks 127.0.0.0/8,168.100.189.0/28
relay_domains porcupine.org
-maps_rbl_domains relays.mail-abuse.org
+maps_rbl_domains dnsbltest.porcupine.org
rbl_reply_maps hash:smtpd_check_access
helo foobar
#
client foo 127.0.0.2
rcpt rname@rdomain
#
-recipient_restrictions reject_rbl_client,relays.mail-abuse.org
+recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org
client spike.porcupine.org 168.100.189.2
rcpt rname@rdomain
client foo 127.0.0.2
rcpt rname@rdomain
-recipient_restrictions reject_rbl_client,relays.mail-abuse.org=127.0.0.2
+recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org=127.0.0.2
client foo 127.0.0.2
rcpt rname@rdomain
client foo 127.0.0.1
OK
>>> relay_domains porcupine.org
OK
->>> maps_rbl_domains relays.mail-abuse.org
+>>> maps_rbl_domains dnsbltest.porcupine.org
OK
>>> rbl_reply_maps hash:smtpd_check_access
OK
>>> client foo 127.0.0.2
OK
>>> rcpt rname@rdomain
-./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using relays.mail-abuse.org; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
-554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using relays.mail-abuse.org
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org
>>> #
->>> recipient_restrictions reject_rbl_client,relays.mail-abuse.org
+>>> recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org
OK
>>> client spike.porcupine.org 168.100.189.2
OK
>>> client foo 127.0.0.2
OK
>>> rcpt rname@rdomain
-./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using relays.mail-abuse.org; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
-554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using relays.mail-abuse.org
->>> recipient_restrictions reject_rbl_client,relays.mail-abuse.org=127.0.0.2
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org
+>>> recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org=127.0.0.2
OK
>>> client foo 127.0.0.2
OK
>>> rcpt rname@rdomain
-./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using relays.mail-abuse.org=127.0.0.2; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
-554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using relays.mail-abuse.org=127.0.0.2
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org=127.0.0.2; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org=127.0.0.2
>>> client foo 127.0.0.1
OK
>>> rcpt rname@rdomain
}
/*
- * Reject non-printable ASCII characters from UTF-8 content.
+ * Reject non-printable ASCII characters in UTF-8 content.
*
* Note: the code below does not find control characters in illegal UTF-8
* sequences. It's OpenSSL's job to produce valid UTF-8, and reportedly,
diff ctable.ref ctable.tmp
rm -f ctable.tmp
+# On Linux, following test may require "modprobe ipv6" to enable IPv6.
+
inet_addr_list_test: inet_addr_list
./inet_addr_list `cat inet_addr_list.in` >inet_addr_list.tmp 2>&1
diff inet_addr_list.ref inet_addr_list.tmp
-./dict_open: warning: pcre map dict_pcre.map, line 1: ignoring extra text after IF
+./dict_open: warning: pcre map dict_pcre.map, line 1: ignoring extra text after IF statement: "fodder"
+./dict_open: warning: pcre map dict_pcre.map, line 1: do not prepend whitespace to statements between IF and ENDIF
./dict_open: warning: pcre map dict_pcre.map, line 5: ignoring extra text after ENDIF
./dict_open: warning: pcre map dict_pcre.map, line 8: unknown regexp option "!": skipping this rule
./dict_open: warning: dict_pcre.map, line 9: no replacement text: using empty string
-./dict_open: warning: regexp map dict_pcre.map, line 10: out of range replacement index "5": skipping this rule
+./dict_open: warning: pcre map dict_pcre.map, line 10: out of range replacement index "5": skipping this rule
./dict_open: warning: pcre map dict_pcre.map, line 17: $number found in negative match replacement text: skipping this rule
./dict_open: warning: pcre map dict_pcre.map, line 22: no regexp: skipping this rule
> get true
if (hex_unquote(raw, STR(hex)) == 0)
msg_fatal("bad input: %.100s", STR(hex));
if (LEN(raw) != len)
- msg_fatal("len %ld != raw len %ld", len, LEN(raw));
+ msg_fatal("len %ld != raw len %ld", (long) len, (long) LEN(raw));
if (vstream_fwrite(VSTREAM_OUT, STR(raw), LEN(raw)) != LEN(raw))
msg_fatal("write error: %m");
}
* Postfix; if not, then Postfix has no business dealing with IPv4
* addresses anyway.
*
- * - Don't bother if the pattern is a bare IPv4 address. That form would
- * have been matched with the strcasecmp() call above.
+ * - Don't bother unless the pattern is either an IPv6 address or net/mask.
*
- * - Don't bother if the pattern isn't an address or address/mask.
+ * We can safely skip IPv4 address patterns because their form is
+ * unambiguous and they did not match in the strcasecmp() calls above.
+ *
+ * XXX We MUST skip (parent) domain names, which may appear in NAMADR_LIST
+ * input, to avoid triggering false cidr_match_parse() errors.
+ *
+ * The last two conditions below are for backwards compatibility with
+ * earlier Postfix versions: don't abort with fatal errors on junk that
+ * was silently ignored (principle of least astonishment).
*/
if (!strchr(addr, ':') != !strchr(pattern, ':')
+ || pattern[strcspn(pattern, ":/")] == 0
|| pattern[strspn(pattern, V4_ADDR_STRING_CHARS)] == 0
|| pattern[strspn(pattern, V6_ADDR_STRING_CHARS "[]/")] != 0)
return (0);
while (--argc && *++argv) {
mask = name_mask("test", table, *argv);
vstream_printf("%s -> 0x%x -> %s\n",
- *argv, mask, str_name_mask((VSTRING *) 0, "mask_test",
- table, mask));
+ *argv, mask, str_name_mask("mask_test", table, mask));
vstream_fflush(VSTREAM_OUT);
}
vstring_free(buf);