1 /* user.c -- User manipulation routines
2  *
3  * Copyright (c) 1994-2008 Carnegie Mellon University.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * 3. The name "Carnegie Mellon University" must not be used to
18  *    endorse or promote products derived from this software without
19  *    prior written permission. For permission or any legal
20  *    details, please contact
21  *      Carnegie Mellon University
22  *      Center for Technology Transfer and Enterprise Creation
23  *      4615 Forbes Avenue
24  *      Suite 302
25  *      Pittsburgh, PA  15213
26  *      (412) 268-7393, fax: (412) 268-7395
27  *      innovation@andrew.cmu.edu
28  *
29  * 4. Redistributions of any form whatsoever must retain the following
30  *    acknowledgment:
31  *    "This product includes software developed by Computing Services
32  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33  *
34  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41  */
42 
43 #include <config.h>
44 
45 #ifdef HAVE_UNISTD_H
46 #include <unistd.h>
47 #endif
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <fcntl.h>
52 #include <errno.h>
53 #include <syslog.h>
54 
55 #if HAVE_DIRENT_H
56 # include <dirent.h>
57 # define NAMLEN(dirent) strlen((dirent)->d_name)
58 #else
59 # define dirent direct
60 # define NAMLEN(dirent) (dirent)->d_namlen
61 # if HAVE_SYS_NDIR_H
62 #  include <sys/ndir.h>
63 # endif
64 # if HAVE_SYS_DIR_H
65 #  include <sys/dir.h>
66 # endif
67 # if HAVE_NDIR_H
68 #  include <ndir.h>
69 # endif
70 #endif
71 
72 #include "assert.h"
73 #include "global.h"
74 #include "mailbox.h"
75 #include "mboxkey.h"
76 #include "mboxlist.h"
77 #include "mboxname.h"
78 #include "proc.h"
79 #include "quota.h"
80 #include "search_engines.h"
81 #include "seen.h"
82 #include "user.h"
83 #include "util.h"
84 #include "xmalloc.h"
85 
86 /* generated headers are not necessarily in current directory */
87 #include "imap/imap_err.h"
88 
89 #ifdef WITH_DAV
90 #include "caldav_alarm.h"
91 #endif
92 
93 #define FNAME_SUBSSUFFIX "sub"
94 
95 #if 0
96 static int user_deleteacl(char *name, int matchlen, int category, void* rock)
97 {
98     /* deleting all references to the user is too slow right now */
99 
100     char *ident = (char *) rock;
101     int r;
102     char *acl;
103     char *rights, *nextid;
104     char *origacl, *aclalloc;
105 
106     r = mboxlist_lookup(name, &origacl, NULL);
107 
108     /* setacl re-calls mboxlist_lookup and will stomp on us */
109     aclalloc = acl = xstrdup(origacl);
110 
111     while (!r && acl) {
112         rights = strchr(acl, '\t');
113         if (!rights) break;
114         *rights++ = '\0';
115 
116         nextid = strchr(rights, '\t');
117         if (!nextid) break;
118         *nextid++ = '\0';
119 
120         if (!strcmp(acl, ident)) {
121             /* delete ACL for ident */
122             if (!r) mboxlist_setacl(name, ident, (char *)0,
123                                     1, ident, NULL);
124         }
125 
126         acl = nextid;
127     }
128 
129     free(aclalloc);
130 
131     return 0;
132 }
133 #endif
134 
user_sieve_path(const char * inuser)135 EXPORTED const char *user_sieve_path(const char *inuser)
136 {
137     static char sieve_path[2048];
138     char hash, *domain;
139     char *user = xstrdupnull(inuser);
140     char *p;
141 
142     /* Make sure it's a real userid, with no ^ escaping!
143      * XXX It's kinda bogus to be handling this here; it should be fixed
144      * XXX much further up somewhere, but that may require deep surgery.
145      */
146     for (p = user; p && *p; p++) {
147         if (*p == '^')
148             *p = '.';
149     }
150 
151     if (config_virtdomains && (domain = strchr(user, '@'))) {
152         char d = (char) dir_hash_c(domain+1, config_fulldirhash);
153         *domain = '\0';  /* split user@domain */
154         hash = (char) dir_hash_c(user, config_fulldirhash);
155         snprintf(sieve_path, sizeof(sieve_path), "%s%s%c/%s/%c/%s",
156                  config_getstring(IMAPOPT_SIEVEDIR),
157                  FNAME_DOMAINDIR, d, domain+1, hash, user);
158         *domain = '@';  /* reassemble user@domain */
159     }
160     else {
161         hash = (char) dir_hash_c(user, config_fulldirhash);
162 
163         snprintf(sieve_path, sizeof(sieve_path), "%s/%c/%s",
164                  config_getstring(IMAPOPT_SIEVEDIR), hash, user);
165     }
166 
167     free(user);
168     return sieve_path;
169 }
170 
user_deletesieve(const char * user)171 static int user_deletesieve(const char *user)
172 {
173     const char *sieve_path;
174     char filename[2048];
175     DIR *mbdir;
176     struct dirent *next = NULL;
177 
178     /* oh well */
179     if(config_getswitch(IMAPOPT_SIEVEUSEHOMEDIR)) return 0;
180 
181     sieve_path = user_sieve_path(user);
182 
183     mbdir = opendir(sieve_path);
184 
185     if (mbdir) {
186         while((next = readdir(mbdir)) != NULL) {
187             if (!strcmp(next->d_name, ".")
188                 || !strcmp(next->d_name, "..")) continue;
189 
190             snprintf(filename, sizeof(filename), "%s/%s",
191                      sieve_path, next->d_name);
192 
193             unlink(filename);
194         }
195 
196         closedir(mbdir);
197 
198         /* remove mbdir */
199         rmdir(sieve_path);
200     }
201 
202     return 0;
203 }
204 
user_deletedata(const char * userid,int wipe_user)205 EXPORTED int user_deletedata(const char *userid, int wipe_user)
206 {
207     char *fname;
208 
209     assert(user_isnamespacelocked(userid));
210 
211     /* delete seen state and mbox keys */
212     if(wipe_user) {
213         seen_delete_user(userid);
214         /* XXX  what do we do about multiple backends? */
215         mboxkey_delete_user(userid);
216     }
217 
218     /* delete subscriptions */
219     fname = user_hash_subs(userid);
220     (void) unlink(fname);
221     free(fname);
222 
223     /* delete quotas */
224     user_deletequotaroots(userid);
225 
226     /* delete sieve scripts */
227     user_deletesieve(userid);
228 
229     /* NOTE: even if conversations aren't enabled, we want to clean up */
230 
231     /* delete conversations file */
232     fname = conversations_getuserpath(userid);
233     (void) unlink(fname);
234     free(fname);
235 
236     /* XXX: one could make an argument for keeping the counters
237      * file forever, so that UIDVALIDITY never gets reused. */
238     fname = user_hash_meta(userid, "counters");
239     (void) unlink(fname);
240     free(fname);
241 
242     /* delete dav database (even if DAV is turned off, this is fine) */
243     fname = user_hash_meta(userid, "dav");
244     (void) unlink(fname);
245     free(fname);
246 
247     /* delete all the search engine data (if any) */
248     search_deluser(userid);
249 
250 #ifdef WITH_DAV
251     /* delete all the calendar alarms for the user */
252     caldav_alarm_delete_user(userid);
253 #endif /* WITH_DAV */
254 
255     proc_killuser(userid);
256 
257     return 0;
258 }
259 
260 struct rename_rock {
261     const char *olduser;
262     const char *newuser;
263     const char *oldinbox;
264     const char *newinbox;
265     int domainchange;
266 };
267 
user_renamesub(const char * name,void * rock)268 static int user_renamesub(const char *name, void* rock)
269 {
270     struct rename_rock *rrock = (struct rename_rock *) rock;
271     char newname[MAX_MAILBOX_BUFFER];
272 
273     if (!strncasecmp(name, "INBOX", 5) &&
274         (name[5] == '\0' || name[5] == '.')) {
275         /* generate new name of personal mailbox */
276         snprintf(newname, sizeof(newname), "%s%s",
277                  rrock->newinbox, name+5);
278         name = newname;
279     }
280     else if (!strncmp(name, rrock->oldinbox, strlen(rrock->oldinbox)) &&
281         (name[strlen(rrock->oldinbox)] == '\0' ||
282          name[strlen(rrock->oldinbox)] == '.')) {
283         /* generate new name of personal mailbox */
284         snprintf(newname, sizeof(newname), "%s%s",
285                  rrock->newinbox, name+strlen(rrock->oldinbox));
286         name = newname;
287     }
288 
289     return mboxlist_changesub(name, rrock->newuser, NULL, 1, 1, 1);
290 }
291 
user_renamesieve(const char * olduser,const char * newuser)292 static int user_renamesieve(const char *olduser, const char *newuser)
293 {
294     char hash, *domain;
295     char oldpath[2048], newpath[2048];
296     int r;
297 
298     /* oh well */
299     if(config_getswitch(IMAPOPT_SIEVEUSEHOMEDIR)) return 0;
300 
301     if (config_virtdomains && (domain = strchr(olduser, '@'))) {
302         char d = (char) dir_hash_c(domain+1, config_fulldirhash);
303         *domain = '\0';  /* split user@domain */
304         hash = (char) dir_hash_c(olduser, config_fulldirhash);
305         snprintf(oldpath, sizeof(oldpath), "%s%s%c/%s/%c/%s",
306                  config_getstring(IMAPOPT_SIEVEDIR),
307                  FNAME_DOMAINDIR, d, domain+1, hash, olduser);
308         *domain = '@';  /* reassemble user@domain */
309     }
310     else {
311         hash = (char) dir_hash_c(olduser, config_fulldirhash);
312 
313         snprintf(oldpath, sizeof(oldpath), "%s/%c/%s",
314                  config_getstring(IMAPOPT_SIEVEDIR), hash, olduser);
315     }
316 
317     if (config_virtdomains && (domain = strchr(newuser, '@'))) {
318         char d = (char) dir_hash_c(domain+1, config_fulldirhash);
319         *domain = '\0';  /* split user@domain */
320         hash = (char) dir_hash_c(newuser, config_fulldirhash);
321         snprintf(newpath, sizeof(newpath), "%s%s%c/%s/%c/%s",
322                  config_getstring(IMAPOPT_SIEVEDIR),
323                  FNAME_DOMAINDIR, d, domain+1, hash, newuser);
324         *domain = '@';  /* reassemble user@domain */
325     }
326     else {
327         hash = (char) dir_hash_c(newuser, config_fulldirhash);
328 
329         snprintf(newpath, sizeof(newpath), "%s/%c/%s",
330                  config_getstring(IMAPOPT_SIEVEDIR), hash, newuser);
331     }
332 
333     /* rename sieve directory
334      *
335      * XXX this doesn't rename sieve scripts
336      */
337     r = rename(oldpath, newpath);
338     if (r < 0) {
339         if (errno == ENOENT) {
340             syslog(LOG_WARNING, "error renaming %s to %s: %m",
341                    oldpath, newpath);
342             /* but maybe the user doesn't have any scripts ? */
343             r = 0;
344         }
345         else if (errno == EXDEV) {
346             syslog(LOG_ERR, "error renaming %s to %s: different filesystems",
347                    oldpath, newpath);
348             /* doh!  need to copy entire directory tree */
349         }
350         else {
351             syslog(LOG_ERR, "error renaming %s to %s: %m", oldpath, newpath);
352         }
353     }
354 
355     return r;
356 }
357 
user_renamedata(const char * olduser,const char * newuser)358 EXPORTED int user_renamedata(const char *olduser, const char *newuser)
359 {
360     struct rename_rock rrock;
361     int i;
362 
363     /* get INBOXes */
364     char *oldinbox = mboxname_user_mbox(olduser, NULL);
365     char *newinbox = mboxname_user_mbox(newuser, NULL);
366 
367     /* copy seen db */
368     seen_rename_user(olduser, newuser);
369 
370     /* setup rock for find operations */
371     rrock.olduser = olduser;
372     rrock.newuser = newuser;
373     rrock.oldinbox = oldinbox;
374     rrock.newinbox = newinbox;
375 
376     /* copy/rename subscriptions - we're using the internal names here */
377     strarray_t *subs = mboxlist_sublist(olduser);
378     for (i = 0; i < strarray_size(subs); i++) {
379         user_renamesub(strarray_nth(subs, i), &rrock);
380     }
381     strarray_free(subs);
382 
383     /* move sieve scripts */
384     user_renamesieve(olduser, newuser);
385 
386     free(oldinbox);
387     free(newinbox);
388 
389     return 0;
390 }
391 
user_renameacl(const struct namespace * namespace,const char * name,const char * olduser,const char * newuser)392 EXPORTED int user_renameacl(const struct namespace *namespace, const char *name,
393                             const char *olduser, const char *newuser)
394 {
395     int r = 0;
396     char *acl;
397     char *rights, *nextid;
398     mbentry_t *mbentry = NULL;
399     char *aclalloc;
400 
401     r = mboxlist_lookup(name, &mbentry, NULL);
402     if (r) return r;
403 
404     /* setacl re-calls mboxlist_lookup and will stomp on us */
405     aclalloc = acl = xstrdup(mbentry->acl);
406 
407     while (!r && acl) {
408         rights = strchr(acl, '\t');
409         if (!rights) break;
410         *rights++ = '\0';
411 
412         nextid = strchr(rights, '\t');
413         if (!nextid) break;
414         *nextid++ = '\0';
415 
416         if (!strcmp(acl, olduser)) {
417             /* copy ACL for olduser to newuser */
418             r = mboxlist_setacl(namespace, name, newuser, rights, 1, newuser, NULL);
419             /* delete ACL for olduser */
420             if (!r)
421                 r = mboxlist_setacl(namespace, name, olduser, (char *)0, 1, newuser, NULL);
422         }
423 
424         acl = nextid;
425     }
426 
427     free(aclalloc);
428     mboxlist_entry_free(&mbentry);
429 
430     return r;
431 }
432 
user_copyquotaroot(const char * oldname,const char * newname)433 EXPORTED int user_copyquotaroot(const char *oldname, const char *newname)
434 {
435     int r = 0;
436     struct quota q;
437 
438     quota_init(&q, oldname);
439     r = quota_read(&q, NULL, 0);
440     if (!r)
441         mboxlist_setquotas(newname, q.limits, 0, 0);
442     quota_free(&q);
443 
444     return r;
445 }
446 
find_p(void * rockp,const char * key,size_t keylen,const char * data,size_t datalen)447 static int find_p(void *rockp,
448                   const char *key, size_t keylen,
449                   const char *data __attribute__((unused)),
450                   size_t datalen __attribute__((unused)))
451 {
452     char *inboxname = (char *)rockp;
453     size_t inboxlen = strlen(inboxname);
454 
455     return (!strncmp(key, inboxname, inboxlen) &&
456             (keylen == inboxlen || key[inboxlen] == '.'));
457 }
458 
find_cb(void * rockp,const char * key,size_t keylen,const char * data,size_t datalen)459 static int find_cb(void *rockp __attribute__((unused)),
460                    const char *key, size_t keylen,
461                    const char *data __attribute__((unused)),
462                    size_t datalen __attribute__((unused)))
463 {
464     char *root;
465     int r;
466 
467     root = xstrndup(key, keylen);
468     r = quota_deleteroot(root, 0);
469     free(root);
470 
471     return r;
472 }
473 
user_deletequotaroots(const char * userid)474 int user_deletequotaroots(const char *userid)
475 {
476     char *inbox = mboxname_user_mbox(userid, NULL);
477     int r = quotadb_foreach(inbox, strlen(inbox), &find_p, &find_cb, inbox);
478     free(inbox);
479     return r;
480 }
481 
user_hash_meta(const char * userid,const char * suffix)482 EXPORTED char *user_hash_meta(const char *userid, const char *suffix)
483 {
484     mbname_t *mbname = NULL;
485     char *result;
486 
487     mbname = mbname_from_userid(userid);
488     result = mboxname_conf_getpath(mbname, suffix);
489 
490     mbname_free(&mbname);
491 
492     return result;
493 }
494 
user_hash_subs(const char * userid)495 HIDDEN char *user_hash_subs(const char *userid)
496 {
497     return user_hash_meta(userid, FNAME_SUBSSUFFIX);
498 }
499 
_namelock_name_from_userid(const char * userid)500 static const char *_namelock_name_from_userid(const char *userid)
501 {
502     const char *p;
503     static struct buf buf = BUF_INITIALIZER;
504     if (!userid) userid = ""; // no userid == global lock
505 
506     buf_setcstr(&buf, "*U*");
507 
508     for (p = userid; *p; p++) {
509         switch(*p) {
510             case '.':
511                 buf_putc(&buf, '^');
512                 break;
513             default:
514                 buf_putc(&buf, *p);
515                 break;
516         }
517     }
518 
519     return buf_cstring(&buf);
520 }
521 
user_namespacelock(const char * userid)522 EXPORTED struct mboxlock *user_namespacelock(const char *userid)
523 {
524     struct mboxlock *namelock;
525     const char *name = _namelock_name_from_userid(userid);
526     int r = mboxname_lock(name, &namelock, LOCK_EXCLUSIVE);
527     if (r) return NULL;
528     return namelock;
529 }
530 
user_isnamespacelocked(const char * userid)531 EXPORTED int user_isnamespacelocked(const char *userid)
532 {
533     const char *name = _namelock_name_from_userid(userid);
534     return mboxname_islocked(name);
535 }
536