masqmail-0.2
view src/local.c @ 184:b3835b6b834b
Security fix! Correct handling of seteuid() return value
See Debian bug #638002, reported by John Lightsey.
When possible the (already available) set_euidgid() function is used.
Additionally, it is unnecessary to change the identity when writing
into an already open file descriptor.
This should fix the problem.
author | markus schnalke <meillo@marmaro.de> |
---|---|
date | Sat, 27 Aug 2011 18:00:40 +0200 |
parents | a80ebfa16cd5 |
children |
line source
1 /* MasqMail
2 Copyright (C) 1999-2001 Oliver Kurth
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 */
19 #include <sys/wait.h>
20 #include <sys/stat.h>
22 #include "masqmail.h"
23 #include "peopen.h"
25 static void
26 message_stream(FILE * out, message * msg, GList * hdr_list, guint flags)
27 {
28 time_t now = time(NULL);
29 GList *node;
31 if (flags & MSGSTR_FROMLINE) {
32 fprintf(out, "From <%s@%s> %s", msg->return_path->local_part, msg->return_path->domain, ctime(&now));
33 }
35 foreach(hdr_list, node) {
36 header *hdr = (header *) (node->data);
37 fputs(hdr->header, out);
38 }
39 putc('\n', out);
40 foreach(msg->data_list, node) {
41 /* From hack: */
42 if (flags & MSGSTR_FROMHACK) {
43 if (strncmp(node->data, "From ", 5) == 0)
44 putc('>', out);
45 }
46 fputs(node->data, out);
47 }
48 putc('\n', out);
49 }
51 gboolean
52 append_file(message * msg, GList * hdr_list, gchar * user)
53 {
54 struct passwd *pw;
55 gboolean ok = FALSE;
57 /* headers may be special for a local delivery */
58 if (hdr_list == NULL)
59 hdr_list = msg->hdr_list;
61 if ((pw = getpwnam(user))) {
62 uid_t saved_uid = geteuid();
63 gid_t saved_gid = getegid();
64 gboolean uid_ok = TRUE, gid_ok = TRUE;
66 if (!conf.run_as_user) {
67 uid_ok = (seteuid(0) == 0);
68 if (uid_ok) {
69 gid_ok = (setegid(conf.mail_gid) == 0);
70 uid_ok = (seteuid(pw->pw_uid) == 0);
71 }
72 }
74 DEBUG(5) debugf("running as euid %d, egid %d\n", geteuid(), getegid());
76 if (uid_ok && gid_ok) {
77 gchar *filename;
78 FILE *out;
80 filename = g_strdup_printf("%s/%s", conf.mail_dir, user);
81 if ((out = fopen(filename, "a"))) {
82 #ifdef USE_LIBLOCKFILE
83 gint err;
84 /* lock file using liblockfile */
85 err = maillock(user, 3);
86 if (err == 0) {
87 #else
88 /* lock file: */
89 struct flock lock;
90 lock.l_type = F_WRLCK;
91 lock.l_whence = SEEK_END;
92 lock.l_start = lock.l_len = 0;
93 if (fcntl(fileno(out), F_SETLK, &lock) != -1) {
94 #endif
95 fchmod(fileno(out), 0600);
96 message_stream(out, msg, hdr_list, MSGSTR_FROMLINE | MSGSTR_FROMHACK);
97 ok = TRUE;
99 /* close when still user */
100 fclose(out);
101 #ifdef USE_LIBLOCKFILE
102 mailunlock();
103 #endif
104 } else {
105 fclose(out);
106 #ifdef USE_LIBLOCKFILE
107 DEBUG(3) debugf("could not lock file %s: error %d\n", filename, err);
108 } /* XEmacs indenting convenience... */
109 #else
110 DEBUG(3) debugf("could not lock file %s: %s\n", filename, strerror(errno));
111 }
112 #endif
113 } else {
114 logwrite(LOG_ALERT, "could not open file %s: %s\n", filename, strerror(errno));
115 }
116 g_free(filename);
118 if (!conf.run_as_user) {
119 uid_ok = (seteuid(0) == 0);
120 if (uid_ok) {
121 gid_ok = (setegid(saved_gid) == 0);
122 uid_ok = (seteuid(saved_uid) == 0);
123 }
124 }
126 if (!uid_ok || !gid_ok) {
127 /* FIXME: if this fails we HAVE to exit, because we shall not run
128 with some users id. But we do not return, and so this message
129 will not be finished, so the user will get the message again
130 next time a delivery is attempted... */
131 logwrite(LOG_ALERT, "could not set back uid or gid after local delivery: %s\n", strerror(errno));
132 logwrite(LOG_ALERT, "uid=%d, gid=%d, euid=%d, egid=%d, want = %d, %d\n",
133 getuid(), getgid(), geteuid(), getegid(), saved_uid, saved_gid);
134 exit(EXIT_FAILURE);
135 }
136 } else {
137 logwrite(LOG_ALERT, "could not set uid or gid for local delivery, uid = %d: %s\n", pw->pw_uid, strerror(errno));
138 }
139 } else {
140 logwrite(LOG_ALERT, "could not find password entry for user %s\n", user);
141 errno = ENOENT; /* getpwnam does not set errno correctly */
142 }
144 return ok;
145 }
147 #ifdef ENABLE_MAILDIR
148 gboolean
149 maildir_out(message * msg, GList * hdr_list, gchar * user, guint flags)
150 {
151 struct passwd *pw;
152 gboolean ok = FALSE;
154 /* headers may be special for a local delivery */
155 if (hdr_list == NULL)
156 hdr_list = msg->hdr_list;
158 if ((pw = getpwnam(user))) {
159 uid_t saved_uid = geteuid();
160 gid_t saved_gid = getegid();
161 gboolean uid_ok = TRUE, gid_ok = TRUE;
163 if (!conf.run_as_user) {
164 uid_ok = (seteuid(0) == 0);
165 if (uid_ok) {
166 gid_ok = (setegid(conf.mail_gid) == 0);
167 uid_ok = (seteuid(pw->pw_uid) == 0);
168 }
169 }
171 DEBUG(5) debugf("running as euid %d, egid %d\n", geteuid(), getegid());
173 if (uid_ok && gid_ok) {
174 char *path = g_strdup_printf("%s/Maildir", pw->pw_dir);
175 struct stat statbuf;
176 int ret;
178 DEBUG(5) debugf(" path = %s\n", path);
180 ok = TRUE;
181 ret = stat(path, &statbuf);
182 if (ret != 0) {
183 ok = FALSE;
184 if (errno == ENOENT) {
185 logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path);
186 if (mkdir(path, 0700) == 0)
187 ok = TRUE;
188 } else
189 logwrite(LOG_ALERT, "stat of %s failed: %s\n", path, strerror(errno));
190 }
191 if (ok) {
192 ok = FALSE;
193 ret = stat(path, &statbuf);
194 if (S_ISDIR(statbuf.st_mode)) {
195 gchar *subdirs[] = { "tmp", "new", "cur" };
196 int i;
197 for (i = 0; i < 3; i++) {
198 char *path1 = g_strdup_printf("%s/%s", path, subdirs[i]);
199 ret = stat(path1, &statbuf);
200 if (ret != 0) {
201 if (errno == ENOENT) {
202 logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path1);
203 if (mkdir(path1, 0700) != 0)
204 break;
205 }
206 }
207 g_free(path1);
208 }
209 if (i == 3) {
210 FILE *out;
211 mode_t saved_mode = umask(066);
212 /* the qmail style unique works only if delivering with different process.
213 We do not fork for each delivery, so our uid is more unique.
214 Hope it is compatible with all MUAs.
215 */
216 gchar *filename = g_strdup_printf("%s/tmp/%s.%s", path, msg->uid, conf.host_name);
218 DEBUG(5) debugf("filename = %s\n", filename);
220 if ((out = fopen(filename, "w"))) {
221 gchar *newname = g_strdup_printf("%s/new/%s.%s", path, msg->uid, conf.host_name);
222 message_stream(out, msg, hdr_list, flags);
223 ok = TRUE;
224 if (fflush(out) == EOF)
225 ok = FALSE;
226 else if (fdatasync(fileno(out)) != 0) {
227 if (errno != EINVAL)
228 /* some fs do not support this.. I hope this also means that it is not necessary */
229 ok = FALSE;
230 }
231 fclose(out);
232 if (rename(filename, newname) != 0) {
233 ok = FALSE;
234 logwrite(LOG_ALERT, "moving %s to %s failed: %s", filename, newname, strerror(errno));
235 }
236 g_free(newname);
237 }
238 umask(saved_mode);
239 g_free(filename);
240 }
241 } else {
242 logwrite(LOG_ALERT, "%s is not a directory\n", path);
243 errno = ENOTDIR;
244 }
245 }
246 if (!conf.run_as_user) {
247 uid_ok = (seteuid(0) == 0);
248 if (uid_ok) {
249 gid_ok = (setegid(saved_gid) == 0);
250 uid_ok = (seteuid(saved_uid) == 0);
251 }
252 }
253 if (!uid_ok || !gid_ok) {
254 /* FIXME: if this fails we HAVE to exit, because we shall not run
255 with some users id. But we do not return, and so this message
256 will not be finished, so the user will get the message again
257 next time a delivery is attempted... */
258 logwrite(LOG_ALERT, "could not set back uid or gid after local delivery: %s\n", strerror(errno));
259 exit(EXIT_FAILURE);
260 }
261 g_free(path);
262 } else {
263 logwrite(LOG_ALERT, "could not set uid or gid for local delivery, uid = %d: %s\n", pw->pw_uid, strerror(errno));
264 }
265 } else {
266 logwrite(LOG_ALERT, "could not find password entry for user %s\n", user);
267 errno = ENOENT; /* getpwnam does not set errno correctly */
268 }
269 return ok;
270 }
271 #endif
273 gboolean
274 pipe_out(message * msg, GList * hdr_list, address * rcpt, gchar * cmd, guint flags)
275 {
276 gchar *envp[40];
277 FILE *out;
278 uid_t saved_uid = geteuid();
279 gid_t saved_gid = getegid();
280 gboolean ok = FALSE;
281 gint i, n;
282 pid_t pid;
283 void (*old_signal) (int);
284 int status;
286 /* set uid and gid to the mail ids */
287 if (!conf.run_as_user) {
288 set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
289 }
291 /* set environment */
292 {
293 gint i = 0;
294 address *ancestor = addr_find_ancestor(rcpt);
296 envp[i++] = g_strdup_printf("SENDER=%s@%s", msg->return_path->local_part, msg->return_path->domain);
297 envp[i++] = g_strdup_printf("SENDER_DOMAIN=%s", msg->return_path->domain);
298 envp[i++] = g_strdup_printf("SENDER_LOCAL=%s", msg->return_path->local_part);
299 envp[i++] = g_strdup_printf("RECEIVED_HOST=%s", msg->received_host ? msg->received_host : "");
301 envp[i++] = g_strdup_printf("RETURN_PATH=%s@%s", msg->return_path->local_part, msg->return_path->domain);
302 envp[i++] = g_strdup_printf("DOMAIN=%s", ancestor->domain);
304 envp[i++] = g_strdup_printf("LOCAL_PART=%s", ancestor->local_part);
305 envp[i++] = g_strdup_printf("USER=%s", ancestor->local_part);
306 envp[i++] = g_strdup_printf("LOGNAME=%s", ancestor->local_part);
308 envp[i++] = g_strdup_printf("MESSAGE_ID=%s", msg->uid);
309 envp[i++] = g_strdup_printf("QUALIFY_DOMAIN=%s", conf.host_name);
311 envp[i] = NULL;
312 n = i;
313 }
315 old_signal = signal(SIGCHLD, SIG_DFL);
317 out = peidopen(cmd, "w", envp, &pid, conf.mail_uid, conf.mail_gid);
318 if (out != NULL) {
319 message_stream(out, msg, hdr_list, flags);
321 fclose(out);
323 waitpid(pid, &status, 0);
325 if (WEXITSTATUS(status) != 0) {
326 int exstat = WEXITSTATUS(status);
327 logwrite(LOG_ALERT, "process returned %d (%s)\n", exstat, ext_strerror(1024 + exstat));
328 errno = 1024 + exstat;
329 } else if (WIFSIGNALED(status)) {
330 logwrite(LOG_ALERT, "process got signal %d\n", WTERMSIG(status));
331 } else
332 ok = TRUE;
334 } else
335 logwrite(LOG_ALERT, "could not open pipe '%s': %s\n", cmd, strerror(errno));
337 signal(SIGCHLD, old_signal);
339 /* free environment */
340 for (i = 0; i < n; i++) {
341 g_free(envp[i]);
342 }
344 /* set uid and gid back */
345 if (!conf.run_as_user) {
346 set_euidgid(saved_uid, saved_gid, NULL, NULL);
347 }
349 return ok;
350 }