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