summaryrefslogtreecommitdiffstats
path: root/src/global/data_redirect.c
blob: 1a8fe0f9cb3ed8d2771c34fdef6cb2069a6132ec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/*++
/* NAME
/*	data_redirect 3
/* SUMMARY
/*	redirect legacy writes to Postfix-owned data directory
/* SYNOPSIS
/*	#include <data_redirect.h>
/*
/*	char	*data_redirect_file(result, path)
/*	VSTRING	*result;
/*	const char *path;
/*
/*	char	*data_redirect_map(result, map)
/*	VSTRING	*result;
/*	const char *map;
/* DESCRIPTION
/*	With Postfix version 2.5 and later, the tlsmgr(8) and
/*	verify(8) servers no longer open cache files with root
/*	privilege. This avoids a potential security loophole where
/*	the ownership of a file (or directory) does not match the
/*	trust level of the content of that file (or directory).
/*
/*	This module implements a migration aid that allows a
/*	transition without disruption of service.
/*
/*	data_redirect_file() detects a request to open a file in a
/*	non-Postfix directory, logs a warning, and redirects the
/*	request to the Postfix-owned data_directory.
/*
/*	data_redirect_map() performs the same function for a limited
/*	subset of file-based lookup tables.
/*
/*	Arguments:
/* .IP result
/*	A possibly redirected copy of the input.
/* .IP path
/*	The pathname that may be redirected.
/* .IP map
/*	The "mapname" or "maptype:mapname" that may be redirected.
/*	The result is always in "maptype:mapname" form.
/* BUGS
/*	Only a few map types are redirected. This is acceptable for
/*	a temporary migration tool.
/* DIAGNOSTICS
/*	Fatal errors: memory allocation failure.
/* CONFIGURATION PARAMETERS
/*	data_directory, location of Postfix-writable files
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <sys/stat.h>
#include <string.h>

/* Utility library. */

#include <msg.h>
#include <vstring.h>
#include <stringops.h>
#include <split_at.h>
#include <name_code.h>
#include <dict_db.h>
#include <dict_dbm.h>
#include <dict_cdb.h>
#include <dict_lmdb.h>
#include <warn_stat.h>

/* Global directory. */

#include <mail_params.h>
#include <dict_proxy.h>
#include <data_redirect.h>

/* Application-specific. */

#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)

 /*
  * Redirect only these map types, so that we don't try stupid things with
  * NIS, *SQL or LDAP. This is a transition feature for legacy TLS and verify
  * configurations, so it does not have to cover every possible map type.
  * 
  * XXX In this same spirit of imperfection we also use hard-coded map names,
  * because maintainers may add map types that the official release doesn't
  * even know about, because map types may be added dynamically on some
  * platforms.
  */
static const NAME_CODE data_redirect_map_types[] = {
    DICT_TYPE_HASH, 1,
    DICT_TYPE_BTREE, 1,
    DICT_TYPE_DBM, 1,
    DICT_TYPE_LMDB, 1,
    DICT_TYPE_CDB, 1,			/* not a read-write map type */
    "sdbm", 1,				/* legacy 3rd-party TLS */
    "dbz", 1,				/* just in case */
    0, 0,
};

/* data_redirect_path - redirect path to Postfix-owned directory */

static char *data_redirect_path(VSTRING *result, const char *path,
			         const char *log_type, const char *log_name)
{
    struct stat st;

#define PATH_DELIMITER "/"

    (void) sane_dirname(result, path);
    if (stat(STR(result), &st) != 0 || st.st_uid == var_owner_uid) {
	vstring_strcpy(result, path);
    } else {
	msg_warn("request to update %s %s in non-%s directory %s",
		 log_type, log_name, var_mail_owner, STR(result));
	msg_warn("redirecting the request to %s-owned %s %s",
		 var_mail_owner, VAR_DATA_DIR, var_data_dir);
	(void) sane_basename(result, path);
	vstring_prepend(result, PATH_DELIMITER, sizeof(PATH_DELIMITER) - 1);
	vstring_prepend(result, var_data_dir, strlen(var_data_dir));
    }
    return (STR(result));
}

/* data_redirect_file - redirect file to Postfix-owned directory */

char   *data_redirect_file(VSTRING *result, const char *path)
{

    /*
     * Sanity check.
     */
    if (path == STR(result))
	msg_panic("data_redirect_file: result clobbers input");

    return (data_redirect_path(result, path, "file", path));
}

char   *data_redirect_map(VSTRING *result, const char *map)
{
    const char *path;
    const char *map_type;
    size_t  map_type_len;

#define MAP_DELIMITER ":"

    /*
     * Sanity check.
     */
    if (map == STR(result))
	msg_panic("data_redirect_map: result clobbers input");

    /*
     * Parse the input into map type and map name.
     */
    path = strchr(map, MAP_DELIMITER[0]);
    if (path != 0) {
	map_type = map;
	map_type_len = path - map;
	path += 1;
    } else {
	map_type = var_db_type;
	map_type_len = strlen(map_type);
	path = map;
    }

    /*
     * Redirect the pathname.
     */
    vstring_strncpy(result, map_type, map_type_len);
    if (name_code(data_redirect_map_types, NAME_CODE_FLAG_NONE, STR(result))) {
	data_redirect_path(result, path, "table", map);
    } else {
	vstring_strcpy(result, path);
    }

    /*
     * (Re)combine the map type with the map name.
     */
    vstring_prepend(result, MAP_DELIMITER, sizeof(MAP_DELIMITER) - 1);
    vstring_prepend(result, map_type, map_type_len);
    return (STR(result));
}

 /*
  * Proof-of-concept test program. This can't be run as automated regression
  * test, because the result depends on main.cf information (mail_owner UID
  * and data_directory pathname) and on local file system details.
  */
#ifdef TEST

#include <unistd.h>
#include <stdlib.h>
#include <vstring_vstream.h>
#include <mail_conf.h>

int     main(int argc, char **argv)
{
    VSTRING *inbuf = vstring_alloc(100);
    VSTRING *result = vstring_alloc(100);
    char   *bufp;
    char   *cmd;
    char   *target;
    char   *junk;

    mail_conf_read();

    while (vstring_get_nonl(inbuf, VSTREAM_IN) != VSTREAM_EOF) {
	bufp = STR(inbuf);
	if (!isatty(0)) {
	    vstream_printf("> %s\n", bufp);
	    vstream_fflush(VSTREAM_OUT);
	}
	if (*bufp == '#')
	    continue;
	if ((cmd = mystrtok(&bufp, " \t")) == 0) {
	    vstream_printf("usage: file path|map maptype:mapname\n");
	    vstream_fflush(VSTREAM_OUT);
	    continue;
	}
	target = mystrtokq(&bufp, " \t");
	junk = mystrtok(&bufp, " \t");
	if (strcmp(cmd, "file") == 0 && target && !junk) {
	    data_redirect_file(result, target);
	    vstream_printf("%s -> %s\n", target, STR(result));
	} else if (strcmp(cmd, "map") == 0 && target && !junk) {
	    data_redirect_map(result, target);
	    vstream_printf("%s -> %s\n", target, STR(result));
	} else {
	    vstream_printf("usage: file path|map maptype:mapname\n");
	}
	vstream_fflush(VSTREAM_OUT);
    }
    vstring_free(inbuf);
    return (0);
}

#endif