masqmail-0.2

view src/local.c @ 27:3654c502a4df

g_malloc terminates the program on failure automatically
author meillo@marmaro.de
date Thu, 06 May 2010 11:50:40 +0200
parents 26e34ae9a3e3
children a80ebfa16cd5
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>
21 #include "masqmail.h"
22 #include "peopen.h"
24 static void
25 message_stream(FILE * out, message * msg, GList * hdr_list, guint flags)
26 {
27 time_t now = time(NULL);
28 GList *node;
30 if (flags & MSGSTR_FROMLINE) {
31 fprintf(out, "From <%s@%s> %s", msg->return_path->local_part, msg->return_path->domain, ctime(&now));
32 }
34 foreach(hdr_list, node) {
35 header *hdr = (header *) (node->data);
36 fputs(hdr->header, out);
37 }
38 putc('\n', out);
39 foreach(msg->data_list, node) {
40 /* From hack: */
41 if (flags & MSGSTR_FROMHACK) {
42 if (strncmp(node->data, "From ", 5) == 0)
43 putc('>', out);
44 }
45 fputs(node->data, out);
46 }
47 putc('\n', out);
48 }
50 gboolean
51 append_file(message * msg, GList * hdr_list, gchar * user)
52 {
53 struct passwd *pw;
54 gboolean ok = FALSE;
56 /* headers may be special for a local delivery */
57 if (hdr_list == NULL)
58 hdr_list = msg->hdr_list;
60 if ((pw = getpwnam(user))) {
61 uid_t saved_uid = geteuid();
62 gid_t saved_gid = getegid();
63 gboolean uid_ok = TRUE, gid_ok = TRUE;
65 if (!conf.run_as_user) {
66 uid_ok = (seteuid(0) == 0);
67 if (uid_ok) {
68 gid_ok = (setegid(conf.mail_gid) == 0);
69 uid_ok = (seteuid(pw->pw_uid) == 0);
70 }
71 }
73 DEBUG(5) debugf("running as euid %d\n", geteuid());
74 DEBUG(5) debugf("running as egid %d\n", 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\n", geteuid());
172 DEBUG(5) debugf("running as egid %d\n", getegid());
174 if (uid_ok && gid_ok) {
175 char *path = g_strdup_printf("%s/Maildir", pw->pw_dir);
176 struct stat statbuf;
177 int ret;
179 DEBUG(5) debugf("path = %s\n", path);
181 ok = TRUE;
182 ret = stat(path, &statbuf);
183 if (ret != 0) {
184 ok = FALSE;
185 if (errno == ENOENT) {
186 logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path);
187 if (mkdir(path, 0700) == 0)
188 ok = TRUE;
189 } else
190 logwrite(LOG_ALERT, "stat of %s failed: %s\n", path, strerror(errno));
191 }
192 if (ok) {
193 ok = FALSE;
194 ret = stat(path, &statbuf);
195 if (S_ISDIR(statbuf.st_mode)) {
196 gchar *subdirs[] = { "tmp", "new", "cur" };
197 int i;
198 for (i = 0; i < 3; i++) {
199 char *path1 = g_strdup_printf("%s/%s", path, subdirs[i]);
200 ret = stat(path1, &statbuf);
201 if (ret != 0) {
202 if (errno == ENOENT) {
203 logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path1);
204 if (mkdir(path1, 0700) != 0)
205 break;
206 }
207 }
208 g_free(path1);
209 }
210 if (i == 3) {
211 FILE *out;
212 mode_t saved_mode = umask(066);
213 /* the qmail style unique works only if delivering with different process.
214 We do not fork for each delivery, so our uid is more unique.
215 Hope it is compatible with all MUAs.
216 */
217 gchar *filename = g_strdup_printf("%s/tmp/%s.%s", path, msg->uid, conf.host_name);
219 DEBUG(5) debugf("filename = %s\n", filename);
221 if ((out = fopen(filename, "w"))) {
222 gchar *newname = g_strdup_printf("%s/new/%s.%s", path, msg->uid, conf.host_name);
223 message_stream(out, msg, hdr_list, flags);
224 ok = TRUE;
225 if (fflush(out) == EOF)
226 ok = FALSE;
227 else if (fdatasync(fileno(out)) != 0) {
228 if (errno != EINVAL)
229 /* some fs do not support this.. I hope this also means that it is not necessary */
230 ok = FALSE;
231 }
232 fclose(out);
233 if (rename(filename, newname) != 0) {
234 ok = FALSE;
235 logwrite(LOG_ALERT, "moving %s to %s failed: %s", filename, newname, strerror(errno));
236 }
237 g_free(newname);
238 }
239 umask(saved_mode);
240 g_free(filename);
241 }
242 } else {
243 logwrite(LOG_ALERT, "%s is not a directory\n", path);
244 errno = ENOTDIR;
245 }
246 }
247 if (!conf.run_as_user) {
248 uid_ok = (seteuid(0) == 0);
249 if (uid_ok) {
250 gid_ok = (setegid(saved_gid) == 0);
251 uid_ok = (seteuid(saved_uid) == 0);
252 }
253 }
254 if (!uid_ok || !gid_ok) {
255 /* FIXME: if this fails we HAVE to exit, because we shall not run
256 with some users id. But we do not return, and so this message
257 will not be finished, so the user will get the message again
258 next time a delivery is attempted... */
259 logwrite(LOG_ALERT, "could not set back uid or gid after local delivery: %s\n", strerror(errno));
260 exit(EXIT_FAILURE);
261 }
262 g_free(path);
263 } else {
264 logwrite(LOG_ALERT, "could not set uid or gid for local delivery, uid = %d: %s\n", pw->pw_uid, strerror(errno));
265 }
266 } else {
267 logwrite(LOG_ALERT, "could not find password entry for user %s\n", user);
268 errno = ENOENT; /* getpwnam does not set errno correctly */
269 }
270 return ok;
271 }
272 #endif
274 gboolean
275 pipe_out(message * msg, GList * hdr_list, address * rcpt, gchar * cmd, guint flags)
276 {
277 gchar *envp[40];
278 FILE *out;
279 uid_t saved_uid = geteuid();
280 gid_t saved_gid = getegid();
281 gboolean ok = FALSE;
282 gint i, n;
283 pid_t pid;
284 void (*old_signal) (int);
285 int status;
287 /* set uid and gid to the mail ids */
288 if (!conf.run_as_user) {
289 set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
290 }
292 /* set environment */
293 {
294 gint i = 0;
295 address *ancestor = addr_find_ancestor(rcpt);
297 envp[i++] = g_strdup_printf("SENDER=%s@%s", msg->return_path->local_part, msg->return_path->domain);
298 envp[i++] = g_strdup_printf("SENDER_DOMAIN=%s", msg->return_path->domain);
299 envp[i++] = g_strdup_printf("SENDER_LOCAL=%s", msg->return_path->local_part);
300 envp[i++] = g_strdup_printf("RECEIVED_HOST=%s", msg->received_host ? msg->received_host : "");
302 envp[i++] = g_strdup_printf("RETURN_PATH=%s@%s", msg->return_path->local_part, msg->return_path->domain);
303 envp[i++] = g_strdup_printf("DOMAIN=%s", ancestor->domain);
305 envp[i++] = g_strdup_printf("LOCAL_PART=%s", ancestor->local_part);
306 envp[i++] = g_strdup_printf("USER=%s", ancestor->local_part);
307 envp[i++] = g_strdup_printf("LOGNAME=%s", ancestor->local_part);
309 envp[i++] = g_strdup_printf("MESSAGE_ID=%s", msg->uid);
310 envp[i++] = g_strdup_printf("QUALIFY_DOMAIN=%s", conf.host_name);
312 envp[i] = NULL;
313 n = i;
314 }
316 old_signal = signal(SIGCHLD, SIG_DFL);
318 out = peidopen(cmd, "w", envp, &pid, conf.mail_uid, conf.mail_gid);
319 if (out != NULL) {
320 message_stream(out, msg, hdr_list, flags);
322 fclose(out);
324 waitpid(pid, &status, 0);
326 if (WEXITSTATUS(status) != 0) {
327 int exstat = WEXITSTATUS(status);
328 logwrite(LOG_ALERT, "process returned %d (%s)\n", exstat, ext_strerror(1024 + exstat));
329 errno = 1024 + exstat;
330 } else if (WIFSIGNALED(status)) {
331 logwrite(LOG_ALERT, "process got signal %d\n", WTERMSIG(status));
332 } else
333 ok = TRUE;
335 } else
336 logwrite(LOG_ALERT, "could not open pipe '%s': %s\n", cmd, strerror(errno));
338 signal(SIGCHLD, old_signal);
340 /* free environment */
341 for (i = 0; i < n; i++) {
342 g_free(envp[i]);
343 }
345 /* set uid and gid back */
346 if (!conf.run_as_user) {
347 set_euidgid(saved_uid, saved_gid, NULL, NULL);
348 }
350 return ok;
351 }