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