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
17 /* XXX Our Popen() main() takes void, temporary global data store */
19 static enum n_file_lock_type a_dotlock_flt
;
20 static int a_dotlock_fd
;
21 struct n_dotlock_info
*a_dotlock_dip
;
24 /* main() of fork(2)ed dot file locker */
26 static int a_dotlock_main(void);
32 /* Use PATH_MAX not NAME_MAX to catch those "we proclaim the minimum value"
33 * problems (SunOS), since the pathconf(3) value comes too late! */
34 char name
[PATH_MAX
+1];
35 struct n_dotlock_info di
;
36 struct stat stb
, fdstb
;
37 enum n_dotlock_state dls
;
40 enum n_file_lock_type flt
;
43 /* Ignore SIGPIPE, we'll see n_ERR_PIPE and "fall through" */
44 safe_signal(SIGPIPE
, SIG_IGN
);
46 /* Get the arguments "passed to us" */
54 dls
= n_DLS_CANT_CHDIR
| n_DLS_ABANDON
;
56 if((cp
= strrchr(di
.di_file_name
, '/')) != NULL
){
57 char const *fname
= cp
+ 1;
59 while(PTRCMP(cp
- 1, >, di
.di_file_name
) && cp
[-1] == '/')
61 cp
= savestrbuf(di
.di_file_name
, PTR2SIZE(cp
- di
.di_file_name
));
65 di
.di_file_name
= fname
;
68 /* So we're here, but then again the file can be a symbolic link!
69 * This is however only true if we do not have realpath(3) available since
70 * that'll have resolved the path already otherwise; nonetheless, let
71 * readlink(2) be a precondition for dotlocking and keep this code */
72 if(lstat(cp
= di
.di_file_name
, &stb
) == -1)
74 if(S_ISLNK(stb
.st_mode
)){
75 /* Use salloc() and hope we stay in built-in buffer.. */
80 for(x
= NULL
, i
= PATH_MAX
;; i
+= PATH_MAX
){
82 sr
= readlink(cp
, x
, i
);
84 dls
= n_DLS_FISHY
| n_DLS_ABANDON
;
87 if(UICMP(z
, sr
, <, i
)){
97 dls
= n_DLS_FISHY
| n_DLS_ABANDON
;
99 /* Bail out if the file has changed its identity in the meanwhile */
100 if(fstat(fd
, &fdstb
) == -1 ||
101 fdstb
.st_dev
!= stb
.st_dev
|| fdstb
.st_ino
!= stb
.st_ino
||
102 fdstb
.st_uid
!= stb
.st_uid
|| fdstb
.st_gid
!= stb
.st_gid
||
103 fdstb
.st_mode
!= stb
.st_mode
)
106 /* Be aware, even if the error is false! Note the shared code in dotlock.h
107 * *requires* that it is possible to create a filename at least one byte
108 * longer than di_lock_name! */
109 do/* while(0) breaker */{
110 # ifdef HAVE_PATHCONF
115 i
= snprintf(name
, sizeof name
, "%s.lock", di
.di_file_name
);
116 if(i
< 0 || UICMP(32, i
, >=, sizeof name
)){
118 dls
= n_DLS_NAMETOOLONG
| n_DLS_ABANDON
;
122 /* fd is a file, not portable to use for _PC_NAME_MAX */
123 # ifdef HAVE_PATHCONF
124 n_err_no
= n_ERR_NONE
;
125 if((pc
= pathconf(".", _PC_NAME_MAX
)) == -1){
126 /* n_err_no unchanged: no limit */
130 if(UICMP(z
, NAME_MAX
- 1, <, i
))
132 # ifdef HAVE_PATHCONF
133 }else if(pc
- 1 >= (long)i
)
140 di
.di_lock_name
= name
;
142 /* We are in the directory of the mailbox for which we have to create
143 * a dotlock file for. Any symbolic links have been resolved.
144 * We don't know whether we have realpath(3) available,and manually
145 * resolving the path is due especially given that S-nail supports the
146 * special "%:" syntax to warp any file into a "system mailbox"; there may
147 * also be multiple system mailbox directories...
148 * So what we do is that we fstat(2) the mailbox and check its UID and
149 * GID against that of our own process: if any of those mismatch we must
150 * either assume a directory we are not allowed to write in, or that we run
151 * via -u/$USER/%USER as someone else, in which case we favour our
152 * privilege-separated dotlock process */
153 assert(cp
!= NULL
); /* Ugly: avoid a useless var and reuse that one */
154 if(access(".", W_OK
)){
155 /* This may however also indicate a read-only filesystem, which is not
156 * really an error from our point of view since the mailbox will degrade
157 * to a readonly one for which no dotlock is needed, then, and errors
158 * may arise only due to actions which require box modifications */
159 if(n_err_no
== n_ERR_ROFS
){
160 dls
= n_DLS_ROFS
| n_DLS_ABANDON
;
165 if(cp
== NULL
|| stb
.st_uid
!= n_user_id
|| stb
.st_gid
!= n_group_id
){
167 char const *args
[13];
169 snprintf(itoabuf
, sizeof itoabuf
, "%" PRIuZ
, di
.di_pollmsecs
);
170 args
[ 0] = VAL_PRIVSEP
;
171 args
[ 1] = (flt
== FLT_READ
? "rdotlock" : "wdotlock");
172 args
[ 2] = "mailbox"; args
[ 3] = di
.di_file_name
;
173 args
[ 4] = "name"; args
[ 5] = di
.di_lock_name
;
174 args
[ 6] = "hostname"; args
[ 7] = di
.di_hostname
;
175 args
[ 8] = "randstr"; args
[ 9] = di
.di_randstr
;
176 args
[10] = "pollmsecs"; args
[11] = itoabuf
;
178 execv(VAL_LIBEXECDIR
"/" VAL_UAGENT
"-privsep", n_UNCONST(args
));
181 write(STDOUT_FILENO
, &dls
, sizeof dls
);
182 /* But fall through and try it with normal privileges! */
185 /* So let's try and call it ourselfs! Note that we don't block signals just
186 * like our privsep child does, the user will anyway be able to remove his
187 * file again, and if we're in -u/$USER mode then we are allowed to access
188 * the user's box: shall we leave behind a stale dotlock then at least we
189 * start a friendly human conversation. Since we cannot handle SIGKILL and
190 * SIGSTOP malicious things could happen whatever we do */
191 safe_signal(SIGHUP
, SIG_IGN
);
192 safe_signal(SIGINT
, SIG_IGN
);
193 safe_signal(SIGQUIT
, SIG_IGN
);
194 safe_signal(SIGTERM
, SIG_IGN
);
197 dls
= a_dotlock_create(&di
);
200 /* Finally: notify our parent about the actual lock state.. */
202 write(STDOUT_FILENO
, &dls
, sizeof dls
);
203 close(STDOUT_FILENO
);
205 /* ..then eventually wait until we shall remove the lock again, which will
206 * be notified via the read returning */
207 if(dls
== n_DLS_NONE
){
208 read(STDIN_FILENO
, &dls
, sizeof dls
);
215 #endif /* HAVE_DOTLOCK */
218 n_dotlock(char const *fname
, int fd
, enum n_file_lock_type flt
,
219 off_t off
, off_t len
, size_t pollmsecs
){
223 n_err(_("Creating dotlock for %s "), n_shexp_quote_cp(fname, FAL0))
226 n_err(_("Trying to lock file %s "), n_shexp_quote_cp(fname, FAL0))
231 struct n_dotlock_info di
;
232 enum n_dotlock_state dls
;
236 union {size_t tries
; int (*ptf
)(void); char const *sh
; ssize_t r
;} u
;
237 bool_t flocked
, didmsg
;
241 if(pollmsecs
== UIZ_MAX
)
242 pollmsecs
= FILE_LOCK_MILLIS
;
251 if(n_poption
& n_PO_D_VV
){
257 for(u
.tries
= 0; !n_file_lock(fd
, flt
, off
, len
, 0);)
258 switch((serr
= n_err_no
)){
262 if(pollmsecs
> 0 && ++u
.tries
< FILE_LOCK_TRIES
){
267 n_msleep(pollmsecs
, FAL0
);
288 /* Create control-pipe for our dot file locker process, which will remove
289 * the lock and terminate once the pipe is closed, for whatever reason */
290 if(pipe_cloexec(cpipe
) == -1){
292 emsg
= N_(" Can't create dotlock file control pipe\n");
296 /* And the locker process itself; it'll be a (rather cheap) thread only
297 * unless the lock has to be placed in the system spool and we have our
298 * privilege-separated dotlock program available, in which case that will be
299 * executed and do "it" */
300 di
.di_file_name
= fname
;
301 di
.di_pollmsecs
= pollmsecs
;
302 /* Initialize some more stuff; query the two strings in the parent in order
303 * to cache the result of the former and anyway minimalize child page-ins.
304 * Especially uname(3) may hang for multiple seconds when it is called the
306 di
.di_hostname
= n_nodename(FAL0
);
307 di
.di_randstr
= n_random_create_cp(16, NULL
);
312 u
.ptf
= &a_dotlock_main
;
313 rv
= Popen((char*)-1, "W", u
.sh
, NULL
, cpipe
[1]);
319 emsg
= N_(" Can't create file lock process\n");
323 /* Let's check whether we were able to create the dotlock file */
325 u
.r
= read(cpipe
[0], &dls
, sizeof dls
);
326 if(UICMP(z
, u
.r
, !=, sizeof dls
)){
327 serr
= (u
.r
!= -1) ? n_ERR_AGAIN
: n_err_no
;
328 dls
= n_DLS_DUNNO
| n_DLS_ABANDON
;
332 if(dls
== n_DLS_NONE
|| (dls
& n_DLS_ABANDON
))
335 switch(dls
& ~n_DLS_ABANDON
){
338 case n_DLS_CANT_CHDIR
:
339 if(n_poption
& n_PO_D_V
)
340 emsg
= N_(" Can't change directory! Please check permissions\n");
343 case n_DLS_NAMETOOLONG
:
344 emsg
= N_("Resulting dotlock filename would be too long\n");
348 assert(dls
& n_DLS_ABANDON
);
349 if(n_poption
& n_PO_D_V
)
350 emsg
= N_(" Read-only filesystem, not creating lock file\n");
354 if(n_poption
& n_PO_D_V
)
355 emsg
= N_(" Can't create a dotlock file, "
356 "please check permissions\n"
357 " (Or ignore by setting *dotlock-ignore-error* variable)\n");
361 if(n_poption
& n_PO_D_V
)
362 emsg
= N_(" Can't find privilege-separated dotlock program\n");
365 case n_DLS_PRIVFAILED
:
366 emsg
= N_(" Privilege-separated dotlock program can't change "
371 emsg
= N_(" It seems there is a stale dotlock file?\n"
372 " Please remove the lock file manually, then retry\n");
376 emsg
= N_(" Fishy! Is someone trying to \"steal\" foreign files?\n"
377 " Please check the mailbox file etc. manually, then retry\n");
378 serr
= n_ERR_AGAIN
; /* ? Hack to ignore *dotlock-ignore-error* xxx */
382 emsg
= N_(" Unspecified dotlock file control process error.\n"
383 " Like broken I/O pipe; this one is unlikely to happen\n");
384 if(serr
!= n_ERR_AGAIN
)
401 n_err(_(". failed\n"));
407 if(dls
& n_DLS_ABANDON
){
416 n_err(". %s\n", (rv
!= NULL
? _("ok") : _("failed")));
419 if(serr
== n_ERR_ROFS
)
421 else if(serr
!= n_ERR_AGAIN
&& serr
!= n_ERR_EXIST
&&
422 ok_blook(dotlock_ignore_error
)){
423 if(n_poption
& n_PO_D_V
)
424 n_err(_(" *dotlock-ignore-error* set: continuing\n"));
441 #endif /* HAVE_DOTLOCK */