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