From: Wietse Z Venema Date: Wed, 15 Jul 2026 05:00:00 +0000 (-0500) Subject: postfix-3.12-20260715 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=8d353a3d28b308b28190e19bd5cd2c5545d7f2b8;p=thirdparty%2Fpostfix.git postfix-3.12-20260715 --- diff --git a/postfix/.indent.pro b/postfix/.indent.pro index 81780bfb5..91d233cad 100644 --- a/postfix/.indent.pro +++ b/postfix/.indent.pro @@ -241,6 +241,8 @@ -TMOCK_APPL_SIG -TMOCK_APPL_STATUS -TMOCK_EXPECT +-TMOCK_FCNTL_REQ +-TMOCK_FLOCK_REQ -TMOCK_OPEN_AS_REQ -TMOCK_SERVER -TMOCK_SPAWN_CMD_REQ diff --git a/postfix/HISTORY b/postfix/HISTORY index 92aa81bcc..7318d3cfc 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -31717,6 +31717,13 @@ Apologies for any names omitted. file descriptor twice (without any adverse effect). Files: nbbio.[hc]. +20260715 + + Code hygiene: myflock() did not reject all invalid requests. + Reported by Qualys, assisted by Claude Mythos Preview. Also + added proper unit tests. Files: myflock.[hc], myflock_test.c, + wrap_fcntl.[hc], mock_fcntl.[hc]. + TODO Reorganize PTEST_LIB, PMOCK_LIB, TESTLIB, TESTLIBS, etc. diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 7e43f5231..e553abe85 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 "20260713" +#define MAIL_RELEASE_DATE "20260715" #define MAIL_VERSION_NUMBER "3.12" #ifdef SNAPSHOT diff --git a/postfix/src/testing/Makefile.in b/postfix/src/testing/Makefile.in index 3a77e6b34..ca6d6841c 100644 --- a/postfix/src/testing/Makefile.in +++ b/postfix/src/testing/Makefile.in @@ -3,11 +3,11 @@ SRCS = mock_myaddrinfo.c mock_dns_lookup.c mock_servent.c \ mock_getaddrinfo.c match_basic.c match_addr.c make_addr.c \ addrinfo_to_string.c make_attr.c match_attr.c \ nosleep.c dict_test_helper.c mock_open_as.c mock_spawn_command.c \ - mock_stat.c msg_capture.c mock_dict.c + mock_stat.c msg_capture.c mock_dict.c mock_fcntl.c LIB_OBJ = match_basic.o match_addr.o make_addr.o addrinfo_to_string.o \ make_attr.o match_attr.o \ dict_test_helper.o mock_open_as.o mock_spawn_command.o \ - mock_stat.o msg_capture.o mock_dict.o + mock_stat.o msg_capture.o mock_dict.o mock_fcntl.o MOCK_OBJ= mock_myaddrinfo.o mock_dns_lookup.o mock_servent.o \ mock_getaddrinfo.o mock_server.o TEST_OBJ = mock_dns_lookup_test.o mock_getaddrinfo_test.o \ @@ -17,7 +17,7 @@ HDRS = mock_myaddrinfo.h mock_dns.h mock_servent.h mock_getaddrinfo.h \ match_basic.h match_addr.h make_addr.h addrinfo_to_string.h \ mock_server.h make_attr.h match_attr.h \ dict_test_helper.h mock_open_as.h mock_spawn_command.h \ - mock_stat.h msg_capture.h mock_dict.h + mock_stat.h msg_capture.h mock_dict.h mock_fcntl.h TESTSRC = DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) CFLAGS = $(DEBUG) $(OPT) $(DEFS) @@ -340,6 +340,18 @@ mock_dns_lookup_test.o: ../../include/vstream.h mock_dns_lookup_test.o: ../../include/vstring.h mock_dns_lookup_test.o: mock_dns.h mock_dns_lookup_test.o: mock_dns_lookup_test.c +mock_fcntl.o: ../../include/argv.h +mock_fcntl.o: ../../include/binhash.h +mock_fcntl.o: ../../include/check_arg.h +mock_fcntl.o: ../../include/msg.h +mock_fcntl.o: ../../include/msg_jmp.h +mock_fcntl.o: ../../include/ptest.h +mock_fcntl.o: ../../include/sys_defs.h +mock_fcntl.o: ../../include/vbuf.h +mock_fcntl.o: ../../include/vstream.h +mock_fcntl.o: ../../include/vstring.h +mock_fcntl.o: mock_fcntl.c +mock_fcntl.o: mock_fcntl.h mock_getaddrinfo.o: ../../include/argv.h mock_getaddrinfo.o: ../../include/check_arg.h mock_getaddrinfo.o: ../../include/msg.h diff --git a/postfix/src/testing/mock_fcntl.c b/postfix/src/testing/mock_fcntl.c new file mode 100644 index 000000000..dab67cc4c --- /dev/null +++ b/postfix/src/testing/mock_fcntl.c @@ -0,0 +1,228 @@ +/*++ +/* NAME +/* mock_fcntl 3 +/* SUMMARY +/* mock fcntl/flock +/* SYNOPSIS +/* #include +/* +/* typedef struct MOCK_FCNTL_REQ { +/* .in +4 +/* int want_fd; +/* int want_cmd; +/* union { +/* .in +4 +/* struct flock fl; +/* int flags; +/* .in -4 +/* } want_arg; +/* int out_ret; +/* int out_errno; +/* .in -4 +/* } MOCK_FCNTL_REQ; +/* +/* void setup_mock_fcntl(const MOCK_FCNTL_REQ *request) +/* +/* void teardown_mock_fcntl(void) +/* +/* typedef struct MOCK_FLOCK_REQ { +/* .in +4 +/* int want_fd; +/* int want_cmd; +/* .in -4 +/* int out_ret; +/* int out_errno; +/* } MOCK_FLOCK_REQ; +/* +/* void setup_mock_flock(const MOCK_FLOCK_REQ *request) +/* +/* void teardown_mock_flock(void) +/* DESCRIPTION +/* This module provides mock fcntl() and flock() implementations +/* that return static results for expected calls. +/* +/* setup_mock_fcntl() adds a mapping from fcntl() requests to +/* fcntl() results. It is an error to enter multiple mappings for +/* the same request. setup_mock_fcntl() makes no copy of its input: +/* .IP want_fd +/* .IP want_cmd +/* .IP want_arg +/* These form the lookup key for the setup_mock_fcntl() mapping. +/* The caller must zero-fill unused space in and around the want_xxx +/* request members. +/* .IP out_ret +/* The mock fcntl() result value. If -1, then out_errno must be +/* non-zero. +/* .IP out_errno +/* The global out_errno upon return from mock fcntl(). If this is +/* non-zero, mock fcntl() must return -1. +/* .PP +/* teardown_mock_fcntl() destroys all mappings entered with +/* setup_mock_fcntl(). +/* +/* The mock fcntl() function looks up the MOCK_FCNTL_REQ information +/* for an fcntl() request, and terminates the test if the request +/* is not expected. +/* +/* setup_mock_flock() and teardown_mock_flock() implement similar +/* functionality for flock() calls. +/* SEE ALSO +/* fcntl(2), flock(2), the real things +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* porcupine.org +/*--*/ + + /* + * System library. + */ +#include +#include +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include + + /* + * Test library. + */ +#include +#include + + /* + * Application-specific. + */ +static BINHASH *mock_fcntl_table; +static BINHASH *mock_flock_table; + +#ifdef HAS_FCNTL_LOCK + +/* setup_mock_fcntl - add request-to-result mapping */ + +void setup_mock_fcntl(const MOCK_FCNTL_REQ *request) +{ + if (mock_fcntl_table == 0) + mock_fcntl_table = binhash_create(10); + if (request->out_ret == -1 && request->out_errno == 0) + ptest_fatal(ptest_ctx_current(), + "setup_mock_fcntl: result -1 requires non-zero errno"); + if (request->out_ret != -1 && request->out_errno != 0) + ptest_fatal(ptest_ctx_current(), + "setup_mock_fcntl: result %d requires zero errno", + request->out_ret); + binhash_enter(mock_fcntl_table, (void *) request, + MOCK_FCNTL_REQ_WANT_SIZE, (void *) request); +} + +/* teardown_mock_fcntl - destroy all request-to-result mappings */ + +void teardown_mock_fcntl(void) +{ + if (mock_fcntl_table) { + binhash_free(mock_fcntl_table, (void (*) (void *)) 0); + mock_fcntl_table = 0; + } +} + +/* fcntl - mock fcntl() implementation */ + +int fcntl(int fd, int cmd,...) +{ + MOCK_FCNTL_REQ mock_key; + MOCK_FCNTL_REQ *mock_info; + va_list ap; + + if (mock_fcntl_table != 0) { + va_start(ap, cmd); + memset((void *) &mock_key, 0, MOCK_FCNTL_REQ_WANT_SIZE); + mock_key.want_fd = fd; + mock_key.want_cmd = cmd; + switch (cmd) { + case F_GETFD: + case F_GETFL: + break; + case F_DUPFD: + case F_SETFD: + case F_SETFL: + mock_key.want_arg.fd = va_arg(ap, int); + break; + case F_SETLK: + case F_SETLKW: + case F_GETLK: + mock_key.want_arg.fl = *va_arg(ap, struct flock *); + break; + } + va_end(ap); + } + if (mock_fcntl_table == 0 + || (mock_info = (MOCK_FCNTL_REQ *) + binhash_find(mock_fcntl_table, &mock_key, + MOCK_FCNTL_REQ_WANT_SIZE)) == 0) + ptest_fatal(ptest_ctx_current(), + "unexpected request: fcntl(%d, %d, ...)", fd, cmd); + errno = mock_info->out_errno; + return (mock_info->out_ret); +} + +#endif /* HAS_FCNTL_LOCK */ + +#ifdef HAS_FLOCK_LOCK + +/* setup_mock_flock - add request-to-result mapping */ + +void setup_mock_flock(const MOCK_FLOCK_REQ *request) +{ + if (mock_flock_table == 0) + mock_flock_table = binhash_create(10); + if (request->out_ret == -1 && request->out_errno == 0) + ptest_fatal(ptest_ctx_current(), + "setup_mock_flock: result -1 requires non-zero errno"); + if (request->out_ret != -1 && request->out_errno != 0) + ptest_fatal(ptest_ctx_current(), + "setup_mock_flock: result %d requires zero errno", + request->out_ret); + binhash_enter(mock_flock_table, (void *) request, + MOCK_FLOCK_REQ_WANT_SIZE, (void *) request); +} + +/* teardown_mock_flock - destroy all request-to-result mappings */ + +void teardown_mock_flock(void) +{ + if (mock_flock_table) { + binhash_free(mock_flock_table, (void (*) (void *)) 0); + mock_flock_table = 0; + } +} + +/* flock - mock flock() implementation */ + +int flock(int fd, int cmd) +{ + MOCK_FLOCK_REQ mock_key; + MOCK_FLOCK_REQ *mock_info; + + mock_key.want_fd = fd; + mock_key.want_cmd = cmd; + if (mock_flock_table == 0 + || (mock_info = (MOCK_FLOCK_REQ *) + binhash_find(mock_flock_table, &mock_key, + MOCK_FLOCK_REQ_WANT_SIZE)) == 0) + ptest_fatal(ptest_ctx_current(), + "unexpected request: flock(%d, %d)", fd, cmd); + errno = mock_info->out_errno; + return (mock_info->out_ret); +} + +#endif /* HAS_FLOCK_LOCK */ diff --git a/postfix/src/testing/mock_fcntl.h b/postfix/src/testing/mock_fcntl.h new file mode 100644 index 000000000..b7dc4ecb3 --- /dev/null +++ b/postfix/src/testing/mock_fcntl.h @@ -0,0 +1,64 @@ +#ifndef _MOCK_FCNTL_H_INCLUDED_ +#define _MOCK_FCNTL_H_INCLUDED_ + +/*++ +/* NAME +/* mock_fcntl 3h +/* SUMMARY +/* mock fcntl/flock +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include +#include /* offsetof() */ + + /* + * External interface. + */ +typedef struct MOCK_FCNTL_REQ { + /* Inputs. */ + int want_fd; + int want_cmd; + union { + struct flock fl; + int fd; + int flags; + } want_arg; + /* Outputs. */ + int out_ret; + int out_errno; +} MOCK_FCNTL_REQ; + +#define MOCK_FCNTL_REQ_WANT_SIZE offsetof(MOCK_FCNTL_REQ, out_ret) + +extern void setup_mock_fcntl(const MOCK_FCNTL_REQ *); +extern void teardown_mock_fcntl(void); + +typedef struct MOCK_FLOCK_REQ { + /* Inputs. */ + int want_fd; + int want_cmd; + /* Outputs. */ + int out_ret; + int out_errno; +} MOCK_FLOCK_REQ; + +#define MOCK_FLOCK_REQ_WANT_SIZE offsetof(MOCK_FLOCK_REQ, out_ret) + +extern void setup_mock_flock(const MOCK_FLOCK_REQ *); +extern void teardown_mock_flock(void); + +/* LICENSE +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* porcupine.org +/*--*/ + +#endif diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in index 754dff0bc..2f9a63918 100644 --- a/postfix/src/util/Makefile.in +++ b/postfix/src/util/Makefile.in @@ -48,7 +48,8 @@ SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \ inet_addr_sizes.c quote_for_json.c mystrerror.c \ sane_sockaddr_to_hostaddr.c normalize_ws.c valid_uri_scheme.c \ clean_ascii_cntrl_space.c normalize_v4mapped_addr.c ossl_digest.c \ - mac_midna.c wrap_stat.c dynamicmaps.c find_inet_service.c wrap_netdb.c + mac_midna.c wrap_stat.c dynamicmaps.c find_inet_service.c wrap_netdb.c \ + wrap_fcntl.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 \ @@ -98,7 +99,7 @@ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \ quote_for_json.o mystrerror.o sane_sockaddr_to_hostaddr.o \ normalize_ws.o valid_uri_scheme.o clean_ascii_cntrl_space.o \ normalize_v4mapped_addr.o ossl_digest.o mac_midna.o wrap_stat.o \ - dynamicmaps.o find_inet_service.o wrap_netdb.o + dynamicmaps.o find_inet_service.o wrap_netdb.o wrap_fcntl.o # MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf. # When hard-linking these, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ), # otherwise it sets the PLUGIN_* macros. @@ -133,13 +134,14 @@ HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \ known_tcp_ports.h sane_strtol.h hash_fnv.h ldseed.h mkmap.h \ inet_prefix_top.h inet_addr_sizes.h valid_uri_scheme.h \ clean_ascii_cntrl_space.h normalize_v4mapped_addr.h ossl_digest.h \ - mac_midna.h wrap_stat.h dynamicmaps.h find_inet_service.h wrap_netdb.h + mac_midna.h wrap_stat.h dynamicmaps.h find_inet_service.h wrap_netdb.h \ + wrap_fcntl.h TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \ sunos5_stream_test.c dup2_pass_on_exec.c argv_test.c dict_pipe_test.c \ dict_stream_test.c dict_cli.c dict_union_test.c \ find_inet_service_test.c hash_fnv_test.c known_tcp_ports_test.c \ msg_output_test.c myaddrinfo_test.c mymalloc_test.c mystrtok_test.c \ - unescape_test.c allprint_test.c + unescape_test.c allprint_test.c myflock_test.c DEFS = -I. -D$(SYSTYPE) CFLAGS = $(DEBUG) $(OPT) $(DEFS) FILES = Makefile $(SRCS) $(HDRS) @@ -163,7 +165,8 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \ dict_union_test dict_pipe_test myaddrinfo_test \ clean_env inet_prefix_top printable readlline quote_for_json \ normalize_ws valid_uri_scheme clean_ascii_cntrl_space \ - normalize_v4mapped_addr_test ossl_digest_test allprint_test + normalize_v4mapped_addr_test ossl_digest_test allprint_test \ + myflock_test PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX) $(LIB_PREFIX)lmdb$(LIB_SUFFIX) \ $(LIB_PREFIX)cdb$(LIB_SUFFIX) $(LIB_PREFIX)sdbm$(LIB_SUFFIX) \ $(LIB_PREFIX)db$(LIB_SUFFIX) @@ -465,6 +468,9 @@ hash_fnv_test: $(LIB) $(TESTLIBS) update allprint_test: $(LIB) $(TESTLIBS) update $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(TESTLIBS) $(SYSLIBS) +myflock_test: $(LIB) $(TESTLIBS) update + $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(TESTLIBS) $(SYSLIBS) + unix_recv_fd: $(LIB) mv $@.o junk $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) @@ -843,6 +849,9 @@ test_hash_fnv: hash_fnv_test test_allprint: allprint_test $(SHLIB_ENV) ${VALGRIND} ./allprint_test +test_myflock: myflock_test + $(SHLIB_ENV) ${VALGRIND} ./myflock_test + hex_code_test: hex_code $(SHLIB_ENV) ${VALGRIND} ./hex_code @@ -2750,7 +2759,7 @@ myflock.o: check_arg.h myflock.o: msg.h myflock.o: myflock.c myflock.o: myflock.h -myflock.o: name_mask.h +myflock.o: name_code.h myflock.o: sys_defs.h myflock.o: vbuf.h myflock.o: vstring.h @@ -3387,6 +3396,10 @@ watchdog.o: posix_signals.h watchdog.o: sys_defs.h watchdog.o: watchdog.c watchdog.o: watchdog.h +wrap_fcntl.o: msg.h +wrap_fcntl.o: sys_defs.h +wrap_fcntl.o: wrap_fcntl.c +wrap_fcntl.o: wrap_fcntl.h wrap_netdb.o: sys_defs.h wrap_netdb.o: wrap_netdb.c wrap_netdb.o: wrap_netdb.h diff --git a/postfix/src/util/myflock.c b/postfix/src/util/myflock.c index 2c8ad5d8a..7a2b3f774 100644 --- a/postfix/src/util/myflock.c +++ b/postfix/src/util/myflock.c @@ -60,6 +60,9 @@ /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* porcupine.org /*--*/ /* System library. */ @@ -81,61 +84,55 @@ #include "msg.h" #include "myflock.h" -#include "name_mask.h" +#include "name_code.h" #include "vstring.h" +#include /* myflock - lock/unlock entire open file */ int myflock(int fd, int lock_style, int operation) { int status; - const static NAME_MASK lock_masks[] = { + const static NAME_CODE lock_style_ux[] = { "MYFLOCK_STYLE_FLOCK", MYFLOCK_STYLE_FLOCK, "MYFLOCK_STYLE_FCNTL", MYFLOCK_STYLE_FCNTL, 0, }; - const static NAME_MASK op_masks[] = { + const static NAME_CODE lock_req_ux[] = { + "MYFLOCK_OP_NONE", MYFLOCK_OP_NONE, "MYFLOCK_OP_SHARED", MYFLOCK_OP_SHARED, "MYFLOCK_OP_EXCLUSIVE", MYFLOCK_OP_EXCLUSIVE, - "MYFLOCK_OP_NOWAIT", MYFLOCK_OP_NOWAIT, 0, }; - - if (msg_verbose) { - VSTRING *style_buf = vstring_alloc(100); - VSTRING *op_buf = vstring_alloc(100); - - msg_info("myflock(%d, %s, %s)", fd, - str_name_mask_opt(style_buf, "lock_style", lock_masks, - lock_style, NAME_MASK_PIPE | NAME_MASK_NUMBER), - operation == MYFLOCK_OP_NONE ? "MYFLOCK_OP_NONE" : - str_name_mask_opt(op_buf, "operation", op_masks, - operation, NAME_MASK_PIPE | NAME_MASK_NUMBER)); - vstring_free(style_buf); - vstring_free(op_buf); - } + int nowait_req = (operation & MYFLOCK_OP_NOWAIT); + int lock_req = (operation & ~MYFLOCK_OP_NOWAIT); /* * Sanity check. */ - if ((operation & (MYFLOCK_OP_BITS)) != operation) + if (lock_style < MYFLOCK_STYLE_FLOCK || lock_style > MYFLOCK_STYLE_FCNTL) + msg_panic("myflock: unsupported lock style: 0x%x", lock_style); + if (lock_req < MYFLOCK_OP_NONE || lock_req > MYFLOCK_OP_EXCLUSIVE) msg_panic("myflock: improper operation type: 0x%x", operation); + if (msg_verbose) { + msg_info("myflock(%d, %s, %s%s)", fd, + str_name_code(lock_style_ux, lock_style), + str_name_code(lock_req_ux, lock_req), + nowait_req ? " | MYFLOCK_OP_NOWAIT" : ""); + } switch (lock_style) { /* * flock() does exactly what we need. Too bad it is not standard. */ #ifdef HAS_FLOCK_LOCK - case MYFLOCK_STYLE_FLOCK: - { - static int lock_ops[] = { - LOCK_UN, LOCK_SH, LOCK_EX, -1, - -1, LOCK_SH | LOCK_NB, LOCK_EX | LOCK_NB, -1 - }; - - while ((status = flock(fd, lock_ops[operation])) < 0 - && errno == EINTR) + case MYFLOCK_STYLE_FLOCK:{ + static int flock_reqs[] = {LOCK_UN, LOCK_SH, LOCK_EX,}; + int flock_req = flock_reqs[lock_req] + | (nowait_req ? LOCK_NB : 0); + + while ((status = flock(fd, flock_req)) < 0 && errno == EINTR) sleep(1); break; } @@ -146,19 +143,15 @@ int myflock(int fd, int lock_style, int operation) * it. */ #ifdef HAS_FCNTL_LOCK - case MYFLOCK_STYLE_FCNTL: - { + case MYFLOCK_STYLE_FCNTL:{ struct flock lock; - int request; - static int lock_ops[] = { - F_UNLCK, F_RDLCK, F_WRLCK - }; + static int fcntl_lock_reqs[] = {F_UNLCK, F_RDLCK, F_WRLCK,}; + int fcntl_req = (nowait_req ? F_SETLK : F_SETLKW); memset((void *) &lock, 0, sizeof(lock)); - lock.l_type = lock_ops[operation & ~MYFLOCK_OP_NOWAIT]; - request = (operation & MYFLOCK_OP_NOWAIT) ? F_SETLK : F_SETLKW; - while ((status = fcntl(fd, request, &lock)) < 0 - && errno == EINTR) + lock.l_type = fcntl_lock_reqs[lock_req]; + + while ((status = fcntl(fd, fcntl_req, &lock)) < 0 && errno == EINTR) sleep(1); break; } @@ -173,7 +166,7 @@ int myflock(int fd, int lock_style, int operation) * Return a consistent result. Some systems return EACCES when a lock is * taken by someone else, and that would complicate error processing. */ - if (status < 0 && (operation & MYFLOCK_OP_NOWAIT) != 0) + if (status < 0 && nowait_req) if (errno == EWOULDBLOCK || errno == EACCES) errno = EAGAIN; diff --git a/postfix/src/util/myflock.h b/postfix/src/util/myflock.h index ee18bdba0..32cf78682 100644 --- a/postfix/src/util/myflock.h +++ b/postfix/src/util/myflock.h @@ -30,9 +30,6 @@ extern int WARN_UNUSED_RESULT myflock(int, int, int); #define MYFLOCK_OP_EXCLUSIVE 2 #define MYFLOCK_OP_NOWAIT 4 -#define MYFLOCK_OP_BITS \ - (MYFLOCK_OP_SHARED | MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) - /* LICENSE /* .ad /* .fi @@ -47,6 +44,9 @@ extern int WARN_UNUSED_RESULT myflock(int, int, int); /* Google, Inc. /* 111 8th Avenue /* New York, NY 10011, USA +/* +/* Wietse Venema +/* porcupine.org /*--*/ #endif diff --git a/postfix/src/util/myflock_test.c b/postfix/src/util/myflock_test.c new file mode 100644 index 000000000..51cf72db3 --- /dev/null +++ b/postfix/src/util/myflock_test.c @@ -0,0 +1,439 @@ + /* + * Test program to verify that myflock() makes the expected flock() and + * fcntl() calls. + */ + + /* + * System library. + */ +#include +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Test library. + */ +#include +#include + +typedef struct PTEST_CASE { + const char *testname; /* identifies test case */ + void (*action) (PTEST_CTX *t, + const struct PTEST_CASE *tp); +} PTEST_CASE; + +static void test_myflock_fcntl_shared_nb(PTEST_CTX *t, + const struct PTEST_CASE *tp) +{ + MOCK_FCNTL_REQ lock_req; + MOCK_FCNTL_REQ unlock_req; + int ret; + + ptest_info(t, "acquire lock"); + memset((void *) &lock_req, 0, sizeof(lock_req)); + lock_req.want_fd = 0; + lock_req.want_cmd = F_SETLK; + lock_req.want_arg.fl.l_type = F_RDLCK; + setup_mock_fcntl(&lock_req); + ret = myflock(0, MYFLOCK_STYLE_FCNTL, + MYFLOCK_OP_SHARED | MYFLOCK_OP_NOWAIT); + if (ret != lock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FCNTL, " + "MYFLOCK_OP_SHARED|MYFLOCK_OP_NOWAIT): got %d, want %d", + ret, lock_req.out_ret); + + ptest_info(t, "release lock"); + memset((void *) &unlock_req, 0, sizeof(lock_req)); + unlock_req.want_fd = 0; + unlock_req.want_cmd = F_SETLKW; + unlock_req.want_arg.fl.l_type = F_UNLCK; + setup_mock_fcntl(&unlock_req); + ret = myflock(0, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE); + if (ret != unlock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FCNTL, " + "MYFLOCK_OP_NONE): got %d, want %d", + ret, unlock_req.out_ret); + + teardown_mock_fcntl(); +} + +static void test_myflock_fcntl_shared_blk(PTEST_CTX *t, + const struct PTEST_CASE *tp) +{ + MOCK_FCNTL_REQ lock_req; + MOCK_FCNTL_REQ unlock_req; + int ret; + + ptest_info(t, "acquire lock"); + memset((void *) &lock_req, 0, sizeof(lock_req)); + lock_req.want_fd = 0; + lock_req.want_cmd = F_SETLKW; + lock_req.want_arg.fl.l_type = F_RDLCK; + setup_mock_fcntl(&lock_req); + ret = myflock(0, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED); + if (ret != lock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FCNTL, " + "MYFLOCK_OP_SHARED): got %d, want %d", + ret, lock_req.out_ret); + + ptest_info(t, "release lock"); + memset((void *) &unlock_req, 0, sizeof(lock_req)); + unlock_req.want_fd = 0; + unlock_req.want_cmd = F_SETLKW; + unlock_req.want_arg.fl.l_type = F_UNLCK; + setup_mock_fcntl(&unlock_req); + ret = myflock(0, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE); + if (ret != unlock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FCNTL, " + "MYFLOCK_OP_NONE): got %d, want %d", + ret, unlock_req.out_ret); + + teardown_mock_fcntl(); +} + +static void test_myflock_fcntl_excl_nb(PTEST_CTX *t, + const struct PTEST_CASE *tp) +{ + MOCK_FCNTL_REQ lock_req; + MOCK_FCNTL_REQ unlock_req; + int ret; + + ptest_info(t, "acquire lock"); + memset((void *) &lock_req, 0, sizeof(lock_req)); + lock_req.want_fd = 0; + lock_req.want_cmd = F_SETLK; + lock_req.want_arg.fl.l_type = F_WRLCK; + setup_mock_fcntl(&lock_req); + ret = myflock(0, MYFLOCK_STYLE_FCNTL, + MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT); + if (ret != lock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FCNTL, " + "MYFLOCK_OP_EXCLUSIVE|MYFLOCK_OP_NOWAIT): got %d, want %d", + ret, lock_req.out_ret); + + ptest_info(t, "release lock"); + memset((void *) &unlock_req, 0, sizeof(lock_req)); + unlock_req.want_fd = 0; + unlock_req.want_cmd = F_SETLKW; + unlock_req.want_arg.fl.l_type = F_UNLCK; + setup_mock_fcntl(&unlock_req); + ret = myflock(0, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE); + if (ret != unlock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FCNTL, " + "MYFLOCK_OP_NONE): got %d, want %d", + ret, unlock_req.out_ret); +} + +static void test_myflock_fcntl_excl_blk(PTEST_CTX *t, + const struct PTEST_CASE *tp) +{ + MOCK_FCNTL_REQ lock_req; + MOCK_FCNTL_REQ unlock_req; + int ret; + + ptest_info(t, "acquire lock"); + memset((void *) &lock_req, 0, sizeof(lock_req)); + lock_req.want_fd = 0; + lock_req.want_cmd = F_SETLKW; + lock_req.want_arg.fl.l_type = F_WRLCK; + setup_mock_fcntl(&lock_req); + ret = myflock(0, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE); + if (ret != lock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FCNTL, " + "MYFLOCK_OP_EXCLUSIVE): got %d, want %d", + ret, lock_req.out_ret); + + ptest_info(t, "release lock"); + memset((void *) &unlock_req, 0, sizeof(lock_req)); + unlock_req.want_fd = 0; + unlock_req.want_cmd = F_SETLKW; + unlock_req.want_arg.fl.l_type = F_UNLCK; + setup_mock_fcntl(&unlock_req); + ret = myflock(0, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE); + if (ret != unlock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FCNTL, " + "MYFLOCK_OP_NONE): got %d, want %d", + ret, unlock_req.out_ret); + + teardown_mock_fcntl(); +} + +static void test_myflock_flock_shared_nb(PTEST_CTX *t, + const struct PTEST_CASE *tp) +{ + MOCK_FLOCK_REQ lock_req; + MOCK_FLOCK_REQ unlock_req; + int ret; + + ptest_info(t, "acquire lock"); + memset((void *) &lock_req, 0, sizeof(lock_req)); + lock_req.want_fd = 0; + lock_req.want_cmd = LOCK_SH | LOCK_NB; + setup_mock_flock(&lock_req); + ret = myflock(0, MYFLOCK_STYLE_FLOCK, + MYFLOCK_OP_SHARED | MYFLOCK_OP_NOWAIT); + if (ret != lock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FLOCK, MYFLOCK_OP_SHARED" + "|MYFLOCK_OP_NOWAIT): got %d, want %d", + ret, lock_req.out_ret); + + ptest_info(t, "release lock"); + memset((void *) &unlock_req, 0, sizeof(lock_req)); + unlock_req.want_fd = 0; + unlock_req.want_cmd = LOCK_UN; + setup_mock_flock(&unlock_req); + ret = myflock(0, MYFLOCK_STYLE_FLOCK, MYFLOCK_OP_NONE); + if (ret != unlock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FLOCK, " + "MYFLOCK_OP_NONE): got %d, want %d", + ret, unlock_req.out_ret); + + teardown_mock_flock(); +} + +static void test_myflock_flock_shared_blk(PTEST_CTX *t, + const struct PTEST_CASE *tp) +{ + MOCK_FLOCK_REQ lock_req; + MOCK_FLOCK_REQ unlock_req; + int ret; + + ptest_info(t, "acquire lock"); + memset((void *) &lock_req, 0, sizeof(lock_req)); + lock_req.want_fd = 0; + lock_req.want_cmd = LOCK_SH; + setup_mock_flock(&lock_req); + ret = myflock(0, MYFLOCK_STYLE_FLOCK, MYFLOCK_OP_SHARED); + if (ret != lock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FLOCK, " + "MYFLOCK_OP_SHARED): got %d, want %d", + ret, lock_req.out_ret); + + ptest_info(t, "release lock"); + memset((void *) &unlock_req, 0, sizeof(lock_req)); + unlock_req.want_fd = 0; + unlock_req.want_cmd = LOCK_UN; + setup_mock_flock(&unlock_req); + ret = myflock(0, MYFLOCK_STYLE_FLOCK, MYFLOCK_OP_NONE); + if (ret != unlock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FLOCK, " + "MYFLOCK_OP_NONE): got %d, want %d", + ret, unlock_req.out_ret); + + teardown_mock_flock(); +} + +static void test_myflock_flock_excl_nb(PTEST_CTX *t, + const struct PTEST_CASE *tp) +{ + MOCK_FLOCK_REQ lock_req; + MOCK_FLOCK_REQ unlock_req; + int ret; + + ptest_info(t, "acquire lock"); + memset((void *) &lock_req, 0, sizeof(lock_req)); + lock_req.want_fd = 0; + lock_req.want_cmd = LOCK_EX | LOCK_NB; + setup_mock_flock(&lock_req); + ret = myflock(0, MYFLOCK_STYLE_FLOCK, + MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT); + if (ret != lock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FLOCK, MYFLOCK_OP_EXCLUSIVE" + "|MYFLOCK_OP_NOWAIT): got %d, want %d", + ret, lock_req.out_ret); + + ptest_info(t, "release lock"); + memset((void *) &unlock_req, 0, sizeof(lock_req)); + unlock_req.want_fd = 0; + unlock_req.want_cmd = LOCK_UN; + setup_mock_flock(&unlock_req); + ret = myflock(0, MYFLOCK_STYLE_FLOCK, MYFLOCK_OP_NONE); + if (ret != unlock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FLOCK, " + "MYFLOCK_OP_NONE): got %d, want %d", + ret, unlock_req.out_ret); + + teardown_mock_flock(); +} + +static void test_myflock_flock_excl_blk(PTEST_CTX *t, + const struct PTEST_CASE *tp) +{ + MOCK_FLOCK_REQ lock_req; + MOCK_FLOCK_REQ unlock_req; + int ret; + + ptest_info(t, "acquire lock"); + memset((void *) &lock_req, 0, sizeof(lock_req)); + lock_req.want_fd = 0; + lock_req.want_cmd = LOCK_EX; + setup_mock_flock(&lock_req); + ret = myflock(0, MYFLOCK_STYLE_FLOCK, MYFLOCK_OP_EXCLUSIVE); + if (ret != lock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FLOCK, " + "MYFLOCK_OP_EXCLUSIVE): got %d, want %d", + ret, lock_req.out_ret); + + ptest_info(t, "release lock"); + memset((void *) &unlock_req, 0, sizeof(lock_req)); + unlock_req.want_fd = 0; + unlock_req.want_cmd = LOCK_UN; + setup_mock_flock(&unlock_req); + ret = myflock(0, MYFLOCK_STYLE_FLOCK, MYFLOCK_OP_NONE); + if (ret != unlock_req.out_ret) + ptest_fatal(t, "myflock(fd, MYFLOCK_STYLE_FLOCK, " + "MYFLOCK_OP_NONE): got %d, want %d", + ret, unlock_req.out_ret); + + teardown_mock_flock(); +} + +static void test_myflock_bad_style_0(PTEST_CTX *t, const struct PTEST_CASE *tp) +{ + expect_ptest_log_event(t, "panic: myflock: unsupported lock style: 0x0"); + switch (myflock(0, MYFLOCK_STYLE_FLOCK - 1, -1)) { + }; +} + +static void test_myflock_bad_style_3(PTEST_CTX *t, const struct PTEST_CASE *tp) +{ + expect_ptest_log_event(t, "panic: myflock: unsupported lock style: 0x3"); + switch (myflock(0, MYFLOCK_STYLE_FCNTL + 1, -1)) { + }; +} + +static void test_myflock_bad_operation_1(PTEST_CTX *t, + const struct PTEST_CASE *tp) +{ + expect_ptest_log_event(t, "panic: myflock: improper operation type: 0xffffffff"); + switch (myflock(0, MYFLOCK_STYLE_FCNTL, -1)) { + }; +} + +static void test_myflock_bad_operation_3(PTEST_CTX *t, + const struct PTEST_CASE *tp) +{ + expect_ptest_log_event(t, "panic: myflock: improper operation type: 0x3"); + switch (myflock(0, MYFLOCK_STYLE_FCNTL, 3)) { + }; +} + +static void test_myflock_propagates_fcntl_error(PTEST_CTX *t, + const struct PTEST_CASE *tp) +{ + MOCK_FCNTL_REQ lock_req; + int fd = -1; + int ret; + + memset((void *) &lock_req, 0, sizeof(lock_req)); + lock_req.want_fd = fd; + lock_req.want_cmd = F_SETLKW; + lock_req.want_arg.fl.l_type = F_WRLCK; + setup_mock_fcntl(&lock_req); + lock_req.out_ret = -1; + lock_req.out_errno = EBADF; + setup_mock_fcntl(&lock_req); + ret = myflock(fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE); + if (ret != lock_req.out_ret) + ptest_error(t, "myflock(-1, MYFLOCK_STYLE_FLOCK, " + "MYFLOCK_OP_EXCLUSIVE): got %d, want %d", + ret, lock_req.out_ret); + if (errno != lock_req.out_errno) + ptest_error(t, "errno: got %d, want %d", errno, lock_req.out_errno); + + teardown_mock_fcntl(); +} + +static void test_myflock_propagates_flock_error(PTEST_CTX *t, + const struct PTEST_CASE *tp) +{ + MOCK_FLOCK_REQ lock_req; + int fd = -1; + int ret; + + memset((void *) &lock_req, 0, sizeof(lock_req)); + lock_req.want_fd = fd; + lock_req.want_cmd = LOCK_EX; + lock_req.out_ret = -1; + lock_req.out_errno = EBADF; + setup_mock_flock(&lock_req); + ret = myflock(fd, MYFLOCK_STYLE_FLOCK, MYFLOCK_OP_EXCLUSIVE); + if (ret != lock_req.out_ret) + ptest_error(t, "myflock(-1, MYFLOCK_STYLE_FLOCK, " + "MYFLOCK_OP_EXCLUSIVE): got %d, want %d", + ret, lock_req.out_ret); + if (errno != lock_req.out_errno) + ptest_error(t, "errno: got %d, want %d", errno, lock_req.out_errno); + + teardown_mock_flock(); +} + + /* + * The test cases. + */ +static const PTEST_CASE ptestcases[] = { + { + .testname = "fcntl style, shared, non-blocking", + .action = test_myflock_fcntl_shared_nb, + }, + { + .testname = "fcntl style, shared, blocking", + .action = test_myflock_fcntl_shared_blk, + }, + { + .testname = "fcntl style, exclusive, non-blocking", + .action = test_myflock_fcntl_excl_nb, + }, + { + .testname = "fcntl style, exclusive, blocking", + .action = test_myflock_fcntl_excl_blk, + }, + { + .testname = "flock style, shared, non-blocking", + .action = test_myflock_flock_shared_nb, + }, + { + .testname = "flock style, shared, blocking", + .action = test_myflock_flock_shared_blk, + }, + { + .testname = "flock style, exclusive, non-blocking", + .action = test_myflock_flock_excl_nb, + }, + { + .testname = "flock style, exclusive, blocking", + .action = test_myflock_flock_excl_blk, + }, + { + .testname = "detects bad lock style 0", + .action = test_myflock_bad_style_0, + }, + { + .testname = "detects bad lock style 3", + .action = test_myflock_bad_style_3, + }, + { + .testname = "detects bad lock operation -1", + .action = test_myflock_bad_operation_1, + }, + { + .testname = "detects bad lock operation 3", + .action = test_myflock_bad_operation_3, + }, + { + .testname = "propagates fcntl error", + .action = test_myflock_propagates_fcntl_error, + }, + { + .testname = "propagates flock error", + .action = test_myflock_propagates_flock_error, + }, +}; + +#include diff --git a/postfix/src/util/wrap_fcntl.c b/postfix/src/util/wrap_fcntl.c new file mode 100644 index 000000000..dec0e48fd --- /dev/null +++ b/postfix/src/util/wrap_fcntl.c @@ -0,0 +1,105 @@ +/*++ +/* NAME +/* wrap_fcntl 3 +/* SUMMARY +/* mockable fcntl/flock wrappers +/* SYNOPSIS +/* #include +/* +/* int wrap_fcntl( +/* int fd, +/* int cmd, +/* ...) +/* +/* int wrap_fcntl( +/* int fd, +/* int cmd) +/* DESCRIPTION +/* This module is a NOOP when the NO_MOCK_WRAPPERS macro is defined. +/* +/* By default this module redirects fcntl() and flock() calls +/* to the above listed wrapper functions that may be overridden +/* with mocks. This arrangement prevents mock fcntl() or flock() +/* functions from interfering with fcntl() or flock() calls made +/* by system libraries or by third-party libraries. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* porcupine.org +/*--*/ + + /* + * System library. + */ +#include +#include +#include +#include + + /* + * Utility library. + */ +#include +#include + +#ifdef HAS_FCNTL_LOCK + +/* wrap_fcntl - mockable wrapper */ + +int wrap_fcntl(int fd, int cmd,...) +{ + va_list ap; + int ret; + +#undef fcntl + + va_start(ap, cmd); + switch (cmd) { + + case F_GETFD: + case F_GETFL: + /* Operations with no third argument. */ + ret = fcntl(fd, cmd); + break; + + case F_DUPFD: + case F_SETFD: + case F_SETFL: + /* Operations with an integer third argument. */ + ret = fcntl(fd, cmd, va_arg(ap, int)); + break; + + case F_SETLK: + case F_SETLKW: + case F_GETLK: + /* Lock operations. */ + ret = fcntl(fd, cmd, va_arg(ap, struct flock *)); + break; + + default: + /* Platform-specific, should never be called. */ + msg_warn("%s: unexpected cmd=%d", __func__, cmd); + ret = -1; + errno = ENOTSUP; + break; + } + va_end(ap); + return (ret); +} + +#endif /* HAS_FCNTL_LOCK */ + +#ifdef HAS_FLOCK_LOCK + +/* wrap_flock - mockable wrapper */ + +int wrap_flock(int fd, int cmd) +{ +#undef flock + return (flock(fd, cmd)); +} + +#endif /* HAS_FLOCK_LOCK */ diff --git a/postfix/src/util/wrap_fcntl.h b/postfix/src/util/wrap_fcntl.h new file mode 100644 index 000000000..c2622d3a1 --- /dev/null +++ b/postfix/src/util/wrap_fcntl.h @@ -0,0 +1,41 @@ +#ifndef _WRAP_FCNTL_H_INCLUDED_ +#define _WRAP_FCNTL_H_INCLUDED_ + +/*++ +/* NAME +/* wrap_fcntl 3h +/* SUMMARY +/* mockable fcntl/flock wrappers +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include +#include + + /* + * Mockable API. + */ +#ifndef NO_MOCK_WRAPPERS +extern int wrap_fcntl(int, int,...); +extern int wrap_flock(int, int); + +#define fcntl(fd, cmd, ...) wrap_fcntl(fd, cmd, __VA_ARGS__) +#define flock(fd, cmd) wrap_flock(fd, cmd) +#endif + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* porcupine.org +/*--*/ + +#endif