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