1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ n_dotlock(): creation of an exclusive "dotlock" file.
4 * Copyright (c) 2016 - 2018 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 will 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 are 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 will 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 n_autorec_alloc() and hope we stay in built-in buffer.. */
80 for(x
= NULL
, i
= PATH_MAX
;; i
+= PATH_MAX
){
81 x
= n_autorec_alloc(i
+1);
82 sr
= readlink(cp
, x
, i
);
84 dls
= n_DLS_FISHY
| n_DLS_ABANDON
;
87 if(UICMP(z
, sr
, <, i
)){
96 dls
= n_DLS_FISHY
| n_DLS_ABANDON
;
98 /* Bail out if the file has changed its identity in the meanwhile */
99 if(fstat(fd
, &fdstb
) == -1 ||
100 fdstb
.st_dev
!= stb
.st_dev
|| fdstb
.st_ino
!= stb
.st_ino
||
101 fdstb
.st_uid
!= stb
.st_uid
|| fdstb
.st_gid
!= stb
.st_gid
||
102 fdstb
.st_mode
!= stb
.st_mode
)
105 /* Be aware, even if the error is false! Note the shared code in dotlock.h
106 * *requires* that it is possible to create a filename at least one byte
107 * longer than di_lock_name! */
108 do/* while(0) breaker */{
109 # ifdef HAVE_PATHCONF
114 i
= snprintf(name
, sizeof name
, "%s.lock", di
.di_file_name
);
115 if(i
< 0 || UICMP(32, i
, >=, sizeof name
)){
117 dls
= n_DLS_NAMETOOLONG
| n_DLS_ABANDON
;
121 /* fd is a file, not portable to use for _PC_NAME_MAX */
122 # ifdef HAVE_PATHCONF
123 n_err_no
= n_ERR_NONE
;
124 if((pc
= pathconf(".", _PC_NAME_MAX
)) == -1){
125 /* n_err_no unchanged: no limit */
129 if(UICMP(z
, NAME_MAX
- 1, <, i
))
131 # ifdef HAVE_PATHCONF
132 }else if(pc
- 1 >= i
)
139 di
.di_lock_name
= name
;
141 /* We are in the directory of the mailbox for which we have to create
142 * a dotlock file for. Any symbolic links have been resolved.
143 * We do not know whether we have realpath(3) available,and manually
144 * resolving the path is due especially given that S-nail supports the
145 * special "%:" syntax to warp any file into a "system mailbox"; there may
146 * also be multiple system mailbox directories...
147 * So what we do is that we fstat(2) the mailbox and check its UID and
148 * GID against that of our own process: if any of those mismatch we must
149 * either assume a directory we are not allowed to write in, or that we run
150 * via -u/$USER/%USER as someone else, in which case we favour our
151 * privilege-separated dotlock process */
152 assert(cp
!= NULL
); /* Ugly: avoid a useless var and reuse that one */
153 if(access(".", W_OK
)){
154 /* This may however also indicate a read-only filesystem, which is not
155 * really an error from our point of view since the mailbox will degrade
156 * to a readonly one for which no dotlock is needed, then, and errors
157 * may arise only due to actions which require box modifications */
158 if(n_err_no
== n_ERR_ROFS
){
159 dls
= n_DLS_ROFS
| n_DLS_ABANDON
;
164 if(cp
== NULL
|| stb
.st_uid
!= n_user_id
|| stb
.st_gid
!= n_group_id
){
166 char const *args
[13];
168 snprintf(itoabuf
, sizeof itoabuf
, "%" PRIuZ
, di
.di_pollmsecs
);
169 args
[ 0] = VAL_PRIVSEP
;
170 args
[ 1] = (flt
== FLT_READ
? "rdotlock" : "wdotlock");
171 args
[ 2] = "mailbox"; args
[ 3] = di
.di_file_name
;
172 args
[ 4] = "name"; args
[ 5] = di
.di_lock_name
;
173 args
[ 6] = "hostname"; args
[ 7] = di
.di_hostname
;
174 args
[ 8] = "randstr"; args
[ 9] = di
.di_randstr
;
175 args
[10] = "pollmsecs"; args
[11] = itoabuf
;
177 execv(VAL_LIBEXECDIR
"/" VAL_UAGENT
"-privsep", n_UNCONST(args
));
180 write(STDOUT_FILENO
, &dls
, sizeof dls
);
181 /* But fall through and try it with normal privileges! */
184 /* So let's try and call it ourselfs! Note we do not block signals just
185 * like our privsep child does, the user will anyway be able to remove his
186 * file again, and if we are in -u/$USER mode then we are allowed to access
187 * the user's box: shall we leave behind a stale dotlock then at least we
188 * start a friendly human conversation. Since we cannot handle SIGKILL and
189 * SIGSTOP malicious things could happen whatever we do */
190 safe_signal(SIGHUP
, SIG_IGN
);
191 safe_signal(SIGINT
, SIG_IGN
);
192 safe_signal(SIGQUIT
, SIG_IGN
);
193 safe_signal(SIGTERM
, SIG_IGN
);
196 dls
= a_dotlock_create(&di
);
199 /* Finally: notify our parent about the actual lock state.. */
201 write(STDOUT_FILENO
, &dls
, sizeof dls
);
202 close(STDOUT_FILENO
);
204 /* ..then eventually wait until we shall remove the lock again, which will
205 * be notified via the read returning */
206 if(dls
== n_DLS_NONE
){
207 read(STDIN_FILENO
, &dls
, sizeof dls
);
214 #endif /* HAVE_DOTLOCK */
217 n_dotlock(char const *fname
, int fd
, enum n_file_lock_type flt
,
218 off_t off
, off_t len
, size_t pollmsecs
){
221 n_err(_("Creating file lock for %s "), n_shexp_quote_cp(fname, FAL0))
225 struct n_dotlock_info di
;
226 enum n_dotlock_state dls
;
230 union {size_t tries
; int (*ptf
)(void); char const *sh
; ssize_t r
;} u
;
231 bool_t flocked
, didmsg
;
235 if(pollmsecs
== UIZ_MAX
)
236 pollmsecs
= FILE_LOCK_MILLIS
;
245 if(n_poption
& n_PO_D_VV
){
251 for(u
.tries
= 0; !n_file_lock(fd
, flt
, off
, len
, 0);)
252 switch((serr
= n_err_no
)){
256 if(pollmsecs
> 0 && ++u
.tries
< FILE_LOCK_TRIES
){
261 n_msleep(pollmsecs
, FAL0
);
282 if(ok_blook(dotlock_disable
)){
287 /* Create control-pipe for our dot file locker process, which will remove
288 * the lock and terminate once the pipe is closed, for whatever reason */
289 if(pipe_cloexec(cpipe
) == -1){
291 emsg
= N_(" Cannot create dotlock file control pipe\n");
295 /* And the locker process itself; it will be a (rather cheap) thread only
296 * unless the lock has to be placed in the system spool and we have our
297 * privilege-separated dotlock program available, in which case that will be
298 * executed and do "it" */
299 di
.di_file_name
= fname
;
300 di
.di_pollmsecs
= pollmsecs
;
301 /* Initialize some more stuff; query the two strings in the parent in order
302 * to cache the result of the former and anyway minimalize child page-ins.
303 * Especially uname(3) may hang for multiple seconds when it is called the
305 di
.di_hostname
= n_nodename(FAL0
);
306 di
.di_randstr
= n_random_create_cp(16, NULL
);
311 u
.ptf
= &a_dotlock_main
;
312 rv
= Popen((char*)-1, "W", u
.sh
, NULL
, cpipe
[1]);
318 emsg
= N_(" Cannot create file lock process\n");
322 /* Let's check whether we were able to create the dotlock file */
324 u
.r
= read(cpipe
[0], &dls
, sizeof dls
);
325 if(UICMP(z
, u
.r
, !=, sizeof dls
)){
326 serr
= (u
.r
!= -1) ? n_ERR_AGAIN
: n_err_no
;
327 dls
= n_DLS_DUNNO
| n_DLS_ABANDON
;
331 if(dls
== n_DLS_NONE
|| (dls
& n_DLS_ABANDON
))
334 switch(dls
& ~n_DLS_ABANDON
){
337 case n_DLS_CANT_CHDIR
:
338 if(n_poption
& n_PO_D_V
)
339 emsg
= N_(" Cannot change directory, please check permissions\n");
342 case n_DLS_NAMETOOLONG
:
343 emsg
= N_("Resulting dotlock filename would be too long\n");
347 assert(dls
& n_DLS_ABANDON
);
348 if(n_poption
& n_PO_D_V
)
349 emsg
= N_(" Read-only filesystem, not creating lock file\n");
353 if((n_psonce
& n_PSO_INTERACTIVE
) || (n_poption
& n_PO_D_V
))
354 emsg
= N_(" Cannot create a dotlock file, "
355 "please check permissions\n"
356 " (Or set *dotlock-disable*, then try again)\n");
360 if((n_psonce
& (n_PSO_INTERACTIVE
| n_PSO_DOTLOCK_PRIVSEP_NOTED
)
361 ) == n_PSO_INTERACTIVE
|| (n_poption
& n_PO_D_V
)){
362 n_psonce
|= n_PSO_DOTLOCK_PRIVSEP_NOTED
;
363 emsg
= N_(" Cannot find privilege-separated dotlock program\n");
367 case n_DLS_PRIVFAILED
:
368 emsg
= N_(" Privilege-separated dotlock program cannot change "
373 emsg
= N_(" It seems there is a stale dotlock file?\n"
374 " Please remove the lock file manually, then retry\n");
378 emsg
= N_(" Fishy! Is someone trying to \"steal\" foreign files?\n"
379 " Please check the mailbox file etc. manually, then retry\n");
380 serr
= n_ERR_AGAIN
; /* ? Hack to ignore *dotlock-ignore-error* xxx */
384 emsg
= N_(" Unspecified dotlock file control process error.\n"
385 " Like broken I/O pipe; this one is unlikely to happen\n");
386 if(serr
!= n_ERR_AGAIN
)
403 n_err(_(". failed\n"));
409 if(dls
& n_DLS_ABANDON
){
418 n_err(". %s\n", (rv
!= NULL
? _("ok") : _("failed")));
421 if(serr
== n_ERR_ROFS
)
423 else if(serr
!= n_ERR_AGAIN
&& serr
!= n_ERR_EXIST
&&
424 ok_blook(dotlock_ignore_error
)){
425 n_OBSOLETE(_("*dotlock-ignore-error*: please use "
426 "*dotlock-disable* instead"));
427 if(n_poption
& n_PO_D_V
)
428 n_err(_(" *dotlock-ignore-error* set: continuing\n"));
445 #endif /* HAVE_DOTLOCK */