Home | History | Annotate | Line # | Download | only in global
      1 /*	$NetBSD: data_redirect.c,v 1.3 2026/05/09 18:49:15 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	data_redirect 3
      6 /* SUMMARY
      7 /*	redirect legacy writes to Postfix-owned data directory
      8 /* SYNOPSIS
      9 /*	#include <data_redirect.h>
     10 /*
     11 /*	char	*data_redirect_file(result, path)
     12 /*	VSTRING	*result;
     13 /*	const char *path;
     14 /*
     15 /*	char	*data_redirect_map(result, map)
     16 /*	VSTRING	*result;
     17 /*	const char *map;
     18 /* DESCRIPTION
     19 /*	With Postfix version 2.5 and later, the tlsmgr(8) and
     20 /*	verify(8) servers no longer open cache files with root
     21 /*	privilege. This avoids a potential security loophole where
     22 /*	the ownership of a file (or directory) does not match the
     23 /*	trust level of the content of that file (or directory).
     24 /*
     25 /*	This module implements a migration aid that allows a
     26 /*	transition without disruption of service.
     27 /*
     28 /*	data_redirect_file() detects a request to open a file in a
     29 /*	non-Postfix directory, logs a warning, and redirects the
     30 /*	request to the Postfix-owned data_directory.
     31 /*
     32 /*	data_redirect_map() performs the same function for a limited
     33 /*	subset of file-based lookup tables.
     34 /*
     35 /*	Arguments:
     36 /* .IP result
     37 /*	A possibly redirected copy of the input.
     38 /* .IP path
     39 /*	The pathname that may be redirected.
     40 /* .IP map
     41 /*	The "mapname" or "maptype:mapname" that may be redirected.
     42 /*	The result is always in "maptype:mapname" form.
     43 /* BUGS
     44 /*	Only a few map types are redirected. This is acceptable for
     45 /*	a temporary migration tool.
     46 /* DIAGNOSTICS
     47 /*	Fatal errors: memory allocation failure.
     48 /* CONFIGURATION PARAMETERS
     49 /*	data_directory, location of Postfix-writable files
     50 /* LICENSE
     51 /* .ad
     52 /* .fi
     53 /*	The Secure Mailer license must be distributed with this software.
     54 /* AUTHOR(S)
     55 /*	Wietse Venema
     56 /*	IBM T.J. Watson Research
     57 /*	P.O. Box 704
     58 /*	Yorktown Heights, NY 10598, USA
     59 /*--*/
     60 
     61 /* System library. */
     62 
     63 #include <sys_defs.h>
     64 #include <sys/stat.h>
     65 #include <string.h>
     66 
     67 /* Utility library. */
     68 
     69 #include <msg.h>
     70 #include <vstring.h>
     71 #include <stringops.h>
     72 #include <split_at.h>
     73 #include <name_code.h>
     74 #include <dict_db.h>
     75 #include <dict_dbm.h>
     76 #include <dict_cdb.h>
     77 #include <dict_lmdb.h>
     78 #include <warn_stat.h>
     79 
     80 /* Global directory. */
     81 
     82 #include <mail_params.h>
     83 #include <dict_proxy.h>
     84 #include <data_redirect.h>
     85 
     86 /* Application-specific. */
     87 
     88 #define STR(x) vstring_str(x)
     89 #define LEN(x) VSTRING_LEN(x)
     90 
     91  /*
     92   * Redirect only these map types, so that we don't try stupid things with
     93   * NIS, *SQL or LDAP. This is a transition feature for legacy TLS and verify
     94   * configurations, so it does not have to cover every possible map type.
     95   *
     96   * XXX In this same spirit of imperfection we also use hard-coded map names,
     97   * because maintainers may add map types that the official release doesn't
     98   * even know about, because map types may be added dynamically on some
     99   * platforms.
    100   */
    101 static const NAME_CODE data_redirect_map_types[] = {
    102     DICT_TYPE_HASH, 1,
    103     DICT_TYPE_BTREE, 1,
    104     DICT_TYPE_DBM, 1,
    105     DICT_TYPE_LMDB, 1,
    106     DICT_TYPE_CDB, 1,			/* not a read-write map type */
    107     "sdbm", 1,				/* legacy 3rd-party TLS */
    108     "dbz", 1,				/* just in case */
    109     0, 0,
    110 };
    111 
    112 /* data_redirect_path - redirect path to Postfix-owned directory */
    113 
    114 static char *data_redirect_path(VSTRING *result, const char *path,
    115 			         const char *log_type, const char *log_name)
    116 {
    117     struct stat st;
    118 
    119 #define PATH_DELIMITER "/"
    120 
    121     (void) sane_dirname(result, path);
    122     if (stat(STR(result), &st) != 0 || st.st_uid == var_owner_uid) {
    123 	vstring_strcpy(result, path);
    124     } else {
    125 	msg_warn("request to update %s %s in non-%s directory %s",
    126 		 log_type, log_name, var_mail_owner, STR(result));
    127 	msg_warn("redirecting the request to %s-owned %s %s",
    128 		 var_mail_owner, VAR_DATA_DIR, var_data_dir);
    129 	(void) sane_basename(result, path);
    130 	vstring_prepend(result, PATH_DELIMITER, sizeof(PATH_DELIMITER) - 1);
    131 	vstring_prepend(result, var_data_dir, strlen(var_data_dir));
    132     }
    133     return (STR(result));
    134 }
    135 
    136 /* data_redirect_file - redirect file to Postfix-owned directory */
    137 
    138 char   *data_redirect_file(VSTRING *result, const char *path)
    139 {
    140 
    141     /*
    142      * Sanity check.
    143      */
    144     if (path == STR(result))
    145 	msg_panic("data_redirect_file: result clobbers input");
    146 
    147     return (data_redirect_path(result, path, "file", path));
    148 }
    149 
    150 char   *data_redirect_map(VSTRING *result, const char *map)
    151 {
    152     const char *path;
    153     const char *map_type;
    154     size_t  map_type_len;
    155 
    156 #define MAP_DELIMITER ":"
    157 
    158     /*
    159      * Sanity check.
    160      */
    161     if (map == STR(result))
    162 	msg_panic("data_redirect_map: result clobbers input");
    163 
    164     /*
    165      * Parse the input into map type and map name.
    166      */
    167     path = strchr(map, MAP_DELIMITER[0]);
    168     if (path != 0) {
    169 	map_type = map;
    170 	map_type_len = path - map;
    171 	path += 1;
    172     } else {
    173 	map_type = var_db_type;
    174 	map_type_len = strlen(map_type);
    175 	path = map;
    176     }
    177 
    178     /*
    179      * Redirect the pathname.
    180      */
    181     vstring_strncpy(result, map_type, map_type_len);
    182     if (name_code(data_redirect_map_types, NAME_CODE_FLAG_NONE, STR(result))) {
    183 	data_redirect_path(result, path, "table", map);
    184     } else {
    185 	vstring_strcpy(result, path);
    186     }
    187 
    188     /*
    189      * (Re)combine the map type with the map name.
    190      */
    191     vstring_prepend(result, MAP_DELIMITER, sizeof(MAP_DELIMITER) - 1);
    192     vstring_prepend(result, map_type, map_type_len);
    193     return (STR(result));
    194 }
    195 
    196  /*
    197   * Proof-of-concept test program. This can't be run as automated regression
    198   * test, because the result depends on main.cf information (mail_owner UID
    199   * and data_directory pathname) and on local file system details.
    200   */
    201 #ifdef TEST
    202 
    203 #include <unistd.h>
    204 #include <stdlib.h>
    205 #include <vstring_vstream.h>
    206 #include <mail_conf.h>
    207 
    208 int     main(int argc, char **argv)
    209 {
    210     VSTRING *inbuf = vstring_alloc(100);
    211     VSTRING *result = vstring_alloc(100);
    212     char   *bufp;
    213     char   *cmd;
    214     char   *target;
    215     char   *junk;
    216 
    217     mail_conf_read();
    218 
    219     while (vstring_get_nonl(inbuf, VSTREAM_IN) != VSTREAM_EOF) {
    220 	bufp = STR(inbuf);
    221 	if (!isatty(0)) {
    222 	    vstream_printf("> %s\n", bufp);
    223 	    vstream_fflush(VSTREAM_OUT);
    224 	}
    225 	if (*bufp == '#')
    226 	    continue;
    227 	if ((cmd = mystrtok(&bufp, " \t")) == 0) {
    228 	    vstream_printf("usage: file path|map maptype:mapname\n");
    229 	    vstream_fflush(VSTREAM_OUT);
    230 	    continue;
    231 	}
    232 	target = mystrtokq(&bufp, " \t", CHARS_BRACE);
    233 	junk = mystrtok(&bufp, " \t");
    234 	if (strcmp(cmd, "file") == 0 && target && !junk) {
    235 	    data_redirect_file(result, target);
    236 	    vstream_printf("%s -> %s\n", target, STR(result));
    237 	} else if (strcmp(cmd, "map") == 0 && target && !junk) {
    238 	    data_redirect_map(result, target);
    239 	    vstream_printf("%s -> %s\n", target, STR(result));
    240 	} else {
    241 	    vstream_printf("usage: file path|map maptype:mapname\n");
    242 	}
    243 	vstream_fflush(VSTREAM_OUT);
    244     }
    245     vstring_free(inbuf);
    246     return (0);
    247 }
    248 
    249 #endif
    250