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'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
)){
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 >= (long)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 don't 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 that we don't block signals just
185 * like our privsep child does, the user will anyway be able to remove his
186 * file again, and if we're 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
){
222 n_err(_("Creating dotlock for %s "), n_shexp_quote_cp(fname, FAL0))
225 n_err(_("Trying to lock file %s "), n_shexp_quote_cp(fname, FAL0))
230 struct n_dotlock_info di
;
231 enum n_dotlock_state dls
;
235 union {size_t tries
; int (*ptf
)(void); char const *sh
; ssize_t r
;} u
;
236 bool_t flocked
, didmsg
;
240 if(pollmsecs
== UIZ_MAX
)
241 pollmsecs
= FILE_LOCK_MILLIS
;
250 if(n_poption
& n_PO_D_VV
){
256 for(u
.tries
= 0; !n_file_lock(fd
, flt
, off
, len
, 0);)
257 switch((serr
= n_err_no
)){
261 if(pollmsecs
> 0 && ++u
.tries
< FILE_LOCK_TRIES
){
266 n_msleep(pollmsecs
, FAL0
);
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_(" Can't create dotlock file control pipe\n");
295 /* And the locker process itself; it'll 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_(" Can't 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_(" Can't 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_poption
& n_PO_D_V
)
354 emsg
= N_(" Can't create a dotlock file, "
355 "please check permissions\n"
356 " (Or ignore by setting *dotlock-ignore-error* variable)\n");
360 if(n_poption
& n_PO_D_V
)
361 emsg
= N_(" Can't find privilege-separated dotlock program\n");
364 case n_DLS_PRIVFAILED
:
365 emsg
= N_(" Privilege-separated dotlock program can't change "
370 emsg
= N_(" It seems there is a stale dotlock file?\n"
371 " Please remove the lock file manually, then retry\n");
375 emsg
= N_(" Fishy! Is someone trying to \"steal\" foreign files?\n"
376 " Please check the mailbox file etc. manually, then retry\n");
377 serr
= n_ERR_AGAIN
; /* ? Hack to ignore *dotlock-ignore-error* xxx */
381 emsg
= N_(" Unspecified dotlock file control process error.\n"
382 " Like broken I/O pipe; this one is unlikely to happen\n");
383 if(serr
!= n_ERR_AGAIN
)
400 n_err(_(". failed\n"));
406 if(dls
& n_DLS_ABANDON
){
415 n_err(". %s\n", (rv
!= NULL
? _("ok") : _("failed")));
418 if(serr
== n_ERR_ROFS
)
420 else if(serr
!= n_ERR_AGAIN
&& serr
!= n_ERR_EXIST
&&
421 ok_blook(dotlock_ignore_error
)){
422 if(n_poption
& n_PO_D_V
)
423 n_err(_(" *dotlock-ignore-error* set: continuing\n"));
440 #endif /* HAVE_DOTLOCK */