1 /*
2 ** Copyright 2003-2012 Double Precision, Inc.
3 ** See COPYING for distribution information.
4 */
5 
6 #include	"maildiraclt.h"
7 #include	"maildirmisc.h"
8 #include	"maildircreate.h"
9 #include	<time.h>
10 #if	HAVE_UNISTD_H
11 #include	<unistd.h>
12 #endif
13 #if HAVE_DIRENT_H
14 #include <dirent.h>
15 #define NAMLEN(dirent) strlen((dirent)->d_name)
16 #else
17 #define dirent direct
18 #define NAMLEN(dirent) (dirent)->d_namlen
19 #if HAVE_SYS_NDIR_H
20 #include <sys/ndir.h>
21 #endif
22 #if HAVE_SYS_DIR_H
23 #include <sys/dir.h>
24 #endif
25 #if HAVE_NDIR_H
26 #include <ndir.h>
27 #endif
28 #endif
29 #include	<string.h>
30 #include	<errno.h>
31 #include	<stdio.h>
32 #include	<stdlib.h>
33 #include	<assert.h>
34 
35 
36 int maildir_acl_disabled=0;
37 
compar_aclt(const void * a,const void * b)38 static int compar_aclt(const void *a, const void *b)
39 {
40 	char ca=*(const char *)a;
41 	char cb=*(const char *)b;
42 
43 	return (int)(unsigned char)ca - (int)(unsigned char)cb;
44 }
45 
46 /* Post-op fixup of an aclt: collate, remove dupes. */
47 
fixup(maildir_aclt * aclt)48 static void fixup(maildir_aclt *aclt)
49 {
50 	char *a, *b;
51 
52 	qsort(*aclt, strlen(*aclt), 1, compar_aclt);
53 
54 	for (a=b=*aclt; *a; a++)
55 	{
56 		if (*a == a[1])
57 			continue;
58 		if ((int)(unsigned char)*a <= ' ')
59 			continue; /* Silently drop bad access rights */
60 
61 		*b++= *a;
62 	}
63 	*b=0;
64 }
65 
validacl(const char * p)66 static int validacl(const char *p)
67 {
68 	while (*p)
69 	{
70 		if ((int)(unsigned char)*p <= ' ')
71 		{
72 			errno=EINVAL;
73 			return -1;
74 		}
75 		++p;
76 	}
77 
78 	return 0;
79 }
80 
maildir_aclt_init(maildir_aclt * aclt,const char * initvalue_cstr,const maildir_aclt * initvalue_cpy)81 int maildir_aclt_init(maildir_aclt *aclt,
82 		      const char *initvalue_cstr,
83 		      const maildir_aclt *initvalue_cpy)
84 {
85 	if (initvalue_cpy)
86 		initvalue_cstr= *initvalue_cpy;
87 
88 	*aclt=NULL;
89 
90 	if (!initvalue_cstr || !*initvalue_cstr)
91 		return 0;
92 
93 	if (validacl(initvalue_cstr) < 0)
94 		return -1;
95 
96 	if ( (*aclt=strdup(initvalue_cstr)) == NULL)
97 		return -1;
98 	fixup(aclt);
99 	return 0;
100 }
101 
102 /* Destroy an aclt after it is no longer used. */
103 
maildir_aclt_destroy(maildir_aclt * aclt)104 void maildir_aclt_destroy(maildir_aclt *aclt)
105 {
106 	if (*aclt)
107 		free(*aclt);
108 }
109 
110 
111 /* Add or remove access chars. */
112 
maildir_aclt_add(maildir_aclt * aclt,const char * add_strs,const maildir_aclt * add_aclt)113 int maildir_aclt_add(maildir_aclt *aclt,
114 		     const char *add_strs,
115 		     const maildir_aclt *add_aclt)
116 {
117 	if (add_aclt)
118 		add_strs= *add_aclt;
119 
120 	if (!add_strs || !*add_strs)
121 		return 0;
122 
123 	if (validacl(add_strs) < 0)
124 		return -1;
125 
126 	if (*aclt)
127 	{
128 		char *p=realloc(*aclt, strlen(*aclt)+strlen(add_strs)+1);
129 
130 		if (!p)
131 			return -1;
132 		strcat(p, add_strs);
133 		*aclt=p;
134 
135 	}
136 	else if ( ((*aclt)=strdup(add_strs)) == NULL)
137 		return -1;
138 
139 	fixup(aclt);
140 	return 0;
141 }
142 
maildir_aclt_del(maildir_aclt * aclt,const char * del_strs,const maildir_aclt * del_aclt)143 int maildir_aclt_del(maildir_aclt *aclt,
144 		     const char *del_strs,
145 		     const maildir_aclt *del_aclt)
146 {
147 	char *a, *b;
148 
149 	if (del_aclt)
150 		del_strs= *del_aclt;
151 
152 	if (!del_strs)
153 		return 0;
154 
155 	if (!*aclt)
156 		return 0;
157 
158 	for (a=b=*aclt; *a; a++)
159 	{
160 		if (strchr(del_strs, *a))
161 			continue;
162 		*b++= *a;
163 	}
164 	*b=0;
165 
166 	if (**aclt == 0)
167 	{
168 		free(*aclt);
169 		*aclt=NULL;
170 	}
171 	return 0;
172 }
173 
174 /* -------------------------------------------------------------------- */
175 
176 
maildir_aclt_list_init(maildir_aclt_list * aclt_list)177 void maildir_aclt_list_init(maildir_aclt_list *aclt_list)
178 {
179 	aclt_list->head=NULL;
180 	aclt_list->tail=NULL;
181 }
182 
maildir_aclt_list_destroy(maildir_aclt_list * aclt_list)183 void maildir_aclt_list_destroy(maildir_aclt_list *aclt_list)
184 {
185 	struct maildir_aclt_node *p;
186 
187 	for (p=aclt_list->head; p; )
188 	{
189 		struct maildir_aclt_node *q=p->next;
190 
191 		free(p->identifier);
192 		maildir_aclt_destroy(&p->acl);
193 		free(p);
194 		p=q;
195 	}
196 	maildir_aclt_list_init(aclt_list);
197 }
198 
199 
200 /* Add an <identifier,acl> pair.  Returns 0 on success, -1 on failure */
201 
maildir_aclt_list_add(maildir_aclt_list * aclt_list,const char * identifier,const char * aclt_str,maildir_aclt * aclt_cpy)202 int maildir_aclt_list_add(maildir_aclt_list *aclt_list,
203 			  const char *identifier,
204 			  const char *aclt_str,
205 			  maildir_aclt *aclt_cpy)
206 {
207 	struct maildir_aclt_node *p;
208 	const char *q;
209 
210 	/* Check for valid identifiers */
211 
212 	for (q=identifier; *q; q++)
213 		if ( (int)(unsigned char)*q <= ' ')
214 		{
215 			errno=EINVAL;
216 			return -1;
217 		}
218 
219 	if (*identifier == 0)
220 	{
221 		errno=EINVAL;
222 		return -1;
223 	}
224 
225 	if (aclt_cpy && *aclt_cpy)
226 		aclt_str= *aclt_cpy;
227 
228 	for (p=aclt_list->head; p; p=p->next)
229 	{
230 		if (strcmp(p->identifier, identifier) == 0)
231 		{
232 			maildir_aclt_destroy(&p->acl);
233 			return maildir_aclt_init(&p->acl, aclt_str, NULL);
234 		}
235 	}
236 
237 	if ((p=malloc(sizeof(*p))) == NULL ||
238 	    (p->identifier=strdup(identifier)) == NULL)
239 	{
240 		if (p) free(p);
241 		return -1;
242 	}
243 
244 	if (maildir_aclt_init(&p->acl, aclt_str, NULL) < 0)
245 	{
246 		free(p->identifier);
247 		free(p);
248 		return -1;
249 	}
250 
251 	p->next=NULL;
252 	if ((p->prev=aclt_list->tail) != NULL)
253 		p->prev->next=p;
254 	else
255 		aclt_list->head=p;
256 	aclt_list->tail=p;
257 	return 0;
258 }
259 
260 /*
261 ** Remove 'identifier' from the ACL list.
262 */
263 
maildir_aclt_list_del(maildir_aclt_list * aclt_list,const char * identifier)264 int maildir_aclt_list_del(maildir_aclt_list *aclt_list,
265 			  const char *identifier)
266 {
267 	struct maildir_aclt_node *p;
268 
269 	for (p=aclt_list->head; p; p=p->next)
270 	{
271 		if (strcmp(p->identifier, identifier) == 0)
272 		{
273 			if (p->prev)
274 				p->prev->next=p->next;
275 			else aclt_list->head=p->next;
276 
277 			if (p->next)
278 				p->next->prev=p->prev;
279 			else aclt_list->tail=p->prev;
280 
281 			maildir_aclt_destroy(&p->acl);
282 			free(p->identifier);
283 			free(p);
284 			return 0;
285 		}
286 	}
287 	return 0;
288 }
289 
290 /*
291 ** Generic enumeration.
292 */
293 
maildir_aclt_list_enum(maildir_aclt_list * aclt_list,int (* cb_func)(const char * identifier,const maildir_aclt * acl,void * cb_arg),void * cb_arg)294 int maildir_aclt_list_enum(maildir_aclt_list *aclt_list,
295 			   int (*cb_func)(const char *identifier,
296 					  const maildir_aclt *acl,
297 					  void *cb_arg),
298 			   void *cb_arg)
299 {
300 	struct maildir_aclt_node *p;
301 	int rc;
302 
303 	for (p=aclt_list->head; p; p=p->next)
304 	{
305 		rc= (*cb_func)(p->identifier, &p->acl, cb_arg);
306 
307 		if (rc)
308 			return rc;
309 	}
310 	return 0;
311 }
312 
maildir_aclt_list_find(maildir_aclt_list * aclt_list,const char * identifier)313 const maildir_aclt *maildir_aclt_list_find(maildir_aclt_list *aclt_list,
314 					   const char *identifier)
315 {
316 	struct maildir_aclt_node *p;
317 
318 	for (p=aclt_list->head; p; p=p->next)
319 	{
320 		if (strcmp(p->identifier, identifier) == 0)
321 			return &p->acl;
322 	}
323 	return NULL;
324 }
325 
326 /* ---------------------------------------------------------------------- */
327 
328 static int maildir_acl_read_check(maildir_aclt_list *aclt_list,
329 				 const char *maildir,
330 				 const char *path);
331 
maildir_acl_read(maildir_aclt_list * aclt_list,const char * maildir,const char * path)332 int maildir_acl_read(maildir_aclt_list *aclt_list,
333 		     const char *maildir,
334 		     const char *path)
335 {
336 	int rc=maildir_acl_read_check(aclt_list, maildir, path);
337 	char *p, *q;
338 
339 	if (rc)
340 		maildir_aclt_list_destroy(aclt_list);
341 
342 	if (rc <= 0)
343 		return rc;
344 
345 	/*
346 	** If the ACL config file for this folder was not found,
347 	** check for the ACL config file for its parent folder.
348 	*/
349 
350 	if ((p=strdup(path)) == NULL)
351 		return -1;
352 
353 	strcpy(p, path);
354 
355 	q=strrchr(p, '.');
356 
357 	if (!q)
358 	{
359 		free(p);
360 		errno=EIO; /* Should not happen */
361 		return -1;
362 	}
363 
364 	*q=0;
365 
366 	rc=maildir_acl_read(aclt_list, maildir, p);
367 	if (rc == 0)
368 	{
369 		/* Make sure to save the default acl list */
370 
371 		rc=maildir_acl_write(aclt_list, maildir, path, NULL, NULL);
372 		if (rc >= 0) /* Ok if rc=1 */
373 			rc=0;
374 		if (rc)
375 			maildir_aclt_list_destroy(aclt_list);
376 	}
377 	free(p);
378 	return rc;
379 }
380 
381 /*
382 ** Attempt to retrieve the ACL set for the specified folder.
383 **
384 ** Returns -1 if error.
385 ** Returns 0 if the ACL was retrieved.
386 ** Returns 1 if the ACL configuration file does not exist.
387 */
388 
389 static int maildir_aclt_add_default_admin(maildir_aclt_list *aclt_list);
390 
maildir_acl_read_check(maildir_aclt_list * aclt_list,const char * maildir,const char * path)391 static int maildir_acl_read_check(maildir_aclt_list *aclt_list,
392 				  const char *maildir,
393 				  const char *path)
394 {
395 	char *p, *q;
396 	FILE *fp;
397 	char buffer[BUFSIZ];
398 
399 	maildir_aclt_list_init(aclt_list);
400 
401 	if (!maildir || !*maildir)
402 		maildir=".";
403 	if (!path || !*path)
404 		path=".";
405 
406 	if (strchr(path, '/') || *path != '.')
407 	{
408 		errno=EINVAL;
409 		return -1;
410 	}
411 
412 	if (maildir_acl_disabled)
413 	{
414 		if (maildir_aclt_list_add(aclt_list, "owner",
415 					  ACL_LOOKUP ACL_READ
416 					  ACL_SEEN ACL_WRITE ACL_INSERT
417 					  ACL_CREATE
418 					  ACL_DELETEFOLDER
419 					  ACL_DELETEMSGS ACL_EXPUNGE,
420 					  NULL) < 0 ||
421 		    maildir_aclt_add_default_admin(aclt_list))
422 		{
423 			maildir_aclt_list_destroy(aclt_list);
424 			return -1;
425 		}
426 		return 0;
427 	}
428 
429 	p=malloc(strlen(maildir)+strlen(path)+2);
430 
431 	if (!p)
432 		return -1;
433 
434 	strcat(strcat(strcpy(p, maildir), "/"), path);
435 
436 	q=malloc(strlen(p)+sizeof("/" ACLFILE));
437 	if (!q)
438 	{
439 		free(p);
440 		return -1;
441 	}
442 	fp=fopen(strcat(strcpy(q, p), "/" ACLFILE), "r");
443 	free(p);
444 	free(q);
445 
446 	if (fp == NULL)
447 	{
448 		if (strcmp(path, ".") == 0)
449 		{
450 			/* INBOX ACL default */
451 
452 			if (maildir_aclt_list_add(aclt_list, "owner",
453 						  ACL_ALL, NULL) < 0 ||
454 			    maildir_aclt_add_default_admin(aclt_list))
455 			{
456 				return -1;
457 			}
458 			return 0;
459 		}
460 
461 		q=malloc(strlen(maildir)+sizeof("/" ACLHIERDIR "/") +
462 			 strlen(path));
463 		if (!q)
464 			return -1;
465 
466 		strcat(strcat(strcpy(q, maildir), "/" ACLHIERDIR "/"),
467 		       path+1);
468 
469 		fp=fopen(q, "r");
470 		free(q);
471 	}
472 
473 	if (!fp && errno != ENOENT)
474 		return -1;
475 
476 	if (!fp)
477 		return 1;
478 
479 	errno=0;
480 
481 	while (fgets(buffer, sizeof(buffer), fp) != NULL)
482 	{
483 		char *p=strchr(buffer, '\n');
484 
485 		if (p) *p=0;
486 
487 		for (p=buffer; *p; p++)
488 			if (*p == ' ')
489 			{
490 				*p=0;
491 				do
492 				{
493 					++p;
494 				} while (*p && *p == ' ');
495 				break;
496 			}
497 
498 		if (maildir_aclt_list_add(aclt_list, buffer, p, NULL) < 0)
499 		{
500 			if (errno != EINVAL)
501 				return -1;
502 			/* Sweep crap in the ACL file under the carpet */
503 		}
504 	}
505 	if (ferror(fp))
506 	{
507 		fclose(fp);
508 		return -1;
509 	}
510 	fclose(fp);
511 	if (maildir_aclt_add_default_admin(aclt_list))
512 		return -1;
513 	return 0;
514 }
515 
516 /*
517 ** Add the default ACL permissions to the administrators group.
518 **
519 ** Make sure that the ACL entry for "administrators" includes all
520 ** rights.
521 **
522 ** Make sure that any ACL entries for "-administrators" or
523 ** "-group=administrators" do not have LOOKUP and ADMIN.
524 */
525 
maildir_aclt_add_default_admin(maildir_aclt_list * aclt_list)526 static int maildir_aclt_add_default_admin(maildir_aclt_list *aclt_list)
527 {
528 	const maildir_aclt *old_acl;
529 
530 	static const char * const drop_acls[]={"-administrators",
531 					       "-group=administrators"};
532 	size_t i;
533 
534 	if ((old_acl=maildir_aclt_list_find(aclt_list, "group=administrators"))
535 	    != NULL)
536 	{
537 		maildir_aclt new_acl;
538 
539 		if (maildir_aclt_init(&new_acl, ACL_ALL, NULL))
540 			return -1;
541 
542 		if (maildir_aclt_add(&new_acl, NULL, old_acl) ||
543 		    maildir_aclt_list_add(aclt_list, "group=administrators",
544 					  NULL, &new_acl))
545 		{
546 			maildir_aclt_destroy(&new_acl);
547 			return -1;
548 		}
549 		maildir_aclt_destroy(&new_acl);
550 	}
551 	else
552 	{
553 		maildir_aclt new_acl;
554 
555 		old_acl=maildir_aclt_list_find(aclt_list, "administrators");
556 
557 		if (maildir_aclt_init(&new_acl, ACL_ALL, NULL))
558 			return -1;
559 
560 		if (maildir_aclt_add(&new_acl, NULL, old_acl) ||
561 		    maildir_aclt_list_add(aclt_list, "administrators",
562 					  NULL, &new_acl))
563 		{
564 			maildir_aclt_destroy(&new_acl);
565 			return -1;
566 		}
567 		maildir_aclt_destroy(&new_acl);
568 	}
569 
570 	for (i=0; i<2; i++)
571 	{
572 		const char *n=drop_acls[i];
573 		if (maildir_aclt_list_del(aclt_list, n) < 0)
574 			return -1;
575 	}
576 
577 	return 0;
578 }
579 
maildir_acl_delete(const char * maildir,const char * path)580 int maildir_acl_delete(const char *maildir,
581 		       const char *path)
582 {
583 	char *p, *q;
584 
585 #if 0
586 	if (strcmp(path, SHARED) == 0)
587 		return 0;
588 
589 	if (strncmp(path, SHARED ".", sizeof(SHARED)) == 0)
590 		return 0;
591 #endif
592 	if (!maildir || !*maildir)
593 		maildir=".";
594 	if (!path || !*path)
595 		path=".";
596 
597 	if (strchr(path, '/') || *path != '.')
598 	{
599 		errno=EINVAL;
600 		return -1;
601 	}
602 
603 	p=malloc(strlen(maildir)+strlen(path)+2);
604 
605 	if (!p)
606 		return -1;
607 
608 	strcat(strcat(strcpy(p, maildir), "/"), path);
609 
610 	q=malloc(strlen(p)+sizeof("/" ACLFILE));
611 	if (!q)
612 	{
613 		free(p);
614 		return -1;
615 	}
616 
617 	unlink(strcat(strcpy(q, p), "/" ACLFILE));
618 	free(p);
619 	free(q);
620 
621 	if (strcmp(path, ".") == 0)
622 	{
623 		/* INBOX ACL default */
624 
625 		return 0;
626 	}
627 
628 	q=malloc(strlen(maildir)+sizeof("/" ACLHIERDIR "/") +
629 		 strlen(path));
630 	if (!q)
631 	{
632 		return -1;
633 	}
634 	strcat(strcat(strcpy(q, maildir), "/" ACLHIERDIR "/"),
635 	       path+1);
636 
637 	unlink(q);
638 	free(q);
639 	return 0;
640 }
641 
642 static int save_acl(const char *identifier, const maildir_aclt *acl,
643 		    void *cb_arg);
644 
645 
is_owner(const char * isme,void * void_arg)646 static int is_owner(const char *isme, void *void_arg)
647 {
648 	if (void_arg && strcmp(isme, (const char *)void_arg) == 0)
649 		return 1;
650 
651 	return strcmp(isme, "owner") == 0;
652 }
653 
is_admin(const char * isme,void * void_arg)654 static int is_admin(const char *isme, void *void_arg)
655 {
656 	return strcmp(isme, "administrators") == 0;
657 
658 	/* We don't need to check for group=administrators, see chk_admin() */
659 }
660 
check_adminrights(maildir_aclt * list)661 static int check_adminrights(maildir_aclt *list)
662 {
663 	if (strchr(maildir_aclt_ascstr(list), ACL_LOOKUP[0]) == NULL ||
664 	    strchr(maildir_aclt_ascstr(list), ACL_ADMINISTER[0]) == NULL)
665 	{
666 		maildir_aclt_destroy(list);
667 		return -1;
668 	}
669 
670 	maildir_aclt_destroy(list);
671 	return 0;
672 }
673 
check_allrights(maildir_aclt * list)674 static int check_allrights(maildir_aclt *list)
675 {
676 	const char *all=ACL_ALL;
677 
678 	while (*all)
679 	{
680 		if (strchr(maildir_aclt_ascstr(list), *all) == NULL)
681 		{
682 			maildir_aclt_destroy(list);
683 			return -1;
684 		}
685 		++all;
686 	}
687 
688 	maildir_aclt_destroy(list);
689 	return 0;
690 }
691 
692 static int maildir_acl_compute_chkowner(maildir_aclt *aclt,
693 					maildir_aclt_list *aclt_list,
694 					int (*cb_func)(const char *isme,
695 						       void *void_arg),
696 					void *void_arg,
697 					int chkowner);
698 
maildir_acl_write(maildir_aclt_list * aclt_list,const char * maildir,const char * path,const char * owner,const char ** err_failedrights)699 int maildir_acl_write(maildir_aclt_list *aclt_list,
700 		      const char *maildir,
701 		      const char *path,
702 
703 		      const char *owner,
704 		      const char **err_failedrights)
705 {
706 	int trycreate;
707 	struct maildir_tmpcreate_info tci;
708 	FILE *fp;
709 	char *p, *q;
710 	const char *dummy_string;
711 	maildir_aclt chkacls;
712 
713 	if (!err_failedrights)
714 		err_failedrights= &dummy_string;
715 
716 	if (!maildir || !*maildir)
717 		maildir=".";
718 	if (!path || !*path)
719 		path=".";
720 
721 	if (strchr(path, '/') || *path != '.')
722 	{
723 		errno=EINVAL;
724 		return -1;
725 	}
726 
727 	if (strcmp(path, ".")) /* Sanity check */
728 		for (dummy_string=path; *dummy_string; dummy_string++)
729 			if (*dummy_string == '.' &&
730 			    (dummy_string[1] == '.' ||
731 			     dummy_string[1] == 0))
732 			{
733 				errno=EINVAL;
734 				return -1;
735 			}
736 
737 
738 	if (maildir_acl_compute_chkowner(&chkacls, aclt_list, is_owner, NULL,
739 					 0))
740 	{
741 		maildir_aclt_destroy(&chkacls);
742 		errno=EINVAL;
743 		return -1;
744 	}
745 
746 	if (check_adminrights(&chkacls))
747 	{
748 		*err_failedrights="owner";
749 		errno=EINVAL;
750 		return -1;
751 	}
752 
753 	if (owner)
754 	{
755 		if (maildir_acl_compute_chkowner(&chkacls, aclt_list, is_owner,
756 						 (void *)owner, 0))
757 		{
758 			maildir_aclt_destroy(&chkacls);
759 			errno=EINVAL;
760 			return -1;
761 		}
762 		if (check_adminrights(&chkacls))
763 		{
764 			*err_failedrights=owner;
765 			errno=EINVAL;
766 			return -1;
767 		}
768 	}
769 
770 	if (maildir_acl_compute(&chkacls, aclt_list, is_admin, NULL))
771 	{
772 		maildir_aclt_destroy(&chkacls);
773 		errno=EINVAL;
774 		return -1;
775 	}
776 	if (check_allrights(&chkacls))
777 	{
778 		errno=EINVAL;
779 		return -1;
780 	}
781 
782 	p=malloc(strlen(maildir)+strlen(path)+2);
783 
784 	if (!p)
785 		return -1;
786 
787 	strcat(strcat(strcpy(p, maildir), "/"), path);
788 
789 	maildir_tmpcreate_init(&tci);
790 
791 	tci.maildir=p;
792 	tci.uniq="acl";
793 	tci.doordie=1;
794 
795 	fp=maildir_tmpcreate_fp(&tci);
796 
797 	trycreate=0;
798 
799 	if (fp)
800 	{
801 		q=malloc(strlen(p) + sizeof("/" ACLFILE));
802 		if (!q)
803 		{
804 			fclose(fp);
805 			unlink(tci.tmpname);
806 			maildir_tmpcreate_free(&tci);
807 			free(p);
808 			return -1;
809 		}
810 		strcat(strcpy(q, p), "/" ACLFILE);
811 		free(tci.newname);
812 		tci.newname=q;
813 		free(p);
814 	}
815 	else
816 	{
817 		free(p);
818 
819 		q=malloc(strlen(maildir)+sizeof("/" ACLHIERDIR "/") +
820 			 strlen(path));
821 		if (!q)
822 		{
823 			maildir_tmpcreate_free(&tci);
824 			return -1;
825 		}
826 		strcat(strcat(strcpy(q, maildir), "/" ACLHIERDIR "/"), path+1);
827 
828 		tci.maildir=maildir;
829 		tci.uniq="acl";
830 		tci.doordie=1;
831 
832 		fp=maildir_tmpcreate_fp(&tci);
833 
834 		if (!fp)
835 		{
836 			free(q);
837 			maildir_tmpcreate_free(&tci);
838 			return -1;
839 		}
840 		free(tci.newname);
841 		tci.newname=q;
842 		trycreate=1;
843 	}
844 
845 	if (maildir_aclt_list_enum(aclt_list, save_acl, fp) < 0 ||
846 	    ferror(fp) || fflush(fp) < 0)
847 	{
848 		fclose(fp);
849 		unlink(tci.tmpname);
850 		maildir_tmpcreate_free(&tci);
851 		return -1;
852 	}
853 	fclose(fp);
854 
855 	if (rename(tci.tmpname, tci.newname) < 0)
856 	{
857 		/* Perhaps ACLHIERDIR needs to be created? */
858 
859 		if (!trycreate)
860 		{
861 			unlink(tci.tmpname);
862 			maildir_tmpcreate_free(&tci);
863 			return -1;
864 		}
865 
866 		p=strrchr(tci.newname, '/');
867 		*p=0;
868 		mkdir(tci.newname, 0755);
869 		*p='/';
870 
871 		if (rename(tci.tmpname, tci.newname) < 0)
872 		{
873 			unlink(tci.tmpname);
874 			maildir_tmpcreate_free(&tci);
875 			return -1;
876 		}
877 	}
878 	maildir_tmpcreate_free(&tci);
879 	return 0;
880 }
881 
save_acl(const char * identifier,const maildir_aclt * acl,void * cb_arg)882 static int save_acl(const char *identifier, const maildir_aclt *acl,
883 		    void *cb_arg)
884 {
885 	if (fprintf((FILE *)cb_arg, "%s %s\n",
886 		    identifier,
887 		    maildir_aclt_ascstr(acl)) < 0)
888 		return -1;
889 	return 0;
890 }
891 
892 struct maildir_acl_resetList {
893 	struct maildir_acl_resetList *next;
894 	char *mbox;
895 };
896 
897 /*
898 ** When a maildir is opened check for stale entries in Maildir/ACLHIERDIR.
899 **
900 ** Maildir/ACLHIERDIR/folder.subfolder should be removed unless there exists
901 ** Maildir/.folder.subfolder.subsubfolder
902 **
903 **
904 ** acl_check_cb is the callback function for maildir_list, which receives
905 ** INBOX.folder.subfolder.subsubfolder.  It goes through the link list with
906 ** Maildir/ACLHIERDIR's contents, and removes folder.subfolder if its found.
907 **
908 ** After maildir_list is done, anything that's left in the list can be safely
909 ** removed.
910 */
911 
acl_check_cb(const char * mbox,void * voidarg)912 static void acl_check_cb(const char *mbox, void *voidarg)
913 {
914 	struct maildir_acl_resetList **l=
915 		(struct maildir_acl_resetList **)voidarg;
916 
917 	if (strncmp(mbox, INBOX ".", sizeof(INBOX ".")-1))
918 		return; /* Huh? */
919 
920 	mbox += sizeof(INBOX ".")-1;
921 
922 	while (*l)
923 	{
924 		int cl= strlen( (*l)->mbox );
925 
926 		if (strncmp(mbox, (*l)->mbox, cl) == 0 &&
927 		    mbox[cl] == '.')
928 		{
929 			struct maildir_acl_resetList *p= *l;
930 
931 			*l= p->next;
932 			free(p->mbox);
933 			free(p);
934 			continue;
935 		}
936 
937 		l= &(*l)->next;
938 	}
939 }
940 
maildir_acl_reset(const char * maildir)941 int maildir_acl_reset(const char *maildir)
942 {
943 	DIR *dirp;
944 	struct dirent *de;
945 	char *p;
946 	struct maildir_acl_resetList *rl=NULL;
947 	struct maildir_acl_resetList *r;
948 	time_t now;
949 	struct stat stat_buf;
950 
951 	p=malloc(strlen(maildir) + sizeof("/" ACLHIERDIR));
952 	if (!p)
953 		return -1;
954 
955 	strcat(strcpy(p, maildir), "/" ACLHIERDIR);
956 
957 	dirp=opendir(p);
958 
959 	if (!dirp)
960 	{
961 		mkdir(p, 0755);
962 		dirp=opendir(p);
963 	}
964 	free(p);
965 
966 	while (dirp && (de=readdir(dirp)) != NULL)
967 	{
968 		if (de->d_name[0] == '.')
969 			continue;
970 
971 		if ((r=malloc(sizeof(struct maildir_acl_resetList))) == NULL
972 		    || (r->mbox=strdup(de->d_name)) == NULL)
973 		{
974 			if (r)
975 				free(r);
976 
977 			while (rl)
978 			{
979 				r=rl;
980 				rl=r->next;
981 				free(r->mbox);
982 				free(r);
983 			}
984 			closedir(dirp);
985 			return -1;
986 		}
987 
988 		r->next=rl;
989 		rl=r;
990 	}
991 	if (dirp) closedir(dirp);
992 
993 	maildir_list(maildir, acl_check_cb, &rl);
994 
995 	time(&now);
996 
997 	while (rl)
998 	{
999 		r=rl;
1000 		rl=r->next;
1001 
1002 		p=malloc(strlen(maildir)+strlen(r->mbox) +
1003 			 sizeof("/" ACLHIERDIR "/"));
1004 		if (p)
1005 		{
1006 			strcat(strcat(strcpy(p, maildir),
1007 				      "/" ACLHIERDIR "/"), r->mbox);
1008 
1009 			/* Only unlink stale entries after 1 hour (race) */
1010 
1011 			if (stat(p, &stat_buf) == 0 &&
1012 			    stat_buf.st_mtime < now - 60*60)
1013 				unlink(p);
1014 			free(p);
1015 		}
1016 		free(r->mbox);
1017 		free(r);
1018 	}
1019 	return 0;
1020 }
1021 
1022 /*
1023 ** An ACL entry for "administrators" or "group=administrators" will match
1024 ** either one.
1025 */
1026 
chk_admin(int (* cb_func)(const char * isme,void * void_arg),const char * identifier,void * void_arg)1027 static int chk_admin(int (*cb_func)(const char *isme,
1028 				    void *void_arg),
1029 		     const char *identifier,
1030 		     void *void_arg)
1031 {
1032 	if (strcmp(identifier, "administrators") == 0 ||
1033 	    strcmp(identifier, "group=administrators") == 0)
1034 	{
1035 		int rc=(*cb_func)("administrators", void_arg);
1036 
1037 		if (rc == 0)
1038 			rc=(*cb_func)("group=administrators", void_arg);
1039 		return rc;
1040 	}
1041 
1042 	return (*cb_func)(identifier, void_arg);
1043 }
1044 
1045 #define ISIDENT(s) \
1046 	(MAILDIR_ACL_ANYONE(s) ? 1: chk_admin(cb_func, (s), void_arg))
1047 
maildir_acl_compute_chkowner(maildir_aclt * aclt,maildir_aclt_list * aclt_list,int (* cb_func)(const char * isme,void * void_arg),void * void_arg,int chkowner)1048 static int maildir_acl_compute_chkowner(maildir_aclt *aclt,
1049 					maildir_aclt_list *aclt_list,
1050 					int (*cb_func)(const char *isme,
1051 						       void *void_arg),
1052 					void *void_arg,
1053 					int chkowner)
1054 {
1055 	struct maildir_aclt_node *p;
1056 	int rc;
1057 
1058 	if (maildir_aclt_init(aclt, "", NULL) < 0)
1059 		return -1;
1060 
1061 	for (p=aclt_list->head; p; p=p->next)
1062 	{
1063 		if (p->identifier[0] == '-')
1064 			continue;
1065 
1066 		rc= ISIDENT(p->identifier);
1067 
1068 		if (rc < 0)
1069 		{
1070 			maildir_aclt_destroy(aclt);
1071 			return rc;
1072 		}
1073 
1074 		if (rc == 0)
1075 			continue;
1076 
1077 		if (maildir_aclt_add(aclt, NULL, &p->acl) < 0)
1078 		{
1079 			maildir_aclt_destroy(aclt);
1080 			return rc;
1081 		}
1082 	}
1083 
1084 	for (p=aclt_list->head; p; p=p->next)
1085 	{
1086 		if (p->identifier[0] != '-')
1087 			continue;
1088 
1089 		rc= ISIDENT(p->identifier+1);
1090 
1091 		if (rc < 0)
1092 		{
1093 			maildir_aclt_destroy(aclt);
1094 			return rc;
1095 		}
1096 
1097 		if (rc == 0)
1098 			continue;
1099 
1100 		if (maildir_aclt_del(aclt, NULL, &p->acl) < 0)
1101 		{
1102 			maildir_aclt_destroy(aclt);
1103 			return rc;
1104 		}
1105 	}
1106 
1107 	/*
1108 	** In our scheme, the owner always gets admin rights.
1109 	*/
1110 
1111 	rc=chkowner ? (*cb_func)("owner", void_arg):0;
1112 
1113 	if (maildir_acl_disabled)
1114 		rc=0;	/* Except when ACLs are disabled */
1115 
1116 	if (rc < 0)
1117 	{
1118 		maildir_aclt_destroy(aclt);
1119 		return rc;
1120 	}
1121 
1122 	if (rc)
1123 	{
1124 		if (maildir_aclt_add(aclt, ACL_ADMINISTER, NULL) < 0)
1125 		{
1126 			maildir_aclt_destroy(aclt);
1127 			return rc;
1128 		}
1129 	}
1130 	return 0;
1131 }
1132 
maildir_acl_compute(maildir_aclt * aclt,maildir_aclt_list * aclt_list,int (* cb_func)(const char * isme,void * void_arg),void * void_arg)1133 int maildir_acl_compute(maildir_aclt *aclt, maildir_aclt_list *aclt_list,
1134 			int (*cb_func)(const char *isme,
1135 				       void *void_arg), void *void_arg)
1136 {
1137 	return maildir_acl_compute_chkowner(aclt, aclt_list, cb_func, void_arg,
1138 					    1);
1139 }
1140 
1141 static int chk_array(const char *identifier, void *void_arg);
1142 
maildir_acl_compute_array(maildir_aclt * aclt,maildir_aclt_list * aclt_list,const char * const * identifiers)1143 int maildir_acl_compute_array(maildir_aclt *aclt,
1144 			      maildir_aclt_list *aclt_list,
1145 			      const char * const *identifiers)
1146 {
1147 	return maildir_acl_compute(aclt, aclt_list, chk_array,
1148 				   (void *)identifiers);
1149 }
1150 
chk_array(const char * identifier,void * void_arg)1151 static int chk_array(const char *identifier, void *void_arg)
1152 {
1153 	const char * const *p=(const char * const *)void_arg;
1154 	size_t i;
1155 
1156 	for (i=0; p[i]; i++)
1157 		if (strcmp(identifier, p[i]) == 0)
1158 			return 1;
1159 	return 0;
1160 }
1161 
1162 /* -------------------------------------------------------------------- */
1163 
maildir_acl_canlistrights(const char * myrights)1164 int maildir_acl_canlistrights(const char *myrights)
1165 {
1166 	return (strchr(myrights, ACL_LOOKUP[0]) ||
1167 		strchr(myrights, ACL_READ[0]) ||
1168 		strchr(myrights, ACL_INSERT[0]) ||
1169 		strchr(myrights, ACL_CREATE[0]) ||
1170 		strchr(myrights, ACL_DELETEFOLDER[0]) ||
1171 		strchr(myrights, ACL_EXPUNGE[0]) ||
1172 		strchr(myrights, ACL_ADMINISTER[0]));
1173 }
1174 
1175 /* --------------------------------------------------------------------- */
1176 
1177 /*
1178 ** Compute owner ACL identifiers applicable to a mailbox that's owned by
1179 ** 'mailbox_owner'.
1180 */
1181 
get_owner_list(int (* cb_func)(const char *,void *),const char * c,const char * mailbox_owner,void * arg)1182 static int get_owner_list( int (*cb_func)(const char *, void *),
1183 			   const char *c,
1184 			   const char *mailbox_owner, void *arg)
1185 {
1186 	char *a;
1187 	int rc;
1188 	const char *p, *q;
1189 
1190 	a=malloc(sizeof("user=")+strlen(c));
1191 	if (!a)
1192 		return -1;
1193 
1194 	strcat(strcpy(a, "user="), c);
1195 
1196 	rc=(*cb_func)(a, arg);
1197 
1198 	if (rc == 0 && strcmp(a, mailbox_owner) == 0)
1199 		rc=(*cb_func)("owner", arg);
1200 	free(a);
1201 
1202 	if (rc)
1203 		return rc;
1204 
1205 	c=getenv("OPTIONS");
1206 
1207 	for (p=c; p && *p; )
1208 	{
1209 		if (*p == ',')
1210 		{
1211 			++p;
1212 			continue;
1213 		}
1214 
1215 		q=p;
1216 		while (*p && *p != ',')
1217 			++p;
1218 
1219 		if (strncmp(q, "group=", 6) == 0)
1220 		{
1221 			a=malloc(p-q+1);
1222 			if (!a)
1223 				return -1;
1224 
1225 			memcpy(a, q, p-q);
1226 			a[p-q]=0;
1227 			rc=(*cb_func)(a, arg);
1228 			free(a);
1229 			if (rc)
1230 				return rc;
1231 		}
1232 	}
1233 	return 0;
1234 }
1235 
count_owner_list(const char * o,void * arg)1236 static int count_owner_list(const char *o, void *arg)
1237 {
1238 	++*(size_t *)arg;
1239 
1240 	return 0;
1241 }
1242 
save_owner_list(const char * o,void * arg)1243 static int save_owner_list(const char *o, void *arg)
1244 {
1245 	char ***p=(char ***)arg;
1246 
1247 	**p=strdup(o);
1248 
1249 	if (**p == NULL)
1250 		return -1;
1251 
1252 	++*p;
1253 	return 0;
1254 }
1255 
maildir_acl_computerights(maildir_aclt * rights,maildir_aclt_list * acl_list,const char * me,const char * folder_owner)1256 int maildir_acl_computerights(maildir_aclt *rights,
1257 			      maildir_aclt_list *acl_list,
1258 			      const char *me,
1259 			      const char *folder_owner)
1260 {
1261 	char **owner_array;
1262 	size_t owner_cnt;
1263 	char **p;
1264 	int rc;
1265 
1266 	owner_cnt=1;
1267 
1268 	if (get_owner_list(count_owner_list, me, folder_owner,
1269 			   (void *)&owner_cnt) ||
1270 	    (owner_array=calloc(owner_cnt, sizeof(char *))) == NULL)
1271 		return -1;
1272 
1273 	p=owner_array;
1274 
1275 	if (get_owner_list(save_owner_list, me, folder_owner, (void *)&p))
1276 	{
1277 		for (owner_cnt=0; owner_array[owner_cnt]; ++owner_cnt)
1278 			free(owner_array[owner_cnt]);
1279 		free(owner_array);
1280 		return -1;
1281 	}
1282 
1283 	rc=maildir_acl_compute_array(rights, acl_list,
1284 				     (const char * const *)owner_array);
1285 
1286 	for (owner_cnt=0; owner_array[owner_cnt]; ++owner_cnt)
1287 		free(owner_array[owner_cnt]);
1288 	free(owner_array);
1289 	return rc;
1290 }
1291