masqmail

view src/local.c @ 6:c9bce6bb2a5d

switched tests dir to ordinary Makefile
author meillo@marmaro.de
date Fri, 26 Sep 2008 22:55:52 +0200
parents
children 26e34ae9a3e3
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 "masqmail.h"
20 #include "peopen.h"
21 #include <sys/wait.h>
23 static
24 void message_stream(FILE *out, message *msg, GList *hdr_list, guint flags)
25 {
26 time_t now = time(NULL);
27 GList *node;
29 if(flags & MSGSTR_FROMLINE){
30 fprintf(out, "From <%s@%s> %s", msg->return_path->local_part,
31 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 append_file(message *msg, GList *hdr_list, gchar *user)
51 {
52 struct passwd *pw;
53 gboolean ok = FALSE;
55 /* headers may be special for a local delivery */
56 if(hdr_list == NULL)
57 hdr_list = msg->hdr_list;
59 if((pw = getpwnam(user))){
60 uid_t saved_uid = geteuid();
61 gid_t saved_gid = getegid();
62 gboolean uid_ok = TRUE, gid_ok = TRUE;
64 if(!conf.run_as_user){
65 uid_ok = (seteuid(0) == 0);
66 if(uid_ok){
67 gid_ok = (setegid(conf.mail_gid) == 0);
68 uid_ok = (seteuid(pw->pw_uid) == 0);
69 }
70 }
72 DEBUG(5) debugf("running as euid %d\n", geteuid());
73 DEBUG(5) debugf("running as egid %d\n", 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);
96 message_stream(out, msg, hdr_list, MSGSTR_FROMLINE|MSGSTR_FROMHACK);
98 ok = TRUE;
100 /* close when still user */
101 fclose(out);
102 #ifdef USE_LIBLOCKFILE
103 mailunlock();
104 #endif
105 }else{
106 fclose(out);
107 #ifdef USE_LIBLOCKFILE
108 DEBUG(3) debugf("could not lock file %s: error %d\n",
109 filename, err);
110 } /* XEmacs indenting convenience... */
111 #else
112 DEBUG(3) debugf("could not lock file %s: %s\n",
113 filename, strerror(errno));
114 }
115 #endif
116 }else{
117 logwrite(LOG_ALERT, "could not open file %s: %s\n",
118 filename, strerror(errno));
119 }
120 g_free(filename);
122 if(!conf.run_as_user){
123 uid_ok = (seteuid(0) == 0);
124 if(uid_ok){
125 gid_ok = (setegid(saved_gid) == 0);
126 uid_ok = (seteuid(saved_uid) == 0);
127 }
128 }
130 if(!uid_ok || !gid_ok){
131 /* FIXME: if this fails we HAVE to exit, because we shall not run
132 with some users id. But we do not return, and so this message
133 will not be finished, so the user will get the message again
134 next time a delivery is attempted... */
135 logwrite(LOG_ALERT,
136 "could not set back uid or gid after local delivery: %s\n",
137 strerror(errno));
138 logwrite(LOG_ALERT,
139 "uid=%d, gid=%d, euid=%d, egid=%d, want = %d, %d\n",
140 getuid(), getgid(), geteuid(), getegid(), saved_uid, saved_gid);
141 exit(EXIT_FAILURE);
142 }
143 }else{
144 logwrite(LOG_ALERT,
145 "could not set uid or gid for local delivery, uid = %d: %s\n",
146 pw->pw_uid, strerror(errno));
147 }
148 }else{
149 logwrite(LOG_ALERT, "could not find password entry for user %s\n", user);
150 errno = ENOENT; /* getpwnam does not set errno correctly */
151 }
153 return ok;
154 }
156 #ifdef ENABLE_MAILDIR
157 gboolean maildir_out(message *msg, GList *hdr_list, gchar *user, guint flags)
158 {
159 struct passwd *pw;
160 gboolean ok = FALSE;
162 /* headers may be special for a local delivery */
163 if(hdr_list == NULL)
164 hdr_list = msg->hdr_list;
166 if((pw = getpwnam(user))){
167 uid_t saved_uid = geteuid();
168 gid_t saved_gid = getegid();
169 gboolean uid_ok = TRUE, gid_ok = TRUE;
171 if(!conf.run_as_user){
172 uid_ok = (seteuid(0) == 0);
173 if(uid_ok){
174 gid_ok = (setegid(conf.mail_gid) == 0);
175 uid_ok = (seteuid(pw->pw_uid) == 0);
176 }
177 }
179 DEBUG(5) debugf("running as euid %d\n", geteuid());
180 DEBUG(5) debugf("running as egid %d\n", getegid());
182 if(uid_ok && gid_ok){
183 char *path = g_strdup_printf("%s/Maildir", pw->pw_dir);
184 struct stat statbuf;
185 int ret;
187 DEBUG(5) debugf("path = %s\n", path);
189 ok = TRUE;
190 ret = stat(path, &statbuf);
191 if(ret != 0){
192 ok = FALSE;
193 if(errno == ENOENT){
194 logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path);
195 if(mkdir(path, 0700) == 0)
196 ok = TRUE;
197 }else
198 logwrite(LOG_ALERT, "stat of %s failed: %s\n", path, strerror(errno));
199 }
200 if(ok){
201 ok = FALSE;
202 ret = stat(path, &statbuf);
203 if(S_ISDIR(statbuf.st_mode)){
204 gchar *subdirs[] = {"tmp", "new", "cur"};
205 int i;
206 for(i = 0; i < 3; i++){
207 char *path1 = g_strdup_printf("%s/%s", path, subdirs[i]);
208 ret = stat(path1, &statbuf);
209 if(ret != 0){
210 if(errno == ENOENT){
211 logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path1);
212 if(mkdir(path1, 0700) != 0) break;
213 }
214 }
215 g_free(path1);
216 }
217 if(i == 3){
218 FILE *out;
219 mode_t saved_mode = umask(066);
220 /* the qmail style unique works only if delivering
221 with different process. We do not fork for each delivery,
222 so our uid is more unique. Hope it is compatible with all
223 MUAs.
224 */
225 gchar *filename = g_strdup_printf("%s/tmp/%s.%s", path, msg->uid, conf.host_name);
227 DEBUG(5) debugf("filename = %s\n", filename);
229 if((out = fopen(filename, "w"))){
230 gchar *newname =
231 g_strdup_printf("%s/new/%s.%s", path, msg->uid, conf.host_name);
232 message_stream(out, msg, hdr_list, flags);
233 ok = TRUE;
234 if(fflush(out) == EOF) ok = FALSE;
235 else if(fdatasync(fileno(out)) != 0){
236 if(errno != EINVAL) /* some fs do not support this..
237 I hope this also means that it is not necessary */
238 ok = FALSE;
239 }
240 fclose(out);
241 if(rename(filename, newname) != 0){
242 ok = FALSE;
243 logwrite(LOG_ALERT, "moving %s to %s failed: %s",
244 filename, newname, strerror(errno));
245 }
246 g_free(newname);
247 }
248 umask(saved_mode);
249 g_free(filename);
250 }
251 }else{
252 logwrite(LOG_ALERT, "%s is not a directory\n", path);
253 errno = ENOTDIR;
254 }
255 }
256 if(!conf.run_as_user){
257 uid_ok = (seteuid(0) == 0);
258 if(uid_ok){
259 gid_ok = (setegid(saved_gid) == 0);
260 uid_ok = (seteuid(saved_uid) == 0);
261 }
262 }
263 if(!uid_ok || !gid_ok){
264 /* FIXME: if this fails we HAVE to exit, because we shall not run
265 with some users id. But we do not return, and so this message
266 will not be finished, so the user will get the message again
267 next time a delivery is attempted... */
268 logwrite(LOG_ALERT,
269 "could not set back uid or gid after local delivery: %s\n",
270 strerror(errno));
271 exit(EXIT_FAILURE);
272 }
273 g_free(path);
274 }else{
275 logwrite(LOG_ALERT,
276 "could not set uid or gid for local delivery, uid = %d: %s\n",
277 pw->pw_uid, strerror(errno));
278 }
279 }else{
280 logwrite(LOG_ALERT, "could not find password entry for user %s\n", user);
281 errno = ENOENT; /* getpwnam does not set errno correctly */
282 }
283 return ok;
284 }
285 #endif
287 gboolean
288 pipe_out(message *msg, GList *hdr_list, address *rcpt, gchar *cmd, guint flags)
289 {
290 gchar *envp[40];
291 FILE *out;
292 uid_t saved_uid = geteuid();
293 gid_t saved_gid = getegid();
294 gboolean ok = FALSE;
295 gint i, n;
296 pid_t pid;
297 void (*old_signal)(int);
298 int status;
300 /* set uid and gid to the mail ids */
301 if(!conf.run_as_user){
302 set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
303 }
305 /* set environment */
306 {
307 gint i = 0;
308 address *ancestor = addr_find_ancestor(rcpt);
310 envp[i++] = g_strdup_printf("SENDER=%s@%s", msg->return_path->local_part, msg->return_path->domain);
311 envp[i++] = g_strdup_printf("SENDER_DOMAIN=%s", msg->return_path->domain);
312 envp[i++] = g_strdup_printf("SENDER_LOCAL=%s", msg->return_path->local_part);
313 envp[i++] = g_strdup_printf("RECEIVED_HOST=%s", msg->received_host ? msg->received_host : "");
315 envp[i++] = g_strdup_printf("RETURN_PATH=%s@%s",
316 msg->return_path->local_part, msg->return_path->domain);
317 envp[i++] = g_strdup_printf("DOMAIN=%s", ancestor->domain);
319 envp[i++] = g_strdup_printf("LOCAL_PART=%s", ancestor->local_part);
320 envp[i++] = g_strdup_printf("USER=%s", ancestor->local_part);
321 envp[i++] = g_strdup_printf("LOGNAME=%s", ancestor->local_part);
323 envp[i++] = g_strdup_printf("MESSAGE_ID=%s", msg->uid);
324 envp[i++] = g_strdup_printf("QUALIFY_DOMAIN=%s", conf.host_name);
326 envp[i] = NULL;
327 n = i;
328 }
330 old_signal = signal(SIGCHLD, SIG_DFL);
332 out = peidopen(cmd, "w", envp, &pid, conf.mail_uid, conf.mail_gid);
333 if(out != NULL){
334 message_stream(out, msg, hdr_list, flags);
336 fclose(out);
338 waitpid(pid, &status, 0);
340 if(WEXITSTATUS(status) != 0){
341 int exstat = WEXITSTATUS(status);
342 logwrite(LOG_ALERT, "process returned %d (%s)\n", exstat, ext_strerror(1024 + exstat));
343 errno = 1024 + exstat;
344 }else if(WIFSIGNALED(status)){
345 logwrite(LOG_ALERT, "process got signal %d\n", WTERMSIG(status));
346 }else
347 ok = TRUE;
349 }else
350 logwrite(LOG_ALERT, "could not open pipe '%s': %s\n", cmd, strerror(errno));
352 signal(SIGCHLD, old_signal);
354 /* free environment */
355 for(i = 0; i < n; i++){
356 g_free(envp[i]);
357 }
359 /* set uid and gid back */
360 if(!conf.run_as_user){
361 set_euidgid(saved_uid, saved_gid, NULL, NULL);
362 }
364 return ok;
365 }