diff --git a/postfix/HISTORY b/postfix/HISTORY index 955c282ad..94c94d7b3 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -29015,3 +29015,32 @@ Apologies for any names omitted. Dovecot auth client did not attempt to create a new connection after an I/O error on an existing connection. Reported by Oleksandr Kozmenko. File: xsasl/xsasl_dovecot_server.c. + +20240315 + + Code health: two typos canceled each other's effect. Fix + by Michael Tokarev. No change in compiler output. File: + util/vstring_vstream.c. + +20250316 + + Bugfix (defect introduced: date 19991116): when appending a + setting to a main.cf or master.cf file that did not end in + a newline character, the "postconf -e" command did not add + an extra newline character before appending the new setting, + causing information to become garbled. Fix by Michael + Tokarev. File: postconf/postconf_edit.c. + +20259317 + + Documentation: added text to clarify the difference between + SMTP connection reuse and TLS session resumption, and that + these can be combined together. File: proto/TLSRPT_README.html. + +20250321 + + Safety: the SQLite client now logs a warning when a query + uses double quotes instead of the Postfix-recommended single + quotes. Oscar Bataille reported that the non-recommended + form is not protected against SQL injection. Files: + global/dict_sqlite.c, global/dict_sqlite_test.c. diff --git a/postfix/README_FILES/TLSRPT_README b/postfix/README_FILES/TLSRPT_README index 91e72b526..4a2e88f35 100644 --- a/postfix/README_FILES/TLSRPT_README +++ b/postfix/README_FILES/TLSRPT_README @@ -7,6 +7,7 @@ TTaabbllee ooff CCoonntteennttss * Introduction * Building Postfix with TLSRPT support * Turning on TLSRPT + * Connection reuse versus session resumption * TLSRPT Status logging * Delivering TLSRPT summaries via email * MTA-STS Support via smtp_tls_policy_maps @@ -27,7 +28,7 @@ A policy for domain example.com could look like this: Instead of mailto:, a policy may specify an https: destination. -The diagram below shows how Postfix TLS handshake success and failure events +The diagram below shows how successful or failed Postfix TLS handshake events are collected and processed into daily summary reports. Postfix SMTP and TLSRPT client TLSRPT collector, Email or HTTP @@ -124,6 +125,36 @@ Notes: For details on how to run the TLSRPT collection and reporting infrastructure, see the documentation at https://github.com/sys4/tlsrpt-reporter. +CCoonnnneeccttiioonn rreeuussee vveerrssuuss sseessssiioonn rreessuummppttiioonn + +The Postfix SMTP client implements two kinds of reuse: + + * SSMMTTPP CCoonnnneeccttiioonn rreeuussee:: a Postfix SMTP client creates a new SMTP connection, + sends one email message, and saves the connection instead of closing it. + Later, some SMTP client reuses that connection, sends an email message, and + saves or closes the connection depending on whether it has reached some + reuse limit. Each connection can be used by only one Postfix SMTP client at + a time. + + * TTLLSS SSeessssiioonn rreessuummppttiioonn:: a Postfix SMTP client saves the result from a "new" + TLS handshake. Later, one or more SMTP clients create a new SMTP connection + and resume the saved TLS session on their new connection. + +Of course there is a third case: + + * CCoommbbiinneedd rreeuussee aanndd rreessuummppttiioonn:: a Postfix SMTP client creates a new SMTP + connection, sends one email message, saves the result from a "new" TLS + handshake, and also saves the connection instead of closing it. Later, one + SMTP client reuses (and saves) that connection, one client at a time, and + one or more clients create a new SMTP connection and resume the saved TLS + session on their new connection. + +In all cases, there is no TLS handshake when a saved SMTP connection is reused, +and there is no "new" TLS handshake when a saved TLS session is resumed. + +As described next, Postfix will by default log and generate only a TLSRPT event +for a "new" TLS handshake. + TTLLSSRRPPTT SSttaattuuss llooggggiinngg With TLSRPT support turned on, the Postfix TLSRPT client will not only report diff --git a/postfix/html/TLSRPT_README.html b/postfix/html/TLSRPT_README.html index 877c41e3a..6d3746e4c 100644 --- a/postfix/html/TLSRPT_README.html +++ b/postfix/html/TLSRPT_README.html @@ -25,6 +25,7 @@
  • Introduction
  • Building Postfix with TLSRPT support
  • Turning on TLSRPT
  • +
  • Connection reuse versus session resumption
  • TLSRPT Status logging
  • Delivering TLSRPT summaries via email
  • MTA-STS Support via smtp_tls_policy_maps
  • @@ -52,8 +53,8 @@ _smtp._tls.example.com. IN TXT "v=TLSRPTv1; rua=mailto:smtp-tls-report@example.c

    Instead of mailto:, a policy may specify an https: destination.

    -

    The diagram below shows how Postfix TLS handshake success and -failure events are collected and processed into daily summary +

    The diagram below shows how successful or failed Postfix TLS +handshake events are collected and processed into daily summary reports.

    @@ -203,6 +204,49 @@ programs should create sockets there.

    infrastructure, see the documentation at https://github.com/sys4/tlsrpt-reporter. +

    Connection reuse versus session resumption +

    + +

    The Postfix SMTP client implements two kinds of reuse:

    + + + +

    Of course there is a third case:

    + + + +

    In all cases, there is no TLS handshake when a saved SMTP connection +is reused, and there is no "new" TLS handshake when a saved TLS session +is resumed.

    + +

    As described next, Postfix will by default log and generate only a +TLSRPT event for a "new" TLS handshake.

    +

    TLSRPT Status logging

    With TLSRPT support turned on, the Postfix TLSRPT client will diff --git a/postfix/proto/TLSRPT_README.html b/postfix/proto/TLSRPT_README.html index 7778a673c..0531b0381 100644 --- a/postfix/proto/TLSRPT_README.html +++ b/postfix/proto/TLSRPT_README.html @@ -25,6 +25,7 @@

  • Introduction
  • Building Postfix with TLSRPT support
  • Turning on TLSRPT
  • +
  • Connection reuse versus session resumption
  • TLSRPT Status logging
  • Delivering TLSRPT summaries via email
  • MTA-STS Support via smtp_tls_policy_maps
  • @@ -52,8 +53,8 @@ _smtp._tls.example.com. IN TXT "v=TLSRPTv1; rua=mailto:smtp-tls-report@example.c

    Instead of mailto:, a policy may specify an https: destination.

    -

    The diagram below shows how Postfix TLS handshake success and -failure events are collected and processed into daily summary +

    The diagram below shows how successful or failed Postfix TLS +handshake events are collected and processed into daily summary reports.

    @@ -203,6 +204,49 @@ programs should create sockets there.

    infrastructure, see the documentation at https://github.com/sys4/tlsrpt-reporter. +

    Connection reuse versus session resumption +

    + +

    The Postfix SMTP client implements two kinds of reuse:

    + + + +

    Of course there is a third case:

    + + + +

    In all cases, there is no TLS handshake when a saved SMTP connection +is reused, and there is no "new" TLS handshake when a saved TLS session +is resumed.

    + +

    As described next, Postfix will by default log and generate only a +TLSRPT event for a "new" TLS handshake.

    +

    TLSRPT Status logging

    With TLSRPT support turned on, the Postfix TLSRPT client will diff --git a/postfix/proto/stop.spell-history b/postfix/proto/stop.spell-history index 1b85ee768..1a66bdb7f 100644 --- a/postfix/proto/stop.spell-history +++ b/postfix/proto/stop.spell-history @@ -104,3 +104,4 @@ Gueven Oemer Kozmenko Oleksandr +Bataille diff --git a/postfix/src/global/Makefile.in b/postfix/src/global/Makefile.in index 67520c7e2..6ed62d412 100644 --- a/postfix/src/global/Makefile.in +++ b/postfix/src/global/Makefile.in @@ -131,7 +131,7 @@ TESTPROG= domain_list dot_lockfile mail_addr_crunch mail_addr_find \ fold_addr smtp_reply_footer mail_addr_map normalize_mailhost_addr \ haproxy_srvr map_search delivered_hdr login_sender_match \ compat_level config_known_tcp_ports hfrom_format rfc2047_code \ - ascii_header_text sendopts_test + ascii_header_text sendopts_test dict_sqlite_test LIBS = ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) LIB_DIR = ../../lib @@ -408,6 +408,10 @@ ascii_header_text: ascii_header_text.c $(LIB) $(LIBS) sendopts_test: sendopts_test.c $(LIB) $(LIBS) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) +dict_sqlite_test: dict_sqlite_test.c dict_sqlite.o $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c dict_sqlite.o $(LIB) $(LIBS) \ + $(SYSLIBS) $(AUXLIBS_SQLITE) + config_known_tcp_ports: config_known_tcp_ports.c $(LIB) $(LIBS) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) @@ -421,7 +425,7 @@ tests: tok822_test mime_tests strip_addr_test tok822_limit_test \ normalize_mailhost_addr_test haproxy_srvr_test map_search_test \ delivered_hdr_test login_sender_match_test compat_level_test \ config_known_tcp_ports_test hfrom_format_test rfc2047_code_test \ - ascii_header_text_test test_sendopts + ascii_header_text_test test_sendopts test_dict_sqlite mime_tests: mime_test mime_nest mime_8bit mime_dom mime_trunc mime_cvt \ mime_cvt2 mime_cvt3 mime_garb1 mime_garb2 mime_garb3 mime_garb4 @@ -794,7 +798,10 @@ ascii_header_text_test: update ascii_header_text $(SHLIB_ENV) $(VALGRIND) ./ascii_header_text test_sendopts: update sendopts_test - -$(SHLIB_ENV) $(VALGRIND) ./sendopts_test + $(SHLIB_ENV) $(VALGRIND) ./sendopts_test + +test_dict_sqlite: update dict_sqlite_test + $(SHLIB_ENV) $(VALGRIND) ./dict_sqlite_test clean: rm -f *.o $(LIB) *core $(TESTPROG) junk $(MAPS) @@ -1331,6 +1338,19 @@ dict_sqlite.o: db_common.h dict_sqlite.o: dict_sqlite.c dict_sqlite.o: dict_sqlite.h dict_sqlite.o: string_list.h +dict_sqlite_test.o: ../../include/argv.h +dict_sqlite_test.o: ../../include/check_arg.h +dict_sqlite_test.o: ../../include/dict.h +dict_sqlite_test.o: ../../include/msg.h +dict_sqlite_test.o: ../../include/msg_vstream.h +dict_sqlite_test.o: ../../include/myflock.h +dict_sqlite_test.o: ../../include/stringops.h +dict_sqlite_test.o: ../../include/sys_defs.h +dict_sqlite_test.o: ../../include/vbuf.h +dict_sqlite_test.o: ../../include/vstream.h +dict_sqlite_test.o: ../../include/vstring.h +dict_sqlite_test.o: dict_sqlite.h +dict_sqlite_test.o: dict_sqlite_test.c domain_list.o: ../../include/argv.h domain_list.o: ../../include/check_arg.h domain_list.o: ../../include/match_list.h diff --git a/postfix/src/global/dict_sqlite.c b/postfix/src/global/dict_sqlite.c index c267ce4c6..c8881f368 100644 --- a/postfix/src/global/dict_sqlite.c +++ b/postfix/src/global/dict_sqlite.c @@ -34,9 +34,13 @@ /* Must be O_RDONLY. /* .IP dict_flags /* See dict_open(3). +/* DIAGNOSTICS +/* dict_sqlite_open() logs a warning when the query parameter value +/* does not use the recommended '' quotes to protect against SQL +/* injection (bad examples; no quotes or "" quotes). /* SEE ALSO /* dict(3) generic dictionary manager -/* sqlite_table(5) sqlite client configuration +/* sqlite_table(5) Postfix sqlite client configuration /* AUTHOR(S) /* Axel Steiner /* ast@treibsand.com @@ -46,12 +50,16 @@ /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* porcupine.org /*--*/ /* System library. */ #include #include +#include #ifdef HAS_SQLITE #include @@ -250,6 +258,38 @@ static const char *dict_sqlite_lookup(DICT *dict, const char *name) retval : 0); } +/* flag_non_recommended_query - as the name says. */ + +static void flag_non_recommended_query(const char *query, + const char *sqlitecf) +{ + const char *cp; + int in_quote; + const int squote = '\''; + const int dquote = '"'; + + for (in_quote = 0, cp = query; *cp != 0; cp++) { + if (in_quote == 0) { + if (*cp == squote || *cp == dquote) + in_quote = *cp; + } else if (*cp == in_quote) { + in_quote = 0; + } + if (in_quote == squote) + continue; + if (*cp == '%') { + if (cp[1] == '%') { + cp += 1; + } else if (ISALNUM(cp[1])) { + msg_warn("%s:%s: query >%s< contains >%.2s< without the " + "recommended '' quotes", DICT_TYPE_SQLITE, sqlitecf, + query, cp); + cp += 1; + } + } + } +} + /* sqlite_parse_config - parse sqlite configuration file */ static void sqlite_parse_config(DICT_SQLITE *dict_sqlite, const char *sqlitecf) @@ -268,6 +308,8 @@ static void sqlite_parse_config(DICT_SQLITE *dict_sqlite, const char *sqlitecf) db_common_sql_build_query(buf, dict_sqlite->parser); dict_sqlite->query = vstring_export(buf); } + /* Flag %[a-zA-Z0-9] if not protected with ''. */ + flag_non_recommended_query(dict_sqlite->query, sqlitecf); dict_sqlite->result_format = cfg_get_str(dict_sqlite->parser, "result_format", "%s", 1, 0); dict_sqlite->expansion_limit = diff --git a/postfix/src/global/dict_sqlite.h b/postfix/src/global/dict_sqlite.h index fb2bdf25f..1c0e8b6a6 100644 --- a/postfix/src/global/dict_sqlite.h +++ b/postfix/src/global/dict_sqlite.h @@ -23,7 +23,6 @@ extern DICT *dict_sqlite_open(const char *, int, int); - /* AUTHOR(S) /* Axel Steiner /* ast@treibsand.com diff --git a/postfix/src/global/dict_sqlite_test.c b/postfix/src/global/dict_sqlite_test.c new file mode 100644 index 000000000..e3cce1e4d --- /dev/null +++ b/postfix/src/global/dict_sqlite_test.c @@ -0,0 +1,247 @@ +/*++ +/* NAME +/* dict_sqlite_test 1t +/* SUMMARY +/* dict_sqlite unit test +/* SYNOPSIS +/* ./dict_sqlite_test +/* DESCRIPTION +/* dict_sqlite_test runs and logs each configured test, reports if +/* a test is a PASS or FAIL, and returns an exit status of zero if +/* all tests are a PASS. +/* +/* Each test creates a temporary test database and a corresponding +/* Postfix sqlite client configuration file, both having unique +/* names. Otherwise, each test is hermetic. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema porcupine.org +/*--*/ + +/* + /* + * System library. + */ +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Global library. + */ +#include + + /* + * TODO(wietse) make this a proper VSTREAM interface or test helper API. + */ + +/* vstream_swap - capture output for testing */ + +static void vstream_swap(VSTREAM *one, VSTREAM *two) +{ + VSTREAM save; + + save = *one; + *one = *two; + *two = save; +} + + /* + * Override the printable.c module because it may break some tests. + * + * TODO(wietse) move this to a fake_printable.c module that can override all + * printable.c global symbols. + */ +int util_utf8_enable; + +char *printable_except(char *string, int replacement, const char *except) +{ + return (string); +} + + /* + * Scaffolding for dict_sqlite(3) tests. + */ + +/* create_and_populate_db - create an empty database and optionally populate */ + +static void create_and_populate_db(char *dbpath, const char *commands) +{ + int fd; + + /* + * Create an empty database file with a unique name. Assume that an + * adversary cannot rename or remove the file. + */ + if ((fd = mkstemp(dbpath)) < 0) + msg_fatal("mkstemp(\"%s\"): %m", dbpath); + if (close(fd) < 0) + msg_fatal("close %s: %m", dbpath); + + /* + * TODO(wietse) Open the database file, prepare and execute commands + * to populate the database, and close the database. + */ + if (commands) { + msg_fatal("commands are not yet supported"); + } +} + +/* create_and_populate_cf - create sqlite_table(5) configuration file */ + +static void create_and_populate_cf(char *cfpath, const char *dbpath, + const char *cftext) +{ + int fd; + VSTREAM *fp; + + /* + * Create an empty sqlite_table(5) configuration file with a unique name. + * Assume that an adversary cannot rename or remove the file. + */ + if ((fd = mkstemp(cfpath)) < 0) + msg_fatal("mkstemp(\"%s\"): %m", cfpath); + if ((fp = vstream_fdopen(fd, O_WRONLY)) == 0) + msg_fatal("vstream_fdopen: %m"); + (void) vstream_fprintf(fp, "%s\ndbpath = %s\n", cftext, dbpath); + if (vstream_fclose(fp) != 0) + msg_fatal("vstream_fdclose: %m"); +} + + /* + * Test structure. Some tests may come their own. + */ +typedef struct TEST_CASE { + const char *label; + int (*action) (const struct TEST_CASE *); + const char *commands; /* commands or null */ + const char *settings; /* sqlite_table(5) */ + const char *exp_warning; /* substring match or null */ +} TEST_CASE; + +#define PASS (0) +#define FAIL (1) + +#define PATH_TEMPLATE "/tmp/test-XXXXXXX" + +/* test_flag_non_recommended_query - flag non-recommended query payloads */ + +static int test_flag_non_recommended_query(const TEST_CASE *tp) +{ + static VSTRING *msg_buf; + VSTREAM *memory_stream; + const char template[] = PATH_TEMPLATE; + char dbpath[sizeof(template)]; + char cfpath[sizeof(template)]; + DICT *dict; + + if (msg_buf == 0) + msg_buf = vstring_alloc(100); + + /* Prepare scaffolding database and configuration files. */ + memcpy(dbpath, template, sizeof(dbpath)); + create_and_populate_db(dbpath, tp->commands); + memcpy(cfpath, template, sizeof(cfpath)); + create_and_populate_cf(cfpath, dbpath, tp->settings); + + /* Run the test with custom STDERR stream. */ + VSTRING_RESET(msg_buf); + VSTRING_TERMINATE(msg_buf); + if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0) + msg_fatal("open memory stream: %m"); + vstream_swap(VSTREAM_ERR, memory_stream); + if ((dict = dict_sqlite_open(cfpath, O_RDONLY, DICT_FLAG_UTF8_REQUEST)) != 0) + dict_close(dict); + vstream_swap(memory_stream, VSTREAM_ERR); + if (vstream_fclose(memory_stream)) + msg_fatal("close memory stream: %m"); + + /* Cleanup scaffolding database and configuration files. */ + if (unlink(dbpath) < 0) + msg_fatal("unlink %s: %m", dbpath); + if (unlink(cfpath) < 0) + msg_fatal("unlink %s: %m", cfpath); + + /* Verify the results. */ + if (tp->exp_warning == 0 && VSTRING_LEN(msg_buf) > 0) { + msg_warn("got warning ``%s'', want ``null''", vstring_str(msg_buf)); + return (FAIL); + } + if (tp->exp_warning != 0 + && strstr(vstring_str(msg_buf), tp->exp_warning) == 0) { + msg_warn("got warning ``%s'', want ``%s''", + vstring_str(msg_buf), tp->exp_warning); + return (FAIL); + } + return (PASS); +} + + /* + * The list of test cases. + */ +static const TEST_CASE test_cases[] = { + + /* + * Tests to flag non-recommended query forms. These create an empty test + * database, and open it with the dict_sqlite client without querying it. + */ + {.label = "no_dynamic_payload", + .action = test_flag_non_recommended_query, + .settings = "query = select a from b where c = 5", + }, + {.label = "dynamic_payload_inside_recommended_quotes", + .action = test_flag_non_recommended_query, + .settings = "query = select a from b where c = 'xx%syy'", + }, + {.label = "dynamic_payload_without_quotes", + .action = test_flag_non_recommended_query, + .settings = "query = select s from b where c = xx%syy", + .exp_warning = "contains >%s< without the recommended '' quotes", + }, + {.label = "payload_inside_double_quotes", + .action = test_flag_non_recommended_query, + .settings = "query = select s from b where c = \"xx%syy\"", + .exp_warning = "contains >%s< without the recommended '' quotes", + }, + + /* + * TODO: Tests that actually populate a test database, and that query it + * with the dict_sqlite client. + */ + {0}, +}; + +int main(int argc, char **argv) +{ + const TEST_CASE *tp; + int pass = 0; + int fail = 0; + + msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR); + + for (tp = test_cases; tp->label != 0; tp++) { + int test_failed; + + msg_info("RUN %s", tp->label); + test_failed = tp->action(tp); + if (test_failed) { + msg_info("FAIL %s", tp->label); + fail++; + } else { + msg_info("PASS %s", tp->label); + pass++; + } + } + msg_info("PASS=%d FAIL=%d", pass, fail); + exit(fail != 0); +} diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 5996ab330..87fd6ced4 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -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 "20250304" +#define MAIL_RELEASE_DATE "20250323" #define MAIL_VERSION_NUMBER "3.11" #ifdef SNAPSHOT diff --git a/postfix/src/postconf/postconf_edit.c b/postfix/src/postconf/postconf_edit.c index 7085acd72..437f2a870 100644 --- a/postfix/src/postconf/postconf_edit.c +++ b/postfix/src/postconf/postconf_edit.c @@ -113,8 +113,13 @@ static char *pcf_find_cf_info(VSTRING *buf, VSTREAM *dst) static char *pcf_next_cf_line(VSTRING *buf, VSTREAM *src, VSTREAM *dst, int *lineno) { char *cp; + int last_char; - while (vstring_get(buf, src) != VSTREAM_EOF) { + while ((last_char = vstring_get(buf, src)) != VSTREAM_EOF) { + if (last_char != '\n') { + VSTRING_ADDCH(buf, '\n'); + VSTRING_TERMINATE(buf); + } if (lineno) *lineno += 1; if ((cp = pcf_find_cf_info(buf, dst)) != 0) diff --git a/postfix/src/util/vstring_vstream.c b/postfix/src/util/vstring_vstream.c index 451cc5017..3bf5e050f 100644 --- a/postfix/src/util/vstring_vstream.c +++ b/postfix/src/util/vstring_vstream.c @@ -123,7 +123,7 @@ /* * Macro to return the last character added to a VSTRING, for consistency. */ -#define VSTRING_GET_RESULT(vp, baselen) \ +#define VSTRING_GET_RESULT(vp, base_len) \ (VSTRING_LEN(vp) > (base_len) ? vstring_end(vp)[-1] : VSTREAM_EOF) /* vstring_get_flags - read line from file, keep newline */ @@ -142,7 +142,7 @@ int vstring_get_flags(VSTRING *vp, VSTREAM *fp, int flags) break; } VSTRING_TERMINATE(vp); - return (VSTRING_GET_RESULT(vp, baselen)); + return (VSTRING_GET_RESULT(vp, base_len)); } /* vstring_get_flags_nonl - read line from file, strip newline */ @@ -158,7 +158,7 @@ int vstring_get_flags_nonl(VSTRING *vp, VSTREAM *fp, int flags) while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n') VSTRING_ADDCH(vp, c); VSTRING_TERMINATE(vp); - return (c == '\n' ? c : VSTRING_GET_RESULT(vp, baselen)); + return (c == '\n' ? c : VSTRING_GET_RESULT(vp, base_len)); } /* vstring_get_flags_null - read null-terminated string from file */ @@ -174,7 +174,7 @@ int vstring_get_flags_null(VSTRING *vp, VSTREAM *fp, int flags) while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0) VSTRING_ADDCH(vp, c); VSTRING_TERMINATE(vp); - return (c == 0 ? c : VSTRING_GET_RESULT(vp, baselen)); + return (c == 0 ? c : VSTRING_GET_RESULT(vp, base_len)); } /* vstring_get_flags_bound - read line from file, keep newline, up to bound */ @@ -197,7 +197,7 @@ int vstring_get_flags_bound(VSTRING *vp, VSTREAM *fp, int flags, break; } VSTRING_TERMINATE(vp); - return (VSTRING_GET_RESULT(vp, baselen)); + return (VSTRING_GET_RESULT(vp, base_len)); } /* vstring_get_flags_nonl_bound - read line from file, strip newline, up to bound */ @@ -217,7 +217,7 @@ int vstring_get_flags_nonl_bound(VSTRING *vp, VSTREAM *fp, int flags, while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n') VSTRING_ADDCH(vp, c); VSTRING_TERMINATE(vp); - return (c == '\n' ? c : VSTRING_GET_RESULT(vp, baselen)); + return (c == '\n' ? c : VSTRING_GET_RESULT(vp, base_len)); } /* vstring_get_flags_null_bound - read null-terminated string from file */ @@ -237,7 +237,7 @@ int vstring_get_flags_null_bound(VSTRING *vp, VSTREAM *fp, int flags, while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0) VSTRING_ADDCH(vp, c); VSTRING_TERMINATE(vp); - return (c == 0 ? c : VSTRING_GET_RESULT(vp, baselen)); + return (c == 0 ? c : VSTRING_GET_RESULT(vp, base_len)); } #ifdef TEST