1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Mailbox file locking.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
8 * Copyright (c) 1996 Christos Zoulas. All rights reserved.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by Christos Zoulas.
21 * 4. The name of the author may not be used to endorse or promote products
22 * derived from this software without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
27 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
28 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
29 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
33 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 #define n_FILE dotlock
38 #ifndef HAVE_AMALGAMATION
42 #include <sys/utsname.h>
44 #define APID_SZ 40 /* sufficient for 128 bits pids XXX nail.h */
45 #define CREATE_RETRIES 5 /* XXX nail.h */
46 #define DOTLOCK_RETRIES 15 /* XXX nail.h */
49 # define O_BITS (O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_SYNC)
51 # define O_BITS (O_WRONLY | O_CREAT | O_TRUNC | O_EXCL)
54 /* TODO Allow safe setgid, optional: check on startup wether in receive mode,
55 * TODO start helper process that is setgid and only does dotlocking.
56 * TODO Approach two, also optional: use a configurable setgid dotlock prog */
57 #define GID_MAYBESET(P) \
58 do if (realgid != effectivegid && !_maybe_setgid(P, effectivegid)) {\
64 do if (realgid != effectivegid && setgid(realgid) == -1) {\
69 /* GID_*() helper: set the gid if the path is in the normal mail spool */
70 static bool_t
_maybe_setgid(char const *name
, gid_t gid
);
72 /* Check if we can write a lock file at all */
73 static bool_t
_dot_dir_access(char const *fname
);
75 /* Create a unique file. O_EXCL does not really work over NFS so we follow
76 * the following trick (inspired by S.R. van den Berg):
77 * - make a mostly unique filename and try to create it
78 * - link the unique filename to our target
79 * - get the link count of the target
80 * - unlink the mostly unique filename
81 * - if the link count was 2, then we are ok; else we've failed */
82 static int create_exclusive(char const *fname
);
84 /* fcntl(2) plus error handling */
85 static bool_t
_dot_fcntl_lock(int fd
, enum flock_type ft
);
87 /* Print a message :) */
88 static void _dot_lock_msg(char const *fname
);
91 _maybe_setgid(char const *name
, gid_t gid
)
93 char const safepath
[] = MAILSPOOL
;
97 if (strncmp(name
, safepath
, sizeof(safepath
) -1) ||
98 strchr(name
+ sizeof(safepath
), '/') != NULL
)
101 rv
= (setgid(gid
) != -1);
107 _dot_dir_access(char const *fname
)
115 path
= ac_alloc(i
+ 1 +1);
117 memcpy(path
, fname
, i
+1);
118 p
= strrchr(path
, '/');
121 if (p
== NULL
|| *path
== '\0') {
126 if ((rv
= is_dir(path
))) {
128 if (!access(path
, R_OK
| W_OK
| X_OK
)) {
131 } else if (errno
!= EINTR
)
141 create_exclusive(char const *fname
) /* TODO revisit! */
143 char path
[PATH_MAX
], apid
[APID_SZ
], *hostname
;
149 int pid
, fd
, serrno
, cc
;
154 hostname
= ut
.nodename
;
157 /* We generate a semi-unique filename, from hostname.(pid ^ usec) */
158 if ((ptr
= strrchr(fname
, '/')) == NULL
)
163 snprintf(path
, sizeof path
, "%.*s.%s.%x",
164 (int)PTR2SIZE(ptr
- fname
), fname
, hostname
, (pid
^ (int)t
));
166 /* We try to create the unique filename */
167 for (ntries
= 0; ntries
< CREATE_RETRIES
; ++ntries
) {
169 fd
= open(path
, O_BITS
, 0);
173 snprintf(apid
, APID_SZ
, "%d", pid
);
174 write(fd
, apid
, strlen(apid
));
177 } else if (serrno
!= EEXIST
) {
183 /* We link the path to the name */
185 cc
= link(path
, fname
);
191 /* Note that we stat our own exclusively created name, not the
192 * destination, since the destination can be affected by others */
193 if (stat(path
, &st
) == -1) {
202 /* If the number of links was two (one for the unique file and one for
203 * the lock), we've won the race */
204 if (st
.st_nlink
!= 2) {
221 _dot_fcntl_lock(int fd
, enum flock_type ft
)
228 case FLOCK_READ
: rv
= F_RDLCK
; break;
229 case FLOCK_WRITE
: rv
= F_WRLCK
; break;
231 case FLOCK_UNLOCK
: rv
= F_UNLCK
; break;
234 /* (For now we restart, but in the future we may not */
237 flp
.l_whence
= SEEK_SET
;
239 while (!(rv
= (fcntl(fd
, F_SETLKW
, &flp
) != -1)) && errno
== EINTR
)
246 _dot_lock_msg(char const *fname
)
249 fprintf(stdout
, _("Creating dot lock for \"%s\""), fname
);
254 fcntl_lock(int fd
, enum flock_type ft
, size_t pollmsecs
)
260 for (rv
= FAL0
, retries
= 0; retries
< DOTLOCK_RETRIES
; ++retries
)
261 if ((rv
= _dot_fcntl_lock(fd
, ft
)) || pollmsecs
== 0)
264 sleep(1); /* TODO pollmsecs -> use finer grain */
270 dot_lock(char const *fname
, int fd
, size_t pollmsecs
)
276 bool_t didmsg
= FAL0
, rv
= FAL0
;
279 if (options
& OPT_D_VV
) {
280 _dot_lock_msg(fname
);
285 while (!_dot_fcntl_lock(fd
, FLOCK_WRITE
))
290 if (pollmsecs
> 0 && ++retries
< DOTLOCK_RETRIES
) {
292 _dot_lock_msg(fname
);
295 sleep(1); /* TODO pollmsecs -> use finer grain */
303 /* If we can't deal with dot-lock files in there, go with the FLOCK lock and
304 * don't fail otherwise */
305 if (!_dot_dir_access(fname
)) {
306 if (options
& OPT_D_V
) /* TODO Really? dotlock's are crucial! Always!?! */
308 _("Can't manage lock files in \"%s\", please check permissions\n"),
315 sigaddset(&nset
, SIGHUP
);
316 sigaddset(&nset
, SIGINT
);
317 sigaddset(&nset
, SIGQUIT
);
318 sigaddset(&nset
, SIGTERM
);
319 sigaddset(&nset
, SIGTTIN
);
320 sigaddset(&nset
, SIGTTOU
);
321 sigaddset(&nset
, SIGTSTP
);
322 sigaddset(&nset
, SIGCHLD
);
324 snprintf(path
, sizeof(path
), "%s.lock", fname
);
326 while (retries
++ < DOTLOCK_RETRIES
) {
327 sigprocmask(SIG_BLOCK
, &nset
, &oset
);
328 rv
= (create_exclusive(path
) == 0);
330 sigprocmask(SIG_SETMASK
, &oset
, NULL
);
334 while (!_dot_fcntl_lock(fd
, FLOCK_UNLOCK
))
335 if (pollmsecs
== 0 || retries
++ >= DOTLOCK_RETRIES
)
339 _dot_lock_msg(fname
);
342 sleep(1); /* TODO pollmsecs -> use finer grain */
345 if (olderrno
!= EEXIST
)
347 if (pollmsecs
== 0) {
352 while (!_dot_fcntl_lock(fd
, FLOCK_WRITE
))
353 if (pollmsecs
== 0 || retries
++ >= DOTLOCK_RETRIES
)
357 _dot_lock_msg(fname
);
360 sleep(1); /* TODO pollmsecs -> use finer grain */
364 fprintf(stderr
, _("Is \"%s\" a stale lock? Please remove file manually.\n"),
374 dot_unlock(char const *fname
)
379 if (!_dot_dir_access(fname
))
382 snprintf(path
, sizeof(path
), "%s.lock", fname
);