1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ n_dotlock(): creation of an exclusive "dotlock" file.
4 * Copyright (c) 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
9 #ifndef HAVE_AMALGAMATION
19 /* XXX Our Popen() main() takes void, temporary global data store */
21 static enum n_file_lock_type a_dotlock_flt
;
22 static int a_dotlock_fd
;
23 struct n_dotlock_info
*a_dotlock_dip
;
26 /* main() of fork(2)ed dot file locker */
28 static int a_dotlock_main(void);
34 /* Use PATH_MAX not NAME_MAX to catch those "we proclaim the minimum value"
35 * problems (SunOS), since the pathconf(3) value comes too late! */
36 char name
[PATH_MAX
+1];
37 struct n_dotlock_info di
;
38 struct stat stb
, fdstb
;
39 enum n_dotlock_state dls
;
42 enum n_file_lock_type flt
;
45 /* Ignore SIGPIPE, we'll see EPIPE and "fall through" */
46 safe_signal(SIGPIPE
, SIG_IGN
);
48 /* Get the arguments "passed to us" */
56 dls
= n_DLS_CANT_CHDIR
| n_DLS_ABANDON
;
58 if((cp
= strrchr(di
.di_file_name
, '/')) != NULL
){
59 char const *fname
= cp
+ 1;
61 while(PTRCMP(cp
- 1, >, di
.di_file_name
) && cp
[-1] == '/')
63 cp
= savestrbuf(di
.di_file_name
, PTR2SIZE(cp
- di
.di_file_name
));
67 di
.di_file_name
= fname
;
70 /* So we're here, but then again the file can be a symbolic link!
71 * This is however only true if we do not have realpath(3) available since
72 * that'll have resolved the path already otherwise; nonetheless, let
73 * readlink(2) be a precondition for dotlocking and keep this code */
74 if(lstat(cp
= di
.di_file_name
, &stb
) == -1)
76 if(S_ISLNK(stb
.st_mode
)){
77 /* Use salloc() and hope we stay in builtin buffer.. */
82 for(x
= NULL
, i
= PATH_MAX
;; i
+= PATH_MAX
){
84 sr
= readlink(cp
, x
, i
);
86 dls
= n_DLS_FISHY
| n_DLS_ABANDON
;
89 if(UICMP(z
, sr
, <, i
)){
99 dls
= n_DLS_FISHY
| n_DLS_ABANDON
;
101 /* Bail out if the file has changed its identity in the meanwhile */
102 if(fstat(fd
, &fdstb
) == -1 ||
103 fdstb
.st_dev
!= stb
.st_dev
|| fdstb
.st_ino
!= stb
.st_ino
||
104 fdstb
.st_uid
!= stb
.st_uid
|| fdstb
.st_gid
!= stb
.st_gid
||
105 fdstb
.st_mode
!= stb
.st_mode
)
108 /* Be aware, even if the error is false! Note the shared code in dotlock.h
109 * *requires* that it is possible to create a filename at least one byte
110 * longer than di_lock_name! */
111 do/* while(0) breaker */{
112 # ifdef HAVE_PATHCONF
115 int i
= snprintf(name
, sizeof name
, "%s.lock", di
.di_file_name
);
117 /* fd is a file, not portable to use for _PC_NAME_MAX */
120 dls
= n_DLS_NAMETOOLONG
| n_DLS_ABANDON
;
123 # ifdef HAVE_PATHCONF
125 if((pc
= pathconf(".", _PC_NAME_MAX
)) == -1){
126 /* errno unchanged: no limit */
129 }else if(pc
- 1 >= (long)i
)
134 if(UICMP(z
, NAME_MAX
- 1, <, (uiz_t
)i
))
138 di
.di_lock_name
= name
;
140 /* We are in the directory of the mailbox for which we have to create
141 * a dotlock file for. We don't know whether we have realpath(3) available,
142 * and manually resolving the path is due especially given that S-nail
143 * supports the special "%:" syntax to warp any file into a "system
144 * mailbox"; there may also be multiple system mailbox directories...
145 * So what we do is that we fstat(2) the mailbox and check its UID and
146 * GID against that of our own process: if any of those mismatch we must
147 * either assume a directory we are not allowed to write in, or that we run
148 * via -u/$USER/%USER as someone else, in which case we favour our
149 * privilege-separated dotlock process */
150 assert(cp
!= NULL
); /* Ugly: avoid a useless var and reuse that one */
151 if(access(".", W_OK
)){
152 /* This may however also indicate a read-only filesystem, which is not
153 * really an error from our point of view since the mailbox will degrade
154 * to a readonly one for which no dotlock is needed, then, and errors
155 * may arise only due to actions which require box modifications */
157 dls
= n_DLS_ROFS
| n_DLS_ABANDON
;
162 if(cp
== NULL
|| stb
.st_uid
!= user_id
|| stb
.st_gid
!= group_id
){
164 char const *args
[13];
166 snprintf(itoabuf
, sizeof itoabuf
, "%" PRIuZ
, di
.di_pollmsecs
);
167 args
[ 0] = VAL_PRIVSEP
;
168 args
[ 1] = (flt
== FLT_READ
? "rdotlock" : "wdotlock");
169 args
[ 2] = "mailbox"; args
[ 3] = di
.di_file_name
;
170 args
[ 4] = "name"; args
[ 5] = di
.di_lock_name
;
171 args
[ 6] = "hostname"; args
[ 7] = di
.di_hostname
;
172 args
[ 8] = "randstr"; args
[ 9] = di
.di_randstr
;
173 args
[10] = "pollmsecs"; args
[11] = itoabuf
;
175 execv(VAL_LIBEXECDIR
"/" VAL_UAGENT
"-privsep", UNCONST(args
));
178 write(STDOUT_FILENO
, &dls
, sizeof dls
);
179 /* But fall through and try it with normal privileges! */
182 /* So let's try and call it ourselfs! Note that we don't block signals just
183 * like our privsep child does, the user will anyway be able to remove his
184 * file again, and if we're in -u/$USER mode then we are allowed to access
185 * the user's box: shall we leave behind a stale dotlock then at least we
186 * start a friendly human conversation. Since we cannot handle SIGKILL and
187 * SIGSTOP malicious things could happen whatever we do */
188 safe_signal(SIGHUP
, SIG_IGN
);
189 safe_signal(SIGINT
, SIG_IGN
);
190 safe_signal(SIGQUIT
, SIG_IGN
);
191 safe_signal(SIGTERM
, SIG_IGN
);
194 dls
= a_dotlock_create(&di
);
197 /* Finally: notify our parent about the actual lock state.. */
199 write(STDOUT_FILENO
, &dls
, sizeof dls
);
200 close(STDOUT_FILENO
);
202 /* ..then eventually wait until we shall remove the lock again, which will
203 * be notified via the read returning */
204 if(dls
== n_DLS_NONE
){
205 read(STDIN_FILENO
, &dls
, sizeof dls
);
212 #endif /* HAVE_DOTLOCK */
215 n_dotlock(char const *fname
, int fd
, enum n_file_lock_type flt
,
216 off_t off
, off_t len
, size_t pollmsecs
){
220 n_err(_("Creating dotlock for %s "), n_shell_quote_cp(fname, FAL0))
223 n_err(_("Trying to lock file %s "), n_shell_quote_cp(fname, FAL0))
228 struct n_dotlock_info di
;
229 enum n_dotlock_state dls
;
233 union {size_t tries
; int (*ptf
)(void); char const *sh
; ssize_t r
;} u
;
234 bool_t flocked
, didmsg
;
245 if(options
& OPT_D_VV
){
251 for(u
.tries
= 0; !n_file_lock(fd
, flt
, off
, len
, 0);)
252 switch((serrno
= errno
)){
256 if(pollmsecs
> 0 && ++u
.tries
< FILE_LOCK_TRIES
){
261 n_msleep(pollmsecs
, FAL0
);
282 /* Create control-pipe for our dot file locker process, which will remove
283 * the lock and terminate once the pipe is closed, for whatever reason */
284 if(pipe_cloexec(cpipe
) == -1){
286 emsg
= N_(" Can't create file lock control pipe\n");
290 /* And the locker process itself; it'll be a (rather cheap) thread only
291 * unless the lock has to be placed in the system spool and we have our
292 * privilege-separated dotlock program available, in which case that will be
293 * executed and do "it" with changed group-id */
294 di
.di_file_name
= fname
;
295 di
.di_pollmsecs
= pollmsecs
;
296 /* Initialize some more stuff; query the two strings in the parent in order
297 * to cache the result of the former and anyway minimalize child page-ins.
298 * Especially uname(3) may hang for multiple seconds when it is called the
300 di
.di_hostname
= nodename(FAL0
);
301 di
.di_randstr
= getrandstring(16);
306 u
.ptf
= &a_dotlock_main
;
307 rv
= Popen((char*)-1, "W", u
.sh
, NULL
, cpipe
[1]);
313 emsg
= N_(" Can't create file lock process\n");
317 /* Let's check whether we were able to create the dotlock file */
319 u
.r
= read(cpipe
[0], &dls
, sizeof dls
);
320 if(UICMP(z
, u
.r
, !=, sizeof dls
)){
321 serrno
= (u
.r
!= -1) ? EAGAIN
: errno
;
322 dls
= n_DLS_DUNNO
| n_DLS_ABANDON
;
326 if(dls
== n_DLS_NONE
|| (dls
& n_DLS_ABANDON
))
329 switch(dls
& ~n_DLS_ABANDON
){
332 case n_DLS_CANT_CHDIR
:
333 if(options
& OPT_D_V
)
334 emsg
= N_(" Can't change directory! Please check permissions\n");
337 case n_DLS_NAMETOOLONG
:
338 emsg
= N_("Resulting dotlock filename would be too long\n");
342 assert(dls
& n_DLS_ABANDON
);
343 if(options
& OPT_D_V
)
344 emsg
= N_(" Read-only filesystem, not creating lock file\n");
348 if(options
& OPT_D_V
)
349 emsg
= N_(" Can't create a lock file! Please check permissions\n"
350 " (Maybe setting *dotlock-ignore-error* variable helps.)\n");
354 if(options
& OPT_D_V
)
355 emsg
= N_(" Can't find privilege-separated file lock program\n");
358 case n_DLS_PRIVFAILED
:
359 emsg
= N_(" Privilege-separated file lock program can't change "
364 emsg
= N_(" It seems there is a stale dotlock file?\n"
365 " Please remove the lock file manually, then retry\n");
369 emsg
= N_(" Fishy! Is someone trying to \"steal\" foreign files?\n"
370 " Please check the mailbox file etc. manually, then retry\n");
371 serrno
= EAGAIN
; /* ? Hack to ignore *dotlock-ignore-error* xxx */
375 emsg
= N_(" Unspecified dotlock file control process error.\n"
376 " Like broken I/O pipe; this one is unlikely to happen\n");
400 if(dls
& n_DLS_ABANDON
){
411 if(flocked
&& (serrno
== EROFS
||
412 (serrno
!= EAGAIN
&& serrno
!= EEXIST
&&
413 ok_blook(dotlock_ignore_error
))))
427 #endif /* HAVE_DOTLOCK */