masqmail-0.2

view src/local.c @ 147:cb42157b3520

improved the Makefile (check diff for details)
author meillo@marmaro.de
date Thu, 08 Jul 2010 00:26:55 +0200
parents f671821d8222
children d1c53e76096f
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, egid %d\n", geteuid(), getegid());
75 if (uid_ok && gid_ok) {
76 gchar *filename;
77 FILE *out;
79 filename = g_strdup_printf("%s/%s", conf.mail_dir, user);
80 if ((out = fopen(filename, "a"))) {
81 #ifdef USE_LIBLOCKFILE
82 gint err;
83 /* lock file using liblockfile */
84 err = maillock(user, 3);
85 if (err == 0) {
86 #else
87 /* lock file: */
88 struct flock lock;
89 lock.l_type = F_WRLCK;
90 lock.l_whence = SEEK_END;
91 lock.l_start = lock.l_len = 0;
92 if (fcntl(fileno(out), F_SETLK, &lock) != -1) {
93 #endif
94 fchmod(fileno(out), 0600);
95 message_stream(out, msg, hdr_list, MSGSTR_FROMLINE | MSGSTR_FROMHACK);
96 ok = TRUE;
98 /* close when still user */
99 fclose(out);
100 #ifdef USE_LIBLOCKFILE
101 mailunlock();
102 #endif
103 } else {
104 fclose(out);
105 #ifdef USE_LIBLOCKFILE
106 DEBUG(3) debugf("could not lock file %s: error %d\n", filename, err);
107 } /* XEmacs indenting convenience... */
108 #else
109 DEBUG(3) debugf("could not lock file %s: %s\n", filename, strerror(errno));
110 }
111 #endif
112 } else {
113 logwrite(LOG_ALERT, "could not open file %s: %s\n", filename, strerror(errno));
114 }
115 g_free(filename);
117 if (!conf.run_as_user) {
118 uid_ok = (seteuid(0) == 0);
119 if (uid_ok) {
120 gid_ok = (setegid(saved_gid) == 0);
121 uid_ok = (seteuid(saved_uid) == 0);
122 }
123 }
125 if (!uid_ok || !gid_ok) {
126 /* FIXME: if this fails we HAVE to exit, because we shall not run
127 with some users id. But we do not return, and so this message
128 will not be finished, so the user will get the message again
129 next time a delivery is attempted... */
130 logwrite(LOG_ALERT, "could not set back uid or gid after local delivery: %s\n", strerror(errno));
131 logwrite(LOG_ALERT, "uid=%d, gid=%d, euid=%d, egid=%d, want = %d, %d\n",
132 getuid(), getgid(), geteuid(), getegid(), saved_uid, saved_gid);
133 exit(EXIT_FAILURE);
134 }
135 } else {
136 logwrite(LOG_ALERT, "could not set uid or gid for local delivery, uid = %d: %s\n", pw->pw_uid, strerror(errno));
137 }
138 } else {
139 logwrite(LOG_ALERT, "could not find password entry for user %s\n", user);
140 errno = ENOENT; /* getpwnam does not set errno correctly */
141 }
143 return ok;
144 }
146 #ifdef ENABLE_MAILDIR
147 gboolean
148 maildir_out(message * msg, GList * hdr_list, gchar * user, guint flags)
149 {
150 struct passwd *pw;
151 gboolean ok = FALSE;
153 /* headers may be special for a local delivery */
154 if (hdr_list == NULL)
155 hdr_list = msg->hdr_list;
157 if ((pw = getpwnam(user))) {
158 uid_t saved_uid = geteuid();
159 gid_t saved_gid = getegid();
160 gboolean uid_ok = TRUE, gid_ok = TRUE;
162 if (!conf.run_as_user) {
163 uid_ok = (seteuid(0) == 0);
164 if (uid_ok) {
165 gid_ok = (setegid(conf.mail_gid) == 0);
166 uid_ok = (seteuid(pw->pw_uid) == 0);
167 }
168 }
170 DEBUG(5) debugf("running as euid %d, egid %d\n", geteuid(), getegid());
172 if (uid_ok && gid_ok) {
173 char *path = g_strdup_printf("%s/Maildir", pw->pw_dir);
174 struct stat statbuf;
175 int ret;
177 DEBUG(5) debugf(" path = %s\n", path);
179 ok = TRUE;
180 ret = stat(path, &statbuf);
181 if (ret != 0) {
182 ok = FALSE;
183 if (errno == ENOENT) {
184 logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path);
185 if (mkdir(path, 0700) == 0)
186 ok = TRUE;
187 } else
188 logwrite(LOG_ALERT, "stat of %s failed: %s\n", path, strerror(errno));
189 }
190 if (ok) {
191 ok = FALSE;
192 ret = stat(path, &statbuf);
193 if (S_ISDIR(statbuf.st_mode)) {
194 gchar *subdirs[] = { "tmp", "new", "cur" };
195 int i;
196 for (i = 0; i < 3; i++) {
197 char *path1 = g_strdup_printf("%s/%s", path, subdirs[i]);
198 ret = stat(path1, &statbuf);
199 if (ret != 0) {
200 if (errno == ENOENT) {
201 logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path1);
202 if (mkdir(path1, 0700) != 0)
203 break;
204 }
205 }
206 g_free(path1);
207 }
208 if (i == 3) {
209 FILE *out;
210 mode_t saved_mode = umask(066);
211 /* the qmail style unique works only if delivering with different process.
212 We do not fork for each delivery, so our uid is more unique.
213 Hope it is compatible with all MUAs.
214 */
215 gchar *filename = g_strdup_printf("%s/tmp/%s.%s", path, msg->uid, conf.host_name);
217 DEBUG(5) debugf("filename = %s\n", filename);
219 if ((out = fopen(filename, "w"))) {
220 gchar *newname = g_strdup_printf("%s/new/%s.%s", path, msg->uid, conf.host_name);
221 message_stream(out, msg, hdr_list, flags);
222 ok = TRUE;
223 if (fflush(out) == EOF)
224 ok = FALSE;
225 else if (fdatasync(fileno(out)) != 0) {
226 if (errno != EINVAL)
227 /* some fs do not support this.. I hope this also means that it is not necessary */
228 ok = FALSE;
229 }
230 fclose(out);
231 if (rename(filename, newname) != 0) {
232 ok = FALSE;
233 logwrite(LOG_ALERT, "moving %s to %s failed: %s", filename, newname, strerror(errno));
234 }
235 g_free(newname);
236 }
237 umask(saved_mode);
238 g_free(filename);
239 }
240 } else {
241 logwrite(LOG_ALERT, "%s is not a directory\n", path);
242 errno = ENOTDIR;
243 }
244 }
245 if (!conf.run_as_user) {
246 uid_ok = (seteuid(0) == 0);
247 if (uid_ok) {
248 gid_ok = (setegid(saved_gid) == 0);
249 uid_ok = (seteuid(saved_uid) == 0);
250 }
251 }
252 if (!uid_ok || !gid_ok) {
253 /* FIXME: if this fails we HAVE to exit, because we shall not run
254 with some users id. But we do not return, and so this message
255 will not be finished, so the user will get the message again
256 next time a delivery is attempted... */
257 logwrite(LOG_ALERT, "could not set back uid or gid after local delivery: %s\n", strerror(errno));
258 exit(EXIT_FAILURE);
259 }
260 g_free(path);
261 } else {
262 logwrite(LOG_ALERT, "could not set uid or gid for local delivery, uid = %d: %s\n", pw->pw_uid, strerror(errno));
263 }
264 } else {
265 logwrite(LOG_ALERT, "could not find password entry for user %s\n", user);
266 errno = ENOENT; /* getpwnam does not set errno correctly */
267 }
268 return ok;
269 }
270 #endif
272 gboolean
273 pipe_out(message * msg, GList * hdr_list, address * rcpt, gchar * cmd, guint flags)
274 {
275 gchar *envp[40];
276 FILE *out;
277 uid_t saved_uid = geteuid();
278 gid_t saved_gid = getegid();
279 gboolean ok = FALSE;
280 gint i, n;
281 pid_t pid;
282 void (*old_signal) (int);
283 int status;
285 /* set uid and gid to the mail ids */
286 if (!conf.run_as_user) {
287 set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
288 }
290 /* set environment */
291 {
292 gint i = 0;
293 address *ancestor = addr_find_ancestor(rcpt);
295 envp[i++] = g_strdup_printf("SENDER=%s@%s", msg->return_path->local_part, msg->return_path->domain);
296 envp[i++] = g_strdup_printf("SENDER_DOMAIN=%s", msg->return_path->domain);
297 envp[i++] = g_strdup_printf("SENDER_LOCAL=%s", msg->return_path->local_part);
298 envp[i++] = g_strdup_printf("RECEIVED_HOST=%s", msg->received_host ? msg->received_host : "");
300 envp[i++] = g_strdup_printf("RETURN_PATH=%s@%s", msg->return_path->local_part, msg->return_path->domain);
301 envp[i++] = g_strdup_printf("DOMAIN=%s", ancestor->domain);
303 envp[i++] = g_strdup_printf("LOCAL_PART=%s", ancestor->local_part);
304 envp[i++] = g_strdup_printf("USER=%s", ancestor->local_part);
305 envp[i++] = g_strdup_printf("LOGNAME=%s", ancestor->local_part);
307 envp[i++] = g_strdup_printf("MESSAGE_ID=%s", msg->uid);
308 envp[i++] = g_strdup_printf("QUALIFY_DOMAIN=%s", conf.host_name);
310 envp[i] = NULL;
311 n = i;
312 }
314 old_signal = signal(SIGCHLD, SIG_DFL);
316 out = peidopen(cmd, "w", envp, &pid, conf.mail_uid, conf.mail_gid);
317 if (out != NULL) {
318 message_stream(out, msg, hdr_list, flags);
320 fclose(out);
322 waitpid(pid, &status, 0);
324 if (WEXITSTATUS(status) != 0) {
325 int exstat = WEXITSTATUS(status);
326 logwrite(LOG_ALERT, "process returned %d (%s)\n", exstat, ext_strerror(1024 + exstat));
327 errno = 1024 + exstat;
328 } else if (WIFSIGNALED(status)) {
329 logwrite(LOG_ALERT, "process got signal %d\n", WTERMSIG(status));
330 } else
331 ok = TRUE;
333 } else
334 logwrite(LOG_ALERT, "could not open pipe '%s': %s\n", cmd, strerror(errno));
336 signal(SIGCHLD, old_signal);
338 /* free environment */
339 for (i = 0; i < n; i++) {
340 g_free(envp[i]);
341 }
343 /* set uid and gid back */
344 if (!conf.run_as_user) {
345 set_euidgid(saved_uid, saved_gid, NULL, NULL);
346 }
348 return ok;
349 }