#include <config.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "assert.h"
#include "glob.h"
#include "global.h"
#include "mailbox.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "xmalloc.h"
#include "mboxname.h"
#include "mboxlist.h"
static char *badmboxpatterns[] = {
"",
"*\t*",
"*\n*",
"*/*",
".*",
"*.",
"*..*",
"user",
};
#define NUM_BADMBOXPATTERNS (sizeof(badmboxpatterns)/sizeof(*badmboxpatterns))
#define XX 127
static const char index_mod64[256] = {
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, 63,XX,XX,XX,
52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX,
XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX,
XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
};
#define CHARMOD64(c) (index_mod64[(unsigned char)(c)])
static int mboxname_tointernal(struct namespace *namespace, const char *name,
const char *userid, char *result)
{
char *cp;
int userlen, domainlen = 0, namelen;
result[0] = '\0';
userlen = userid ? strlen(userid) : 0;
namelen = strlen(name);
if (config_virtdomains) {
if (userid && (cp = strrchr(userid, '@'))) {
userlen = cp++ - userid;
if (!(config_defdomain && !strcasecmp(config_defdomain, cp))) {
domainlen = strlen(cp)+1;
if (domainlen > MAX_MAILBOX_NAME)
return IMAP_MAILBOX_BADNAME;
sprintf(result, "%s!", cp);
}
}
if ((cp = strrchr(name, '@'))) {
namelen = cp - name;
if (config_defdomain && !strcasecmp(config_defdomain, cp+1)) {
if (domainlen) {
return IMAP_MAILBOX_BADNAME;
}
}
else {
if ((!domainlen && !namespace->isadmin) ||
(domainlen && strcasecmp(userid+userlen, cp))) {
return IMAP_MAILBOX_BADNAME;
}
domainlen = strlen(cp+1)+1;
if (domainlen > MAX_MAILBOX_NAME)
return IMAP_MAILBOX_BADNAME;
sprintf(result, "%s!", cp+1);
}
}
}
result += domainlen;
if ((name[0] == 'i' || name[0] == 'I') &&
!strncasecmp(name, "inbox", 5) &&
(namelen == 5 || name[5] == namespace->hier_sep)) {
if (!userid || ((cp = strchr(userid, namespace->hier_sep)) &&
(cp - userid < userlen))) {
return IMAP_MAILBOX_BADNAME;
}
if (domainlen+namelen+userlen > MAX_MAILBOX_NAME) {
return IMAP_MAILBOX_BADNAME;
}
sprintf(result, "user.%.*s%.*s", userlen, userid, namelen-5, name+5);
mboxname_hiersep_tointernal(namespace, result+5+userlen, 0);
return 0;
}
if (domainlen+namelen > MAX_MAILBOX_NAME) {
return IMAP_MAILBOX_BADNAME;
}
sprintf(result, "%.*s", namelen, name);
mboxname_hiersep_tointernal(namespace, result, 0);
return 0;
}
static int mboxname_tointernal_alt(struct namespace *namespace,
const char *name,
const char *userid, char *result)
{
char *cp;
int userlen, domainlen = 0, namelen;
int prefixlen;
size_t resultlen;
result[0] = '\0';
userlen = userid ? strlen(userid) : 0;
namelen = strlen(name);
if (config_virtdomains) {
if (userid && (cp = strchr(userid, '@'))) {
userlen = cp++ - userid;
if (!(config_defdomain && !strcasecmp(config_defdomain, cp))) {
domainlen = strlen(cp)+1;
if (domainlen > MAX_MAILBOX_NAME)
return IMAP_MAILBOX_BADNAME;
sprintf(result, "%s!", cp);
}
}
if ((cp = strrchr(name, '@'))) {
namelen = cp - name;
if (config_defdomain && !strcasecmp(config_defdomain, cp+1)) {
if (domainlen) {
return IMAP_MAILBOX_BADNAME;
}
}
else {
if ((!domainlen && !namespace->isadmin) ||
(domainlen && strcasecmp(userid+userlen, cp))) {
return IMAP_MAILBOX_BADNAME;
}
domainlen = strlen(cp+1)+1;
if (domainlen > MAX_MAILBOX_NAME)
return IMAP_MAILBOX_BADNAME;
sprintf(result, "%s!", cp+1);
}
}
}
result += domainlen;
prefixlen = strlen(namespace->prefix[NAMESPACE_SHARED]);
if(prefixlen == 0) return IMAP_MAILBOX_BADNAME;
if (!strncmp(name, namespace->prefix[NAMESPACE_SHARED], prefixlen-1) &&
(namelen == prefixlen-1 || name[prefixlen-1] == namespace->hier_sep)) {
if (namelen == prefixlen-1) {
return IMAP_MAILBOX_BADNAME;
}
if (domainlen+namelen-prefixlen > MAX_MAILBOX_NAME) {
return IMAP_MAILBOX_BADNAME;
}
sprintf(result, "%.*s", namelen-prefixlen, name+prefixlen);
mboxname_hiersep_tointernal(namespace, result, 0);
return 0;
}
prefixlen = strlen(namespace->prefix[NAMESPACE_USER]);
if(prefixlen == 0) return IMAP_MAILBOX_BADNAME;
if (!strncmp(name, namespace->prefix[NAMESPACE_USER], prefixlen-1) &&
(namelen == prefixlen-1 || name[prefixlen-1] == namespace->hier_sep)) {
if (namelen == prefixlen-1) {
return IMAP_MAILBOX_BADNAME;
}
if (domainlen+namelen-prefixlen+5 > MAX_MAILBOX_NAME) {
return IMAP_MAILBOX_BADNAME;
}
sprintf(result, "user.%.*s", namelen-prefixlen, name+prefixlen);
mboxname_hiersep_tointernal(namespace, result+5, 0);
return 0;
}
if (!userid || ((cp = strchr(userid, namespace->hier_sep)) &&
(cp - userid < userlen))) {
return IMAP_MAILBOX_BADNAME;
}
if (domainlen+userlen+5 > MAX_MAILBOX_NAME) {
return IMAP_MAILBOX_BADNAME;
}
sprintf(result, "user.%.*s", userlen, userid);
if ((name[0] == 'i' || name[0] == 'I') &&
!strncasecmp(name, "inbox", 5) &&
(namelen == 5 || name[5] == namespace->hier_sep)) {
if (name[5] == namespace->hier_sep) {
return IMAP_MAILBOX_BADNAME;
}
return 0;
}
resultlen = strlen(result);
if (domainlen+resultlen+6+namelen > MAX_MAILBOX_NAME) {
return IMAP_MAILBOX_BADNAME;
}
snprintf(result+resultlen, MAX_MAILBOX_NAME+1-resultlen, ".%.*s",
namelen, name);
mboxname_hiersep_tointernal(namespace, result+6+userlen, 0);
return 0;
}
static int mboxname_toexternal(struct namespace *namespace, const char *name,
const char *userid, char *result)
{
char *domain = NULL, *cp;
size_t domainlen = 0, resultlen;
result[0] = '\0';
if(strlen(name) > MAX_MAILBOX_NAME) return IMAP_MAILBOX_BADNAME;
if (config_virtdomains && (cp = strchr(name, '!'))) {
domain = (char*) name;
domainlen = cp++ - name;
name = cp;
if (userid && (cp = strchr(userid, '@')) &&
(strlen(++cp) == domainlen) && !strncmp(domain, cp, domainlen))
domain = NULL;
}
strcpy(result, name);
mboxname_hiersep_toexternal(namespace, result, 0);
resultlen = strlen(result);
if (domain) {
if(resultlen+domainlen+1 > MAX_MAILBOX_NAME)
return IMAP_MAILBOX_BADNAME;
snprintf(result+resultlen, MAX_MAILBOX_NAME+1-resultlen,
"@%.*s", domainlen, domain);
}
return 0;
}
static int mboxname_toexternal_alt(struct namespace *namespace, const char *name,
const char *userid, char *result)
{
char *domain;
size_t userlen, resultlen;
result[0] = '\0';
if(strlen(name) > MAX_MAILBOX_NAME) return IMAP_MAILBOX_BADNAME;
if (!userid) return IMAP_MAILBOX_BADNAME;
userlen = strlen(userid);
if (config_virtdomains && (domain = strchr(userid, '@'))) {
size_t domainlen = strlen(domain);
userlen = domain - userid;
if (!strncmp(name, domain+1, domainlen-1) &&
name[domainlen-1] == '!') {
name += domainlen;
}
}
if (!strncasecmp(name, "inbox", 5) &&
(name[5] == '\0' || name[5] == '.')) {
if (name[5] == '\0')
strcpy(result, name);
else
strcpy(result, name+6);
}
else if (!strncmp(name, "user.", 5) &&
!strncmp(name+5, userid, userlen) &&
(name[5+userlen] == '\0' ||
name[5+userlen] == '.')) {
if (name[5+userlen] == '\0')
strcpy(result, "INBOX");
else
strcpy(result, name+5+userlen+1);
}
else if (!strncmp(name, "user", 4) &&
(name[4] == '\0' || name[4] == '.')) {
size_t prefixlen = strlen(namespace->prefix[NAMESPACE_USER]);
if ((prefixlen > MAX_MAILBOX_NAME) ||
((name[4] == '.') &&
((prefixlen+1+strlen(name+5)) > MAX_MAILBOX_NAME)))
return IMAP_MAILBOX_BADNAME;
sprintf(result, "%.*s",
prefixlen-1, namespace->prefix[NAMESPACE_USER]);
resultlen = strlen(result);
if (name[4] == '.') {
sprintf(result+resultlen, "%c%s", namespace->hier_sep, name+5);
}
}
else {
if (!strncmp(name, namespace->prefix[NAMESPACE_SHARED],
strlen(namespace->prefix[NAMESPACE_SHARED])-1)) {
strcpy(result, name);
}
else {
strcpy(result, namespace->prefix[NAMESPACE_SHARED]);
strcat(result, name);
}
}
mboxname_hiersep_toexternal(namespace, result, 0);
return 0;
}
int mboxname_init_namespace(struct namespace *namespace, int isadmin)
{
const char *prefix;
assert(namespace != NULL);
namespace->isadmin = isadmin;
namespace->hier_sep =
config_getswitch(IMAPOPT_UNIXHIERARCHYSEP) ? '/' : '.';
namespace->isalt = !isadmin && config_getswitch(IMAPOPT_ALTNAMESPACE);
if (namespace->isalt) {
strcpy(namespace->prefix[NAMESPACE_INBOX], "");
prefix = config_getstring(IMAPOPT_USERPREFIX);
if (!prefix || strlen(prefix) == 0 ||
strlen(prefix) >= MAX_NAMESPACE_PREFIX ||
strchr(prefix,namespace->hier_sep) != NULL)
return IMAP_NAMESPACE_BADPREFIX;
sprintf(namespace->prefix[NAMESPACE_USER], "%.*s%c",
MAX_NAMESPACE_PREFIX-1, prefix, namespace->hier_sep);
prefix = config_getstring(IMAPOPT_SHAREDPREFIX);
if (!prefix || strlen(prefix) == 0 ||
strlen(prefix) >= MAX_NAMESPACE_PREFIX ||
strchr(prefix, namespace->hier_sep) != NULL ||
!strncmp(namespace->prefix[NAMESPACE_USER], prefix, strlen(prefix)))
return IMAP_NAMESPACE_BADPREFIX;
sprintf(namespace->prefix[NAMESPACE_SHARED], "%.*s%c",
MAX_NAMESPACE_PREFIX-1, prefix, namespace->hier_sep);
namespace->mboxname_tointernal = mboxname_tointernal_alt;
namespace->mboxname_toexternal = mboxname_toexternal_alt;
namespace->mboxlist_findall = mboxlist_findall_alt;
namespace->mboxlist_findsub = mboxlist_findsub_alt;
}
else {
sprintf(namespace->prefix[NAMESPACE_INBOX], "%s%c",
"INBOX", namespace->hier_sep);
sprintf(namespace->prefix[NAMESPACE_USER], "%s%c",
"user", namespace->hier_sep);
strcpy(namespace->prefix[NAMESPACE_SHARED], "");
namespace->mboxname_tointernal = mboxname_tointernal;
namespace->mboxname_toexternal = mboxname_toexternal;
namespace->mboxlist_findall = mboxlist_findall;
namespace->mboxlist_findsub = mboxlist_findsub;
}
return 0;
}
char *mboxname_hiersep_tointernal(struct namespace *namespace, char *name,
int length)
{
char *p;
assert(namespace != NULL);
assert(namespace->hier_sep == '.' || namespace->hier_sep == '/');
if (!length) length = strlen(name);
if (namespace->hier_sep == '/') {
for (p = name; *p && length; p++, length--) {
if (*p == '/') *p = '.';
else if (*p == '.') *p = DOTCHAR;
}
}
return name;
}
char *mboxname_hiersep_toexternal(struct namespace *namespace, char *name,
int length)
{
char *p;
assert(namespace != NULL);
assert(namespace->hier_sep == '.' || namespace->hier_sep == '/');
if (!length) length=strlen(name);
if (namespace->hier_sep == '/') {
for (p = name; *p && length; p++, length--) {
if (*p == '.') *p = '/';
else if (*p == DOTCHAR) *p = '.';
}
}
return name;
}
int mboxname_userownsmailbox(const char *userid, const char *name)
{
struct namespace internal = { '.', 0, 0, { "INBOX.", "user.", "" },
NULL, NULL, NULL, NULL };
char inboxname[MAX_MAILBOX_NAME+1];
if (!mboxname_tointernal(&internal, "INBOX", userid, inboxname) &&
!strncmp(name, inboxname, strlen(inboxname)) &&
(name[strlen(inboxname)] == '\0' || name[strlen(inboxname)] == '.')) {
return 1;
}
return 0;
}
char *mboxname_isusermailbox(const char *name, int isinbox)
{
const char *p;
if (((!strncmp(name, "user.", 5) && (p = name+5)) ||
((p = strstr(name, "!user.")) && (p += 6))) &&
(!isinbox || !strchr(p, '.')))
return (char*) p;
else
return NULL;
}
int mboxname_netnewscheck(char *name)
{
int c;
int sawnonnumeric = 0;
while ((c = *name++)!=0) {
switch (c) {
case '.':
if (!sawnonnumeric) return IMAP_MAILBOX_BADNAME;
sawnonnumeric = 0;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
default:
sawnonnumeric = 1;
break;
}
}
if (!sawnonnumeric) return IMAP_MAILBOX_BADNAME;
return 0;
}
#define GOODCHARS " +,-.0123456789:=@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"
int mboxname_policycheck(char *name)
{
unsigned i;
struct glob *g;
int sawutf7 = 0;
unsigned c1, c2, c3, c4, c5, c6, c7, c8;
int ucs4;
int unixsep;
unixsep = config_getswitch(IMAPOPT_UNIXHIERARCHYSEP);
if (strlen(name) > MAX_MAILBOX_NAME) return IMAP_MAILBOX_BADNAME;
for (i = 0; i < NUM_BADMBOXPATTERNS; i++) {
g = glob_init(badmboxpatterns[i], 0);
if (GLOB_TEST(g, name) != -1) {
glob_free(&g);
return IMAP_MAILBOX_BADNAME;
}
glob_free(&g);
}
if (*name == '~') return IMAP_MAILBOX_BADNAME;
while (*name) {
if (*name == '&') {
name++;
while (*name != '-') {
if (sawutf7) {
return IMAP_MAILBOX_BADNAME;
}
if ((c1 = CHARMOD64(*name++)) == XX ||
(c2 = CHARMOD64(*name++)) == XX ||
(c3 = CHARMOD64(*name++)) == XX) {
return IMAP_MAILBOX_BADNAME;
}
ucs4 = (c1 << 10) | (c2 << 4) | (c3 >> 2);
if ((ucs4 & 0xff80) == 0 || (ucs4 & 0xf800) == 0xd800) {
return IMAP_MAILBOX_BADNAME;
}
if (*name == '-') {
if (c3 & 0x03) return IMAP_MAILBOX_BADNAME;
break;
}
if ((c4 = CHARMOD64(*name++)) == XX ||
(c5 = CHARMOD64(*name++)) == XX ||
(c6 = CHARMOD64(*name++)) == XX) {
return IMAP_MAILBOX_BADNAME;
}
ucs4 = ((c3 & 0x03) << 14) | (c4 << 8) | (c5 << 2) | (c6 >> 4);
if ((ucs4 & 0xff80) == 0 || (ucs4 & 0xf800) == 0xd800) {
return IMAP_MAILBOX_BADNAME;
}
if (*name == '-') {
if (c6 & 0x0f) return IMAP_MAILBOX_BADNAME;
break;
}
if ((c7 = CHARMOD64(*name++)) == XX ||
(c8 = CHARMOD64(*name++)) == XX) {
return IMAP_MAILBOX_BADNAME;
}
ucs4 = ((c6 & 0x0f) << 12) | (c7 << 6) | c8;
if ((ucs4 & 0xff80) == 0 || (ucs4 & 0xf800) == 0xd800) {
return IMAP_MAILBOX_BADNAME;
}
}
if (name[-1] == '&') sawutf7 = 0;
else sawutf7 = 1;
name++;
}
else {
if (!strchr(GOODCHARS, *name) &&
!(unixsep && *name == DOTCHAR))
return IMAP_MAILBOX_BADNAME;
name++;
sawutf7 = 0;
}
}
return 0;
}