1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ n_dotlock(): creation of an exclusive "dotlock" file.
4 * Copyright (c) 2016 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
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 if(i
< 0 || UICMP(32, i
, >=, sizeof name
)){
119 dls
= n_DLS_NAMETOOLONG
| n_DLS_ABANDON
;
123 /* fd is a file, not portable to use for _PC_NAME_MAX */
124 # ifdef HAVE_PATHCONF
126 if((pc
= pathconf(".", _PC_NAME_MAX
)) == -1){
127 /* errno unchanged: no limit */
131 if(UICMP(z
, NAME_MAX
- 1, <, i
))
133 # ifdef HAVE_PATHCONF
134 }else if(pc
- 1 >= (long)i
)
141 di
.di_lock_name
= name
;
143 /* We are in the directory of the mailbox for which we have to create
144 * a dotlock file for. Any symbolic links have been resolved.
145 * We don't know whether we have realpath(3) available,and manually
146 * resolving the path is due especially given that S-nail supports the
147 * special "%:" syntax to warp any file into a "system mailbox"; there may
148 * also be multiple system mailbox directories...
149 * So what we do is that we fstat(2) the mailbox and check its UID and
150 * GID against that of our own process: if any of those mismatch we must
151 * either assume a directory we are not allowed to write in, or that we run
152 * via -u/$USER/%USER as someone else, in which case we favour our
153 * privilege-separated dotlock process */
154 assert(cp
!= NULL
); /* Ugly: avoid a useless var and reuse that one */
155 if(access(".", W_OK
)){
156 /* This may however also indicate a read-only filesystem, which is not
157 * really an error from our point of view since the mailbox will degrade
158 * to a readonly one for which no dotlock is needed, then, and errors
159 * may arise only due to actions which require box modifications */
161 dls
= n_DLS_ROFS
| n_DLS_ABANDON
;
166 if(cp
== NULL
|| stb
.st_uid
!= n_user_id
|| stb
.st_gid
!= n_group_id
){
168 char const *args
[13];
170 snprintf(itoabuf
, sizeof itoabuf
, "%" PRIuZ
, di
.di_pollmsecs
);
171 args
[ 0] = VAL_PRIVSEP
;
172 args
[ 1] = (flt
== FLT_READ
? "rdotlock" : "wdotlock");
173 args
[ 2] = "mailbox"; args
[ 3] = di
.di_file_name
;
174 args
[ 4] = "name"; args
[ 5] = di
.di_lock_name
;
175 args
[ 6] = "hostname"; args
[ 7] = di
.di_hostname
;
176 args
[ 8] = "randstr"; args
[ 9] = di
.di_randstr
;
177 args
[10] = "pollmsecs"; args
[11] = itoabuf
;
179 execv(VAL_LIBEXECDIR
"/" VAL_UAGENT
"-privsep", n_UNCONST(args
));
182 write(STDOUT_FILENO
, &dls
, sizeof dls
);
183 /* But fall through and try it with normal privileges! */
186 /* So let's try and call it ourselfs! Note that we don't block signals just
187 * like our privsep child does, the user will anyway be able to remove his
188 * file again, and if we're in -u/$USER mode then we are allowed to access
189 * the user's box: shall we leave behind a stale dotlock then at least we
190 * start a friendly human conversation. Since we cannot handle SIGKILL and
191 * SIGSTOP malicious things could happen whatever we do */
192 safe_signal(SIGHUP
, SIG_IGN
);
193 safe_signal(SIGINT
, SIG_IGN
);
194 safe_signal(SIGQUIT
, SIG_IGN
);
195 safe_signal(SIGTERM
, SIG_IGN
);
198 dls
= a_dotlock_create(&di
);
201 /* Finally: notify our parent about the actual lock state.. */
203 write(STDOUT_FILENO
, &dls
, sizeof dls
);
204 close(STDOUT_FILENO
);
206 /* ..then eventually wait until we shall remove the lock again, which will
207 * be notified via the read returning */
208 if(dls
== n_DLS_NONE
){
209 read(STDIN_FILENO
, &dls
, sizeof dls
);
216 #endif /* HAVE_DOTLOCK */
219 n_dotlock(char const *fname
, int fd
, enum n_file_lock_type flt
,
220 off_t off
, off_t len
, size_t pollmsecs
){
224 n_err(_("Creating dotlock for %s "), n_shexp_quote_cp(fname, FAL0))
227 n_err(_("Trying to lock file %s "), n_shexp_quote_cp(fname, FAL0))
232 struct n_dotlock_info di
;
233 enum n_dotlock_state dls
;
237 union {size_t tries
; int (*ptf
)(void); char const *sh
; ssize_t r
;} u
;
238 bool_t flocked
, didmsg
;
242 if(pollmsecs
== UIZ_MAX
)
243 pollmsecs
= FILE_LOCK_MILLIS
;
252 if(n_poption
& n_PO_D_VV
){
258 for(u
.tries
= 0; !n_file_lock(fd
, flt
, off
, len
, 0);)
259 switch((serrno
= errno
)){
263 if(pollmsecs
> 0 && ++u
.tries
< FILE_LOCK_TRIES
){
268 n_msleep(pollmsecs
, FAL0
);
289 /* Create control-pipe for our dot file locker process, which will remove
290 * the lock and terminate once the pipe is closed, for whatever reason */
291 if(pipe_cloexec(cpipe
) == -1){
293 emsg
= N_(" Can't create dotlock file control pipe\n");
297 /* And the locker process itself; it'll be a (rather cheap) thread only
298 * unless the lock has to be placed in the system spool and we have our
299 * privilege-separated dotlock program available, in which case that will be
300 * executed and do "it" */
301 di
.di_file_name
= fname
;
302 di
.di_pollmsecs
= pollmsecs
;
303 /* Initialize some more stuff; query the two strings in the parent in order
304 * to cache the result of the former and anyway minimalize child page-ins.
305 * Especially uname(3) may hang for multiple seconds when it is called the
307 di
.di_hostname
= nodename(FAL0
);
308 di
.di_randstr
= getrandstring(16);
313 u
.ptf
= &a_dotlock_main
;
314 rv
= Popen((char*)-1, "W", u
.sh
, NULL
, cpipe
[1]);
320 emsg
= N_(" Can't create file lock process\n");
324 /* Let's check whether we were able to create the dotlock file */
326 u
.r
= read(cpipe
[0], &dls
, sizeof dls
);
327 if(UICMP(z
, u
.r
, !=, sizeof dls
)){
328 serrno
= (u
.r
!= -1) ? EAGAIN
: errno
;
329 dls
= n_DLS_DUNNO
| n_DLS_ABANDON
;
333 if(dls
== n_DLS_NONE
|| (dls
& n_DLS_ABANDON
))
336 switch(dls
& ~n_DLS_ABANDON
){
339 case n_DLS_CANT_CHDIR
:
340 if(n_poption
& n_PO_D_V
)
341 emsg
= N_(" Can't change directory! Please check permissions\n");
344 case n_DLS_NAMETOOLONG
:
345 emsg
= N_("Resulting dotlock filename would be too long\n");
349 assert(dls
& n_DLS_ABANDON
);
350 if(n_poption
& n_PO_D_V
)
351 emsg
= N_(" Read-only filesystem, not creating lock file\n");
355 if(n_poption
& n_PO_D_V
)
356 emsg
= N_(" Can't create a dotlock file, "
357 "please check permissions\n"
358 " (Or ignore by setting *dotlock-ignore-error* variable)\n");
362 if(n_poption
& n_PO_D_V
)
363 emsg
= N_(" Can't find privilege-separated dotlock program\n");
366 case n_DLS_PRIVFAILED
:
367 emsg
= N_(" Privilege-separated dotlock program can't change "
372 emsg
= N_(" It seems there is a stale dotlock file?\n"
373 " Please remove the lock file manually, then retry\n");
377 emsg
= N_(" Fishy! Is someone trying to \"steal\" foreign files?\n"
378 " Please check the mailbox file etc. manually, then retry\n");
379 serrno
= EAGAIN
; /* ? Hack to ignore *dotlock-ignore-error* xxx */
383 emsg
= N_(" Unspecified dotlock file control process error.\n"
384 " Like broken I/O pipe; this one is unlikely to happen\n");
402 n_err(_(". failed\n"));
408 if(dls
& n_DLS_ABANDON
){
417 n_err(". %s\n", (rv
!= NULL
? _("ok") : _("failed")));
422 else if(serrno
!= EAGAIN
&& serrno
!= EEXIST
&&
423 ok_blook(dotlock_ignore_error
)){
424 if(n_poption
& n_PO_D_V
)
425 n_err(_(" *dotlock-ignore-error* set: continuing\n"));
442 #endif /* HAVE_DOTLOCK */