/*
Copyright (C) 1999-2004 IC & S dbmail@ic-s.nl
Copyright (c) 2005-2006 NFG Net Facilities Group BV support@nfg.nl
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
*
* Miscelaneous functions */
#include "dbmail.h"
#define THIS_MODULE "misc"
const char AcceptedMailboxnameChars[] =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789-=/ _.&,+@()[]'#";
/**
* abbreviated names of the months
*/
const char *month_desc[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
/* returned by date_sql2imap() */
#define IMAP_STANDARD_DATE "03-Nov-1979 00:00:00 +0000"
char _imapdate[IMAP_INTERNALDATE_LEN] = IMAP_STANDARD_DATE;
/* returned by date_imap2sql() */
#define SQL_STANDARD_DATE "1979-11-03 00:00:00"
char _sqldate[SQL_INTERNALDATE_LEN + 1] = SQL_STANDARD_DATE;
const int month_len[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
extern const char *imap_flag_desc_escaped[];
static struct DbmailIconv *ic;
#undef max
#define max(x,y) ( (x) > (y) ? (x) : (y) )
/* only locally used */
typedef struct {
GTree *tree;
GList *list;
int condition;
} tree_merger_t;
int drop_privileges(char *newuser, char *newgroup)
{
/* will drop running program's priviledges to newuser and newgroup */
struct passwd *pwd;
struct group *grp;
grp = getgrnam(newgroup);
if (grp == NULL) {
TRACE(TRACE_ERROR, "could not find group %s\n", newgroup);
return -1;
}
pwd = getpwnam(newuser);
if (pwd == NULL) {
TRACE(TRACE_ERROR, "could not find user %s\n", newuser);
return -1;
}
if (setgid(grp->gr_gid) != 0) {
TRACE(TRACE_ERROR, "could not set gid to %s\n", newgroup);
return -1;
}
if (setuid(pwd->pw_uid) != 0) {
TRACE(TRACE_ERROR, "could not set uid to %s\n", newuser);
return -1;
}
return 0;
}
void create_unique_id(char *target, u64_t message_idnr)
{
char *a_message_idnr, *a_rand;
char *md5_str;
a_message_idnr = g_strdup_printf("%llu",message_idnr);
a_rand = g_strdup_printf("%d",g_random_int());
if (message_idnr != 0)
snprintf(target, UID_SIZE, "%s:%s",
a_message_idnr, a_rand);
else
snprintf(target, UID_SIZE, "%s", a_rand);
md5_str = dm_md5((unsigned char *)target);
snprintf(target, UID_SIZE, "%s", md5_str);
TRACE(TRACE_DEBUG, "created: %s", target);
g_free(md5_str);
g_free(a_message_idnr);
g_free(a_rand);
}
void create_current_timestring(timestring_t * timestring)
{
time_t td;
struct tm tm;
if (time(&td) == -1)
TRACE(TRACE_FATAL, "error getting time from OS");
tm = *localtime(&td); /* get components */
strftime((char *) timestring, sizeof(timestring_t),
"%Y-%m-%d %H:%M:%S", &tm);
}
char *mailbox_add_namespace(const char *mailbox_name, u64_t owner_idnr,
u64_t user_idnr)
{
char *fq;
char *owner;
GString *t;
if (mailbox_name == NULL) {
TRACE(TRACE_ERROR, "error, mailbox_name is NULL.");
return NULL;
}
if (user_idnr == owner_idnr)
/* mailbox owned by current user */
return g_strdup(mailbox_name);
/* else */
if ((owner = auth_get_userid(owner_idnr))==NULL)
return NULL;
t = g_string_new("");
if (strcmp(owner, PUBLIC_FOLDER_USER) == 0)
g_string_printf(t, "%s%s%s", NAMESPACE_PUBLIC, MAILBOX_SEPARATOR, mailbox_name);
else
g_string_printf(t, "%s%s%s%s%s", NAMESPACE_USER, MAILBOX_SEPARATOR, owner,
MAILBOX_SEPARATOR, mailbox_name);
g_free(owner);
fq = t->str;
g_string_free(t,FALSE);
return fq;
}
/* Strips off the #Users or #Public namespace, returning
* the simple name, the namespace and username, if present. */
const char *mailbox_remove_namespace(const char *fq_name,
char **namespace, char **username)
{
const char *temp = NULL, *user = NULL, *mbox = NULL;
static size_t ns_user_len = 0;
static size_t ns_publ_len = 0;
size_t fq_name_len;
if(ns_user_len==0) {
ns_user_len = strlen(NAMESPACE_USER);
ns_publ_len = strlen(NAMESPACE_PUBLIC);
}
if (username) *username = NULL;
if (namespace) *namespace = NULL;
fq_name_len = strlen(fq_name);
// i.e. '#Users/someuser/foldername'
// fail on '#Users*' and '#Users%'
// assume a slash in '#Users/foo*' and '#Users/foo%' like this '#Users/foo/*'
if (fq_name_len >= ns_user_len && strncasecmp(fq_name, NAMESPACE_USER, ns_user_len) == 0) {
if (namespace) *namespace = NAMESPACE_USER;
int end = 0, err = 0, slash = 0;
// We'll use a simple state machine to parse through this.
for (temp = &fq_name[ns_user_len]; !end && !err; temp++) {
switch (*temp) {
case '/':
if (!user) {
user = temp + 1;
} else if (user && !mbox) {
mbox = temp + 1;
slash = 1;
} else if (user && mbox) {
end = 1;
}
break;
case '*':
if (!user)
err = 1;
mbox = temp;
break;
case '%':
if (!user)
err = 1;
mbox = temp;
break;
case '\0':
if (!user)
err = 1;
end = 1;
break;
}
}
if (err) {
TRACE(TRACE_MESSAGE, "Illegal mailbox name");
return NULL;
}
if (!user || user + slash == mbox) {
TRACE(TRACE_DEBUG, "Username not found");
return NULL;
}
if (!mbox) {
TRACE(TRACE_DEBUG, "Mailbox not found");
return NULL;
}
TRACE(TRACE_DEBUG, "Copying out username [%s] of length [%zu]",
user, (size_t)(mbox - user - slash));
if (username) *username = g_strndup(user, mbox - user - slash);
return mbox;
}
// i.e. '#Public/foldername'
// accept #Public* and #Public% also
if (fq_name_len >= ns_publ_len && strncasecmp(fq_name, NAMESPACE_PUBLIC, ns_publ_len) == 0) {
if (namespace) *namespace = NAMESPACE_PUBLIC;
if (username) *username = g_strdup(PUBLIC_FOLDER_USER);
// Drop the slash between the namespace and the mailbox spec
if (fq_name[ns_publ_len] == '/')
return &fq_name[ns_publ_len+1];
// But if the slash wasn't there, it means we have #Public*, and that's OK.
return &fq_name[ns_publ_len];
}
return fq_name;
}
int ci_write(FILE * fd, char * msg, ...)
{
va_list ap;
va_start(ap, msg);
if (feof(fd) || vfprintf(fd,msg,ap) < 0 || fflush(fd) < 0) {
va_end(ap);
return -1;
}
va_end(ap);
return 0;
}
/* Return 0 is all's well. Returns something else if not... */
/* Reads up to 'maxlen' bytes from instream and places a pointer to an
* allocated array in m_buf.
*
* If maxlen is 0, allocates and returns "" (zero length string).
* If maxlen is -1, reads until EOF.
*/
int read_from_stream(FILE * instream, char **m_buf, int maxlen)
{
size_t f_len = 0;
size_t f_pos = 0;
char *f_buf = NULL;
int c;
/* Allocate a zero length string on length 0. */
if (maxlen == 0) {
*m_buf = g_strdup("");
return 0;
}
/* Allocate enough space for everything we're going to read. */
if (maxlen > 0) {
f_len = maxlen + 1;
}
/* Start with a default size and keep reading until EOF. */
if (maxlen == -1) {
f_len = 1024;
maxlen = INT_MAX;
}
f_buf = g_new0(char, f_len);
while ((int)f_pos < maxlen) {
if (f_pos + 1 >= f_len) {
f_buf = g_renew(char, f_buf, (f_len *= 2));
}
c = fgetc(instream);
if (c == EOF)
break;
f_buf[f_pos++] = (char)c;
}
if (f_pos)
f_buf[f_pos] = '\0';
*m_buf = f_buf;
return 0;
}
/* Finds what lurks between two bounding symbols.
* Allocates and fills retchar with the string.
*
* Return values are:
* 0 on success (found and allocated)
* -1 on failure (not found)
* -2 on memory error (found but allocation failed)
*
* The caller is responsible for free()ing *retchar.
* */
int find_bounded(const char * const value, char left, char right,
char **retchar, size_t * retsize, size_t * retlast)
{
char *tmpleft;
char *tmpright;
size_t tmplen;
tmpleft = (char *)value;
tmpright = (char *)(value + strlen(value));
while (tmpleft[0] != left && tmpleft < tmpright)
tmpleft++;
while (tmpright[0] != right && tmpright > tmpleft)
tmpright--;
if (tmpleft[0] != left || tmpright[0] != right) {
TRACE(TRACE_INFO, "Found nothing between '%c' and '%c'", left, right);
*retchar = NULL;
*retsize = 0;
*retlast = 0;
return -1;
} else {
/* Step left up to skip the actual left thinger */
if (tmpright != tmpleft)
tmpleft++;
tmplen = tmpright - tmpleft;
*retchar = g_new0(char, tmplen + 1);
if (*retchar == NULL) {
*retchar = NULL;
*retsize = 0;
*retlast = 0;
TRACE(TRACE_INFO, "Found [%s] of length [%zu] between '%c' and '%c' so next skip [%zu]", *retchar, *retsize,
left, right, *retlast);
return -2;
}
strncpy(*retchar, tmpleft, tmplen);
(*retchar)[tmplen] = '\0';
*retsize = tmplen;
*retlast = tmpright - value;
TRACE(TRACE_INFO, "Found [%s] of length [%zu] between '%c' and '%c' so next skip [%zu]", *retchar, *retsize, left,
right, *retlast);
return 0;
}
}
int zap_between(const char * const instring, signed char left, signed char right,
char **outstring, size_t *outlen, size_t *zaplen)
{
char *start, *end;
char *incopy = g_strdup(instring);
int clipleft = 0, clipright = 0;
if (!incopy)
return -2;
// Should we clip the left char, too?
if (left < 0) {
left = (signed char)(0 - left);
clipleft = 1;
}
// Should we clip the right char, too?
if (right < 0) {
right = (signed char)(0 - right);
clipright = 1;
}
start = strchr(incopy, left);
end = strrchr(incopy, right);
if (!start || !end) {
g_free(incopy);
return -1;
}
if (!clipleft) start++;
if (clipright) end++;
memmove(start, end, strlen(end)+1);
if (outstring)
*outstring = incopy;
if (outlen)
*outlen = strlen(incopy);
if (zaplen)
*zaplen = (end - start);
return 0;
}
/*
*
*
* Some basic string handling utilities
*
*
*
*/
GString * g_list_join(GList * list, const gchar * sep)
{
GString *string = g_string_new("");
if (sep == NULL)
sep="";
if (list == NULL)
return string;
list = g_list_first(list);
g_string_append_printf(string,"%s",(gchar *)list->data);
while((list = g_list_next(list))) {
g_string_append_printf(string,"%s%s", sep,(gchar *)list->data);
if (! g_list_next(list))
break;
}
return string;
}
GString * g_list_join_u64(GList * list, const gchar * sep)
{
u64_t *token;
GString *string = g_string_new("");
if (sep == NULL)
sep="";
if (list == NULL)
return string;
list = g_list_first(list);
token = (u64_t*)list->data;
g_string_append_printf(string,"%llu",*token);
while((list = g_list_next(list))) {
token = (u64_t*)list->data;
g_string_append_printf(string,"%s%llu", sep,*token);
if (! g_list_next(list))
break;
}
return string;
}
GList * g_string_split(GString * string, const gchar * sep)
{
GList * list = NULL;
char **array;
int i, len = 0;
if (string->len == 0)
return NULL;
array = (char **)g_strsplit((const gchar *)string->str, sep, 0);
while(array[len++]);
len--;
for (i=0; i<len; i++)
list = g_list_append(list,g_strdup(array[i]));
g_strfreev(array);
return list;
}
/*
* append a formatted string to a GList
*/
GList * g_list_append_printf(GList * list, const char * format, ...)
{
va_list argp;
va_start(argp, format);
list = g_list_append(list, g_strdup_vprintf(format, argp));
va_end(argp);
return list;
}
char * g_strcasestr(const char *haystack, const char *needle)
{
// Like strstr, but case insensitive.
size_t n = strlen(needle);
for (; *haystack; haystack++) {
if (g_ascii_strncasecmp(haystack, needle, n) == 0)
return (char *)haystack;
}
return NULL;
}
/*
* return newly allocated escaped strings
*/
char * dm_stresc(const char * from)
{
return dm_strnesc(from, strlen(from));
}
/*
* return newly allocated sql-escaped string with
* a maximum length of len
*/
char * dm_strnesc(const char * from, size_t len)
{
char *to;
assert(from);
len = min(strlen(from),len);
to = g_new0(char, (len + 1) * 2 + 1);
db_escape_string(to, from, len);
return to;
}
/*
* replace all multi-spaces with single spaces
*/
static void pack_char(char *in, char c)
{
char *saved;
char *tmp = g_strdup(in);
saved = tmp;
while(*tmp) {
if ((*tmp == c) && (*(tmp+1) == c)) {
tmp++;
} else {
*in++=*tmp++;
}
}
g_free(saved);
*in='\0';
}
/*
*
* replace tabs with spaces and all multi-spaces with single spaces
*
*/
void dm_pack_spaces(char *in)
{
/* replace tabs with spaces */
g_strdelimit(in,"\t",' ');
pack_char(in,' ');
}
/*
* base-subject
*
*/
static void _strip_blob_prefix(char *subject)
{
char *tmp = g_strdup(subject);
char *saved = tmp;
if (*tmp == '[') {
while (*tmp != '\0' && *tmp != ']')
tmp++;
if (*tmp != ']') {
g_free(saved);
return;
}
g_strstrip(++tmp); // skip ']'
if (strlen(tmp) > 0)
strncpy(subject,tmp,strlen(tmp)+1);
}
g_free(saved);
return;
}
static void _strip_refwd(char *subject)
{
char *tmp, *saved;
if (! (strncasecmp(subject,"re",2)==0 || strncasecmp(subject,"fw",2)==0))
return;
tmp = g_strdup(subject);
saved = tmp;
if (strncasecmp(tmp,"fwd",3)==0)
tmp+=3;
else if ((strncasecmp(tmp,"re",2)==0) || (strncasecmp(tmp,"fw",2)==0))
tmp+=2;
g_strstrip(tmp);
if (strlen(tmp) > 0)
_strip_blob_prefix(tmp);
if (*tmp!=':') {
g_free(saved);
return;
}
g_strstrip(++tmp); // skip ':'
if (strlen(tmp) > 0)
strncpy(subject,tmp,strlen(tmp)+1);
g_free(saved);
}
static void _strip_sub_leader(char *subject)
{
unsigned len;
/* strip blobs prefixes */
while (1==1) {
len = strlen(subject);
_strip_blob_prefix(subject);
if (strlen(subject)==len)
break;
}
/* strip refwd prefixes */
_strip_refwd(subject);
}
char * dm_base_subject(const char *subject)
{
unsigned offset, len, olen;
char *tmp, *saved;
// we expect utf-8 or 7-bit data
if (subject == NULL) return NULL;
if (g_mime_utils_text_is_8bit((unsigned char *)subject, strlen(subject)))
tmp = g_strdup(subject);
else
tmp = dbmail_iconv_decode_text(subject);
saved = tmp;
dm_pack_spaces(tmp);
g_strstrip(tmp);
while (1==1) {
olen = strlen(tmp);
while (g_str_has_suffix(tmp,"(fwd)")) {
offset = strlen(tmp) - 5;
tmp[offset] = '\0';
g_strstrip(tmp);
}
while (1==1) {
len = strlen(tmp);
_strip_sub_leader(tmp);
if (strlen(tmp)==len)
break;
}
if (g_str_has_suffix(tmp,"]") && strncasecmp(tmp,"[fwd:",5)==0 ) {
offset=strlen(tmp)-1;
tmp[offset]='\0';
tmp+=5;
g_strstrip(tmp);
}
while (g_str_has_prefix(tmp,":") && (strlen(tmp) > 1))
g_strstrip(++tmp);
if (strlen(tmp)==olen)
break;
}
tmp = g_strdup(tmp);
g_free(saved);
return tmp;
}
/*
* \brief listexpression match for imap (rfc2060)
* \param p pattern
* \param s string to search
* \param x separator string ("." or "/"- multichar okay; e.g. "Ï€" would work f
* you can find a IMAP client that read rfc2060)
* \param flags presently only LISTEX_NOCASE -- if you want case-insensitive
* "folders"
* \return 1 indicates a match
*/
#define LISTEX_NOCASE 1
int listex_match(const char *p, const char *s,
const char *x, int flags)
{
int i, p8;
p8=0;
while (*p) {
if (!p8 && *p == '%') {
p++;
while (*s) {
for (i = 0; x[i] && x[i] == s[i]; i++);
if (! x[i]) {
s += i;
break;
}
s++;
}
/* %. */
for (i = 0; x[i] && x[i] == p[i]; i++);
if (! x[i]) p += i;
if (*s && *p) return listex_match(p,s,x,flags); // We have more to look at - lets look again.
if (*s || *p) return 0;
return 1;
}
if (!p8 && *p == '*') {
/* use recursive for synchronize */
p++;
if (!(*p)) return 1;
while (*s) {
if (listex_match(p,s,x,flags)) return 1;
s++;
}
return 0;
}
if (!p8 && *p == *x) {
for (i = 0; x[i] && p[i] == x[i] && p[i] == s[i]; i++);
if (! x[i]) {
p += i; s += i;
continue; /* sync'd */
}
/* fall; try regular search */
}
if ( (*p == *s)||
((flags & LISTEX_NOCASE) && (tolower(*p) == tolower(*s)))) {
p8=(((unsigned char)*p) > 0xC0);
p++; s++;
} else {
/* failed */
return 0;
}
}
if (*p || *s) return 0;
return 1;
}
u64_t dm_getguid(unsigned int serverid)
{
char s[30];
struct timeval tv;
assert((int)serverid >= 0);
if (gettimeofday(&tv,NULL))
return 0;
snprintf(s,30,"%ld%06ld%02u", tv.tv_sec, tv.tv_usec,serverid);
return (u64_t)strtoll(s,NULL,10);
}
sa_family_t dm_get_client_sockaddr(clientinfo_t *ci, struct sockaddr *saddr)
{
#define maxsocklen 128
union {
struct sockaddr sa;
char data[maxsocklen];
} un;
socklen_t len;
len = maxsocklen;
if (getsockname(fileno(ci->tx), (struct sockaddr *)un.data, &len) < 0)
return (sa_family_t) -1;
memcpy(saddr, &un.sa, sizeof(un.sa));
return (un.sa.sa_family);
}
int dm_sock_score(const char *base, const char *test)
{
struct cidrfilter *basefilter, *testfilter;
int result = 0;
char *t;
if ((! base) || (! test))
return 0;
t = strstr(base,"unix:");
if (t==base) {
base = strstr(base,":");
test = strstr(test,":");
return (fnmatch(base,test,0) ? 0 : 1);
}
t = strstr(base,"inet:");
if (t!=base)
return 0;
if (! test)
return 0;
basefilter = cidr_new(base);
testfilter = cidr_new(test);
if (strlen(test)==0) {
result = 32;
} else if (basefilter && testfilter) {
result = cidr_match(basefilter,testfilter);
}
cidr_free(basefilter);
cidr_free(testfilter);
TRACE(TRACE_DEBUG, "base[%s] test[%s] => [%d]", base, test, result);
return result;
}
static int socket_match(const char *base, const char *test)
{
return (dm_sock_score(base,test) ? 0 : 1);
}
int dm_sock_compare(const char *clientsock, const char *sock_allow, const char *sock_deny)
{
int result = DM_EGENERAL;
assert(clientsock);
if ( (strlen(sock_allow) == 0) && (strlen(sock_deny) == 0) ) {
result = DM_SUCCESS;
} else if (strlen(sock_deny) > 0 && socket_match(sock_deny, clientsock)==0) {
result = DM_EGENERAL;
} else if (strlen(sock_allow) > 0 && socket_match(sock_allow, clientsock)==0) {
result = DM_SUCCESS;
}
TRACE(TRACE_DEBUG, "clientsock [%s] sock_allow[%s], sock_deny [%s] => [%d]", clientsock, sock_allow, sock_deny, result);
return result;
}
/* dm_valid_format
* check if str is a valid format string containing a single "%s" for use in
* printf style calls
* \return 1 format is invalid
* \return 0 format is valid
*/
int dm_valid_format(const char *str)
{
char *left, *right;
left = index(str,'%');
right = rindex(str,'%');
if (! (left && right && left==right))
return DM_EGENERAL;
if (*(left+1) != 's')
return DM_EGENERAL;
return DM_SUCCESS;
}
/*
* checkmailboxname()
*
* performs a check to see if the mailboxname is valid
* returns 0 if invalid, 1 otherwise
*/
int checkmailboxname(const char *s)
{
int i;
if (strlen(s) == 0)
return 0; /* empty name is not valid */
if (strlen(s) >= IMAP_MAX_MAILBOX_NAMELEN)
return 0; /* a too large string is not valid */
/* check for invalid characters */
for (i = 0; s[i]; i++) {
if (!strchr(AcceptedMailboxnameChars, s[i])) {
/* dirty hack to allow namespaces to function */
if (i == 0 && s[0] == '#')
continue;
/* wrong char found */
return 0;
}
}
/* check for double '/' */
for (i = 1; s[i]; i++) {
if (s[i] == '/' && s[i - 1] == '/')
return 0;
}
/* check if the name consists of a single '/' */
if (strlen(s) == 1 && s[0] == '/')
return 0;
return 1;
}
/*
* check_date()
*
* checks a date for IMAP-date validity:
* dd-MMM-yyyy
* 01234567890
* month three-letter specifier
*/
// Define len if "01-Jan-1970" string
#define STRLEN_MINDATA 11
int check_date(const char *date)
{
char sub[4];
int days, i, j=1;
size_t l;
l = strlen(date);
if (l != STRLEN_MINDATA && l != STRLEN_MINDATA-1)
return 0;
j = (l==STRLEN_MINDATA) ? 0 : 1;
if (date[2 - j] != '-' || date[6 - j] != '-')
return 0;
days = strtoul(date, NULL, 10);
strncpy(sub, &date[3 - j], 3);
sub[3] = 0;
for (i = 0; i < 12; i++) {
if (strcasecmp(month_desc[i], sub) == 0)
break;
}
if (i >= 12 || days > month_len[i])
return 0;
for (i = 7; i < 11; i++)
if (!isdigit(date[i - j]))
return 0;
return 1;
}
/*
* check_msg_set()
*
* checks if s represents a valid message set
*/
int check_msg_set(const char *s)
{
int i, indigit=0, result = 1;
if (!s || (!isdigit(s[0]) && s[0]!= '*') )
return 0;
for (i = 0; s[i]; i++) {
if (isdigit(s[i]) || s[i]=='*')
indigit = 1;
else if (s[i] == ',') {
if (!indigit) {
result = 0;
break;
}
indigit = 0;
} else if (s[i] == ':') {
if (!indigit) {
result = 0;
break;
}
indigit = 0;
} else {
result = 0;
break;
}
}
TRACE(TRACE_DEBUG, "[%s] [%s]", s, result ? "ok" : "fail" );
return result;
}
/*
* convert a mySQL date (yyyy-mm-dd hh:mm:ss) to a valid IMAP internal date:
* dd-mon-yyyy hh:mm:ss with mon characters (i.e. 'Apr' for april)
* return value is valid until next function call.
* NOTE: if date is not valid, IMAP_STANDARD_DATE is returned
*/
char *date_sql2imap(const char *sqldate)
{
struct tm tm_sql_date;
time_t ltime;
char *last;
char t[IMAP_INTERNALDATE_LEN];
char q[IMAP_INTERNALDATE_LEN];
// bsd needs:
memset(&tm_sql_date, 0, sizeof(struct tm));
last = strptime(sqldate,"%Y-%m-%d %H:%M:%S", &tm_sql_date);
if ( (last == NULL) || (*last != '\0') ) {
strcpy(_imapdate, IMAP_STANDARD_DATE);
return _imapdate;
}
strftime(q, sizeof(q), "%d-%b-%Y %H:%M:%S", &tm_sql_date);
ltime = mktime (&tm_sql_date);
localtime_r(<ime, &tm_sql_date);
strftime(t, sizeof(t), "%z", &tm_sql_date);
if (t[0] != '%') {
snprintf(_imapdate,IMAP_INTERNALDATE_LEN, "%s %s", q, t);
return _imapdate;
}
// oops, no %z on solaris (FIXME)
snprintf(_imapdate,IMAP_INTERNALDATE_LEN, "%s +0000", q);
return _imapdate;
}
/*
* convert TO a mySQL date (yyyy-mm-dd) FROM a valid IMAP internal date:
* 0123456789
* dd-mon-yyyy with mon characters (i.e. 'Apr' for april)
* 01234567890
* OR
* d-mon-yyyy
* return value is valid until next function call.
*/
char *date_imap2sql(const char *imapdate)
{
struct tm tm;
char *last_char;
// bsd needs this:
memset(&tm, 0, sizeof(struct tm));
last_char = strptime(imapdate, "%d-%b-%Y", &tm);
if (last_char == NULL || *last_char != '\0') {
TRACE(TRACE_DEBUG, "error parsing IMAP date %s", imapdate);
return NULL;
}
(void) strftime(_sqldate, SQL_INTERNALDATE_LEN,
"%Y-%m-%d 00:00:00", &tm);
return _sqldate;
}
int num_from_imapdate(const char *date)
{
int j = 0, i;
char datenum[] = "YYYYMMDD";
char sub[4];
if (date[1] == ' ' || date[1] == '-')
j = 1;
strncpy(datenum, &date[7 - j], 4);
strncpy(sub, &date[3 - j], 3);
sub[3] = 0;
for (i = 0; i < 12; i++) {
if (strcasecmp(sub, month_desc[i]) == 0)
break;
}
i++;
if (i > 12)
i = 12;
sprintf(&datenum[4], "%02d", i);
if (j) {
datenum[6] = '0';
datenum[7] = date[0];
} else {
datenum[6] = date[0];
datenum[7] = date[1];
}
return atoi(datenum);
}
void g_list_destroy(GList *l)
{
l = g_list_first(l);
g_list_foreach(l,(GFunc)g_free,NULL);
l = g_list_first(l);
g_list_free(l);
}
static gboolean traverse_tree_keys(gpointer key, gpointer value UNUSED, GList **l)
{
*(GList **)l = g_list_prepend(*(GList **)l, key);
return FALSE;
}
static gboolean traverse_tree_values(gpointer key UNUSED, gpointer value, GList **l)
{
*(GList **)l = g_list_prepend(*(GList **)l, value);
return FALSE;
}
GList * g_tree_keys(GTree *tree)
{
GList *l = NULL;
g_tree_foreach(tree, (GTraverseFunc)traverse_tree_keys, &l);
return g_list_reverse(l);
}
GList * g_tree_values(GTree *tree)
{
GList *l = NULL;
g_tree_foreach(tree, (GTraverseFunc)traverse_tree_values, &l);
return g_list_reverse(l);
}
/*
* boolean merge of two GTrees. The result is stored in GTree *a.
* the state of GTree *b is undefined: it may or may not have been changed,
* depending on whether or not key/value pairs were moved from b to a.
* Both trees are safe to destroy afterwards, assuming g_tree_new_full was used
* for their construction.
*/
static gboolean traverse_tree_merger(gpointer key, gpointer value UNUSED, tree_merger_t **merger)
{
tree_merger_t *t = *(tree_merger_t **)merger;
GTree *tree = t->tree;
int condition = t->condition;
switch(condition) {
case IST_SUBSEARCH_NOT:
break;
default:
case IST_SUBSEARCH_OR:
case IST_SUBSEARCH_AND:
if (! g_tree_lookup(tree,key))
(*(tree_merger_t **)merger)->list = g_list_prepend((*(tree_merger_t **)merger)->list,key);
break;
}
return FALSE;
}
int g_tree_merge(GTree *a, GTree *b, int condition)
{
char *type = NULL;
GList *keys = NULL;
int alen = 0, blen=0, klen=0;
gpointer key;
gpointer value;
g_return_val_if_fail(a && b,1);
tree_merger_t *merger = g_new0(tree_merger_t,1);
alen = g_tree_nnodes(a);
blen = g_tree_nnodes(b);
switch(condition) {
case IST_SUBSEARCH_AND:
type=g_strdup("AND");
/* delete from A all keys not in B */
merger->tree = b;
merger->condition = IST_SUBSEARCH_AND;
g_tree_foreach(a,(GTraverseFunc)traverse_tree_merger, &merger);
keys = g_list_first(merger->list);
if (! (klen = g_list_length(keys)))
break;
if (klen > 1)
keys = g_list_reverse(merger->list);
while (keys->data) {
g_tree_remove(a,keys->data);
if (! g_list_next(keys))
break;
keys = g_list_next(keys);
}
break;
case IST_SUBSEARCH_OR:
type=g_strdup("OR");
if (! g_tree_nnodes(b) > 0)
break;
merger->tree = a;
merger->condition = IST_SUBSEARCH_OR;
g_tree_foreach(b,(GTraverseFunc)traverse_tree_merger, &merger);
keys = g_list_first(merger->list);
if (! (klen = g_list_length(keys)))
break;
if (klen > 1)
keys = g_list_reverse(keys);
/* add to A all keys in B */
while (keys->data) {
g_tree_lookup_extended(b,keys->data,&key,&value);
g_tree_steal(b,keys->data);
g_tree_insert(a,key,value);
if (! g_list_next(keys))
break;
keys = g_list_next(keys);
}
break;
case IST_SUBSEARCH_NOT:
type=g_strdup("NOT");
keys = g_tree_keys(b);
if (! g_list_length(keys))
break;
while (keys->data) {
// remove from A keys also in B
if (g_tree_lookup(a,keys->data)) {
g_tree_remove(a,keys->data);
} else {
// add to A all keys in B not in A
g_tree_lookup_extended(b,keys->data,&key,&value);
g_tree_steal(b,keys->data);
g_tree_insert(a,key,value);
}
if (! g_list_next(keys))
break;
keys = g_list_next(keys);
}
keys = g_list_first(keys);
g_list_free(keys);
break;
}
TRACE(TRACE_DEBUG,"(%p) (%p): a[%d] [%s] b[%d] -> a[%d]",
a, b, alen, type, blen,
g_tree_nnodes(a));
merger->list = g_list_first(merger->list);
g_list_free(merger->list);
g_free(merger);
g_free(type);
return 0;
}
gint ucmp(const u64_t *a, const u64_t *b)
{
u64_t x,y;
x = (u64_t)*a;
y = (u64_t)*b;
if (x>y)
return 1;
if (x==y)
return 0;
return -1;
}
/* Read from instream until ".\r\n", discarding what is read. */
int discard_client_input(FILE * instream)
{
int ch, ns;
socklen_t l;
clearerr(instream);
for (ns = 0; (ch = fgetc(instream)) != EOF;) {
if (ch == '\r') {
if (ns == 4) {
/* \r\n.\r */
ns = 5;
} else {
/* \r */
ns = 1;
}
} else if (ch == '\n') {
if (ns == 1) {
/* \r\n */
ns = 2;
} else if (ns == 5) {
/* complete: \r\n.\r\n */
return 0;
} else {
/* .\n ? */
TRACE(TRACE_ERROR, "bare LF.");
}
} else if (ch == '.' && ns == 3) {
/* \r\n. */
ns = 4;
}
if ((ch = fileno(instream)) != -1) {
/* okay, look for error slippage */
l = 0;
if (getpeername(ns, (struct sockaddr *)"", &l) == -1 && errno != ENOTSOCK) {
TRACE(TRACE_ERROR, "unexpected failure from socket layer (client hangup?)");
}
}
}
TRACE(TRACE_ERROR, "unexpected EOF from stdio (client hangup?)");
return 0;
}
/* Following the advice of:
* "Secure Programming for Linux and Unix HOWTO"
* Chapter 8: Carefully Call Out to Other Resources */
char * dm_shellesc(const char * command)
{
char *safe_command;
int pos, end, len;
// These are the potentially unsafe characters:
// & ; ` ' \ " | * ? ~ < > ^ ( ) [ ] { } $ \n \r
// # ! \t \ (space)
len = strlen(command);
if (! (safe_command = g_new0(char,(len + 1) * 2 + 1)))
return NULL;
for (pos = end = 0; pos < len; pos++) {
switch (command[pos]) {
case '&':
case ';':
case '`':
case '\'':
case '\\':
case '"':
case '|':
case '*':
case '?':
case '~':
case '<':
case '>':
case '^':
case '(':
case ')':
case '[':
case ']':
case '{':
case '}':
case '$':
case '\n':
case '\r':
case '\t':
case ' ':
case '#':
case '!':
// Add an escape before the offending char.
safe_command[end++] = '\\';
default:
// And then put in the character itself.
safe_command[end++] = command[pos];
break;
}
}
/* The string is already initialized,
* but let's be extra double sure. */
safe_command[end] = '\0';
return safe_command;
}
/* some basic imap type utils */
char *dbmail_imap_plist_collapse(const char *in)
{
/*
* collapse "(NIL) (NIL)" to "(NIL)(NIL)"
*
* do for bodystructure, and only for addresslists in the envelope
*/
char *p;
char **sublists;
g_return_val_if_fail(in,NULL);
sublists = g_strsplit(in,") (",0);
p = g_strjoinv(")(",sublists);
g_strfreev(sublists);
return p;
}
/*
* build a parenthisized list (4.4) from a GList
*/
char *dbmail_imap_plist_as_string(GList * list)
{
char *p;
size_t l;
GString * tmp1 = g_string_new("");
GString * tmp2 = g_list_join(list, " ");
g_string_printf(tmp1,"(%s)", tmp2->str);
/*
* strip empty outer parenthesis
* "((NIL NIL))" to "(NIL NIL)"
*/
p = tmp1->str;
l = tmp1->len;
while (tmp1->len>4 && p[0]=='(' && p[l-1]==')' && p[1]=='(' && p[l-2]==')') {
tmp1 = g_string_truncate(tmp1,l-1);
tmp1 = g_string_erase(tmp1,0,1);
p=tmp1->str;
}
g_string_free(tmp1,FALSE);
g_string_free(tmp2,TRUE);
return p;
}
void dbmail_imap_plist_free(GList *l)
{
g_list_destroy(l);
}
/*
* return a quoted or literal astring
*/
char *dbmail_imap_astring_as_string(const char *s)
{
int i;
const char *p;
char *r, *t, *l = NULL;
char first, last, penult = '\\';
if (! s)
return g_strdup("\"\"");
l = g_strdup(s);
t = l;
/* strip off dquote */
first = s[0];
last = s[strlen(s)-1];
if (strlen(s) > 2)
penult = s[strlen(s)-2];
if ((first == '"') && (last == '"') && (penult != '\\')) {
l[strlen(l)-1] = '\0';
l++;
}
for (i=0; l[i]; i++) {
if ((l[i] & 0x80) || (l[i] == '\r') || (l[i] == '\n') || (l[i] == '"') || (l[i] == '\\')) {
if ((l[i] == '"') && (i>0) && (l[i-1] != '\\'))
p = s;
else
p = l;
r = g_strdup_printf("{%lu}\r\n%s", (unsigned long) strlen(p), p);
g_free(t);
return r;
}
}
r = g_strdup_printf("\"%s\"", l);
g_free(t);
return r;
}
/* structure and envelope tools */
static void _structure_part_handle_part(GMimeObject *part, gpointer data, gboolean extension);
static void _structure_part_text(GMimeObject *part, gpointer data, gboolean extension);
static void _structure_part_multipart(GMimeObject *part, gpointer data, gboolean extension);
static void _structure_part_message_rfc822(GMimeObject *part, gpointer data, gboolean extension);
static void get_param_list(gpointer key, gpointer value, gpointer data)
{
gchar *s = g_mime_utils_header_encode_text((const char *)((GMimeParam *)value)->value);
*(GList **)data = g_list_append_printf(*(GList **)data, "\"%s\"", (char *)key);
*(GList **)data = g_list_append_printf(*(GList **)data, "\"%s\"", s);
g_free(s);
}
static GList * imap_append_hash_as_string(GList *list, GHashTable *hash)
{
GList *l = NULL;
char *s;
if (hash)
g_hash_table_foreach(hash, get_param_list, (gpointer)&(l));
if (l) {
s = dbmail_imap_plist_as_string(l);
list = g_list_append_printf(list, "%s", s);
g_free(s);
g_list_destroy(l);
} else {
list = g_list_append_printf(list, "NIL");
}
return list;
}
static GList * imap_append_disposition_as_string(GList *list, GMimeObject *part)
{
GList *t = NULL;
GMimeDisposition *disposition;
char *result;
const char *disp = g_mime_object_get_header(part, "Content-Disposition");
if(disp) {
disposition = g_mime_disposition_new(disp);
t = g_list_append_printf(t,"\"%s\"",disposition->disposition);
/* paramlist */
t = imap_append_hash_as_string(t, disposition->param_hash);
result = dbmail_imap_plist_as_string(t);
list = g_list_append_printf(list,"%s",result);
g_free(result);
g_list_destroy(t);
g_mime_disposition_destroy(disposition);
} else {
list = g_list_append_printf(list,"NIL");
}
return list;
}
#define imap_append_header_as_string(list, part, header) \
imap_append_header_as_string_default(list, part, header, "NIL")
static GList * imap_append_header_as_string_default(GList *list,
GMimeObject *part, const char *header, char *def)
{
char *result;
char *s;
if((result = (char *)g_mime_object_get_header(part, header))) {
s = dbmail_imap_astring_as_string(result);
list = g_list_append_printf(list, "%s", s);
g_free(s);
} else {
list = g_list_append_printf(list, def);
}
return list;
}
static void imap_part_get_sizes(GMimeObject *part, size_t * size, size_t * lines)
{
char *v, *h, *t;
GString *b;
int i;
size_t s = 0, l = 0;
/* get encoded size */
h = g_mime_object_get_headers(part);
t = g_mime_object_to_string(part);
b = g_string_new(t);
g_free(t);
s = strlen(h);
if (b->len > s)
s++;
b = g_string_erase(b,0,s);
t = get_crlf_encoded(b->str);
s = strlen(t);
/* count body lines */
v = t;
i = 0;
while (v[i++]) {
if (v[i]=='\n')
l++;
}
if (s >=2 && v[s-2] != '\n')
l++;
g_free(h);
g_free(t);
g_string_free(b,TRUE);
*size = s;
*lines = l;
}
void _structure_part_handle_part(GMimeObject *part, gpointer data, gboolean extension)
{
const GMimeContentType *type;
GMimeObject *object;
if (GMIME_IS_MESSAGE(part))
object = g_mime_message_get_mime_part(GMIME_MESSAGE(part));
else
object = part;
type = g_mime_object_get_content_type(object);
if (! type) {
if (GMIME_IS_MESSAGE(part))
g_object_unref(object);
return;
}
/* multipart composite */
if (g_mime_content_type_is_type(type,"multipart","*"))
_structure_part_multipart(object,data, extension);
/* message included as mimepart */
else if (g_mime_content_type_is_type(type,"message","rfc822"))
_structure_part_message_rfc822(object,data, extension);
/* simple message */
else
_structure_part_text(object,data, extension);
if (GMIME_IS_MESSAGE(part))
g_object_unref(object);
}
void _structure_part_multipart(GMimeObject *part, gpointer data, gboolean extension)
{
GMimeMultipart *multipart;
GMimeObject *subpart, *object;
GList *list = NULL;
GList *alist = NULL;
GString *s;
int i,j;
const GMimeContentType *type;
gchar *b;
if (GMIME_IS_MESSAGE(part))
object = g_mime_message_get_mime_part(GMIME_MESSAGE(part));
else
object = part;
type = g_mime_object_get_content_type(object);
if (! type){
if (GMIME_IS_MESSAGE(part)) g_object_unref(object);
return;
}
multipart = GMIME_MULTIPART(object);
i = g_mime_multipart_get_number(multipart);
b = g_mime_content_type_to_string(type);
TRACE(TRACE_DEBUG,"parse [%d] parts for [%s] with boundary [%s]",
i, b, g_mime_multipart_get_boundary(multipart));
g_free(b);
/* loop over parts for base info */
for (j=0; j<i; j++) {
subpart = g_mime_multipart_get_part(multipart,j);
_structure_part_handle_part(subpart,&alist,extension);
g_object_unref(subpart);
}
/* sub-type */
alist = g_list_append_printf(alist,"\"%s\"", type->subtype);
/* extension data (only for multipart, in case of BODYSTRUCTURE command argument) */
if (extension) {
/* paramlist */
list = imap_append_hash_as_string(list, type->param_hash);
/* disposition */
list = imap_append_disposition_as_string(list, object);
/* language */
list = imap_append_header_as_string(list,object,"Content-Language");
/* location */
list = imap_append_header_as_string(list,object,"Content-Location");
s = g_list_join(list," ");
alist = g_list_append(alist,s->str);
g_list_destroy(list);
g_string_free(s,FALSE);
}
/* done*/
*(GList **)data = (gpointer)g_list_append(*(GList **)data,dbmail_imap_plist_as_string(alist));
g_list_destroy(alist);
if (GMIME_IS_MESSAGE(part)) g_object_unref(object);
}
void _structure_part_message_rfc822(GMimeObject *part, gpointer data, gboolean extension)
{
char *result, *b;
GList *list = NULL;
size_t s, l=0;
GMimeObject *object;
const GMimeContentType *type;
GMimeMessage *tmpmes;
if (GMIME_IS_MESSAGE(part))
object = g_mime_message_get_mime_part(GMIME_MESSAGE(part));
else
object = part;
type = g_mime_object_get_content_type(object);
if (! type){
if (GMIME_IS_MESSAGE(part)) g_object_unref(object);
return;
}
/* type/subtype */
list = g_list_append_printf(list,"\"%s\"", type->type);
list = g_list_append_printf(list,"\"%s\"", type->subtype);
/* paramlist */
list = imap_append_hash_as_string(list, type->param_hash);
/* body id */
if ((result = (char *)g_mime_object_get_content_id(object)))
list = g_list_append_printf(list,"\"%s\"", result);
else
list = g_list_append_printf(list,"NIL");
/* body description */
list = imap_append_header_as_string(list,object,"Content-Description");
/* body encoding */
list = imap_append_header_as_string_default(list,object,"Content-Transfer-Encoding", "\"7BIT\"");
/* body size */
imap_part_get_sizes(object,&s,&l);
list = g_list_append_printf(list,"%d", s);
/* envelope structure */
b = imap_get_envelope(tmpmes = g_mime_message_part_get_message(GMIME_MESSAGE_PART(part)));
list = g_list_append_printf(list,"%s", b?b:"NIL");
g_object_unref(tmpmes);
g_free(b);
/* body structure */
b = imap_get_structure(tmpmes = g_mime_message_part_get_message(GMIME_MESSAGE_PART(part)), extension);
list = g_list_append_printf(list,"%s", b?b:"NIL");
g_object_unref(tmpmes);
g_free(b);
/* lines */
list = g_list_append_printf(list,"%d", l);
/* done*/
*(GList **)data = (gpointer)g_list_append(*(GList **)data,dbmail_imap_plist_as_string(list));
g_list_destroy(list);
if (GMIME_IS_MESSAGE(part)) g_object_unref(object);
}
void _structure_part_text(GMimeObject *part, gpointer data, gboolean extension)
{
char *result;
GList *list = NULL;
size_t s, l=0;
GMimeObject *object;
const GMimeContentType *type;
if (GMIME_IS_MESSAGE(part))
object = g_mime_message_get_mime_part(GMIME_MESSAGE(part));
else
object = part;
type = g_mime_object_get_content_type(object);
if (! type){
if (GMIME_IS_MESSAGE(part)) g_object_unref(object);
return;
}
/* type/subtype */
list = g_list_append_printf(list,"\"%s\"", type->type);
list = g_list_append_printf(list,"\"%s\"", type->subtype);
/* paramlist */
list = imap_append_hash_as_string(list, type->param_hash);
/* body id */
if ((result = (char *)g_mime_object_get_content_id(object)))
list = g_list_append_printf(list,"\"%s\"", result);
else
list = g_list_append_printf(list,"NIL");
/* body description */
list = imap_append_header_as_string(list,object,"Content-Description");
/* body encoding */
list = imap_append_header_as_string_default(list,object,"Content-Transfer-Encoding", "\"7BIT\"");
/* body size */
imap_part_get_sizes(part,&s,&l);
list = g_list_append_printf(list,"%d", s);
/* body lines */
if (g_mime_content_type_is_type(type,"text","*"))
list = g_list_append_printf(list,"%d", l);
/* extension data in case of BODYSTRUCTURE */
if (extension) {
/* body md5 */
list = imap_append_header_as_string(list,object,"Content-MD5");
/* body disposition */
list = imap_append_disposition_as_string(list,object);
/* body language */
list = imap_append_header_as_string(list,object,"Content-Language");
/* body location */
list = imap_append_header_as_string(list,object,"Content-Location");
}
/* done*/
*(GList **)data = (gpointer)g_list_append(*(GList **)data, dbmail_imap_plist_as_string(list));
g_list_destroy(list);
if (GMIME_IS_MESSAGE(part)) g_object_unref(object);
}
GList* dbmail_imap_append_alist_as_plist(GList *list, const InternetAddressList *ialist)
{
GList *t = NULL, *p = NULL;
InternetAddress *ia = NULL;
InternetAddressList *ial;
gchar *s = NULL, *st = NULL;
gchar **tokens;
gchar *name;
gchar *mailbox;
if (ialist==NULL)
return g_list_append_printf(list, "NIL");
ial = (InternetAddressList *)ialist;
while(ial->address) {
ia = ial->address;
g_return_val_if_fail(ia!=NULL, list);
switch (ia->type) {
case INTERNET_ADDRESS_NONE:
TRACE(TRACE_DEBUG, "nothing doing.");
break;
case INTERNET_ADDRESS_GROUP:
TRACE(TRACE_DEBUG, "recursing into address group [%s].", ia->name);
/* Address list beginning. */
p = g_list_append_printf(p, "(NIL NIL \"%s\" NIL)", ia->name);
/* Dive into the address list.
* Careful, this builds up the stack; it's not a tail call.
*/
t = dbmail_imap_append_alist_as_plist(t, ia->value.members);
s = dbmail_imap_plist_as_string(t);
/* Only use the results if they're interesting --
* (NIL) is the special case of nothing inside the group.
*/
if (strcmp(s, "(NIL)") != 0) {
/* Lop off the extra parens at each end.
* Really do the pointer math carefully.
*/
size_t slen = strlen(s);
if (slen) slen--;
s[slen] = '\0';
p = g_list_append_printf(p, "%s", (slen ? s+1 : s));
}
g_free(s);
g_list_destroy(t);
t = NULL;
/* Address list ending. */
// p = g_list_append_printf(p, "(NIL NIL NIL NIL)", ia->name);
break;
case INTERNET_ADDRESS_NAME:
TRACE(TRACE_DEBUG, "handling a standard address [%s] [%s].", ia->name, ia->value.addr);
/* personal name */
if (ia->name && ia->value.addr) {
name = g_mime_utils_header_encode_phrase((const char *)ia->name);
g_strdelimit(name,"\"\\",' ');
g_strstrip(name);
s = dbmail_imap_astring_as_string(name);
t = g_list_append_printf(t, "%s", s);
g_free(name);
g_free(s);
} else {
t = g_list_append_printf(t, "NIL");
}
/* source route */
t = g_list_append_printf(t, "NIL");
/* mailbox name and host name */
if ((mailbox = ia->value.addr ? ia->value.addr : ia->name) != NULL) {
/* defensive mode for 'To: "foo@bar.org"' addresses */
g_strstrip(g_strdelimit(mailbox,"\"",' '));
tokens = g_strsplit(mailbox,"@",2);
/* mailbox name */
if (tokens[0])
t = g_list_append_printf(t, "\"%s\"", tokens[0]);
else
t = g_list_append_printf(t, "NIL");
/* host name */
/* Note that if tokens[0] was null, we must
* short-circuit because tokens[1] is out of bounds! */
if (tokens[0] && tokens[1])
t = g_list_append_printf(t, "\"%s\"", tokens[1]);
else
t = g_list_append_printf(t, "NIL");
g_strfreev(tokens);
} else {
t = g_list_append_printf(t, "NIL NIL");
}
s = dbmail_imap_plist_as_string(t);
p = g_list_append_printf(p, "%s", s);
g_free(s);
g_list_destroy(t);
t = NULL;
break;
}
/* Bottom of the while loop.
* Advance the address list.
*/
if (ial->next == NULL)
break;
ial = ial->next;
}
/* Tack it onto the outer list. */
if (p) {
s = dbmail_imap_plist_as_string(p);
st = dbmail_imap_plist_collapse(s);
list = g_list_append_printf(list, "(%s)", st);
g_free(s);
g_free(st);
g_list_destroy(p);
} else {
list = g_list_append_printf(list, "NIL");
}
return list;
}
/* structure access point */
char * imap_get_structure(GMimeMessage *message, gboolean extension)
{
GList *structure = NULL;
GMimeContentType *type;
GMimeObject *part;
char *s, *t;
part = g_mime_message_get_mime_part(message);
type = (GMimeContentType *)g_mime_object_get_content_type(part);
if (! type) {
TRACE(TRACE_DEBUG,"error getting content_type");
g_object_unref(part);
return NULL;
}
s = g_mime_content_type_to_string(type);
TRACE(TRACE_DEBUG,"message type: [%s]", s);
g_free(s);
/* multipart composite */
if (g_mime_content_type_is_type(type,"multipart","*"))
_structure_part_multipart(part,(gpointer)&structure, extension);
/* message included as mimepart */
else if (g_mime_content_type_is_type(type,"message","rfc822"))
_structure_part_message_rfc822(part,(gpointer)&structure, extension);
/* as simple message */
else
_structure_part_text(part,(gpointer)&structure, extension);
s = dbmail_imap_plist_as_string(structure);
t = dbmail_imap_plist_collapse(s);
g_free(s);
g_list_destroy(structure);
g_object_unref(part);
return t;
}
static GList * envelope_address_part(GList *list, GMimeMessage *message, const char *header)
{
const char *result;
char *t;
InternetAddressList *alist;
char *result_enc, *charset;
charset = message_get_charset(message);
result = g_mime_message_get_header(message,header);
if (result) {
result_enc = dbmail_iconv_str_to_utf8(result, charset);
t = imap_cleanup_address(result_enc);
g_free(result_enc);
alist = internet_address_parse_string(t);
g_free(t);
list = dbmail_imap_append_alist_as_plist(list, (const InternetAddressList *)alist);
internet_address_list_destroy(alist);
alist = NULL;
} else {
list = g_list_append_printf(list,"NIL");
}
g_free(charset);
return list;
}
static void get_msg_charset_frompart(GMimeObject *part, gpointer data)
{
const char *charset=NULL;
if (*((char **)data)==NULL && (charset=g_mime_object_get_content_type_parameter(part,"charset"))) {
*((char **)data)=g_strdup(charset);
}
return;
}
char * message_get_charset(GMimeMessage *message)
{
GMimeObject *mime_part=NULL;
char *mess_charset=NULL;
if (message)
mime_part=g_mime_message_get_mime_part(message);
if (mime_part) {
const char * charset = NULL;
if ((charset=g_mime_object_get_content_type_parameter(mime_part,"charset")))
mess_charset=g_strdup(charset);
g_object_unref(mime_part);
}
if (mess_charset==NULL)
g_mime_message_foreach_part(message,get_msg_charset_frompart,&mess_charset);
return mess_charset;
}
void dbmail_iconv_init(void)
{
static gboolean initialized = FALSE;
iconv_t tmp_i = (iconv_t)-1;
if (initialized)
return;
ic = g_new0(struct DbmailIconv,1);
memset(ic->db_charset,'\0', FIELDSIZE);
memset(ic->msg_charset,'\0', FIELDSIZE);
ic->to_db = (iconv_t)-1;
ic->from_msg = (iconv_t)-1;
GETCONFIGVALUE("ENCODING", "DBMAIL", ic->db_charset);
GETCONFIGVALUE("DEFAULT_MSG_ENCODING", "DBMAIL", ic->msg_charset);
if(ic->db_charset[0]) {
if ((tmp_i=g_mime_iconv_open(ic->db_charset,"UTF-8")) == (iconv_t)-1) {
g_strlcpy(ic->db_charset, g_mime_locale_charset(),FIELDSIZE);
} else {
g_mime_iconv_close(tmp_i);
}
} else {
g_strlcpy(ic->db_charset,g_mime_locale_charset(), FIELDSIZE);
}
if (ic->msg_charset[0]) {
if ((tmp_i = g_mime_iconv_open(ic->msg_charset,"UTF-8")) == (iconv_t)-1) {
g_strlcpy(ic->msg_charset, g_mime_locale_charset(), FIELDSIZE);
} else {
g_mime_iconv_close(tmp_i);
}
} else {
g_strlcpy(ic->msg_charset, g_mime_locale_charset(), FIELDSIZE);
}
TRACE(TRACE_DEBUG,"Initialize DB encoding surface [UTF-8..%s]", ic->db_charset);
ic->to_db = g_mime_iconv_open(ic->db_charset,"UTF-8");
assert(ic->to_db != (iconv_t)-1);
TRACE(TRACE_DEBUG,"Initialize DB decoding surface [%s..UTF-8]", ic->db_charset);
ic->from_db = g_mime_iconv_open("UTF-8", ic->db_charset);
assert(ic->to_db != (iconv_t)-1);
TRACE(TRACE_DEBUG,"Initialize default MSG decoding surface [%s..UTF-8]", ic->msg_charset);
ic->from_msg=g_mime_iconv_open("UTF-8",ic->msg_charset);
assert(ic->from_msg != (iconv_t)-1);
initialized = TRUE;
}
/* convert not encoded field to utf8 */
char * dbmail_iconv_str_to_utf8(const char* str_in, const char *charset)
{
char * subj=NULL;
iconv_t conv_iconv = (iconv_t)-1;
dbmail_iconv_init();
if (str_in==NULL)
return NULL;
if (g_utf8_validate((const gchar *)str_in, -1, NULL) || !g_mime_utils_text_is_8bit((unsigned char *)str_in, strlen(str_in)))
return g_strdup(str_in);
if (charset) {
if ((conv_iconv=g_mime_iconv_open("UTF-8",charset)) != (iconv_t)-1) {
subj=g_mime_iconv_strdup(conv_iconv,str_in);
g_mime_iconv_close(conv_iconv);
}
}
if (subj==NULL)
subj=g_mime_iconv_strdup(ic->from_msg,str_in);
if (subj==NULL) {
subj=g_strdup(str_in);
char *p;
for(p=subj;*p;p++)
if(*p & 0x80) *p='?';
}
return subj;
}
/* convert not encoded field to database encoding */
char * dbmail_iconv_str_to_db(const char* str_in, const char *charset)
{
char * subj=NULL;
iconv_t conv_iconv;
dbmail_iconv_init();
if ( str_in == NULL )
return NULL;
if (! g_mime_utils_text_is_8bit((unsigned char *)str_in, strlen(str_in)) )
return g_strdup(str_in);
if ((subj=g_mime_iconv_strdup(ic->to_db,str_in)) != NULL)
return subj;
if (charset) {
if ((conv_iconv=g_mime_iconv_open(ic->db_charset,charset)) != (iconv_t)-1) {
subj=g_mime_iconv_strdup(conv_iconv,str_in);
g_mime_iconv_close(conv_iconv);
}
}
if (subj==NULL) {
char *subj2;
if ((subj2 = g_mime_iconv_strdup(ic->from_msg,str_in)) != NULL) {
subj = g_mime_iconv_strdup(ic->to_db, subj2);
g_free(subj2);
}
}
if (subj==NULL) {
subj=g_strdup(str_in);
char *p;
for(p=subj;*p;p++)
if(*p & 0x80) *p='?';
}
return subj;
}
/* encode string from database encoding to mime (7-bit) */
char * dbmail_iconv_db_to_utf7(const char* str_in)
{
char * subj=NULL;
dbmail_iconv_init();
if (str_in==NULL)
return NULL;
if (!g_mime_utils_text_is_8bit((unsigned char *)str_in, strlen(str_in)))
return g_strdup(str_in);
if ((! g_utf8_validate((const char *)str_in,-1,NULL)) && ((subj=g_mime_iconv_strdup(ic->from_db, str_in))!=NULL)) {
gchar *subj2;
subj2 = g_mime_utils_header_encode_text((const char *)subj);
g_free(subj);
return subj2;
}
return g_mime_utils_header_encode_text(str_in);
}
/* work around a bug in gmime (< 2.2.10) where utf7 strings are not completely decoded */
char * dbmail_iconv_decode_text(const char *in)
{
size_t i=0, l=0, r=0, len, wlen=0;
char p2=0, p = 0, c, n = 0;
char *res, *s;
gboolean inchar = FALSE, inword = FALSE;
GString *buf = g_string_new("");
GString *str = g_string_new("");
len = strlen(in);
for (i = 0; i<len; i++)
{
c = in[i];
n = in[i+1];
if ((c == '=') && (n == '?') && (inword == FALSE) && (inchar == FALSE)) {
inchar = TRUE;
l = i;
} else if (((p2 == 'q') || (p2 == 'Q') || (p2 == 'b') || (p2 == 'B')) && (p == '?') && inchar) {
inchar = FALSE;
inword = TRUE;
wlen = 0;
} else if ((p2 == '?') && (p == '=') && inword && wlen) {
inword = FALSE;
r = i;
} else if (inword) {
wlen++;
}
if (l < r) {
s = g_mime_utils_header_decode_text(buf->str);
g_string_append_printf(str, "%s", s);
g_free(s);
l = r;
i = r;
g_string_printf(buf,"%s","");
g_string_append_c(str, in[i]);
} else if (inword || inchar) {
g_string_append_c(buf, in[i]);
} else {
g_string_append_c(str, in[i]);
}
p2 = p;
p = c;
}
if (buf->len) {
s = g_mime_utils_header_decode_text(buf->str);
g_string_append_printf(str, "%s", s);
g_free(s);
}
g_string_free(buf,TRUE);
res = str->str;
g_string_free(str,FALSE);
return res;
}
/*
* dbmail_iconv_decode_address
* \param address the raw address header value
* \result allocated string containing a utf8 encoded representation
* of the input address
*
* paranoia rulez here, since input is untrusted
*/
char * dbmail_iconv_decode_address(char *address)
{
InternetAddressList *l;
char *r = NULL, *t = NULL, *s = NULL;
if (address == NULL) return NULL;
// first make the address rfc2047 compliant if it happens to
// contain 8bit data
if ( (g_mime_utils_text_is_8bit((unsigned char *)address, strlen(address))) )
s = g_mime_utils_header_encode_text((const char *)address);
else
s = g_strdup(address);
// cleanup broken addresses before parsing
t = imap_cleanup_address((const char *)s); g_free(s);
// feed the result to gmime's address parser and
// render it back to a hopefully now sane string
l = internet_address_parse_string(t); g_free(t);
s = internet_address_list_to_string(l, FALSE);
internet_address_list_destroy(l);
// now we're set to decode the rfc2047 address header
// into clean utf8
r = dbmail_iconv_decode_text(s); g_free(s);
// oops: if the rfc2047 encoded address names contain
// encoded double-quotes, imap_cleanup_address has added quotes that are now
// redundant. we need to strip those
pack_char(r,'"');
return r;
}
char * dbmail_iconv_decode_field(const char *in, const char *charset, gboolean isaddr)
{
char *tmp_raw;
char *value;
if ((tmp_raw = dbmail_iconv_str_to_utf8((const char *)in, charset)) == NULL) {
TRACE(TRACE_WARNING, "unable to decode headervalue [%s] using charset [%s]", in, charset);
return NULL;
}
if (isaddr)
value = dbmail_iconv_decode_address(tmp_raw);
else
value = dbmail_iconv_decode_text(tmp_raw);
return value;
}
/* envelope access point */
char * imap_get_envelope(GMimeMessage *message)
{
GMimeObject *part;
GList *list = NULL;
char *result;
char *s = NULL, *t = NULL;
if (! GMIME_IS_MESSAGE(message))
return NULL;
part = GMIME_OBJECT(message);
/* date */
result = g_mime_message_get_date_string(message);
if (result) {
t = dbmail_imap_astring_as_string(result);
list = g_list_append_printf(list,"%s", t);
g_free(result);
g_free(t);
result = NULL;
} else {
list = g_list_append_printf(list,"NIL");
}
/* subject */
result = (char *)g_mime_message_get_header(message,"Subject");
if (result) {
char *charset = message_get_charset(message);
char * subj = dbmail_iconv_str_to_utf8(result, charset);
g_free(charset);
s = g_mime_utils_header_encode_text((const char *)subj);
t = dbmail_imap_astring_as_string(s);
g_free(s);
g_free(subj);
list = g_list_append_printf(list,"%s", t);
g_free(t);
} else {
list = g_list_append_printf(list,"NIL");
}
/* from */
list = envelope_address_part(list, message, "From");
/* sender */
if (g_mime_message_get_header(message,"Sender"))
list = envelope_address_part(list, message, "Sender");
else
list = envelope_address_part(list, message, "From");
/* reply-to */
if (g_mime_message_get_header(message,"Reply-to"))
list = envelope_address_part(list, message, "Reply-to");
else
list = envelope_address_part(list, message, "From");
/* to */
list = envelope_address_part(list, message, "To");
/* cc */
list = envelope_address_part(list, message, "Cc");
/* bcc */
list = envelope_address_part(list, message, "Bcc");
/* in-reply-to */
list = imap_append_header_as_string(list,part,"In-Reply-to");
/* message-id */
result = (char *)g_mime_message_get_message_id(message);
if (result && (! g_strrstr(result,"="))) {
t = g_strdup_printf("<%s>", result);
s = dbmail_imap_astring_as_string(t);
list = g_list_append_printf(list,"%s", s);
g_free(s);
g_free(t);
} else {
list = g_list_append_printf(list,"NIL");
}
s = dbmail_imap_plist_as_string(list);
g_list_destroy(list);
return s;
}
char * imap_get_logical_part(const GMimeObject *object, const char * specifier)
{
gchar *t=NULL;
GString *s = g_string_new("");
if (strcasecmp(specifier,"HEADER")==0 || strcasecmp(specifier,"MIME")==0) {
t = g_mime_object_get_headers(GMIME_OBJECT(object));
g_string_printf(s,"%s\n", t);
g_free(t);
}
else if (strcasecmp(specifier,"TEXT")==0) {
t = g_mime_object_get_body(GMIME_OBJECT(object));
g_string_printf(s,"%s",t);
g_free(t);
}
t = s->str;
g_string_free(s,FALSE);
return t;
}
GMimeObject * imap_get_partspec(const GMimeObject *message, const char *partspec)
{
GMimeObject *object;
GMimeContentType *type;
char *part;
guint index;
guint i;
assert(message);
assert(partspec);
GString *t = g_string_new(partspec);
GList *specs = g_string_split(t,".");
g_string_free(t,TRUE);
object = GMIME_OBJECT(message);
if (!object) {
TRACE(TRACE_INFO, "message is not an object");
return NULL;
}
for (i=0; i< g_list_length(specs); i++) {
part = g_list_nth_data(specs,i);
if (! (index = strtol((const char *)part, NULL, 0)))
break;
if (GMIME_IS_MESSAGE(object))
object = GMIME_OBJECT(GMIME_MESSAGE(object)->mime_part);
type = (GMimeContentType *)g_mime_object_get_content_type(object);
if (g_mime_content_type_is_type(type,"multipart","*")) {
object = g_mime_multipart_get_part((GMimeMultipart *)object, (int)index-1);
if (!object) {
TRACE(TRACE_INFO, "object part [%d] is null", (int)index-1);
return NULL;
}
if (! GMIME_IS_OBJECT(object)) {
TRACE(TRACE_INFO, "object part [%d] is not an object", (int)index-1);
return NULL;
}
type = (GMimeContentType *)g_mime_object_get_content_type(object);
}
// for message/rfc822 parts we want the contained message,
// not the mime-part as such
if (g_mime_content_type_is_type(type,"message","rfc822")) {
object = GMIME_OBJECT(GMIME_MESSAGE_PART(object)->message);
if (!object) {
TRACE(TRACE_INFO, "rfc822 part is null");
return NULL;
}
if (! GMIME_IS_OBJECT(object)) {
TRACE(TRACE_INFO, "rfc822 part is not an object");
return NULL;
}
}
}
return object;
}
/* Ugly hacks because sometimes GMime is too strict. */
char * imap_cleanup_address(const char *a)
{
char *r, *t;
char *inptr;
char prev, next=0;
unsigned incode=0, inquote=0;
size_t i, l;
GString *s;
if (!a || !a[0])
return g_strdup("");
s = g_string_new("");
t = g_strdup(a);
// un-fold and collapse tabs and spaces
g_strdelimit(t,"\n",' ');
dm_pack_spaces(t);
inptr = t;
inptr = g_strstrip(inptr);
prev = inptr[0];
l = strlen(inptr);
for (i = 0; i < l - 1; i++) {
next = inptr[i+1];
if (incode && (inptr[i] == '"' || inptr[i] == ' '))
continue; // skip illegal chars inquote
if ((! inquote) && inptr[i]=='"')
inquote = 1;
else if (inquote && inptr[i] == '"')
inquote = 0;
// quote encoded string
if (inptr[i] == '=' && next == '?' && (! incode)) {
incode=1;
if (prev != '"' && (! inquote)) {
g_string_append_c(s,'"');
inquote = 1;
}
}
g_string_append_c(s,inptr[i]);
if (inquote && incode && prev == '?' && inptr[i] == '=' && (next == '"' || next == ' ' || next == '<')) {
if ((next != '"' ) && ((i < l-2) && (inptr[i+2] != '='))) {
g_string_append_c(s, '"');
inquote = 0;
}
if (next == '<')
g_string_append_c(s,' ');
incode=0;
}
prev = inptr[i];
}
inptr+=i;
if (*inptr)
g_string_append(s,inptr);
g_free(t);
if (g_str_has_suffix(s->str,";"))
s = g_string_truncate(s,s->len-1);
/* This second hack changes semicolons into commas when not preceded by a colon.
* The purpose is to fix broken syntax like this: "one@dom; two@dom"
* But to allow correct syntax like this: "Group: one@dom, two@dom;"
*/
int colon = 0;
for (i = 0; i < s->len; i++) {
switch (s->str[i]) {
case ':':
colon = 1;
break;
case ';':
s->str[i] = ',';
break;
}
if (colon)
break;
}
r = s->str;
g_string_free(s,FALSE);
return r;
}
char * imap_flags_as_string(msginfo_t *msginfo)
{
GList *sublist = NULL;
int j;
char *s;
for (j = 0; j < IMAP_NFLAGS; j++) {
if (msginfo->flags[j])
sublist = g_list_append(sublist,g_strdup((gchar *)imap_flag_desc_escaped[j]));
}
s = dbmail_imap_plist_as_string(sublist);
g_list_destroy(sublist);
return s;
}
long long unsigned dm_strtoull(const char *nptr, char **endptr, int base)
{
errno = 0;
long long int r = strtoll(nptr, endptr, base);
if (errno)
return (long long unsigned)0;
if (r < 0) {
errno = EINVAL;
return (long long unsigned)0;
}
return (long long unsigned)r;
}
syntax highlighted by Code2HTML, v. 0.9.1