1/*++
2/* NAME
3/*	deliver_include 3
4/* SUMMARY
5/*	deliver to addresses listed in include file
6/* SYNOPSIS
7/*	#include "local.h"
8/*
9/*	int	deliver_include(state, usr_attr, path)
10/*	LOCAL_STATE state;
11/*	USER_ATTR usr_attr;
12/*	char	*path;
13/* DESCRIPTION
14/*	deliver_include() processes the contents of the named include
15/*	file and delivers to each address listed. Some sanity checks
16/*	are done on the include file permissions and type.
17/*
18/*	Arguments:
19/* .IP state
20/*	The attributes that specify the message, recipient and more.
21/*	Attributes describing alias, include, or forward expansion.
22/*	A table with the results from expanding aliases or lists.
23/*	A table with delivered-to: addresses taken from the message.
24/* .IP usr_attr
25/*	Attributes describing user rights and environment.
26/* .IP path
27/*	Pathname of the include file.
28/* DIAGNOSTICS
29/*	Fatal errors: out of memory. Warnings: bad include file type
30/*	or permissions. The result is non-zero when delivery should be
31/*	tried again.
32/* SEE ALSO
33/*	token(3) tokenize list
34/* LICENSE
35/* .ad
36/* .fi
37/*	The Secure Mailer license must be distributed with this software.
38/* AUTHOR(S)
39/*	Wietse Venema
40/*	IBM T.J. Watson Research
41/*	P.O. Box 704
42/*	Yorktown Heights, NY 10598, USA
43/*--*/
44
45/* System library. */
46
47#include <sys_defs.h>
48#include <sys/stat.h>
49#include <unistd.h>
50#include <string.h>
51#include <fcntl.h>
52#include <errno.h>
53
54/* Utility library. */
55
56#include <msg.h>
57#include <htable.h>
58#include <mymalloc.h>
59#include <vstream.h>
60#include <open_as.h>
61#include <stat_as.h>
62#include <iostuff.h>
63#include <mypwd.h>
64
65/* Global library. */
66
67#include <bounce.h>
68#include <defer.h>
69#include <been_here.h>
70#include <mail_params.h>
71#include <ext_prop.h>
72#include <sent.h>
73
74/* Application-specific. */
75
76#include "local.h"
77
78/* deliver_include - open include file and deliver */
79
80int     deliver_include(LOCAL_STATE state, USER_ATTR usr_attr, char *path)
81{
82    const char *myname = "deliver_include";
83    struct stat st;
84    struct mypasswd *file_pwd = 0;
85    int     status;
86    VSTREAM *fp;
87    int     fd;
88
89    /*
90     * Make verbose logging easier to understand.
91     */
92    state.level++;
93    if (msg_verbose)
94	MSG_LOG_STATE(myname, state);
95
96    /*
97     * DUPLICATE ELIMINATION
98     *
99     * Don't process this include file more than once as this particular user.
100     */
101    if (been_here(state.dup_filter, "include %ld %s", (long) usr_attr.uid, path))
102	return (0);
103    state.msg_attr.exp_from = state.msg_attr.local;
104
105    /*
106     * Can of worms. Allow this include file to be symlinked, but disallow
107     * inclusion of special files or of files with world write permission
108     * enabled.
109     */
110    if (*path != '/') {
111	msg_warn(":include:%s uses a relative path", path);
112	dsb_simple(state.msg_attr.why, "5.3.5",
113		   "mail system configuration error");
114	return (bounce_append(BOUNCE_FLAGS(state.request),
115			      BOUNCE_ATTR(state.msg_attr)));
116    }
117    if (stat_as(path, &st, usr_attr.uid, usr_attr.gid) < 0) {
118	msg_warn("unable to lookup :include: file %s: %m", path);
119	dsb_simple(state.msg_attr.why, "5.3.5",
120		   "mail system configuration error");
121	return (bounce_append(BOUNCE_FLAGS(state.request),
122			      BOUNCE_ATTR(state.msg_attr)));
123    }
124    if (S_ISREG(st.st_mode) == 0) {
125	msg_warn(":include: file %s is not a regular file", path);
126	dsb_simple(state.msg_attr.why, "5.3.5",
127		   "mail system configuration error");
128	return (bounce_append(BOUNCE_FLAGS(state.request),
129			      BOUNCE_ATTR(state.msg_attr)));
130    }
131    if (st.st_mode & S_IWOTH) {
132	msg_warn(":include: file %s is world writable", path);
133	dsb_simple(state.msg_attr.why, "5.3.5",
134		   "mail system configuration error");
135	return (bounce_append(BOUNCE_FLAGS(state.request),
136			      BOUNCE_ATTR(state.msg_attr)));
137    }
138
139    /*
140     * DELIVERY POLICY
141     *
142     * Set the expansion type attribute so that we can decide if destinations
143     * such as /file/name and |command are allowed at all.
144     */
145    state.msg_attr.exp_type = EXPAND_TYPE_INCL;
146
147    /*
148     * DELIVERY RIGHTS
149     *
150     * When a non-root include file is listed in a root-owned alias, use the
151     * rights of the include file owner.  We do not want to give the include
152     * file owner control of the default account.
153     *
154     * When an include file is listed in a user-owned alias or .forward file,
155     * leave the delivery rights alone. Users should not be able to make
156     * things happen with someone else's rights just by including some file
157     * that is owned by their victim.
158     */
159    if (usr_attr.uid == 0) {
160	if ((errno = mypwuid_err(st.st_uid, &file_pwd)) != 0 || file_pwd == 0) {
161	    msg_warn(errno ? "cannot find username for uid %ld: %m" :
162		     "cannot find username for uid %ld", (long) st.st_uid);
163	    msg_warn("%s: cannot find :include: file owner username", path);
164	    dsb_simple(state.msg_attr.why, "4.3.5",
165		       "mail system configuration error");
166	    return (defer_append(BOUNCE_FLAGS(state.request),
167				 BOUNCE_ATTR(state.msg_attr)));
168	}
169	if (file_pwd->pw_uid != 0)
170	    SET_USER_ATTR(usr_attr, file_pwd, state.level);
171    }
172
173    /*
174     * MESSAGE FORWARDING
175     *
176     * When no owner attribute is set (either via an owner- alias, or as part of
177     * .forward file processing), set the owner attribute, to disable direct
178     * delivery of local recipients. By now it is clear that the owner
179     * attribute should have been called forwarder instead.
180     */
181    if (state.msg_attr.owner == 0)
182	state.msg_attr.owner = state.msg_attr.rcpt.address;
183
184    /*
185     * From here on no early returns or we have a memory leak.
186     *
187     * FILE OPEN RIGHTS
188     *
189     * Use the delivery rights to open the include file. When no delivery rights
190     * were established sofar, the file containing the :include: is owned by
191     * root, so it should be OK to open any file that is accessible to root.
192     * The command and file delivery routines are responsible for setting the
193     * proper delivery rights. These are the rights of the default user, in
194     * case the :include: is in a root-owned alias.
195     *
196     * Don't propagate unmatched extensions unless permitted to do so.
197     */
198#define FOPEN_AS(p,u,g) ((fd = open_as(p,O_RDONLY,0,u,g)) >= 0 ? \
199				vstream_fdopen(fd,O_RDONLY) : 0)
200
201    if ((fp = FOPEN_AS(path, usr_attr.uid, usr_attr.gid)) == 0) {
202	msg_warn("cannot open include file %s: %m", path);
203	dsb_simple(state.msg_attr.why, "5.3.5",
204		   "mail system configuration error");
205	status = bounce_append(BOUNCE_FLAGS(state.request),
206			       BOUNCE_ATTR(state.msg_attr));
207    } else {
208	if ((local_ext_prop_mask & EXT_PROP_INCLUDE) == 0)
209	    state.msg_attr.unmatched = 0;
210	close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC);
211	status = deliver_token_stream(state, usr_attr, fp, (int *) 0);
212	if (vstream_fclose(fp))
213	    msg_warn("close %s: %m", path);
214    }
215
216    /*
217     * Cleanup.
218     */
219    if (file_pwd)
220	mypwfree(file_pwd);
221
222    return (status);
223}
224