diff options
author | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
---|---|---|
committer | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
commit | 094ca96844842928810f14844413109fc6cdd890 (patch) | |
tree | e60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /imap/src/c-client | |
download | alpine-094ca96844842928810f14844413109fc6cdd890.tar.xz |
Initial Alpine Version
Diffstat (limited to 'imap/src/c-client')
36 files changed, 27616 insertions, 0 deletions
diff --git a/imap/src/c-client/auth_ext.c b/imap/src/c-client/auth_ext.c new file mode 100644 index 00000000..61dfc1b8 --- /dev/null +++ b/imap/src/c-client/auth_ext.c @@ -0,0 +1,96 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: EXTERNAL authenticator + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 6 April 2005 + * Last Edited: 30 August 2006 + */ + +long auth_external_client (authchallenge_t challenger,authrespond_t responder, + char *service,NETMBX *mb,void *stream, + unsigned long *trial,char *user); +char *auth_external_server (authresponse_t responder,int argc,char *argv[]); + +AUTHENTICATOR auth_ext = { /* secure, has full auth, hidden */ + AU_SECURE | AU_AUTHUSER | AU_HIDE, + "EXTERNAL", /* authenticator name */ + NIL, /* always valid */ + auth_external_client, /* client method */ + auth_external_server, /* server method */ + NIL /* next authenticator */ +}; + +/* Client authenticator + * Accepts: challenger function + * responder function + * SASL service name + * parsed network mailbox structure + * stream argument for functions + * pointer to current trial count + * returned user name + * Returns: T if success, NIL otherwise, number of trials incremented if retry + */ + +long auth_external_client (authchallenge_t challenger,authrespond_t responder, + char *service,NETMBX *mb,void *stream, + unsigned long *trial,char *user) +{ + void *challenge; + unsigned long clen; + long ret = NIL; + *trial = 65535; /* never retry */ + if (challenge = (*challenger) (stream,&clen)) { + fs_give ((void **) &challenge); + /* send authorization id (empty string OK) */ + if ((*responder) (stream,strcpy (user,mb->user),strlen (mb->user))) { + if (challenge = (*challenger) (stream,&clen)) + fs_give ((void **) &challenge); + else ret = LONGT; /* check the authentication */ + } + } + return ret; +} + + +/* Server authenticator + * Accepts: responder function + * argument count + * argument vector + * Returns: authenticated user name or NIL + */ + +char *auth_external_server (authresponse_t responder,int argc,char *argv[]) +{ + unsigned long len; + char *authid; + char *authenid = (char *) mail_parameters (NIL,GET_EXTERNALAUTHID,NIL); + char *ret = NIL; + /* get authorization identity */ + if (authenid && (authid = (*responder) ("",0,&len))) { + /* note: responders null-terminate */ + if (*authid ? authserver_login (authid,authenid,argc,argv) : + authserver_login (authenid,NIL,argc,argv)) ret = myusername (); + fs_give ((void **) &authid); + } + return ret; +} diff --git a/imap/src/c-client/auth_gss.c b/imap/src/c-client/auth_gss.c new file mode 100644 index 00000000..26bf9e50 --- /dev/null +++ b/imap/src/c-client/auth_gss.c @@ -0,0 +1,423 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: GSSAPI authenticator + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 12 January 1998 + * Last Edited: 30 August 2006 + */ + + +long auth_gssapi_valid (void); +long auth_gssapi_client (authchallenge_t challenger,authrespond_t responder, + char *service,NETMBX *mb,void *stream, + unsigned long *trial,char *user); +long auth_gssapi_client_work (authchallenge_t challenger,gss_buffer_desc chal, + authrespond_t responder,char *service,NETMBX *mb, + void *stream,char *user,kinit_t ki); +char *auth_gssapi_server (authresponse_t responder,int argc,char *argv[]); + + +AUTHENTICATOR auth_gss = { + AU_SECURE | AU_AUTHUSER, /* secure authenticator */ + "GSSAPI", /* authenticator name */ + auth_gssapi_valid, /* check if valid */ + auth_gssapi_client, /* client method */ + auth_gssapi_server, /* server method */ + NIL /* next authenticator */ +}; + +#define AUTH_GSSAPI_P_NONE 1 +#define AUTH_GSSAPI_P_INTEGRITY 2 +#define AUTH_GSSAPI_P_PRIVACY 4 + +#define AUTH_GSSAPI_C_MAXSIZE 8192 + +#define SERVER_LOG(x,y) syslog (LOG_ALERT,x,y) + +/* Check if GSSAPI valid on this system + * Returns: T if valid, NIL otherwise + */ + +long auth_gssapi_valid (void) +{ + char tmp[MAILTMPLEN]; + OM_uint32 smn; + gss_buffer_desc buf; + gss_name_t name; + /* make service name */ + sprintf (tmp,"%s@%s",(char *) mail_parameters (NIL,GET_SERVICENAME,NIL), + mylocalhost ()); + buf.length = strlen (buf.value = tmp); + /* see if can build a name */ + if (gss_import_name (&smn,&buf,GSS_C_NT_HOSTBASED_SERVICE,&name) != + GSS_S_COMPLETE) return NIL; + /* remove server method if no keytab */ + if (!kerberos_server_valid ()) auth_gss.server = NIL; + gss_release_name (&smn,&name);/* finished with name */ + return LONGT; +} + +/* Client authenticator + * Accepts: challenger function + * responder function + * SASL service name + * parsed network mailbox structure + * stream argument for functions + * pointer to current trial count + * returned user name + * Returns: T if success, NIL otherwise, number of trials incremented if retry + */ + +long auth_gssapi_client (authchallenge_t challenger,authrespond_t responder, + char *service,NETMBX *mb,void *stream, + unsigned long *trial,char *user) +{ + gss_buffer_desc chal; + kinit_t ki = (kinit_t) mail_parameters (NIL,GET_KINIT,NIL); + long ret = NIL; + *trial = 65535; /* never retry */ + /* get initial (empty) challenge */ + if (chal.value = (*challenger) (stream,(unsigned long *) &chal.length)) { + if (chal.length) { /* abort if challenge non-empty */ + mm_log ("Server bug: non-empty initial GSSAPI challenge",WARN); + (*responder) (stream,NIL,0); + ret = LONGT; /* will get a BAD response back */ + } + else if (mb->authuser[0] && strcmp (mb->authuser,myusername ())) { + mm_log ("Can't use Kerberos: invalid /authuser",WARN); + (*responder) (stream,NIL,0); + ret = LONGT; /* will get a BAD response back */ + } + else ret = auth_gssapi_client_work (challenger,chal,responder,service,mb, + stream,user,ki); + } + return ret; +} + +/* Client authenticator worker function + * Accepts: challenger function + * responder function + * SASL service name + * parsed network mailbox structure + * stream argument for functions + * returned user name + * kinit function pointer if should retry with kinit + * Returns: T if success, NIL otherwise + */ + +long auth_gssapi_client_work (authchallenge_t challenger,gss_buffer_desc chal, + authrespond_t responder,char *service,NETMBX *mb, + void *stream,char *user,kinit_t ki) +{ + char tmp[MAILTMPLEN]; + OM_uint32 smj,smn,dsmj,dsmn; + OM_uint32 mctx = 0; + gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; + gss_buffer_desc resp,buf; + long i; + int conf; + gss_qop_t qop; + gss_name_t crname = NIL; + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + void *data; + long ret = NIL; + sprintf (tmp,"%s@%s",service,mb->host); + buf.length = strlen (buf.value = tmp); + /* get service name */ + if (gss_import_name (&smn,&buf,GSS_C_NT_HOSTBASED_SERVICE,&crname) != + GSS_S_COMPLETE) { + mm_log ("Can't import Kerberos service name",WARN); + (*responder) (stream,NIL,0); + } + else { + data = (*bn) (BLOCK_SENSITIVE,NIL); + /* negotiate with KDC */ + smj = gss_init_sec_context (&smn,GSS_C_NO_CREDENTIAL,&ctx,crname,NIL, + GSS_C_INTEG_FLAG | GSS_C_MUTUAL_FLAG | + GSS_C_REPLAY_FLAG,0,GSS_C_NO_CHANNEL_BINDINGS, + GSS_C_NO_BUFFER,NIL,&resp,NIL,NIL); + (*bn) (BLOCK_NONSENSITIVE,data); + + /* while continuation needed */ + while (smj == GSS_S_CONTINUE_NEEDED) { + if (chal.value) fs_give ((void **) &chal.value); + /* send response, get next challenge */ + i = (*responder) (stream,resp.value,resp.length) && + (chal.value = (*challenger) (stream,(unsigned long *) &chal.length)); + gss_release_buffer (&smn,&resp); + if (i) { /* negotiate continuation with KDC */ + data = (*bn) (BLOCK_SENSITIVE,NIL); + switch (smj = /* make sure continuation going OK */ + gss_init_sec_context (&smn,GSS_C_NO_CREDENTIAL,&ctx, + crname,GSS_C_NO_OID,GSS_C_INTEG_FLAG | + GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,0, + GSS_C_NO_CHANNEL_BINDINGS,&chal,NIL, + &resp,NIL,NIL)) { + case GSS_S_CONTINUE_NEEDED: + case GSS_S_COMPLETE: + break; + default: /* error, don't need context any more */ + gss_delete_sec_context (&smn,&ctx,NIL); + } + (*bn) (BLOCK_NONSENSITIVE,data); + } + else { /* error in continuation */ + mm_log ("Error in negotiating Kerberos continuation",WARN); + (*responder) (stream,NIL,0); + /* don't need context any more */ + gss_delete_sec_context (&smn,&ctx,NIL); + break; + } + } + + switch (smj) { /* done - deal with final condition */ + case GSS_S_COMPLETE: + if (chal.value) fs_give ((void **) &chal.value); + /* get prot mechanisms and max size */ + if ((*responder) (stream,resp.value ? resp.value : "",resp.length) && + (chal.value = (*challenger) (stream,(unsigned long *)&chal.length))&& + (gss_unwrap (&smn,ctx,&chal,&resp,&conf,&qop) == GSS_S_COMPLETE) && + (resp.length >= 4) && (*((char *) resp.value) & AUTH_GSSAPI_P_NONE)){ + /* make copy of flags and length */ + memcpy (tmp,resp.value,4); + gss_release_buffer (&smn,&resp); + /* no session protection */ + tmp[0] = AUTH_GSSAPI_P_NONE; + /* install user name */ + strcpy (tmp+4,strcpy (user,mb->user[0] ? mb->user : myusername ())); + buf.value = tmp; buf.length = strlen (user) + 4; + /* successful negotiation */ + switch (smj = gss_wrap (&smn,ctx,NIL,qop,&buf,&conf,&resp)) { + case GSS_S_COMPLETE: + if ((*responder) (stream,resp.value,resp.length)) ret = T; + gss_release_buffer (&smn,&resp); + break; + default: + do switch (dsmj = gss_display_status (&dsmn,smj,GSS_C_GSS_CODE, + GSS_C_NO_OID,&mctx,&resp)) { + case GSS_S_COMPLETE: + mctx = 0; + case GSS_S_CONTINUE_NEEDED: + sprintf (tmp,"Unknown gss_wrap failure: %s",(char *) resp.value); + mm_log (tmp,WARN); + gss_release_buffer (&dsmn,&resp); + } + while (dsmj == GSS_S_CONTINUE_NEEDED); + do switch (dsmj = gss_display_status (&dsmn,smn,GSS_C_MECH_CODE, + GSS_C_NO_OID,&mctx,&resp)) { + case GSS_S_COMPLETE: + case GSS_S_CONTINUE_NEEDED: + sprintf (tmp,"GSSAPI mechanism status: %s",(char *) resp.value); + mm_log (tmp,WARN); + gss_release_buffer (&dsmn,&resp); + } + while (dsmj == GSS_S_CONTINUE_NEEDED); + (*responder) (stream,NIL,0); + } + } + /* flush final challenge */ + if (chal.value) fs_give ((void **) &chal.value); + /* don't need context any more */ + gss_delete_sec_context (&smn,&ctx,NIL); + break; + + case GSS_S_CREDENTIALS_EXPIRED: + if (chal.value) fs_give ((void **) &chal.value); + /* retry if application kinits */ + if (ki && (*ki) (mb->host,"Kerberos credentials expired")) + ret = auth_gssapi_client_work (challenger,chal,responder,service,mb, + stream,user,NIL); + else { /* application can't kinit */ + sprintf (tmp,"Kerberos credentials expired (try running kinit) for %s", + mb->host); + mm_log (tmp,WARN); + (*responder) (stream,NIL,0); + } + break; + case GSS_S_FAILURE: + if (chal.value) fs_give ((void **) &chal.value); + do switch (dsmj = gss_display_status (&dsmn,smn,GSS_C_MECH_CODE, + GSS_C_NO_OID,&mctx,&resp)) { + case GSS_S_COMPLETE: /* end of message, can kinit? */ + if (ki && kerberos_try_kinit (smn) && + (*ki) (mb->host,(char *) resp.value)) { + gss_release_buffer (&dsmn,&resp); + ret = auth_gssapi_client_work (challenger,chal,responder,service,mb, + stream,user,NIL); + break; /* done */ + } + else (*responder) (stream,NIL,0); + case GSS_S_CONTINUE_NEEDED: + sprintf (tmp,kerberos_try_kinit (smn) ? + "Kerberos error: %.80s (try running kinit) for %.80s" : + "GSSAPI failure: %s for %.80s",(char *) resp.value,mb->host); + mm_log (tmp,WARN); + gss_release_buffer (&dsmn,&resp); + } while (dsmj == GSS_S_CONTINUE_NEEDED); + break; + + default: /* miscellaneous errors */ + if (chal.value) fs_give ((void **) &chal.value); + do switch (dsmj = gss_display_status (&dsmn,smj,GSS_C_GSS_CODE, + GSS_C_NO_OID,&mctx,&resp)) { + case GSS_S_COMPLETE: + mctx = 0; + case GSS_S_CONTINUE_NEEDED: + sprintf (tmp,"Unknown GSSAPI failure: %s",(char *) resp.value); + mm_log (tmp,WARN); + gss_release_buffer (&dsmn,&resp); + } + while (dsmj == GSS_S_CONTINUE_NEEDED); + do switch (dsmj = gss_display_status (&dsmn,smn,GSS_C_MECH_CODE, + GSS_C_NO_OID,&mctx,&resp)) { + case GSS_S_COMPLETE: + case GSS_S_CONTINUE_NEEDED: + sprintf (tmp,"GSSAPI mechanism status: %s",(char *) resp.value); + mm_log (tmp,WARN); + gss_release_buffer (&dsmn,&resp); + } + while (dsmj == GSS_S_CONTINUE_NEEDED); + (*responder) (stream,NIL,0); + break; + } + /* finished with credentials name */ + if (crname) gss_release_name (&smn,&crname); + } + return ret; /* return status */ +} + +/* Server authenticator + * Accepts: responder function + * argument count + * argument vector + * Returns: authenticated user name or NIL + */ + +char *auth_gssapi_server (authresponse_t responder,int argc,char *argv[]) +{ + char *ret = NIL; + char tmp[MAILTMPLEN]; + unsigned long maxsize = htonl (AUTH_GSSAPI_C_MAXSIZE); + int conf; + OM_uint32 smj,smn,dsmj,dsmn,flags; + OM_uint32 mctx = 0; + gss_name_t crname,name; + gss_OID mech; + gss_buffer_desc chal,resp,buf; + gss_cred_id_t crd; + gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; + gss_qop_t qop = GSS_C_QOP_DEFAULT; + /* make service name */ + sprintf (tmp,"%s@%s",(char *) mail_parameters (NIL,GET_SERVICENAME,NIL), + tcp_serverhost ()); + buf.length = strlen (buf.value = tmp); + /* acquire credentials */ + if ((gss_import_name (&smn,&buf,GSS_C_NT_HOSTBASED_SERVICE,&crname)) == + GSS_S_COMPLETE) { + if ((smj = gss_acquire_cred (&smn,crname,0,NIL,GSS_C_ACCEPT,&crd,NIL,NIL)) + == GSS_S_COMPLETE) { + if (resp.value = (*responder) ("",0,(unsigned long *) &resp.length)) { + do { /* negotiate authentication */ + smj = gss_accept_sec_context (&smn,&ctx,crd,&resp, + GSS_C_NO_CHANNEL_BINDINGS,&name,&mech, + &chal,&flags,NIL,NIL); + /* don't need response any more */ + fs_give ((void **) &resp.value); + switch (smj) { /* how did it go? */ + case GSS_S_COMPLETE: /* successful */ + case GSS_S_CONTINUE_NEEDED: + if (chal.value) { /* send challenge, get next response */ + resp.value = (*responder) (chal.value,chal.length, + (unsigned long *) &resp.length); + gss_release_buffer (&smn,&chal); + } + break; + } + } + while (resp.value && resp.length && (smj == GSS_S_CONTINUE_NEEDED)); + + /* successful exchange? */ + if ((smj == GSS_S_COMPLETE) && + (gss_display_name (&smn,name,&buf,&mech) == GSS_S_COMPLETE)) { + /* send security and size */ + memcpy (resp.value = tmp,(void *) &maxsize,resp.length = 4); + tmp[0] = AUTH_GSSAPI_P_NONE; + if (gss_wrap (&smn,ctx,NIL,qop,&resp,&conf,&chal) == GSS_S_COMPLETE){ + resp.value = (*responder) (chal.value,chal.length, + (unsigned long *) &resp.length); + gss_release_buffer (&smn,&chal); + if (gss_unwrap (&smn,ctx,&resp,&chal,&conf,&qop)==GSS_S_COMPLETE) { + /* client request valid */ + if (chal.value && (chal.length > 4) && + (chal.length < (MAILTMPLEN - 1)) && + memcpy (tmp,chal.value,chal.length) && + (tmp[0] & AUTH_GSSAPI_P_NONE)) { + /* tie off authorization ID */ + tmp[chal.length] = '\0'; + ret = kerberos_login (tmp+4,buf.value,argc,argv); + } + /* done with user name */ + gss_release_buffer (&smn,&chal); + } + /* finished with response */ + fs_give ((void **) &resp.value); + } + /* don't need name buffer any more */ + gss_release_buffer (&smn,&buf); + } + /* don't need client name any more */ + gss_release_name (&smn,&name); + /* don't need context any more */ + if (ctx != GSS_C_NO_CONTEXT) gss_delete_sec_context (&smn,&ctx,NIL); + } + /* finished with credentials */ + gss_release_cred (&smn,&crd); + } + + else { /* can't acquire credentials! */ + if (gss_display_name (&dsmn,crname,&buf,&mech) == GSS_S_COMPLETE) + SERVER_LOG ("Failed to acquire credentials for %s",buf.value); + if (smj != GSS_S_FAILURE) do + switch (dsmj = gss_display_status (&dsmn,smj,GSS_C_GSS_CODE, + GSS_C_NO_OID,&mctx,&resp)) { + case GSS_S_COMPLETE: + mctx = 0; + case GSS_S_CONTINUE_NEEDED: + SERVER_LOG ("Unknown GSSAPI failure: %s",resp.value); + gss_release_buffer (&dsmn,&resp); + } + while (dsmj == GSS_S_CONTINUE_NEEDED); + do switch (dsmj = gss_display_status (&dsmn,smn,GSS_C_MECH_CODE, + GSS_C_NO_OID,&mctx,&resp)) { + case GSS_S_COMPLETE: + case GSS_S_CONTINUE_NEEDED: + SERVER_LOG ("GSSAPI mechanism status: %s",resp.value); + gss_release_buffer (&dsmn,&resp); + } + while (dsmj == GSS_S_CONTINUE_NEEDED); + } + /* finished with credentials name */ + gss_release_name (&smn,&crname); + } + return ret; /* return status */ +} diff --git a/imap/src/c-client/auth_log.c b/imap/src/c-client/auth_log.c new file mode 100644 index 00000000..1e1b1b5c --- /dev/null +++ b/imap/src/c-client/auth_log.c @@ -0,0 +1,117 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Login authenticator + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 5 December 1995 + * Last Edited: 30 August 2006 + */ + +long auth_login_client (authchallenge_t challenger,authrespond_t responder, + char *service,NETMBX *mb,void *stream, + unsigned long *trial,char *user); +char *auth_login_server (authresponse_t responder,int argc,char *argv[]); + +AUTHENTICATOR auth_log = { + AU_HIDE, /* hidden */ + "LOGIN", /* authenticator name */ + NIL, /* always valid */ + auth_login_client, /* client method */ + auth_login_server, /* server method */ + NIL /* next authenticator */ +}; + +#define PWD_USER "User Name" +#define PWD_PWD "Password" + +/* Client authenticator + * Accepts: challenger function + * responder function + * SASL service name + * parsed network mailbox structure + * stream argument for functions + * pointer to current trial count + * returned user name + * Returns: T if success, NIL otherwise, number of trials incremented if retry + */ + +long auth_login_client (authchallenge_t challenger,authrespond_t responder, + char *service,NETMBX *mb,void *stream, + unsigned long *trial,char *user) +{ + char pwd[MAILTMPLEN]; + void *challenge; + unsigned long clen; + long ret = NIL; + /* get user name prompt */ + if (challenge = (*challenger) (stream,&clen)) { + fs_give ((void **) &challenge); + pwd[0] = NIL; /* prompt user */ + mm_login (mb,user,pwd,*trial); + if (!pwd[0]) { /* user requested abort */ + (*responder) (stream,NIL,0); + *trial = 0; /* cancel subsequent attempts */ + ret = LONGT; /* will get a BAD response back */ + } + /* send user name */ + else if ((*responder) (stream,user,strlen (user)) && + (challenge = (*challenger) (stream,&clen))) { + fs_give ((void **) &challenge); + /* send password */ + if ((*responder) (stream,pwd,strlen (pwd))) { + if (challenge = (*challenger) (stream,&clen)) + fs_give ((void **) &challenge); + else { + ++*trial; /* can try again if necessary */ + ret = LONGT; /* check the authentication */ + } + } + } + } + memset (pwd,0,MAILTMPLEN); /* erase password */ + if (!ret) *trial = 65535; /* don't retry if bad protocol */ + return ret; +} + + +/* Server authenticator + * Accepts: responder function + * argument count + * argument vector + * Returns: authenticated user name or NIL + */ + +char *auth_login_server (authresponse_t responder,int argc,char *argv[]) +{ + char *ret = NIL; + char *user,*pass,*authuser; + if (user = (*responder) (PWD_USER,sizeof (PWD_USER),NIL)) { + if (pass = (*responder) (PWD_PWD,sizeof (PWD_PWD),NIL)) { + /* delimit user from possible admin */ + if (authuser = strchr (user,'*')) *authuser++ = '\0'; + if (server_login (user,pass,authuser,argc,argv)) ret = myusername (); + fs_give ((void **) &pass); + } + fs_give ((void **) &user); + } + return ret; +} diff --git a/imap/src/c-client/auth_md5.c b/imap/src/c-client/auth_md5.c new file mode 100644 index 00000000..29ab947d --- /dev/null +++ b/imap/src/c-client/auth_md5.c @@ -0,0 +1,495 @@ +/* ======================================================================== + * Copyright 1988-2007 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: CRAM-MD5 authenticator + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 21 October 1998 + * Last Edited: 30 January 2007 + */ + +/* MD5 context */ + +#define MD5BLKLEN 64 /* MD5 block length */ +#define MD5DIGLEN 16 /* MD5 digest length */ + +typedef struct { + unsigned long chigh; /* high 32bits of byte count */ + unsigned long clow; /* low 32bits of byte count */ + unsigned long state[4]; /* state (ABCD) */ + unsigned char buf[MD5BLKLEN]; /* input buffer */ + unsigned char *ptr; /* buffer position */ +} MD5CONTEXT; + + +/* Prototypes */ + +long auth_md5_valid (void); +long auth_md5_client (authchallenge_t challenger,authrespond_t responder, + char *service,NETMBX *mb,void *stream, + unsigned long *trial,char *user); +char *auth_md5_server (authresponse_t responder,int argc,char *argv[]); +char *auth_md5_pwd (char *user); +char *apop_login (char *chal,char *user,char *md5,int argc,char *argv[]); +char *hmac_md5 (char *text,unsigned long tl,char *key,unsigned long kl); +void md5_init (MD5CONTEXT *ctx); +void md5_update (MD5CONTEXT *ctx,unsigned char *data,unsigned long len); +void md5_final (unsigned char *digest,MD5CONTEXT *ctx); +static void md5_transform (unsigned long *state,unsigned char *block); +static void md5_encode (unsigned char *dst,unsigned long *src,int len); +static void md5_decode (unsigned long *dst,unsigned char *src,int len); + + +/* Authenticator linkage */ + +AUTHENTICATOR auth_md5 = { + AU_SECURE, /* secure authenticator */ + "CRAM-MD5", /* authenticator name */ + auth_md5_valid, /* check if valid */ + auth_md5_client, /* client method */ + auth_md5_server, /* server method */ + NIL /* next authenticator */ +}; + +/* Check if CRAM-MD5 valid on this system + * Returns: T, always + */ + +long auth_md5_valid (void) +{ + struct stat sbuf; + /* server forbids MD5 if no MD5 enable file */ + if (stat (MD5ENABLE,&sbuf)) auth_md5.server = NIL; + return T; /* MD5 is otherwise valid */ +} + + +/* Client authenticator + * Accepts: challenger function + * responder function + * SASL service name + * parsed network mailbox structure + * stream argument for functions + * pointer to current trial count + * returned user name + * Returns: T if success, NIL otherwise, number of trials incremented if retry + */ + +long auth_md5_client (authchallenge_t challenger,authrespond_t responder, + char *service,NETMBX *mb,void *stream, + unsigned long *trial,char *user) +{ + char pwd[MAILTMPLEN]; + void *challenge; + unsigned long clen; + long ret = NIL; + /* get challenge */ + if (challenge = (*challenger) (stream,&clen)) { + pwd[0] = NIL; /* prompt user */ + mm_login (mb,user,pwd,*trial); + if (!pwd[0]) { /* user requested abort */ + fs_give ((void **) &challenge); + (*responder) (stream,NIL,0); + *trial = 0; /* cancel subsequent attempts */ + ret = LONGT; /* will get a BAD response back */ + } + else { /* got password, build response */ + sprintf (pwd,"%.65s %.33s",user,hmac_md5 (challenge,clen, + pwd,strlen (pwd))); + fs_give ((void **) &challenge); + /* send credentials, allow retry if OK */ + if ((*responder) (stream,pwd,strlen (pwd))) { + if (challenge = (*challenger) (stream,&clen)) + fs_give ((void **) &challenge); + else { + ++*trial; /* can try again if necessary */ + ret = LONGT; /* check the authentication */ + } + } + } + } + memset (pwd,0,MAILTMPLEN); /* erase password in case not overwritten */ + if (!ret) *trial = 65535; /* don't retry if bad protocol */ + return ret; +} + +/* Server authenticator + * Accepts: responder function + * argument count + * argument vector + * Returns: authenticated user name or NIL + * + * This is much hairier than it needs to be due to the necessary of zapping + * the password data. + */ + +static int md5try = MAXLOGINTRIALS; + +char *auth_md5_server (authresponse_t responder,int argc,char *argv[]) +{ + char *ret = NIL; + char *p,*u,*user,*authuser,*hash,chal[MAILTMPLEN]; + unsigned long cl,pl; + /* generate challenge */ + sprintf (chal,"<%lu.%lu@%s>",(unsigned long) getpid (), + (unsigned long) time (0),mylocalhost ()); + /* send challenge, get user and hash */ + if (user = (*responder) (chal,cl = strlen (chal),NIL)) { + /* got user, locate hash */ + if (hash = strrchr (user,' ')) { + *hash++ = '\0'; /* tie off user */ + /* see if authentication user */ + if (authuser = strchr (user,'*')) *authuser++ = '\0'; + /* get password */ + if (p = auth_md5_pwd ((authuser && *authuser) ? authuser : user)) { + pl = strlen (p); + u = (md5try && !strcmp (hash,hmac_md5 (chal,cl,p,pl))) ? user : NIL; + memset (p,0,pl); /* erase sensitive information */ + fs_give ((void **) &p); /* flush erased password */ + /* now log in for real */ + if (u && authserver_login (u,authuser,argc,argv)) ret = myusername (); + else if (md5try) --md5try; + } + } + fs_give ((void **) &user); + } + if (!ret) sleep (3); /* slow down possible cracker */ + return ret; +} + +/* Return MD5 password for user + * Accepts: user name + * Returns: plaintext password if success, else NIL + * + * This is much hairier than it needs to be due to the necessary of zapping + * the password data. That's why we don't use stdio here. + */ + +char *auth_md5_pwd (char *user) +{ + struct stat sbuf; + int fd = open (MD5ENABLE,O_RDONLY,NIL); + unsigned char *s,*t,*buf,*lusr,*lret; + char *r; + char *ret = NIL; + if (fd >= 0) { /* found the file? */ + fstat (fd,&sbuf); /* yes, slurp it into memory */ + read (fd,buf = (char *) fs_get (sbuf.st_size + 1),sbuf.st_size); + /* see if any uppercase characters in user */ + for (s = user; *s && ((*s < 'A') || (*s > 'Z')); s++); + /* yes, make lowercase copy */ + lusr = *s ? lcase (cpystr (user)) : NIL; + for (s = strtok_r ((char *) buf,"\015\012",&r),lret = NIL; s; + s = ret ? NIL : strtok_r (NIL,"\015\012",&r)) + /* must be valid entry line */ + if (*s && (*s != '#') && (t = strchr (s,'\t')) && t[1]) { + *t++ = '\0'; /* found tab, tie off user, point to pwd */ + if (!strcmp (s,user)) ret = cpystr (t); + else if (lusr && !lret) if (!strcmp (s,lusr)) lret = t; + } + /* accept case-independent name */ + if (!ret && lret) ret = cpystr (lret); + /* don't need lowercase copy any more */ + if (lusr) fs_give ((void **) &lusr); + /* erase sensitive information from buffer */ + memset (buf,0,sbuf.st_size + 1); + fs_give ((void **) &buf); /* flush the buffer */ + close (fd); /* don't need file any longer */ + } + return ret; /* return password */ +} + +/* APOP server login + * Accepts: challenge + * desired user name + * purported MD5 + * argument count + * argument vector + * Returns: authenticated user name or NIL + */ + +char *apop_login (char *chal,char *user,char *md5,int argc,char *argv[]) +{ + int i,j; + char *ret = NIL; + char *s,*authuser,tmp[MAILTMPLEN]; + unsigned char digest[MD5DIGLEN]; + MD5CONTEXT ctx; + char *hex = "0123456789abcdef"; + /* see if authentication user */ + if (authuser = strchr (user,'*')) *authuser++ = '\0'; + /* get password */ + if (s = auth_md5_pwd ((authuser && *authuser) ? authuser : user)) { + md5_init (&ctx); /* initialize MD5 context */ + /* build string to get MD5 digest */ + sprintf (tmp,"%.128s%.128s",chal,s); + memset (s,0,strlen (s)); /* erase sensitive information */ + fs_give ((void **) &s); /* flush erased password */ + md5_update (&ctx,(unsigned char *) tmp,strlen (tmp)); + memset (tmp,0,MAILTMPLEN); /* erase sensitive information */ + md5_final (digest,&ctx); + /* convert to printable hex */ + for (i = 0, s = tmp; i < MD5DIGLEN; i++) { + *s++ = hex[(j = digest[i]) >> 4]; + *s++ = hex[j & 0xf]; + } + *s = '\0'; /* tie off hash text */ + memset (digest,0,MD5DIGLEN);/* erase sensitive information */ + if (md5try && !strcmp (md5,tmp) && + authserver_login (user,authuser,argc,argv)) + ret = cpystr (myusername ()); + else if (md5try) --md5try; + memset (tmp,0,MAILTMPLEN); /* erase sensitive information */ + } + if (!ret) sleep (3); /* slow down possible cracker */ + return ret; +} + +/* + * RFC 2104 HMAC hashing + * Accepts: text to hash + * text length + * key + * key length + * Returns: hash as text, always + */ + +char *hmac_md5 (char *text,unsigned long tl,char *key,unsigned long kl) +{ + int i,j; + static char hshbuf[2*MD5DIGLEN + 1]; + char *s; + MD5CONTEXT ctx; + char *hex = "0123456789abcdef"; + unsigned char digest[MD5DIGLEN],k_ipad[MD5BLKLEN+1],k_opad[MD5BLKLEN+1]; + if (kl > MD5BLKLEN) { /* key longer than pad length? */ + md5_init (&ctx); /* yes, set key as MD5(key) */ + md5_update (&ctx,(unsigned char *) key,kl); + md5_final (digest,&ctx); + key = (char *) digest; + kl = MD5DIGLEN; + } + memcpy (k_ipad,key,kl); /* store key in pads */ + memset (k_ipad+kl,0,(MD5BLKLEN+1)-kl); + memcpy (k_opad,k_ipad,MD5BLKLEN+1); + /* XOR key with ipad and opad values */ + for (i = 0; i < MD5BLKLEN; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + md5_init (&ctx); /* inner MD5: hash ipad and text */ + md5_update (&ctx,k_ipad,MD5BLKLEN); + md5_update (&ctx,(unsigned char *) text,tl); + md5_final (digest,&ctx); + md5_init (&ctx); /* outer MD5: hash opad and inner results */ + md5_update (&ctx,k_opad,MD5BLKLEN); + md5_update (&ctx,digest,MD5DIGLEN); + md5_final (digest,&ctx); + /* convert to printable hex */ + for (i = 0, s = hshbuf; i < MD5DIGLEN; i++) { + *s++ = hex[(j = digest[i]) >> 4]; + *s++ = hex[j & 0xf]; + } + *s = '\0'; /* tie off hash text */ + return hshbuf; +} + +/* Everything after this point is derived from the RSA Data Security, Inc. + * MD5 Message-Digest Algorithm + */ + +/* You may wonder why these strange "a &= 0xffffffff;" statements are here. + * This is to ensure correct results on machines with a unsigned long size of + * larger than 32 bits. + */ + +#define RND1(a,b,c,d,x,s,ac) \ + a += ((b & c) | (d & ~b)) + x + (unsigned long) ac; \ + a &= 0xffffffff; \ + a = b + ((a << s) | (a >> (32 - s))); + +#define RND2(a,b,c,d,x,s,ac) \ + a += ((b & d) | (c & ~d)) + x + (unsigned long) ac; \ + a &= 0xffffffff; \ + a = b + ((a << s) | (a >> (32 - s))); + +#define RND3(a,b,c,d,x,s,ac) \ + a += (b ^ c ^ d) + x + (unsigned long) ac; \ + a &= 0xffffffff; \ + a = b + ((a << s) | (a >> (32 - s))); + +#define RND4(a,b,c,d,x,s,ac) \ + a += (c ^ (b | ~d)) + x + (unsigned long) ac; \ + a &= 0xffffffff; \ + a = b + ((a << s) | (a >> (32 - s))); + +/* Initialize MD5 context + * Accepts: context to initialize + */ + +void md5_init (MD5CONTEXT *ctx) +{ + ctx->clow = ctx->chigh = 0; /* initialize byte count to zero */ + /* initialization constants */ + ctx->state[0] = 0x67452301; ctx->state[1] = 0xefcdab89; + ctx->state[2] = 0x98badcfe; ctx->state[3] = 0x10325476; + ctx->ptr = ctx->buf; /* reset buffer pointer */ +} + + +/* MD5 add data to context + * Accepts: context + * input data + * length of data + */ + +void md5_update (MD5CONTEXT *ctx,unsigned char *data,unsigned long len) +{ + unsigned long i = (ctx->buf + MD5BLKLEN) - ctx->ptr; + /* update double precision number of bytes */ + if ((ctx->clow += len) < len) ctx->chigh++; + while (i <= len) { /* copy/transform data, 64 bytes at a time */ + memcpy (ctx->ptr,data,i); /* fill up 64 byte chunk */ + md5_transform (ctx->state,ctx->ptr = ctx->buf); + data += i,len -= i,i = MD5BLKLEN; + } + memcpy (ctx->ptr,data,len); /* copy final bit of data in buffer */ + ctx->ptr += len; /* update buffer pointer */ +} + +/* MD5 Finalization + * Accepts: destination digest + * context + */ + +void md5_final (unsigned char *digest,MD5CONTEXT *ctx) +{ + unsigned long i,bits[2]; + bits[0] = ctx->clow << 3; /* calculate length in bits (before padding) */ + bits[1] = (ctx->chigh << 3) + (ctx->clow >> 29); + *ctx->ptr++ = 0x80; /* padding byte */ + if ((i = (ctx->buf + MD5BLKLEN) - ctx->ptr) < 8) { + memset (ctx->ptr,0,i); /* pad out buffer with zeros */ + md5_transform (ctx->state,ctx->buf); + /* pad out with zeros, leaving 8 bytes */ + memset (ctx->buf,0,MD5BLKLEN - 8); + ctx->ptr = ctx->buf + MD5BLKLEN - 8; + } + else if (i -= 8) { /* need to pad this buffer? */ + memset (ctx->ptr,0,i); /* yes, pad out with zeros, leaving 8 bytes */ + ctx->ptr += i; + } + md5_encode (ctx->ptr,bits,2); /* make LSB-first length */ + md5_transform (ctx->state,ctx->buf); + /* store state in digest */ + md5_encode (digest,ctx->state,4); + /* erase context */ + memset (ctx,0,sizeof (MD5CONTEXT)); +} + +/* MD5 basic transformation + * Accepts: state vector + * current 64-byte block + */ + +static void md5_transform (unsigned long *state,unsigned char *block) +{ + unsigned long a = state[0],b = state[1],c = state[2],d = state[3],x[16]; + md5_decode (x,block,16); /* decode into 16 longs */ + /* round 1 */ + RND1 (a,b,c,d,x[ 0], 7,0xd76aa478); RND1 (d,a,b,c,x[ 1],12,0xe8c7b756); + RND1 (c,d,a,b,x[ 2],17,0x242070db); RND1 (b,c,d,a,x[ 3],22,0xc1bdceee); + RND1 (a,b,c,d,x[ 4], 7,0xf57c0faf); RND1 (d,a,b,c,x[ 5],12,0x4787c62a); + RND1 (c,d,a,b,x[ 6],17,0xa8304613); RND1 (b,c,d,a,x[ 7],22,0xfd469501); + RND1 (a,b,c,d,x[ 8], 7,0x698098d8); RND1 (d,a,b,c,x[ 9],12,0x8b44f7af); + RND1 (c,d,a,b,x[10],17,0xffff5bb1); RND1 (b,c,d,a,x[11],22,0x895cd7be); + RND1 (a,b,c,d,x[12], 7,0x6b901122); RND1 (d,a,b,c,x[13],12,0xfd987193); + RND1 (c,d,a,b,x[14],17,0xa679438e); RND1 (b,c,d,a,x[15],22,0x49b40821); + /* round 2 */ + RND2 (a,b,c,d,x[ 1], 5,0xf61e2562); RND2 (d,a,b,c,x[ 6], 9,0xc040b340); + RND2 (c,d,a,b,x[11],14,0x265e5a51); RND2 (b,c,d,a,x[ 0],20,0xe9b6c7aa); + RND2 (a,b,c,d,x[ 5], 5,0xd62f105d); RND2 (d,a,b,c,x[10], 9, 0x2441453); + RND2 (c,d,a,b,x[15],14,0xd8a1e681); RND2 (b,c,d,a,x[ 4],20,0xe7d3fbc8); + RND2 (a,b,c,d,x[ 9], 5,0x21e1cde6); RND2 (d,a,b,c,x[14], 9,0xc33707d6); + RND2 (c,d,a,b,x[ 3],14,0xf4d50d87); RND2 (b,c,d,a,x[ 8],20,0x455a14ed); + RND2 (a,b,c,d,x[13], 5,0xa9e3e905); RND2 (d,a,b,c,x[ 2], 9,0xfcefa3f8); + RND2 (c,d,a,b,x[ 7],14,0x676f02d9); RND2 (b,c,d,a,x[12],20,0x8d2a4c8a); + /* round 3 */ + RND3 (a,b,c,d,x[ 5], 4,0xfffa3942); RND3 (d,a,b,c,x[ 8],11,0x8771f681); + RND3 (c,d,a,b,x[11],16,0x6d9d6122); RND3 (b,c,d,a,x[14],23,0xfde5380c); + RND3 (a,b,c,d,x[ 1], 4,0xa4beea44); RND3 (d,a,b,c,x[ 4],11,0x4bdecfa9); + RND3 (c,d,a,b,x[ 7],16,0xf6bb4b60); RND3 (b,c,d,a,x[10],23,0xbebfbc70); + RND3 (a,b,c,d,x[13], 4,0x289b7ec6); RND3 (d,a,b,c,x[ 0],11,0xeaa127fa); + RND3 (c,d,a,b,x[ 3],16,0xd4ef3085); RND3 (b,c,d,a,x[ 6],23, 0x4881d05); + RND3 (a,b,c,d,x[ 9], 4,0xd9d4d039); RND3 (d,a,b,c,x[12],11,0xe6db99e5); + RND3 (c,d,a,b,x[15],16,0x1fa27cf8); RND3 (b,c,d,a,x[ 2],23,0xc4ac5665); + /* round 4 */ + RND4 (a,b,c,d,x[ 0], 6,0xf4292244); RND4 (d,a,b,c,x[ 7],10,0x432aff97); + RND4 (c,d,a,b,x[14],15,0xab9423a7); RND4 (b,c,d,a,x[ 5],21,0xfc93a039); + RND4 (a,b,c,d,x[12], 6,0x655b59c3); RND4 (d,a,b,c,x[ 3],10,0x8f0ccc92); + RND4 (c,d,a,b,x[10],15,0xffeff47d); RND4 (b,c,d,a,x[ 1],21,0x85845dd1); + RND4 (a,b,c,d,x[ 8], 6,0x6fa87e4f); RND4 (d,a,b,c,x[15],10,0xfe2ce6e0); + RND4 (c,d,a,b,x[ 6],15,0xa3014314); RND4 (b,c,d,a,x[13],21,0x4e0811a1); + RND4 (a,b,c,d,x[ 4], 6,0xf7537e82); RND4 (d,a,b,c,x[11],10,0xbd3af235); + RND4 (c,d,a,b,x[ 2],15,0x2ad7d2bb); RND4 (b,c,d,a,x[ 9],21,0xeb86d391); + /* update state */ + state[0] += a; state[1] += b; state[2] += c; state[3] += d; + memset (x,0,sizeof (x)); /* erase sensitive data */ +} + +/* You may wonder why these strange "& 0xff" maskings are here. This is to + * ensure correct results on machines with a char size of larger than 8 bits. + * For example, the KCC compiler on the PDP-10 uses 9-bit chars. + */ + +/* MD5 encode unsigned long into LSB-first bytes + * Accepts: destination pointer + * source + * length of source + */ + +static void md5_encode (unsigned char *dst,unsigned long *src,int len) +{ + int i; + for (i = 0; i < len; i++) { + *dst++ = (unsigned char) (src[i] & 0xff); + *dst++ = (unsigned char) ((src[i] >> 8) & 0xff); + *dst++ = (unsigned char) ((src[i] >> 16) & 0xff); + *dst++ = (unsigned char) ((src[i] >> 24) & 0xff); + } +} + + +/* MD5 decode LSB-first bytes into unsigned long + * Accepts: destination pointer + * source + * length of destination + */ + +static void md5_decode (unsigned long *dst,unsigned char *src,int len) +{ + int i, j; + for (i = 0, j = 0; i < len; i++, j += 4) + dst[i] = ((unsigned long) (src[j] & 0xff)) | + (((unsigned long) (src[j+1] & 0xff)) << 8) | + (((unsigned long) (src[j+2] & 0xff)) << 16) | + (((unsigned long) (src[j+3] & 0xff)) << 24); +} diff --git a/imap/src/c-client/auth_pla.c b/imap/src/c-client/auth_pla.c new file mode 100644 index 00000000..73ca2259 --- /dev/null +++ b/imap/src/c-client/auth_pla.c @@ -0,0 +1,133 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Plain authenticator + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 22 September 1998 + * Last Edited: 30 August 2006 + */ + +long auth_plain_client (authchallenge_t challenger,authrespond_t responder, + char *service,NETMBX *mb,void *stream, + unsigned long *trial,char *user); +char *auth_plain_server (authresponse_t responder,int argc,char *argv[]); + +AUTHENTICATOR auth_pla = { + AU_AUTHUSER | AU_HIDE, /* allow authuser, hidden */ + "PLAIN", /* authenticator name */ + NIL, /* always valid */ + auth_plain_client, /* client method */ + auth_plain_server, /* server method */ + NIL /* next authenticator */ +}; + +/* Client authenticator + * Accepts: challenger function + * responder function + * SASL service name + * parsed network mailbox structure + * stream argument for functions + * pointer to current trial count + * returned user name + * Returns: T if success, NIL otherwise, number of trials incremented if retry + */ + +long auth_plain_client (authchallenge_t challenger,authrespond_t responder, + char *service,NETMBX *mb,void *stream, + unsigned long *trial,char *user) +{ + char *u,pwd[MAILTMPLEN]; + void *challenge; + unsigned long clen; + long ret = NIL; + /* snarl if not SSL/TLS session */ + if (!mb->sslflag && !mb->tlsflag) + mm_log ("SECURITY PROBLEM: insecure server advertised AUTH=PLAIN",WARN); + /* get initial (empty) challenge */ + if (challenge = (*challenger) (stream,&clen)) { + fs_give ((void **) &challenge); + if (clen) { /* abort if challenge non-empty */ + mm_log ("Server bug: non-empty initial PLAIN challenge",WARN); + (*responder) (stream,NIL,0); + ret = LONGT; /* will get a BAD response back */ + } + pwd[0] = NIL; /* prompt user if empty challenge */ + mm_login (mb,user,pwd,*trial); + if (!pwd[0]) { /* empty challenge or user requested abort */ + (*responder) (stream,NIL,0); + *trial = 0; /* cancel subsequent attempts */ + ret = LONGT; /* will get a BAD response back */ + } + else { + unsigned long rlen = + strlen (mb->authuser) + strlen (user) + strlen (pwd) + 2; + char *response = (char *) fs_get (rlen); + char *t = response; /* copy authorization id */ + if (mb->authuser[0]) for (u = user; *u; *t++ = *u++); + *t++ = '\0'; /* delimiting NUL */ + /* copy authentication id */ + for (u = mb->authuser[0] ? mb->authuser : user; *u; *t++ = *u++); + *t++ = '\0'; /* delimiting NUL */ + /* copy password */ + for (u = pwd; *u; *t++ = *u++); + /* send credentials */ + if ((*responder) (stream,response,rlen)) { + if (challenge = (*challenger) (stream,&clen)) + fs_give ((void **) &challenge); + else { + ++*trial; /* can try again if necessary */ + ret = LONGT; /* check the authentication */ + } + } + memset (response,0,rlen); /* erase credentials */ + fs_give ((void **) &response); + } + } + memset (pwd,0,MAILTMPLEN); /* erase password */ + if (!ret) *trial = 65535; /* don't retry if bad protocol */ + return ret; +} + +/* Server authenticator + * Accepts: responder function + * argument count + * argument vector + * Returns: authenticated user name or NIL + */ + +char *auth_plain_server (authresponse_t responder,int argc,char *argv[]) +{ + char *ret = NIL; + char *user,*aid,*pass; + unsigned long len; + /* get user name */ + if (aid = (*responder) ("",0,&len)) { + /* note: responders null-terminate */ + if ((((unsigned long) ((user = aid + strlen (aid) + 1) - aid)) < len) && + (((unsigned long) ((pass = user + strlen (user) + 1) - aid)) < len) && + (((unsigned long) ((pass + strlen (pass)) - aid)) == len) && + (*aid ? server_login (aid,pass,user,argc,argv) : + server_login (user,pass,NIL,argc,argv))) ret = myusername (); + fs_give ((void **) &aid); + } + return ret; +} diff --git a/imap/src/c-client/c-client.h b/imap/src/c-client/c-client.h new file mode 100644 index 00000000..7bf3710a --- /dev/null +++ b/imap/src/c-client/c-client.h @@ -0,0 +1,55 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: c-client master include for application programs + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 19 May 2000 + * Last Edited: 6 December 2006 + */ + +#ifndef __CCLIENT_H /* nobody should include this twice... */ +#define __CCLIENT_H + +#ifdef __cplusplus /* help out people who use C++ compilers */ +extern "C" { + /* If you use gcc, you may also have to use -fno-operator-names */ +#define private cclientPrivate /* private to c-client */ +#define and cclientAnd /* C99 doesn't realize that ISO 646 is dead */ +#define or cclientOr +#define not cclientNot +#endif + +#include "mail.h" /* primary interfaces */ +#include "osdep.h" /* OS-dependent routines */ +#include "rfc822.h" /* RFC822 and MIME routines */ +#include "smtp.h" /* SMTP sending routines */ +#include "nntp.h" /* NNTP sending routines */ +#include "utf8.h" /* Unicode and charset routines */ +#include "utf8aux.h" /* Unicode auxillary routines */ +#include "misc.h" /* miscellaneous utility routines */ + +#ifdef __cplusplus /* undo the C++ mischief */ +#undef private +} +#endif + +#endif diff --git a/imap/src/c-client/env.h b/imap/src/c-client/env.h new file mode 100644 index 00000000..d03e7e63 --- /dev/null +++ b/imap/src/c-client/env.h @@ -0,0 +1,45 @@ +/* ======================================================================== + * Copyright 1988-2008 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Environment routines + * + * Author: Mark Crispin + * UW Technology + * University of Washington + * Seattle, WA 98195 + * Internet: MRC@Washington.EDU + * + * Date: 1 August 1988 + * Last Edited: 13 February 2008 + */ + +/* Function prototypes */ + +long pmatch_full (unsigned char *s,unsigned char *pat,unsigned char delim); +long dmatch (unsigned char *s,unsigned char *pat,unsigned char delim); +void *env_parameters (long function,void *value); +void rfc822_date (char *date); +void rfc822_timezone (char *s,void *t); +void internal_date (char *date); +long server_input_wait (long seconds); +void server_init (char *server,char *service,char *sasl, + void *clkint,void *kodint,void *hupint,void *trmint, + void *staint); +long server_login (char *user,char *pass,char *authuser,int argc,char *argv[]); +long authserver_login (char *user,char *authuser,int argc,char *argv[]); +long anonymous_login (int argc,char *argv[]); +char *mylocalhost (void); +char *myhomedir (void); +char *mailboxfile (char *dst,char *name); +MAILSTREAM *default_proto (long type); diff --git a/imap/src/c-client/flstring.c b/imap/src/c-client/flstring.c new file mode 100644 index 00000000..bbdbe3c8 --- /dev/null +++ b/imap/src/c-client/flstring.c @@ -0,0 +1,91 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: File string routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 15 April 1997 + * Last Edited: 6 December 2006 + */ + + +#include <stdio.h> +#include "mail.h" +#include "flstring.h" + +/* String driver for stdio file strings */ + +static void file_string_init (STRING *s,void *data,unsigned long size); +static char file_string_next (STRING *s); +static void file_string_setpos (STRING *s,unsigned long i); + +STRINGDRIVER file_string = { + file_string_init, /* initialize string structure */ + file_string_next, /* get next byte in string structure */ + file_string_setpos /* set position in string structure */ +}; + + +/* Initialize mail string structure for file + * Accepts: string structure + * pointer to string + * size of string + */ + +static void file_string_init (STRING *s,void *data,unsigned long size) +{ + s->data = data; /* note file descriptor */ + /* big enough for one byte */ + s->chunk = s->curpos = (char *) &s->data1; + s->size = size; /* data size */ + s->cursize = s->chunksize = 1;/* always call stdio */ + file_string_setpos (s,0); /* initial offset is 0 */ +} + + +/* Get next character from string + * Accepts: string structure + * Returns: character, string structure chunk refreshed + */ + +static char file_string_next (STRING *s) +{ + char ret = *s->curpos; + s->offset++; /* advance position */ + s->cursize = 1; /* reset size */ + *s->curpos = (char) getc ((FILE *) s->data); + return ret; +} + + +/* Set string pointer position + * Accepts: string structure + * new position + */ + +static void file_string_setpos (STRING *s,unsigned long i) +{ + s->offset = i; /* note new offset */ + fseek ((FILE *) s->data,i,SEEK_SET); + /* in case using returnstringstruct hack */ + s->chunk = s->curpos = (char *) &s->data1; + *s->curpos = (char) getc ((FILE *) s->data); +} diff --git a/imap/src/c-client/flstring.h b/imap/src/c-client/flstring.h new file mode 100644 index 00000000..be6a831c --- /dev/null +++ b/imap/src/c-client/flstring.h @@ -0,0 +1,30 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: File string routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 15 April 1997 + * Last Edited: 30 August 2006 + */ + + +extern STRINGDRIVER file_string; diff --git a/imap/src/c-client/fs.h b/imap/src/c-client/fs.h new file mode 100644 index 00000000..7bf32136 --- /dev/null +++ b/imap/src/c-client/fs.h @@ -0,0 +1,34 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Free storage management routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 1 August 1988 + * Last Edited: 30 August 2006 + */ + + +/* Function prototypes */ + +void *fs_get (size_t size); +void fs_resize (void **block,size_t size); +void fs_give (void **block); diff --git a/imap/src/c-client/ftl.h b/imap/src/c-client/ftl.h new file mode 100644 index 00000000..59348ad5 --- /dev/null +++ b/imap/src/c-client/ftl.h @@ -0,0 +1,32 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Crash management routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 1 August 1988 + * Last Edited: 30 August 2006 + */ + + +/* Function prototypes */ + +void fatal (char *string); diff --git a/imap/src/c-client/imap4r1.c b/imap/src/c-client/imap4r1.c new file mode 100644 index 00000000..1409b37d --- /dev/null +++ b/imap/src/c-client/imap4r1.c @@ -0,0 +1,5672 @@ +/* ======================================================================== + * Copyright 1988-2008 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Interactive Message Access Protocol 4rev1 (IMAP4R1) routines + * + * Author: Mark Crispin + * UW Technology + * University of Washington + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 15 June 1988 + * Last Edited: 8 May 2008 + * + * This original version of this file is + * Copyright 1988 Stanford University + * and was developed in the Symbolic Systems Resources Group of the Knowledge + * Systems Laboratory at Stanford University in 1987-88, and was funded by the + * Biomedical Research Technology Program of the National Institutes of Health + * under grant number RR-00785. + */ + + +#include <ctype.h> +#include <stdio.h> +#include <time.h> +#include "c-client.h" +#include "imap4r1.h" + +/* Parameters */ + +#define IMAPLOOKAHEAD 20 /* envelope lookahead */ +#define IMAPUIDLOOKAHEAD 1000 /* UID lookahead */ +#define IMAPTCPPORT (long) 143 /* assigned TCP contact port */ +#define IMAPSSLPORT (long) 993 /* assigned SSL TCP contact port */ +#define MAXCOMMAND 1000 /* RFC 2683 guideline for cmd line length */ +#define IDLETIMEOUT (long) 30 /* defined in RFC 3501 */ +#define MAXSERVERLIT 0x7ffffffe /* maximum server literal size + * must be smaller than 4294967295 + */ + + +/* Parsed reply message from imap_reply */ + +typedef struct imap_parsed_reply { + unsigned char *line; /* original reply string pointer */ + unsigned char *tag; /* command tag this reply is for */ + unsigned char *key; /* reply keyword */ + unsigned char *text; /* subsequent text */ +} IMAPPARSEDREPLY; + + +#define IMAPTMPLEN 16*MAILTMPLEN + + +/* IMAP4 I/O stream local data */ + +typedef struct imap_local { + NETSTREAM *netstream; /* TCP I/O stream */ + IMAPPARSEDREPLY reply; /* last parsed reply */ + MAILSTATUS *stat; /* status to fill in */ + IMAPCAP cap; /* server capabilities */ + char *appendmailbox; /* mailbox being appended to */ + unsigned int uidsearch : 1; /* UID searching */ + unsigned int byeseen : 1; /* saw a BYE response */ + /* got implicit capabilities */ + unsigned int gotcapability : 1; + unsigned int sensitive : 1; /* sensitive data in progress */ + unsigned int tlsflag : 1; /* TLS session */ + unsigned int tlssslv23 : 1; /* TLS using SSLv23 client method */ + unsigned int notlsflag : 1; /* TLS not used in session */ + unsigned int sslflag : 1; /* SSL session */ + unsigned int novalidate : 1; /* certificate not validated */ + unsigned int filter : 1; /* filter SEARCH/SORT/THREAD results */ + unsigned int loser : 1; /* server is a loser */ + unsigned int saslcancel : 1; /* SASL cancelled by protocol */ + long authflags; /* required flags for authenticators */ + unsigned long sortsize; /* sort return data size */ + unsigned long *sortdata; /* sort return data */ + struct { + unsigned long uid; /* last UID returned */ + unsigned long msgno; /* last msgno returned */ + } lastuid; + NAMESPACE **namespace; /* namespace return data */ + THREADNODE *threaddata; /* thread return data */ + char *referral; /* last referral */ + char *prefix; /* find prefix */ + char *user; /* logged-in user */ + char *reform; /* reformed sequence */ + char tmp[IMAPTMPLEN]; /* temporary buffer */ + SEARCHSET *lookahead; /* fetch lookahead */ +} IMAPLOCAL; + + +/* Convenient access to local data */ + +#define LOCAL ((IMAPLOCAL *) stream->local) + +/* Arguments to imap_send() */ + +typedef struct imap_argument { + int type; /* argument type */ + void *text; /* argument text */ +} IMAPARG; + + +/* imap_send() argument types */ + +#define ATOM 0 +#define NUMBER 1 +#define FLAGS 2 +#define ASTRING 3 +#define LITERAL 4 +#define LIST 5 +#define SEARCHPROGRAM 6 +#define SORTPROGRAM 7 +#define BODYTEXT 8 +#define BODYPEEK 9 +#define BODYCLOSE 10 +#define SEQUENCE 11 +#define LISTMAILBOX 12 +#define MULTIAPPEND 13 +#define SNLIST 14 +#define MULTIAPPENDREDO 15 + + +/* Append data */ + +typedef struct append_data { + append_t af; + void *data; + char *flags; + char *date; + STRING *message; +} APPENDDATA; + +/* Function prototypes */ + +DRIVER *imap_valid (char *name); +void *imap_parameters (long function,void *value); +void imap_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); +void imap_list (MAILSTREAM *stream,char *ref,char *pat); +void imap_lsub (MAILSTREAM *stream,char *ref,char *pat); +void imap_list_work (MAILSTREAM *stream,char *cmd,char *ref,char *pat, + char *contents); +long imap_subscribe (MAILSTREAM *stream,char *mailbox); +long imap_unsubscribe (MAILSTREAM *stream,char *mailbox); +long imap_create (MAILSTREAM *stream,char *mailbox); +long imap_delete (MAILSTREAM *stream,char *mailbox); +long imap_rename (MAILSTREAM *stream,char *old,char *newname); +long imap_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2); +long imap_status (MAILSTREAM *stream,char *mbx,long flags); +MAILSTREAM *imap_open (MAILSTREAM *stream); +IMAPPARSEDREPLY *imap_rimap (MAILSTREAM *stream,char *service,NETMBX *mb, + char *usr,char *tmp); +long imap_anon (MAILSTREAM *stream,char *tmp); +long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr); +long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr); +void *imap_challenge (void *stream,unsigned long *len); +long imap_response (void *stream,char *s,unsigned long size); +void imap_close (MAILSTREAM *stream,long options); +void imap_fast (MAILSTREAM *stream,char *sequence,long flags); +void imap_flags (MAILSTREAM *stream,char *sequence,long flags); +long imap_overview (MAILSTREAM *stream,overview_t ofn); +ENVELOPE *imap_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body, + long flags); +long imap_msgdata (MAILSTREAM *stream,unsigned long msgno,char *section, + unsigned long first,unsigned long last,STRINGLIST *lines, + long flags); +unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno); +unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid); +void imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags); +long imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags); +unsigned long *imap_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, + SORTPGM *pgm,long flags); +THREADNODE *imap_thread (MAILSTREAM *stream,char *type,char *charset, + SEARCHPGM *spg,long flags); +THREADNODE *imap_thread_work (MAILSTREAM *stream,char *type,char *charset, + SEARCHPGM *spg,long flags); +long imap_ping (MAILSTREAM *stream); +void imap_check (MAILSTREAM *stream); +long imap_expunge (MAILSTREAM *stream,char *sequence,long options); +long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); +long imap_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); +long imap_append_referral (char *mailbox,char *tmp,append_t af,void *data, + char *flags,char *date,STRING *message, + APPENDDATA *map,long options); +IMAPPARSEDREPLY *imap_append_single (MAILSTREAM *stream,char *mailbox, + char *flags,char *date,STRING *message); + +void imap_gc (MAILSTREAM *stream,long gcflags); +void imap_gc_body (BODY *body); +void imap_capability (MAILSTREAM *stream); +long imap_acl_work (MAILSTREAM *stream,char *command,IMAPARG *args[]); + +IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,IMAPARG *args[]); +IMAPPARSEDREPLY *imap_sout (MAILSTREAM *stream,char *tag,char *base,char **s); +long imap_soutr (MAILSTREAM *stream,char *string); +IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s, + SIZEDTEXT *as,long wildok,char *limit); +IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s, + STRING *st); +IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char *base, + char **s,SEARCHPGM *pgm,char *limit); +char *imap_send_spgm_trim (char *base,char *s,char *text); +IMAPPARSEDREPLY *imap_send_sset (MAILSTREAM *stream,char *tag,char *base, + char **s,SEARCHSET *set,char *prefix, + char *limit); +IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char *base, + char **s,char *name,STRINGLIST *list, + char *limit); +void imap_send_sdate (char **s,char *name,unsigned short date); +IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag); +IMAPPARSEDREPLY *imap_parse_reply (MAILSTREAM *stream,char *text); +IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text); +long imap_OK (MAILSTREAM *stream,IMAPPARSEDREPLY *reply); +void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply); +void imap_parse_response (MAILSTREAM *stream,char *text,long errflg,long ntfy); +NAMESPACE *imap_parse_namespace (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply); +THREADNODE *imap_parse_thread (MAILSTREAM *stream,unsigned char **txtptr); +void imap_parse_header (MAILSTREAM *stream,ENVELOPE **env,SIZEDTEXT *hdr, + STRINGLIST *stl); +void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env, + unsigned char **txtptr,IMAPPARSEDREPLY *reply); +ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply); +ADDRESS *imap_parse_address (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply); +void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt, + unsigned char **txtptr); +unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag); +unsigned char *imap_parse_astring (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply,unsigned long *len); +unsigned char *imap_parse_string (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply,GETS_DATA *md, + unsigned long *len,long flags); +void imap_parse_body (GETS_DATA *md,char *seg,unsigned char **txtptr, + IMAPPARSEDREPLY *reply); +void imap_parse_body_structure (MAILSTREAM *stream,BODY *body, + unsigned char **txtptr,IMAPPARSEDREPLY *reply); +PARAMETER *imap_parse_body_parameter (MAILSTREAM *stream, + unsigned char **txtptr, + IMAPPARSEDREPLY *reply); +void imap_parse_disposition (MAILSTREAM *stream,BODY *body, + unsigned char **txtptr,IMAPPARSEDREPLY *reply); +STRINGLIST *imap_parse_language (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply); +STRINGLIST *imap_parse_stringlist (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply); +void imap_parse_extension (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply); +void imap_parse_capabilities (MAILSTREAM *stream,char *t); +IMAPPARSEDREPLY *imap_fetch (MAILSTREAM *stream,char *sequence,long flags); +char *imap_reform_sequence (MAILSTREAM *stream,char *sequence,long flags); + +/* Driver dispatch used by MAIL */ + +DRIVER imapdriver = { + "imap", /* driver name */ + /* driver flags */ + DR_MAIL|DR_NEWS|DR_NAMESPACE|DR_CRLF|DR_RECYCLE|DR_HALFOPEN, + (DRIVER *) NIL, /* next driver */ + imap_valid, /* mailbox is valid for us */ + imap_parameters, /* manipulate parameters */ + imap_scan, /* scan mailboxes */ + imap_list, /* find mailboxes */ + imap_lsub, /* find subscribed mailboxes */ + imap_subscribe, /* subscribe to mailbox */ + imap_unsubscribe, /* unsubscribe from mailbox */ + imap_create, /* create mailbox */ + imap_delete, /* delete mailbox */ + imap_rename, /* rename mailbox */ + imap_status, /* status of mailbox */ + imap_open, /* open mailbox */ + imap_close, /* close mailbox */ + imap_fast, /* fetch message "fast" attributes */ + imap_flags, /* fetch message flags */ + imap_overview, /* fetch overview */ + imap_structure, /* fetch message envelopes */ + NIL, /* fetch message header */ + NIL, /* fetch message body */ + imap_msgdata, /* fetch partial message */ + imap_uid, /* unique identifier */ + imap_msgno, /* message number */ + imap_flag, /* modify flags */ + NIL, /* per-message modify flags */ + imap_search, /* search for message based on criteria */ + imap_sort, /* sort messages */ + imap_thread, /* thread messages */ + imap_ping, /* ping mailbox to see if still alive */ + imap_check, /* check for new messages */ + imap_expunge, /* expunge deleted messages */ + imap_copy, /* copy messages to another mailbox */ + imap_append, /* append string message to mailbox */ + imap_gc /* garbage collect stream */ +}; + + /* prototype stream */ +MAILSTREAM imapproto = {&imapdriver}; + + /* driver parameters */ +static unsigned long imap_maxlogintrials = MAXLOGINTRIALS; +static long imap_lookahead = IMAPLOOKAHEAD; +static long imap_uidlookahead = IMAPUIDLOOKAHEAD; +static long imap_fetchlookaheadlimit = IMAPLOOKAHEAD; +static long imap_defaultport = 0; +static long imap_sslport = 0; +static long imap_tryssl = NIL; +static long imap_prefetch = IMAPLOOKAHEAD; +static long imap_closeonerror = NIL; +static imapenvelope_t imap_envelope = NIL; +static imapreferral_t imap_referral = NIL; +static char *imap_extrahdrs = NIL; + + /* constants */ +static char *hdrheader[] = { + "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-MD5 Content-Disposition Content-Language Content-Location", + "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Disposition Content-Language Content-Location", + "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Language Content-Location", + "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Location", + "BODY.PEEK[HEADER.FIELDS (Newsgroups" +}; +static char *hdrtrailer ="Followup-To References)]"; + +/* IMAP validate mailbox + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *imap_valid (char *name) +{ + return mail_valid_net (name,&imapdriver,NIL,NIL); +} + + +/* IMAP manipulate driver parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *imap_parameters (long function,void *value) +{ + switch ((int) function) { + case GET_NAMESPACE: + if (((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->cap.namespace && + !((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->namespace) + imap_send (((MAILSTREAM *) value),"NAMESPACE",NIL); + value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->namespace; + break; + case GET_THREADERS: + value = (void *) + ((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->cap.threader; + break; + case SET_FETCHLOOKAHEAD: /* must use pointer from GET_FETCHLOOKAHEAD */ + fatal ("SET_FETCHLOOKAHEAD not permitted"); + case GET_FETCHLOOKAHEAD: + value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->lookahead; + break; + case SET_MAXLOGINTRIALS: + imap_maxlogintrials = (long) value; + break; + case GET_MAXLOGINTRIALS: + value = (void *) imap_maxlogintrials; + break; + case SET_LOOKAHEAD: + imap_lookahead = (long) value; + break; + case GET_LOOKAHEAD: + value = (void *) imap_lookahead; + break; + case SET_UIDLOOKAHEAD: + imap_uidlookahead = (long) value; + break; + case GET_UIDLOOKAHEAD: + value = (void *) imap_uidlookahead; + break; + + case SET_IMAPPORT: + imap_defaultport = (long) value; + break; + case GET_IMAPPORT: + value = (void *) imap_defaultport; + break; + case SET_SSLIMAPPORT: + imap_sslport = (long) value; + break; + case GET_SSLIMAPPORT: + value = (void *) imap_sslport; + break; + case SET_PREFETCH: + imap_prefetch = (long) value; + break; + case GET_PREFETCH: + value = (void *) imap_prefetch; + break; + case SET_CLOSEONERROR: + imap_closeonerror = (long) value; + break; + case GET_CLOSEONERROR: + value = (void *) imap_closeonerror; + break; + case SET_IMAPENVELOPE: + imap_envelope = (imapenvelope_t) value; + break; + case GET_IMAPENVELOPE: + value = (void *) imap_envelope; + break; + case SET_IMAPREFERRAL: + imap_referral = (imapreferral_t) value; + break; + case GET_IMAPREFERRAL: + value = (void *) imap_referral; + break; + case SET_IMAPEXTRAHEADERS: + imap_extrahdrs = (char *) value; + break; + case GET_IMAPEXTRAHEADERS: + value = (void *) imap_extrahdrs; + break; + case SET_IMAPTRYSSL: + imap_tryssl = (long) value; + break; + case GET_IMAPTRYSSL: + value = (void *) imap_tryssl; + break; + case SET_FETCHLOOKAHEADLIMIT: + imap_fetchlookaheadlimit = (long) value; + break; + case GET_FETCHLOOKAHEADLIMIT: + value = (void *) imap_fetchlookaheadlimit; + break; + + case SET_IDLETIMEOUT: + fatal ("SET_IDLETIMEOUT not permitted"); + case GET_IDLETIMEOUT: + value = (void *) IDLETIMEOUT; + break; + default: + value = NIL; /* error case */ + break; + } + return value; +} + +/* IMAP scan mailboxes + * Accepts: mail stream + * reference + * pattern to search + * string to scan + */ + +void imap_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) +{ + imap_list_work (stream,"SCAN",ref,pat,contents); +} + + +/* IMAP list mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void imap_list (MAILSTREAM *stream,char *ref,char *pat) +{ + imap_list_work (stream,"LIST",ref,pat,NIL); +} + + +/* IMAP list subscribed mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void imap_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + void *sdb = NIL; + char *s,mbx[MAILTMPLEN]; + /* do it on the server */ + imap_list_work (stream,"LSUB",ref,pat,NIL); + if (*pat == '{') { /* if remote pattern, must be IMAP */ + if (!imap_valid (pat)) return; + ref = NIL; /* good IMAP pattern, punt reference */ + } + /* if remote reference, must be valid IMAP */ + if (ref && (*ref == '{') && !imap_valid (ref)) return; + /* kludgy application of reference */ + if (ref && *ref) sprintf (mbx,"%s%s",ref,pat); + else strcpy (mbx,pat); + + if (s = sm_read (&sdb)) do if (imap_valid (s) && pmatch (s,mbx)) + mm_lsub (stream,NIL,s,NIL); + while (s = sm_read (&sdb)); /* until no more subscriptions */ +} + +/* IMAP find list of mailboxes + * Accepts: mail stream + * list command + * reference + * pattern to search + * string to scan + */ + +void imap_list_work (MAILSTREAM *stream,char *cmd,char *ref,char *pat, + char *contents) +{ + MAILSTREAM *st = stream; + int pl; + char *s,prefix[MAILTMPLEN],mbx[MAILTMPLEN]; + IMAPARG *args[4],aref,apat,acont; + if (ref && *ref) { /* have a reference? */ + if (!(imap_valid (ref) && /* make sure valid IMAP name and open stream */ + ((stream && LOCAL && LOCAL->netstream) || + (stream = mail_open (NIL,ref,OP_HALFOPEN|OP_SILENT))))) return; + /* calculate prefix length */ + pl = strchr (ref,'}') + 1 - ref; + strncpy (prefix,ref,pl); /* build prefix */ + prefix[pl] = '\0'; /* tie off prefix */ + ref += pl; /* update reference */ + } + else { + if (!(imap_valid (pat) && /* make sure valid IMAP name and open stream */ + ((stream && LOCAL && LOCAL->netstream) || + (stream = mail_open (NIL,pat,OP_HALFOPEN|OP_SILENT))))) return; + /* calculate prefix length */ + pl = strchr (pat,'}') + 1 - pat; + strncpy (prefix,pat,pl); /* build prefix */ + prefix[pl] = '\0'; /* tie off prefix */ + pat += pl; /* update reference */ + } + LOCAL->prefix = prefix; /* note prefix */ + if (contents) { /* want to do a scan? */ + if (LEVELSCAN (stream)) { /* make sure permitted */ + args[0] = &aref; args[1] = &apat; args[2] = &acont; args[3] = NIL; + aref.type = ASTRING; aref.text = (void *) (ref ? ref : ""); + apat.type = LISTMAILBOX; apat.text = (void *) pat; + acont.type = ASTRING; acont.text = (void *) contents; + imap_send (stream,cmd,args); + } + else mm_log ("Scan not valid on this IMAP server",ERROR); + } + + else if (LEVELIMAP4 (stream)){/* easy if IMAP4 */ + args[0] = &aref; args[1] = &apat; args[2] = NIL; + aref.type = ASTRING; aref.text = (void *) (ref ? ref : ""); + apat.type = LISTMAILBOX; apat.text = (void *) pat; + /* referrals armed? */ + if (LOCAL->cap.mbx_ref && mail_parameters (stream,GET_IMAPREFERRAL,NIL)) { + /* yes, convert LIST -> RLIST */ + if (!compare_cstring (cmd,"LIST")) cmd = "RLIST"; + /* and convert LSUB -> RLSUB */ + else if (!compare_cstring (cmd,"LSUB")) cmd = "RLSUB"; + } + imap_send (stream,cmd,args); + } + else if (LEVEL1176 (stream)) {/* convert to IMAP2 format wildcard */ + /* kludgy application of reference */ + if (ref && *ref) sprintf (mbx,"%s%s",ref,pat); + else strcpy (mbx,pat); + for (s = mbx; *s; s++) if (*s == '%') *s = '*'; + args[0] = &apat; args[1] = NIL; + apat.type = LISTMAILBOX; apat.text = (void *) mbx; + if (!(strstr (cmd,"LIST") &&/* if list, try IMAP2bis, then RFC-1176 */ + strcmp (imap_send (stream,"FIND ALL.MAILBOXES",args)->key,"BAD")) && + !strcmp (imap_send (stream,"FIND MAILBOXES",args)->key,"BAD")) + LOCAL->cap.rfc1176 = NIL; /* must be RFC-1064 */ + } + LOCAL->prefix = NIL; /* no more prefix */ + /* close temporary stream if we made one */ + if (stream != st) mail_close (stream); +} + +/* IMAP subscribe to mailbox + * Accepts: mail stream + * mailbox to add to subscription list + * Returns: T on success, NIL on failure + */ + +long imap_subscribe (MAILSTREAM *stream,char *mailbox) +{ + MAILSTREAM *st = stream; + long ret = ((stream && LOCAL && LOCAL->netstream) || + (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT))) ? + imap_manage (stream,mailbox,LEVELIMAP4 (stream) ? + "Subscribe" : "Subscribe Mailbox",NIL) : NIL; + /* toss out temporary stream */ + if (st != stream) mail_close (stream); + return ret; +} + + +/* IMAP unsubscribe to mailbox + * Accepts: mail stream + * mailbox to delete from manage list + * Returns: T on success, NIL on failure + */ + +long imap_unsubscribe (MAILSTREAM *stream,char *mailbox) +{ + MAILSTREAM *st = stream; + long ret = ((stream && LOCAL && LOCAL->netstream) || + (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT))) ? + imap_manage (stream,mailbox,LEVELIMAP4 (stream) ? + "Unsubscribe" : "Unsubscribe Mailbox",NIL) : NIL; + /* toss out temporary stream */ + if (st != stream) mail_close (stream); + return ret; +} + +/* IMAP create mailbox + * Accepts: mail stream + * mailbox name to create + * Returns: T on success, NIL on failure + */ + +long imap_create (MAILSTREAM *stream,char *mailbox) +{ + return imap_manage (stream,mailbox,"Create",NIL); +} + + +/* IMAP delete mailbox + * Accepts: mail stream + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long imap_delete (MAILSTREAM *stream,char *mailbox) +{ + return imap_manage (stream,mailbox,"Delete",NIL); +} + + +/* IMAP rename mailbox + * Accepts: mail stream + * old mailbox name + * new mailbox name + * Returns: T on success, NIL on failure + */ + +long imap_rename (MAILSTREAM *stream,char *old,char *newname) +{ + return imap_manage (stream,old,"Rename",newname); +} + +/* IMAP manage a mailbox + * Accepts: mail stream + * mailbox to manipulate + * command to execute + * optional second argument + * Returns: T on success, NIL on failure + */ + +long imap_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2) +{ + MAILSTREAM *st = stream; + IMAPPARSEDREPLY *reply; + long ret = NIL; + char mbx[MAILTMPLEN],mbx2[MAILTMPLEN]; + IMAPARG *args[3],ambx,amb2; + imapreferral_t ir = + (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL); + ambx.type = amb2.type = ASTRING; ambx.text = (void *) mbx; + amb2.text = (void *) mbx2; + args[0] = &ambx; args[1] = args[2] = NIL; + /* require valid names and open stream */ + if (mail_valid_net (mailbox,&imapdriver,NIL,mbx) && + (arg2 ? mail_valid_net (arg2,&imapdriver,NIL,mbx2) : &imapdriver) && + ((stream && LOCAL && LOCAL->netstream) || + (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT)))) { + if (arg2) args[1] = &amb2; /* second arg present? */ + if (!(ret = (imap_OK (stream,reply = imap_send (stream,command,args)))) && + ir && LOCAL->referral) { + long code = -1; + switch (*command) { /* which command was it? */ + case 'S': code = REFSUBSCRIBE; break; + case 'U': code = REFUNSUBSCRIBE; break; + case 'C': code = REFCREATE; break; + case 'D': code = REFDELETE; break; + case 'R': code = REFRENAME; break; + default: + fatal ("impossible referral command"); + } + if ((code >= 0) && (mailbox = (*ir) (stream,LOCAL->referral,code))) + ret = imap_manage (NIL,mailbox,command,(*command == 'R') ? + (mailbox + strlen (mailbox) + 1) : NIL); + } + mm_log (reply->text,ret ? NIL : ERROR); + /* toss out temporary stream */ + if (st != stream) mail_close (stream); + } + return ret; +} + +/* IMAP status + * Accepts: mail stream + * mailbox name + * status flags + * Returns: T on success, NIL on failure + */ + +long imap_status (MAILSTREAM *stream,char *mbx,long flags) +{ + IMAPARG *args[3],ambx,aflg; + char tmp[MAILTMPLEN]; + NETMBX mb; + unsigned long i; + long ret = NIL; + MAILSTREAM *tstream = NIL; + /* use given stream if (rev1 or halfopen) and + right host */ + if (!((stream && (LEVELIMAP4rev1 (stream) || stream->halfopen) && + mail_usable_network_stream (stream,mbx)) || + (stream = tstream = mail_open (NIL,mbx,OP_HALFOPEN|OP_SILENT)))) + return NIL; + /* parse mailbox name */ + mail_valid_net_parse (mbx,&mb); + args[0] = &ambx;args[1] = NIL;/* set up first argument as mailbox */ + ambx.type = ASTRING; ambx.text = (void *) mb.mailbox; + if (LEVELIMAP4rev1 (stream)) {/* have STATUS command? */ + imapreferral_t ir; + aflg.type = FLAGS; aflg.text = (void *) tmp; + args[1] = &aflg; args[2] = NIL; + tmp[0] = tmp[1] = '\0'; /* build flag list */ + if (flags & SA_MESSAGES) strcat (tmp," MESSAGES"); + if (flags & SA_RECENT) strcat (tmp," RECENT"); + if (flags & SA_UNSEEN) strcat (tmp," UNSEEN"); + if (flags & SA_UIDNEXT) strcat (tmp," UIDNEXT"); + if (flags & SA_UIDVALIDITY) strcat (tmp," UIDVALIDITY"); + tmp[0] = '('; + strcat (tmp,")"); + /* send "STATUS mailbox flag" */ + if (imap_OK (stream,imap_send (stream,"STATUS",args))) ret = T; + else if ((ir = (imapreferral_t) + mail_parameters (stream,GET_IMAPREFERRAL,NIL)) && + LOCAL->referral && + (mbx = (*ir) (stream,LOCAL->referral,REFSTATUS))) + ret = imap_status (NIL,mbx,flags | (stream->debug ? SA_DEBUG : NIL)); + } + + /* IMAP2 way */ + else if (imap_OK (stream,imap_send (stream,"EXAMINE",args))) { + MAILSTATUS status; + status.flags = flags & ~ (SA_UIDNEXT | SA_UIDVALIDITY); + status.messages = stream->nmsgs; + status.recent = stream->recent; + status.unseen = 0; + if (flags & SA_UNSEEN) { /* must search to get unseen messages */ + /* clear search vector */ + for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = NIL; + if (imap_OK (stream,imap_send (stream,"SEARCH UNSEEN",NIL))) + for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++) + if (mail_elt (stream,i)->searched) status.unseen++; + } + strcpy (strchr (strcpy (tmp,stream->mailbox),'}') + 1,mb.mailbox); + /* pass status to main program */ + mm_status (stream,tmp,&status); + ret = T; /* note success */ + } + if (tstream) mail_close (tstream); + return ret; /* success */ +} + +/* IMAP open + * Accepts: stream to open + * Returns: stream to use on success, NIL on failure + */ + +MAILSTREAM *imap_open (MAILSTREAM *stream) +{ + unsigned long i,j; + char *s,tmp[MAILTMPLEN],usr[MAILTMPLEN]; + NETMBX mb; + IMAPPARSEDREPLY *reply = NIL; + imapreferral_t ir = + (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL); + /* return prototype for OP_PROTOTYPE call */ + if (!stream) return &imapproto; + mail_valid_net_parse (stream->mailbox,&mb); + usr[0] = '\0'; /* initially no user name */ + if (LOCAL) { /* if stream opened earlier by us */ + /* recycle if still alive */ + if (LOCAL->netstream && (!stream->halfopen || LOCAL->cap.unselect)) { + i = stream->silent; /* temporarily mark silent */ + stream->silent = T; /* don't give mm_exists() events */ + j = imap_ping (stream); /* learn if stream still alive */ + stream->silent = i; /* restore prior state */ + if (j) { /* was stream still alive? */ + sprintf (tmp,"Reusing connection to %s",net_host (LOCAL->netstream)); + if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user=\"%s\"", + LOCAL->user); + if (!stream->silent) mm_log (tmp,(long) NIL); + /* unselect if now want halfopen */ + if (stream->halfopen) imap_send (stream,"UNSELECT",NIL); + } + else imap_close (stream,NIL); + } + else imap_close (stream,NIL); + } + /* copy flags from name */ + if (mb.dbgflag) stream->debug = T; + if (mb.readonlyflag) stream->rdonly = T; + if (mb.anoflag) stream->anonymous = T; + if (mb.secflag) stream->secure = T; + if (mb.trysslflag || imap_tryssl) stream->tryssl = T; + + if (!LOCAL) { /* open new connection if no recycle */ + NETDRIVER *ssld = (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL); + unsigned long defprt = imap_defaultport ? imap_defaultport : IMAPTCPPORT; + unsigned long sslport = imap_sslport ? imap_sslport : IMAPSSLPORT; + stream->local = /* instantiate localdata */ + (void *) memset (fs_get (sizeof (IMAPLOCAL)),0,sizeof (IMAPLOCAL)); + /* assume IMAP2bis server */ + LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T; + /* in case server is a loser */ + if (mb.loser) LOCAL->loser = T; + /* desirable authenticators */ + LOCAL->authflags = (stream->secure ? AU_SECURE : NIL) | + (mb.authuser[0] ? AU_AUTHUSER : NIL); + /* IMAP connection open logic is more complex than net_open() normally + * deals with, because of the simap and rimap hacks. + * If the session is anonymous, a specific port is given, or if /ssl or + * /tls is set, do net_open() since those conditions override everything + * else. + */ + if (stream->anonymous || mb.port || mb.sslflag || mb.tlsflag) + reply = (LOCAL->netstream = net_open (&mb,NIL,defprt,ssld,"*imaps", + sslport)) ? + imap_reply (stream,NIL) : NIL; + /* + * No overriding conditions, so get the best connection that we can. In + * order, attempt to open via simap, tryssl, rimap, and finally TCP. + */ + /* try simap */ + else if (reply = imap_rimap (stream,"*imap",&mb,usr,tmp)); + else if (ssld && /* try tryssl if enabled */ + (stream->tryssl || mail_parameters (NIL,GET_TRYSSLFIRST,NIL)) && + (LOCAL->netstream = + net_open_work (ssld,mb.host,"*imaps",sslport,mb.port, + (mb.novalidate ? NET_NOVALIDATECERT : 0) | + NET_SILENT | NET_TRYSSL))) { + if (net_sout (LOCAL->netstream,"",0)) { + mb.sslflag = T; + reply = imap_reply (stream,NIL); + } + else { /* flush fake SSL stream */ + net_close (LOCAL->netstream); + LOCAL->netstream = NIL; + } + } + /* try rimap first, then TCP */ + else if (!(reply = imap_rimap (stream,"imap",&mb,usr,tmp)) && + (LOCAL->netstream = net_open (&mb,NIL,defprt,NIL,NIL,NIL))) + reply = imap_reply (stream,NIL); + /* make sure greeting is good */ + if (!reply || strcmp (reply->tag,"*") || + (strcmp (reply->key,"OK") && strcmp (reply->key,"PREAUTH"))) { + if (reply) mm_log (reply->text,ERROR); + return NIL; /* lost during greeting */ + } + + /* if connected and not preauthenticated */ + if (LOCAL->netstream && strcmp (reply->key,"PREAUTH")) { + sslstart_t stls = (sslstart_t) mail_parameters (NIL,GET_SSLSTART,NIL); + /* get server capabilities */ + if (!LOCAL->gotcapability) imap_capability (stream); + if (LOCAL->netstream && /* does server support STARTTLS? */ + stls && LOCAL->cap.starttls && !mb.sslflag && !mb.notlsflag && + imap_OK (stream,imap_send (stream,"STARTTLS",NIL))) { + mb.tlsflag = T; /* TLS OK, get into TLS at this end */ + LOCAL->netstream->dtb = ssld; + if (!(LOCAL->netstream->stream = + (*stls) (LOCAL->netstream->stream,mb.host, + (mb.tlssslv23 ? NIL : NET_TLSCLIENT) | + (mb.novalidate ? NET_NOVALIDATECERT : NIL)))) { + /* drat, drop this connection */ + if (LOCAL->netstream) net_close (LOCAL->netstream); + LOCAL->netstream = NIL; + } + /* get capabilities now that TLS in effect */ + if (LOCAL->netstream) imap_capability (stream); + } + else if (mb.tlsflag) { /* user specified /tls but can't do it */ + mm_log ("Unable to negotiate TLS with this server",ERROR); + return NIL; + } + if (LOCAL->netstream) { /* still in the land of the living? */ + if ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL)) { + /* remote name for authentication */ + strncpy (mb.host,(long) mail_parameters(NIL,GET_SASLUSESPTRNAME,NIL)? + net_remotehost (LOCAL->netstream) : + net_host (LOCAL->netstream),NETMAXHOST-1); + mb.host[NETMAXHOST-1] = '\0'; + } + /* need new capabilities after login */ + LOCAL->gotcapability = NIL; + if (!(stream->anonymous ? imap_anon (stream,tmp) : + (LOCAL->cap.auth ? imap_auth (stream,&mb,tmp,usr) : + imap_login (stream,&mb,tmp,usr)))) { + /* failed, is there a referral? */ + if (ir && LOCAL->referral && + (s = (*ir) (stream,LOCAL->referral,REFAUTHFAILED))) { + imap_close (stream,NIL); + fs_give ((void **) &stream->mailbox); + /* set as new mailbox name to open */ + stream->mailbox = s; + return imap_open (stream); + } + return NIL; /* authentication failed */ + } + else if (ir && LOCAL->referral && + (s = (*ir) (stream,LOCAL->referral,REFAUTH))) { + imap_close (stream,NIL); + fs_give ((void **) &stream->mailbox); + stream->mailbox = s; /* set as new mailbox name to open */ + /* recurse to log in on real site */ + return imap_open (stream); + } + } + } + /* get server capabilities again */ + if (LOCAL->netstream && !LOCAL->gotcapability) imap_capability (stream); + /* save state for future recycling */ + if (mb.tlsflag) LOCAL->tlsflag = T; + if (mb.tlssslv23) LOCAL->tlssslv23 = T; + if (mb.notlsflag) LOCAL->notlsflag = T; + if (mb.sslflag) LOCAL->sslflag = T; + if (mb.novalidate) LOCAL->novalidate = T; + if (mb.loser) LOCAL->loser = T; + } + + if (LOCAL->netstream) { /* still have a connection? */ + stream->perm_seen = stream->perm_deleted = stream->perm_answered = + stream->perm_draft = LEVELIMAP4 (stream) ? NIL : T; + stream->perm_user_flags = LEVELIMAP4 (stream) ? NIL : 0xffffffff; + stream->sequence++; /* bump sequence number */ + sprintf (tmp,"{%s",(long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ? + net_host (LOCAL->netstream) : mb.host); + if (!((i = net_port (LOCAL->netstream)) & 0xffff0000)) + sprintf (tmp + strlen (tmp),":%lu",i); + strcat (tmp,"/imap"); + if (LOCAL->tlsflag) strcat (tmp,"/tls"); + if (LOCAL->tlssslv23) strcat (tmp,"/tls-sslv23"); + if (LOCAL->notlsflag) strcat (tmp,"/notls"); + if (LOCAL->sslflag) strcat (tmp,"/ssl"); + if (LOCAL->novalidate) strcat (tmp,"/novalidate-cert"); + if (LOCAL->loser) strcat (tmp,"/loser"); + if (stream->secure) strcat (tmp,"/secure"); + if (stream->rdonly) strcat (tmp,"/readonly"); + if (stream->anonymous) strcat (tmp,"/anonymous"); + else { /* record user name */ + if (!LOCAL->user && usr[0]) LOCAL->user = cpystr (usr); + if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user=\"%s\"", + LOCAL->user); + } + strcat (tmp,"}"); + + if (!stream->halfopen) { /* wants to open a mailbox? */ + IMAPARG *args[2]; + IMAPARG ambx; + ambx.type = ASTRING; + ambx.text = (void *) mb.mailbox; + args[0] = &ambx; args[1] = NIL; + stream->nmsgs = 0; + if (imap_OK (stream,reply = imap_send (stream,stream->rdonly ? + "EXAMINE": "SELECT",args))) { + strcat (tmp,mb.mailbox);/* mailbox name */ + if (!stream->nmsgs && !stream->silent) + mm_log ("Mailbox is empty",(long) NIL); + /* note if an INBOX or not */ + stream->inbox = !compare_cstring (mb.mailbox,"INBOX"); + } + else if (ir && LOCAL->referral && + (s = (*ir) (stream,LOCAL->referral,REFSELECT))) { + imap_close (stream,NIL); + fs_give ((void **) &stream->mailbox); + stream->mailbox = s; /* set as new mailbox name to open */ + return imap_open (stream); + } + else { + mm_log (reply->text,ERROR); + if (imap_closeonerror) return NIL; + stream->halfopen = T; /* let him keep it half-open */ + } + } + if (stream->halfopen) { /* half-open connection? */ + strcat (tmp,"<no_mailbox>"); + /* make sure dummy message counts */ + mail_exists (stream,(long) 0); + mail_recent (stream,(long) 0); + } + fs_give ((void **) &stream->mailbox); + stream->mailbox = cpystr (tmp); + } + /* success if stream open */ + return LOCAL->netstream ? stream : NIL; +} + +/* IMAP rimap connect + * Accepts: MAIL stream + * NETMBX specification + * service to use + * user name + * scratch buffer + * Returns: parsed reply if success, else NIL + */ + +IMAPPARSEDREPLY *imap_rimap (MAILSTREAM *stream,char *service,NETMBX *mb, + char *usr,char *tmp) +{ + unsigned long i; + char c[2]; + NETSTREAM *tstream; + IMAPPARSEDREPLY *reply = NIL; + /* try rimap open */ + if (!mb->norsh && (tstream = net_aopen (NIL,mb,service,usr))) { + /* if success, see if reasonable banner */ + if (net_getbuffer (tstream,(long) 1,c) && (*c == '*')) { + i = 0; /* copy to buffer */ + do tmp[i++] = *c; + while (net_getbuffer (tstream,(long) 1,c) && (*c != '\015') && + (*c != '\012') && (i < (MAILTMPLEN-1))); + tmp[i] = '\0'; /* tie off */ + /* snarfed a valid greeting? */ + if ((*c == '\015') && net_getbuffer (tstream,(long) 1,c) && + (*c == '\012') && + !strcmp ((reply = imap_parse_reply (stream,cpystr (tmp)))->tag,"*")){ + /* parse line as IMAP */ + imap_parse_unsolicited (stream,reply); + /* make sure greeting is good */ + if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH")) { + LOCAL->netstream = tstream; + return reply; /* return success */ + } + } + } + net_close (tstream); /* failed, punt the temporary netstream */ + } + return NIL; +} + +/* IMAP log in as anonymous + * Accepts: stream to authenticate + * scratch buffer + * Returns: T on success, NIL on failure + */ + +long imap_anon (MAILSTREAM *stream,char *tmp) +{ + IMAPPARSEDREPLY *reply; + char *s = net_localhost (LOCAL->netstream); + if (LOCAL->cap.authanon) { + char tag[16]; + unsigned long i; + char *broken = "[CLOSED] IMAP connection broken (anonymous auth)"; + sprintf (tag,"%08lx",0xffffffff & (stream->gensym++)); + /* build command */ + sprintf (tmp,"%s AUTHENTICATE ANONYMOUS",tag); + if (!imap_soutr (stream,tmp)) { + mm_log (broken,ERROR); + return NIL; + } + if (imap_challenge (stream,&i)) imap_response (stream,s,strlen (s)); + /* get response */ + if (!(reply = &LOCAL->reply)->tag) reply = imap_fake (stream,tag,broken); + /* what we wanted? */ + if (compare_cstring (reply->tag,tag)) { + /* abort if don't have tagged response */ + while (compare_cstring ((reply = imap_reply (stream,tag))->tag,tag)) + imap_soutr (stream,"*"); + } + } + else { + IMAPARG *args[2]; + IMAPARG ausr; + ausr.type = ASTRING; + ausr.text = (void *) s; + args[0] = &ausr; args[1] = NIL; + /* send "LOGIN anonymous <host>" */ + reply = imap_send (stream,"LOGIN ANONYMOUS",args); + } + /* success if reply OK */ + if (imap_OK (stream,reply)) return T; + mm_log (reply->text,ERROR); + return NIL; +} + +/* IMAP authenticate + * Accepts: stream to authenticate + * parsed network mailbox structure + * scratch buffer + * place to return user name + * Returns: T on success, NIL on failure + */ + +long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr) +{ + unsigned long trial,ua; + int ok; + char tag[16]; + char *lsterr = NIL; + AUTHENTICATOR *at; + IMAPPARSEDREPLY *reply; + for (ua = LOCAL->cap.auth, LOCAL->saslcancel = NIL; LOCAL->netstream && ua && + (at = mail_lookup_auth (find_rightmost_bit (&ua) + 1));) { + if (lsterr) { /* previous authenticator failed? */ + sprintf (tmp,"Retrying using %s authentication after %.80s", + at->name,lsterr); + mm_log (tmp,NIL); + fs_give ((void **) &lsterr); + } + trial = 0; /* initial trial count */ + tmp[0] = '\0'; /* no error */ + do { /* gensym a new tag */ + if (lsterr) { /* previous attempt with this one failed? */ + sprintf (tmp,"Retrying %s authentication after %.80s",at->name,lsterr); + mm_log (tmp,WARN); + fs_give ((void **) &lsterr); + } + LOCAL->saslcancel = NIL; + sprintf (tag,"%08lx",0xffffffff & (stream->gensym++)); + /* build command */ + sprintf (tmp,"%s AUTHENTICATE %s",tag,at->name); + if (imap_soutr (stream,tmp)) { + /* hide client authentication responses */ + if (!(at->flags & AU_SECURE)) LOCAL->sensitive = T; + ok = (*at->client) (imap_challenge,imap_response,"imap",mb,stream, + &trial,usr); + LOCAL->sensitive = NIL; /* unhide */ + /* make sure have a response */ + if (!(reply = &LOCAL->reply)->tag) + reply = imap_fake (stream,tag, + "[CLOSED] IMAP connection broken (authenticate)"); + else if (compare_cstring (reply->tag,tag)) + while (compare_cstring ((reply = imap_reply (stream,tag))->tag,tag)) + imap_soutr (stream,"*"); + /* good if SASL ok and success response */ + if (ok && imap_OK (stream,reply)) return T; + if (!trial) { /* if main program requested cancellation */ + mm_log ("IMAP Authentication cancelled",ERROR); + return NIL; + } + /* no error if protocol-initiated cancel */ + lsterr = cpystr (reply->text); + } + } + while (LOCAL->netstream && !LOCAL->byeseen && trial && + (trial < imap_maxlogintrials)); + } + if (lsterr) { /* previous authenticator failed? */ + if (!LOCAL->saslcancel) { /* don't do this if a cancel */ + sprintf (tmp,"Can not authenticate to IMAP server: %.80s",lsterr); + mm_log (tmp,ERROR); + } + fs_give ((void **) &lsterr); + } + return NIL; /* ran out of authenticators */ +} + +/* IMAP login + * Accepts: stream to login + * parsed network mailbox structure + * scratch buffer of length MAILTMPLEN + * place to return user name + * Returns: T on success, NIL on failure + */ + +long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr) +{ + unsigned long trial = 0; + IMAPPARSEDREPLY *reply; + IMAPARG *args[3]; + IMAPARG ausr,apwd; + long ret = NIL; + if (stream->secure) /* never do LOGIN if want security */ + mm_log ("Can't do secure authentication with this server",ERROR); + /* never do LOGIN if server disabled it */ + else if (LOCAL->cap.logindisabled) + mm_log ("Server disables LOGIN, no recognized SASL authenticator",ERROR); + else if (mb->authuser[0]) /* never do LOGIN with /authuser */ + mm_log ("Can't do /authuser with this server",ERROR); + else { /* OK to try login */ + ausr.type = apwd.type = ASTRING; + ausr.text = (void *) usr; + apwd.text = (void *) pwd; + args[0] = &ausr; args[1] = &apwd; args[2] = NIL; + do { + pwd[0] = 0; /* prompt user for password */ + mm_login (mb,usr,pwd,trial++); + if (pwd[0]) { /* send login command if have password */ + LOCAL->sensitive = T; /* hide this command */ + /* send "LOGIN usr pwd" */ + if (imap_OK (stream,reply = imap_send (stream,"LOGIN",args))) + ret = LONGT; /* success */ + else { + mm_log (reply->text,WARN); + if (!LOCAL->referral && (trial == imap_maxlogintrials)) + mm_log ("Too many login failures",ERROR); + } + LOCAL->sensitive = NIL; /* unhide */ + } + /* user refused to give password */ + else mm_log ("Login aborted",ERROR); + } while (!ret && pwd[0] && (trial < imap_maxlogintrials) && + LOCAL->netstream && !LOCAL->byeseen && !LOCAL->referral); + } + memset (pwd,0,MAILTMPLEN); /* erase password */ + return ret; +} + +/* Get challenge to authenticator in binary + * Accepts: stream + * pointer to returned size + * Returns: challenge or NIL if not challenge + */ + +void *imap_challenge (void *s,unsigned long *len) +{ + char tmp[MAILTMPLEN]; + void *ret = NIL; + MAILSTREAM *stream = (MAILSTREAM *) s; + IMAPPARSEDREPLY *reply = NIL; + /* get tagged response or challenge */ + while (stream && LOCAL->netstream && + (reply = imap_parse_reply (stream,net_getline (LOCAL->netstream))) && + !strcmp (reply->tag,"*")) imap_parse_unsolicited (stream,reply); + /* parse challenge if have one */ + if (stream && LOCAL->netstream && reply && reply->tag && + (*reply->tag == '+') && !reply->tag[1] && reply->text && + !(ret = rfc822_base64 ((unsigned char *) reply->text, + strlen (reply->text),len))) { + sprintf (tmp,"IMAP SERVER BUG (invalid challenge): %.80s", + (char *) reply->text); + mm_log (tmp,ERROR); + } + return ret; +} + + +/* Send authenticator response in BASE64 + * Accepts: MAIL stream + * string to send + * length of string + * Returns: T if successful, else NIL + */ + +long imap_response (void *s,char *response,unsigned long size) +{ + MAILSTREAM *stream = (MAILSTREAM *) s; + unsigned long i,j,ret; + char *t,*u; + if (response) { /* make CRLFless BASE64 string */ + if (size) { + for (t = (char *) rfc822_binary ((void *) response,size,&i),u = t,j = 0; + j < i; j++) if (t[j] > ' ') *u++ = t[j]; + *u = '\0'; /* tie off string for mm_dlog() */ + if (stream->debug) mail_dlog (t,LOCAL->sensitive); + /* append CRLF */ + *u++ = '\015'; *u++ = '\012'; + ret = net_sout (LOCAL->netstream,t,u - t); + fs_give ((void **) &t); + } + else ret = imap_soutr (stream,""); + } + else { /* abort requested */ + ret = imap_soutr (stream,"*"); + LOCAL->saslcancel = T; /* mark protocol-requested SASL cancel */ + } + return ret; +} + +/* IMAP close + * Accepts: MAIL stream + * option flags + */ + +void imap_close (MAILSTREAM *stream,long options) +{ + THREADER *thr,*t; + IMAPPARSEDREPLY *reply; + if (stream && LOCAL) { /* send "LOGOUT" */ + if (!LOCAL->byeseen) { /* don't even think of doing it if saw a BYE */ + /* expunge silently if requested */ + if (options & CL_EXPUNGE) + imap_send (stream,LEVELIMAP4 (stream) ? "CLOSE" : "EXPUNGE",NIL); + if (LOCAL->netstream && + !imap_OK (stream,reply = imap_send (stream,"LOGOUT",NIL))) + mm_log (reply->text,WARN); + } + /* close NET connection if still open */ + if (LOCAL->netstream) net_close (LOCAL->netstream); + LOCAL->netstream = NIL; + /* free up memory */ + if (LOCAL->sortdata) fs_give ((void **) &LOCAL->sortdata); + if (LOCAL->namespace) { + mail_free_namespace (&LOCAL->namespace[0]); + mail_free_namespace (&LOCAL->namespace[1]); + mail_free_namespace (&LOCAL->namespace[2]); + fs_give ((void **) &LOCAL->namespace); + } + if (LOCAL->threaddata) mail_free_threadnode (&LOCAL->threaddata); + /* flush threaders */ + if (thr = LOCAL->cap.threader) while (t = thr) { + fs_give ((void **) &t->name); + thr = t->next; + fs_give ((void **) &t); + } + if (LOCAL->referral) fs_give ((void **) &LOCAL->referral); + if (LOCAL->user) fs_give ((void **) &LOCAL->user); + if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line); + if (LOCAL->reform) fs_give ((void **) &LOCAL->reform); + /* nuke the local data */ + fs_give ((void **) &stream->local); + } +} + +/* IMAP fetch fast information + * Accepts: MAIL stream + * sequence + * option flags + * + * Generally, imap_structure is preferred + */ + +void imap_fast (MAILSTREAM *stream,char *sequence,long flags) +{ + IMAPPARSEDREPLY *reply = imap_fetch (stream,sequence,flags & FT_UID); + if (!imap_OK (stream,reply)) mm_log (reply->text,ERROR); +} + + +/* IMAP fetch flags + * Accepts: MAIL stream + * sequence + * option flags + */ + +void imap_flags (MAILSTREAM *stream,char *sequence,long flags) +{ /* send "FETCH sequence FLAGS" */ + char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH"; + IMAPPARSEDREPLY *reply; + IMAPARG *args[3],aseq,aatt; + if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence, + flags & FT_UID); + aseq.type = SEQUENCE; aseq.text = (void *) sequence; + aatt.type = ATOM; aatt.text = (void *) "FLAGS"; + args[0] = &aseq; args[1] = &aatt; args[2] = NIL; + if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) + mm_log (reply->text,ERROR); +} + +/* IMAP fetch overview + * Accepts: MAIL stream, sequence bits set + * pointer to overview return function + * Returns: T if successful, NIL otherwise + */ + +long imap_overview (MAILSTREAM *stream,overview_t ofn) +{ + MESSAGECACHE *elt; + ENVELOPE *env; + OVERVIEW ov; + char *s,*t; + unsigned long i,start,last,len,slen; + if (!LOCAL->netstream) return NIL; + /* build overview sequence */ + for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i) + if ((elt = mail_elt (stream,i))->sequence) { + if (!elt->private.msg.env) { + if (s) { /* continuing a sequence */ + if (i == last + 1) last = i; + else { /* end of range */ + if (last != start) sprintf (t,":%lu,%lu",last,i); + else sprintf (t,",%lu",i); + if ((len - (slen = (t += strlen (t)) - s)) < 20) { + fs_resize ((void **) &s,len += MAILTMPLEN); + t = s + slen; /* relocate current pointer */ + } + start = last = i; /* begin a new range */ + } + } + else { /* first time, start new buffer */ + s = (char *) fs_get (len = MAILTMPLEN); + sprintf (s,"%lu",start = last = i); + t = s + strlen (s); /* end of buffer */ + } + } + } + /* last sequence */ + if (last != start) sprintf (t,":%lu",last); + if (s) { /* prefetch as needed */ + imap_fetch (stream,s,FT_NEEDENV); + fs_give ((void **) &s); + } + ov.optional.lines = 0; /* now overview each message */ + ov.optional.xref = NIL; + if (ofn) for (i = 1; i <= stream->nmsgs; i++) + if (((elt = mail_elt (stream,i))->sequence) && + (env = mail_fetch_structure (stream,i,NIL,NIL)) && ofn) { + ov.subject = env->subject; + ov.from = env->from; + ov.date = env->date; + ov.message_id = env->message_id; + ov.references = env->references; + ov.optional.octets = elt->rfc822_size; + (*ofn) (stream,mail_uid (stream,i),&ov,i); + } + return LONGT; +} + +/* IMAP fetch structure + * Accepts: MAIL stream + * message # to fetch + * pointer to return body + * option flags + * Returns: envelope of this message, body returned in body value + * + * Fetches the "fast" information as well + */ + +ENVELOPE *imap_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body, + long flags) +{ + unsigned long i,j,k,x; + char *s,seq[MAILTMPLEN],tmp[MAILTMPLEN]; + MESSAGECACHE *elt; + ENVELOPE **env; + BODY **b; + IMAPPARSEDREPLY *reply = NIL; + IMAPARG *args[3],aseq,aatt; + SEARCHSET *set = LOCAL->lookahead; + LOCAL->lookahead = NIL; + args[0] = &aseq; args[1] = &aatt; args[2] = NIL; + aseq.type = SEQUENCE; aseq.text = (void *) seq; + aatt.type = ATOM; aatt.text = NIL; + if (flags & FT_UID) /* see if can find msgno from UID */ + for (i = 1; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->private.uid == msgno) { + msgno = i; /* found msgno, use it from now on */ + flags &= ~FT_UID; /* no longer a UID fetch */ + } + sprintf (s = seq,"%lu",msgno);/* initial sequence */ + if (LEVELIMAP4 (stream) && (flags & FT_UID)) { + /* UID fetching is requested and we can't map the UID to a message sequence + * number. Assume that the message isn't cached at all. + */ + if (!imap_OK (stream,reply = imap_fetch (stream,seq,FT_NEEDENV + + (body ? FT_NEEDBODY : NIL) + + (flags & (FT_UID + FT_NOHDRS))))) + mm_log (reply->text,ERROR); + /* now hunt for this UID */ + for (i = 1; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->private.uid == msgno) { + if (body) *body = elt->private.msg.body; + return elt->private.msg.env; + } + if (body) *body = NIL; /* can't find the UID */ + return NIL; + } + elt = mail_elt (stream,msgno);/* get cache pointer */ + if (stream->scache) { /* short caching? */ + env = &stream->env; /* use temporaries on the stream */ + b = &stream->body; + if (msgno != stream->msgno){/* flush old poop if a different message */ + mail_free_envelope (env); + mail_free_body (b); + stream->msgno = msgno; /* this is now the current short cache msg */ + } + } + + else { /* normal cache */ + env = &elt->private.msg.env;/* get envelope and body pointers */ + b = &elt->private.msg.body; + /* prefetch if don't have envelope */ + if (!(flags & FT_NOLOOKAHEAD) && + ((!*env || (*env)->incomplete) || + (body && !*b && LEVELIMAP2bis (stream)))) { + if (set) { /* have a lookahead list? */ + MESSAGE *msg; + for (k = imap_fetchlookaheadlimit; + k && set && (((s += strlen (s)) - seq) < (MAXCOMMAND - 30)); + set = set->next) { + i = (set->first == 0xffffffff) ? stream->nmsgs : + min (set->first,stream->nmsgs); + if (j = (set->last == 0xffffffff) ? stream->nmsgs : + min (set->last,stream->nmsgs)) { + if (i > j) { /* swap the range if backwards */ + x = i; i = j; j = x; + } + /* find first message not msgno or in cache */ + while (((i == msgno) || + ((msg = &(mail_elt (stream,i)->private.msg))->env && + (!body || msg->body))) && (i++ < j)); + /* until range or lookahead finished */ + while (k && (i <= j)) { + /* find first cached message in range */ + for (x = i + 1; (x <= j) && + !((msg = &(mail_elt (stream,x)->private.msg))->env && + (!body || msg->body)); x++); + if (i == --x) { /* only one message? */ + sprintf (s += strlen (s),",%lu",i++); + k--; /* prefetching one message */ + } + else { /* a range to prefetch */ + sprintf (s += strlen (s),",%lu:%lu",i,x); + i = 1 + x - i; /* number of messages in this range */ + /* still can look ahead some more? */ + if (k = (k > i) ? k - i : 0) + /* yes, scan further in this range */ + for (i = x + 2; (i <= j) && + ((i == msgno) || + ((msg = &(mail_elt (stream,i)->private.msg))->env && + (!body || msg->body))); + i++); + } + } + } + else if ((i != msgno) && !mail_elt (stream,i)->private.msg.env) { + sprintf (s += strlen (s),",%lu",i); + k--; /* prefetching one message */ + } + } + } + /* build message number list */ + else for (i = msgno+1,k = imap_lookahead; k && (i <= stream->nmsgs); i++) + if (!mail_elt (stream,i)->private.msg.env) { + s += strlen (s); /* find string end, see if nearing end */ + if ((s - seq) > (MAILTMPLEN - 20)) break; + sprintf (s,",%lu",i); /* append message */ + for (j = i + 1, k--; /* hunt for last message without an envelope */ + k && (j <= stream->nmsgs) && + !mail_elt (stream,j)->private.msg.env; j++, k--); + /* if different, make a range */ + if (i != --j) sprintf (s + strlen (s),":%lu",i = j); + } + } + } + + if (!stream->lock) { /* no-op if stream locked */ + /* Build the fetch attributes. Unlike imap_fetch(), this tries not to + * fetch data that is already cached. However, since it is based on the + * message requested and not on any of the prefetched messages, it can + * goof, either by fetching data already cached or not prefetching data + * that isn't cached (but was cached in the message requested). + * Fortunately, no great harm is done. If it doesn't prefetch the data, + * it will get it when the affected message(s) are requested. + */ + if (!elt->private.uid && LEVELIMAP4 (stream)) strcpy (tmp," UID"); + else tmp[0] = '\0'; /* initialize command */ + /* need envelope? */ + if (!*env || (*env)->incomplete) { + strcat (tmp," ENVELOPE"); /* yes, get it and possible extra poop */ + if (!(flags & FT_NOHDRS) && LEVELIMAP4rev1 (stream)) { + if (imap_extrahdrs) sprintf (tmp + strlen (tmp)," %s %s %s", + hdrheader[LOCAL->cap.extlevel], + imap_extrahdrs,hdrtrailer); + else sprintf (tmp + strlen (tmp)," %s %s", + hdrheader[LOCAL->cap.extlevel],hdrtrailer); + } + } + /* need body? */ + if (body && !*b && LEVELIMAP2bis (stream)) + strcat (tmp,LEVELIMAP4 (stream) ? " BODYSTRUCTURE" : " BODY"); + if (!elt->day) strcat (tmp," INTERNALDATE"); + if (!elt->rfc822_size) strcat (tmp," RFC822.SIZE"); + if (tmp[0]) { /* anything to do? */ + tmp[0] = '('; /* make into a list */ + strcat (tmp," FLAGS)"); /* always get current flags */ + aatt.text = (void *) tmp; /* do the built command */ + if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args))) { + /* failed, probably RFC-1176 server */ + if (!LEVELIMAP4 (stream) && LEVELIMAP2bis (stream) && body && !*b){ + aatt.text = (void *) "ALL"; + if (imap_OK (stream,reply = imap_send (stream,"FETCH",args))) + /* doesn't have body capabilities */ + LOCAL->cap.imap2bis = NIL; + else mm_log (reply->text,ERROR); + } + else mm_log (reply->text,ERROR); + } + } + } + if (body) { /* wants to return body */ + if (!*b && !LEVELIMAP2bis (stream)) { + /* simulate body structure fetch for IMAP2 */ + *b = mail_initbody (mail_newbody ()); + (*b)->subtype = cpystr (rfc822_default_subtype ((*b)->type)); + ((*b)->parameter = mail_newbody_parameter ())->attribute = + cpystr ("CHARSET"); + (*b)->parameter->value = cpystr ("US-ASCII"); + s = mail_fetch_text (stream,msgno,NIL,&i,flags); + (*b)->size.bytes = i; + while (i--) if (*s++ == '\n') (*b)->size.lines++; + } + *body = *b; /* return the body */ + } + return *env; /* return the envelope */ +} + +/* IMAP fetch message data + * Accepts: MAIL stream + * message number + * section specifier + * offset of first designated byte or 0 to start at beginning + * maximum number of bytes or 0 for all bytes + * lines to fetch if header + * flags + * Returns: T on success, NIL on failure + */ + +long imap_msgdata (MAILSTREAM *stream,unsigned long msgno,char *section, + unsigned long first,unsigned long last,STRINGLIST *lines, + long flags) +{ + int i; + char *t,tmp[MAILTMPLEN],partial[40],seq[40]; + char *noextend,*nopartial,*nolines,*nopeek,*nononpeek; + char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH"; + IMAPPARSEDREPLY *reply; + IMAPARG *args[5],*auxargs[3],aseq,aatt,alns,acls,aflg; + noextend = nopartial = nolines = nopeek = nononpeek = NIL; + /* does searching desire a lookahead? */ + if ((flags & FT_SEARCHLOOKAHEAD) && (msgno < stream->nmsgs) && + !stream->scache) { + sprintf (seq,"%lu:%lu",msgno, + (unsigned long) min (msgno + IMAPLOOKAHEAD,stream->nmsgs)); + aseq.type = SEQUENCE; + aseq.text = (void *) seq; + } + else { /* no, do it the easy way */ + aseq.type = NUMBER; + aseq.text = (void *) msgno; + } + aatt.type = ATOM; /* assume atomic attribute */ + alns.type = LIST; alns.text = (void *) lines; + acls.type = BODYCLOSE; acls.text = (void *) partial; + aflg.type = ATOM; aflg.text = (void *) "FLAGS"; + args[0] = &aseq; args[1] = &aatt; args[2] = args[3] = args[4] = NIL; + auxargs[0] = &aseq; auxargs[1] = &aflg; auxargs[2] = NIL; + partial[0] = '\0'; /* initially no partial specifier */ + if (LEVELIMAP4rev1 (stream)) {/* easy if IMAP4rev1 server */ + /* HEADER fetching with special handling? */ + if (!strcmp (section,"HEADER") && (lines || (flags & FT_PREFETCHTEXT))) { + if (lines) { /* want specific header lines? */ + aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT; + aatt.text = (void *) ((flags & FT_NOT) ? + "HEADER.FIELDS.NOT" : "HEADER.FIELDS"); + args[2] = &alns; args[3] = &acls; + } + /* must be prefetching */ + else aatt.text = (void *) ((flags & FT_PEEK) ? + "(BODY.PEEK[HEADER] BODY.PEEK[TEXT])" : + "(BODY[HEADER] BODY[TEXT])"); + } + else { /* simple case */ + aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT; + aatt.text = (void *) section; + args[2] = &acls; + } + if (first || last) sprintf (partial,"<%lu.%lu>",first,last ? last:-1); + } + + /* IMAP4 did not have: + * . HEADER body part (can simulate with BODY[0] or BODY.PEEK[0]) + * . TEXT body part (can simulate top-level with RFC822.TEXT or + * RFC822.TEXT.PEEK) + * . MIME body part + * . (usable) partial fetching + * . (usable) selective header line fetching + */ + else if (LEVEL1730 (stream)) {/* IMAP4 (RFC 1730) compatibility */ + /* BODY[HEADER] becomes BODY.PEEK[0] */ + if (!strcmp (section,"HEADER")) + aatt.text = (void *) + ((flags & FT_PREFETCHTEXT) ? + ((flags & FT_PEEK) ? "(BODY.PEEK[0] RFC822.TEXT.PEEK)" : + "(BODY[0] RFC822.TEXT)") : + ((flags & FT_PEEK) ? "BODY.PEEK[0]" : "BODY[0]")); + /* BODY[TEXT] becomes RFC822.TEXT */ + else if (!strcmp (section,"TEXT")) + aatt.text = (void *) ((flags & FT_PEEK) ? "RFC822.TEXT.PEEK" : + "RFC822.TEXT"); + else if (!section[0]) /* BODY[] becomes RFC822 */ + aatt.text = (void *) ((flags & FT_PEEK) ? "RFC822.PEEK" : "RFC822"); + /* nested header */ + else if (t = strstr (section,".HEADER")) { + aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT; + args[2] = &acls; /* will need to close section */ + aatt.text = (void *) tmp; /* convert .HEADER to .0 */ + strncpy (tmp,section,t-section); + strcpy (tmp+(t-section),".0"); + } + else { /* IMAP4 body part */ + aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT; + args[2] = &acls; /* will need to close section */ + aatt.text = (void *) section; + } + if (strstr (section,".MIME") || strstr (section,".TEXT")) noextend = "4"; + if (first || last) nopartial = "4"; + if (lines) nolines = "4"; + } + + /* IMAP2bis did not have: + * . HEADER body part (can simulate peeking top-level with RFC822.HEADER) + * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT) + * . MIME body part + * . partial fetching + * . selective header line fetching + * . non-peeking header fetching + * . peeking body fetching + */ + /* IMAP2bis compatibility */ + else if (LEVELIMAP2bis (stream)) { + /* BODY[HEADER] becomes RFC822.HEADER */ + if (!strcmp (section,"HEADER")) { + aatt.text = (void *) + ((flags & FT_PREFETCHTEXT) ? + "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER"); + if (flags & FT_PEEK) flags &= ~FT_PEEK; + else nononpeek = "2bis"; + } + /* BODY[TEXT] becomes RFC822.TEXT */ + else if (!strcmp (section,"TEXT")) aatt.text = (void *) "RFC822.TEXT"; + /* BODY[] becomes RFC822 */ + else if (!section[0]) aatt.text = (void *) "RFC822"; + else { /* IMAP2bis body part */ + aatt.type = BODYTEXT; + args[2] = &acls; /* will need to close section */ + aatt.text = (void *) section; + } + if (strstr (section,".HEADER") || strstr (section,".MIME") || + strstr (section,".TEXT")) noextend = "2bis"; + if (first || last) nopartial = "2bis"; + if (lines) nolines = "2bis"; + if (flags & FT_PEEK) nopeek = "2bis"; + } + + /* IMAP2 did not have: + * . HEADER body part (can simulate peeking top-level with RFC822.HEADER) + * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT) + * . MIME body part + * . multiple body parts (can simulate BODY[1] with RFC822.TEXT) + * . partial fetching + * . selective header line fetching + * . non-peeking header fetching + * . peeking body fetching + */ + else { /* IMAP2 (RFC 1176/1064) compatibility */ + /* BODY[HEADER] */ + if (!strcmp (section,"HEADER")) { + aatt.text = (void *) ((flags & FT_PREFETCHTEXT) ? + "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER"); + if (flags & FT_PEEK) flags &= ~FT_PEEK; + nononpeek = "2"; + } + /* BODY[TEXT] becomes RFC822.TEXT */ + else if (!strcmp (section,"TEXT")) aatt.text = (void *) "RFC822.TEXT"; + /* BODY[1] treated like RFC822.TEXT */ + else if (!strcmp (section,"1")) { + SIZEDTEXT text; + MESSAGECACHE *elt = mail_elt (stream,msgno); + /* have a cached RFC822.TEXT? */ + if (elt->private.msg.text.text.data) { + text.size = elt->private.msg.text.text.size; + /* should move instead of copy */ + text.data = memcpy (fs_get (text.size+1), + elt->private.msg.text.text.data,text.size); + (t = (char *) text.data)[text.size] = '\0'; + imap_cache (stream,msgno,"1",NIL,&text); + return LONGT; /* don't have to do any fetches */ + } + /* otherwise do RFC822.TEXT */ + aatt.text = (void *) "RFC822.TEXT"; + } + /* BODY[] becomes RFC822 */ + else if (!section[0]) aatt.text = (void *) "RFC822"; + else noextend = "2"; /* how did we get here? */ + if (flags & FT_PEEK) nopeek = "2"; + if (first || last) nopartial = "2"; + if (lines) nolines = "2"; + } + + /* Report unavailable functionalities. The application can use the helpful + * LEVELIMAPREV1, LEVELIMAP4, and LEVELIMAP2bis operations provided in + * imap4r1.h to avoid triggering these errors. There aren't any workarounds + * for these restrictions. + */ + if (noextend) { + sprintf (tmp,"[NOTIMAP4REV1] IMAP%s server can't do extended body fetch", + noextend); + mm_log (tmp,ERROR); + return NIL; /* can't do anything close either */ + } + if (nopartial) { + sprintf (tmp,"[NOTIMAP4REV1] IMAP%s server can't do partial fetch", + nopartial); + mm_notify (stream,tmp,WARN); + } + if (nolines) { + sprintf(tmp,"[NOTIMAP4REV1] IMAP%s server can't do selective header fetch", + nolines); + mm_notify (stream,tmp,WARN); + } + + /* trying to do unsupported peek behavior? */ + if ((t = nopeek) || (t = nononpeek)) { + /* get most recent \Seen setting */ + if (!imap_OK (stream,reply = imap_send (stream,cmd,auxargs))) + mm_log (reply->text,WARN); + /* note current setting of \Seen flag */ + if (!(i = mail_elt (stream,msgno)->seen)) { + sprintf (tmp,nopeek ? /* only babble if \Seen not set */ + "[NOTIMAP4] Simulating peeking fetch in IMAP%s" : + "[NOTIMAP4] Simulating non-peeking header fetch in IMAP%s",t); + mm_notify (stream,tmp,NIL); + } + /* send the fetch command */ + if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) { + mm_log (reply->text,ERROR); + return NIL; /* failure */ + } + /* send command if need to reset \Seen */ + if (((nopeek && !i && mail_elt (stream,msgno)->seen && + (aflg.text = "-FLAGS \\Seen")) || + ((nononpeek && !mail_elt (stream,msgno)->seen) && + (aflg.text = "+FLAGS \\Seen"))) && + !imap_OK (stream,reply = imap_send (stream,"STORE",auxargs))) + mm_log (reply->text,WARN); + } + /* simple case if traditional behavior */ + else if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) { + mm_log (reply->text,ERROR); + return NIL; /* failure */ + } + /* simulate BODY[1] return for RFC 1064/1176 */ + if (!LEVELIMAP2bis (stream) && !strcmp (section,"1")) { + SIZEDTEXT text; + MESSAGECACHE *elt = mail_elt (stream,msgno); + text.size = elt->private.msg.text.text.size; + /* should move instead of copy */ + text.data = memcpy (fs_get (text.size+1),elt->private.msg.text.text.data, + text.size); + (t = (char *) text.data)[text.size] = '\0'; + imap_cache (stream,msgno,"1",NIL,&text); + } + return LONGT; +} + +/* IMAP fetch UID + * Accepts: MAIL stream + * message number + * Returns: UID + */ + +unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno) +{ + MESSAGECACHE *elt; + IMAPPARSEDREPLY *reply; + IMAPARG *args[3],aseq,aatt; + char *s,seq[MAILTMPLEN]; + unsigned long i,j,k; + /* IMAP2 didn't have UIDs */ + if (!LEVELIMAP4 (stream)) return msgno; + /* do we know its UID yet? */ + if (!(elt = mail_elt (stream,msgno))->private.uid) { + aseq.type = SEQUENCE; aseq.text = (void *) seq; + aatt.type = ATOM; aatt.text = (void *) "UID"; + args[0] = &aseq; args[1] = &aatt; args[2] = NIL; + sprintf (seq,"%lu",msgno); + if (k = imap_uidlookahead) {/* build UID list */ + for (i = msgno + 1, s = seq; k && (i <= stream->nmsgs); i++) + if (!mail_elt (stream,i)->private.uid) { + s += strlen (s); /* find string end, see if nearing end */ + if ((s - seq) > (MAILTMPLEN - 20)) break; + sprintf (s,",%lu",i); /* append message */ + for (j = i + 1, k--; /* hunt for last message without a UID */ + k && (j <= stream->nmsgs) && !mail_elt (stream,j)->private.uid; + j++, k--); + /* if different, make a range */ + if (i != --j) sprintf (s + strlen (s),":%lu",i = j); + } + } + /* send "FETCH msgno UID" */ + if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args))) + mm_log (reply->text,ERROR); + } + return elt->private.uid; /* return our UID now */ +} + +/* IMAP fetch message number from UID + * Accepts: MAIL stream + * UID + * Returns: message number + */ + +unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid) +{ + IMAPPARSEDREPLY *reply; + IMAPARG *args[3],aseq,aatt; + char seq[MAILTMPLEN]; + int holes = 0; + unsigned long i,msgno; + /* IMAP2 didn't have UIDs */ + if (!LEVELIMAP4 (stream)) return uid; + /* This really should be a binary search, but since there are likely to be + * holes in the msgno->UID map it's hard to do. + */ + for (msgno = 1; msgno <= stream->nmsgs; msgno++) { + if (!(i = mail_elt (stream,msgno)->private.uid)) holes = T; + else if (i == uid) return msgno; + } + if (holes) { /* have holes in cache? */ + /* yes, have server hunt for UID */ + LOCAL->lastuid.uid = LOCAL->lastuid.msgno = 0; + aseq.type = SEQUENCE; aseq.text = (void *) seq; + aatt.type = ATOM; aatt.text = (void *) "UID"; + args[0] = &aseq; args[1] = &aatt; args[2] = NIL; + sprintf (seq,"%lu",uid); + /* send "UID FETCH uid UID" */ + if (!imap_OK (stream,reply = imap_send (stream,"UID FETCH",args))) + mm_log (reply->text,ERROR); + if (LOCAL->lastuid.uid) { /* got any results from FETCH? */ + if ((LOCAL->lastuid.uid == uid) && + /* what, me paranoid? */ + (LOCAL->lastuid.msgno <= stream->nmsgs) && + (mail_elt (stream,LOCAL->lastuid.msgno)->private.uid == uid)) + /* got it the easy way */ + return LOCAL->lastuid.msgno; + /* sigh, do another linear search... */ + for (msgno = 1; msgno <= stream->nmsgs; msgno++) + if (mail_elt (stream,msgno)->private.uid == uid) return msgno; + } + } + return 0; /* didn't find the UID anywhere */ +} + +/* IMAP modify flags + * Accepts: MAIL stream + * sequence + * flag(s) + * option flags + */ + +void imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags) +{ + char *cmd = (LEVELIMAP4 (stream) && (flags & ST_UID)) ? "UID STORE":"STORE"; + IMAPPARSEDREPLY *reply; + IMAPARG *args[4],aseq,ascm,aflg; + if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence, + flags & ST_UID); + aseq.type = SEQUENCE; aseq.text = (void *) sequence; + ascm.type = ATOM; ascm.text = (void *) + ((flags & ST_SET) ? + ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ? + "+Flags.silent" : "+Flags") : + ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ? + "-Flags.silent" : "-Flags")); + aflg.type = FLAGS; aflg.text = (void *) flag; + args[0] = &aseq; args[1] = &ascm; args[2] = &aflg; args[3] = NIL; + /* send "STORE sequence +Flags flag" */ + if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) + mm_log (reply->text,ERROR); +} + +/* IMAP search for messages + * Accepts: MAIL stream + * character set + * search program + * option flags + * Returns: T on success, NIL on failure + */ + +long imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags) +{ + unsigned long i,j,k; + char *s; + IMAPPARSEDREPLY *reply; + MESSAGECACHE *elt; + if ((flags & SE_NOSERVER) || /* if want to do local search */ + LOCAL->loser || /* or loser */ + (!LEVELIMAP4 (stream) && /* or old server but new functions... */ + (charset || (flags & SE_UID) || pgm->msgno || pgm->uid || pgm->or || + pgm->not || pgm->header || pgm->larger || pgm->smaller || + pgm->sentbefore || pgm->senton || pgm->sentsince || pgm->draft || + pgm->undraft || pgm->return_path || pgm->sender || pgm->reply_to || + pgm->message_id || pgm->in_reply_to || pgm->newsgroups || + pgm->followup_to || pgm->references)) || + (!LEVELWITHIN (stream) && (pgm->older || pgm->younger))) { + if ((flags & SE_NOLOCAL) || + !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER)) + return NIL; + } + /* do silly ALL or seq-only search locally */ + else if (!(flags & (SE_NOLOCAL|SE_SILLYOK)) && + !(pgm->uid || pgm->or || pgm->not || + pgm->header || pgm->from || pgm->to || pgm->cc || pgm->bcc || + pgm->subject || pgm->body || pgm->text || + pgm->larger || pgm->smaller || + pgm->sentbefore || pgm->senton || pgm->sentsince || + pgm->before || pgm->on || pgm->since || + pgm->answered || pgm->unanswered || + pgm->deleted || pgm->undeleted || pgm->draft || pgm->undraft || + pgm->flagged || pgm->unflagged || pgm->recent || pgm->old || + pgm->seen || pgm->unseen || + pgm->keyword || pgm->unkeyword || + pgm->return_path || pgm->sender || + pgm->reply_to || pgm->in_reply_to || pgm->message_id || + pgm->newsgroups || pgm->followup_to || pgm->references)) { + if (!mail_search_default (stream,NIL,pgm,flags | SE_NOSERVER)) + fatal ("impossible mail_search_default() failure"); + } + + else { /* do server-based SEARCH */ + char *cmd = (flags & SE_UID) ? "UID SEARCH" : "SEARCH"; + IMAPARG *args[4],apgm,aatt,achs; + SEARCHSET *ss,*set; + args[1] = args[2] = args[3] = NIL; + apgm.type = SEARCHPROGRAM; apgm.text = (void *) pgm; + if (charset) { /* optional charset argument requested */ + args[0] = &aatt; args[1] = &achs; args[2] = &apgm; + aatt.type = ATOM; aatt.text = (void *) "CHARSET"; + achs.type = ASTRING; achs.text = (void *) charset; + } + else args[0] = &apgm; /* no charset argument */ + /* tell receiver that these will be UIDs */ + LOCAL->uidsearch = (flags & SE_UID) ? T : NIL; + reply = imap_send (stream,cmd,args); + /* did server barf with that searchpgm? */ + if (!(flags & SE_UID) && pgm && (ss = pgm->msgno) && + !strcmp (reply->key,"BAD")) { + LOCAL->filter = T; /* retry, filtering SEARCH results */ + for (i = 1; i <= stream->nmsgs; i++) + mail_elt (stream,i)->private.filter = NIL; + for (set = ss; set; set = set->next) if (i = set->first) { + /* single message becomes one-message range */ + if (!(j = set->last)) j = i; + else if (j < i) { /* swap reversed range */ + i = set->last; j = set->first; + } + while (i <= j) mail_elt (stream,i++)->private.filter = T; + } + pgm->msgno = NIL; /* and without the searchset */ + reply = imap_send (stream,cmd,args); + pgm->msgno = ss; /* restore searchset */ + LOCAL->filter = NIL; /* turn off filtering */ + } + LOCAL->uidsearch = NIL; + /* do locally if server won't grok */ + if (!strcmp (reply->key,"BAD")) { + if ((flags & SE_NOLOCAL) || + !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER)) + return NIL; + } + else if (!imap_OK (stream,reply)) { + mm_log (reply->text,ERROR); + return NIL; + } + } + + /* can never pre-fetch with a short cache */ + if ((k = imap_prefetch) && !(flags & (SE_NOPREFETCH | SE_UID)) && + !stream->scache) { /* only if prefetching permitted */ + s = LOCAL->tmp; /* build sequence in temporary buffer */ + *s = '\0'; /* initially nothing */ + /* search through mailbox */ + for (i = 1; k && (i <= stream->nmsgs); ++i) + /* for searched messages with no envelope */ + if ((elt = mail_elt (stream,i)) && elt->searched && + !mail_elt (stream,i)->private.msg.env) { + /* prepend with comma if not first time */ + if (LOCAL->tmp[0]) *s++ = ','; + sprintf (s,"%lu",j = i);/* output message number */ + s += strlen (s); /* point at end of string */ + k--; /* count one up */ + /* search for possible end of range */ + while (k && (i < stream->nmsgs) && + (elt = mail_elt (stream,i+1))->searched && + !elt->private.msg.env) i++,k--; + if (i != j) { /* if a range */ + sprintf (s,":%lu",i); /* output delimiter and end of range */ + s += strlen (s); /* point at end of string */ + } + if ((s - LOCAL->tmp) > (IMAPTMPLEN - 50)) break; + } + if (LOCAL->tmp[0]) { /* anything to pre-fetch? */ + /* pre-fetch envelopes for the first imap_prefetch number of messages */ + if (!imap_OK (stream,reply = + imap_fetch (stream,s = cpystr (LOCAL->tmp),FT_NEEDENV + + ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL) + + ((flags & SE_NEEDBODY) ? FT_NEEDBODY : NIL)))) + mm_log (reply->text,ERROR); + fs_give ((void **) &s); /* flush copy of sequence */ + } + } + return LONGT; +} + +/* IMAP sort messages + * Accepts: mail stream + * character set + * search program + * sort program + * option flags + * Returns: vector of sorted message sequences or NIL if error + */ + +unsigned long *imap_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, + SORTPGM *pgm,long flags) +{ + unsigned long i,j,start,last; + unsigned long *ret = NIL; + pgm->nmsgs = 0; /* start off with no messages */ + /* can use server-based sort? */ + if (LEVELSORT (stream) && !(flags & SE_NOSERVER) && + (!spg || (LEVELWITHIN (stream) || !(spg->older || spg->younger)))) { + char *cmd = (flags & SE_UID) ? "UID SORT" : "SORT"; + IMAPARG *args[4],apgm,achs,aspg; + IMAPPARSEDREPLY *reply; + SEARCHSET *ss = NIL; + SEARCHPGM *tsp = NIL; + apgm.type = SORTPROGRAM; apgm.text = (void *) pgm; + achs.type = ASTRING; achs.text = (void *) (charset ? charset : "US-ASCII"); + aspg.type = SEARCHPROGRAM; + /* did he provide a searchpgm? */ + if (!(aspg.text = (void *) spg)) { + for (i = 1,start = last = 0; i <= stream->nmsgs; ++i) + if (mail_elt (stream,i)->searched) { + if (ss) { /* continuing a sequence */ + if (i == last + 1) last = i; + else { /* end of range */ + if (last != start) ss->last = last; + (ss = ss->next = mail_newsearchset ())->first = i; + start = last = i; /* begin a new range */ + } + } + else { /* first time, start new searchpgm */ + (tsp = mail_newsearchpgm ())->msgno = ss = mail_newsearchset (); + ss->first = start = last = i; + } + } + /* nothing to sort if no messages */ + if (!(aspg.text = (void *) tsp)) return NIL; + /* else install last sequence */ + if (last != start) ss->last = last; + } + + args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL; + /* ask server to do it */ + reply = imap_send (stream,cmd,args); + if (tsp) { /* was there a temporary searchpgm? */ + aspg.text = NIL; /* yes, flush it */ + mail_free_searchpgm (&tsp); + /* did server barf with that searchpgm? */ + if (!(flags & SE_UID) && !strcmp (reply->key,"BAD")) { + LOCAL->filter = T; /* retry, filtering SORT/THREAD results */ + reply = imap_send (stream,cmd,args); + LOCAL->filter = NIL; /* turn off filtering */ + } + } + /* do locally if server barfs */ + if (!strcmp (reply->key,"BAD")) + return (flags & SE_NOLOCAL) ? NIL : + imap_sort (stream,charset,spg,pgm,flags | SE_NOSERVER); + /* server sorted OK? */ + else if (imap_OK (stream,reply)) { + pgm->nmsgs = LOCAL->sortsize; + ret = LOCAL->sortdata; + LOCAL->sortdata = NIL; /* mail program is responsible for flushing */ + } + else mm_log (reply->text,ERROR); + } + + /* not much can do if short caching */ + else if (stream->scache) ret = mail_sort_msgs (stream,charset,spg,pgm,flags); + else { /* try to be a bit more clever */ + char *s,*t; + unsigned long len; + MESSAGECACHE *elt; + SORTCACHE **sc; + SORTPGM *sp; + long ftflags = 0; + /* see if need envelopes */ + for (sp = pgm; sp && !ftflags; sp = sp->next) switch (sp->function) { + case SORTDATE: case SORTFROM: case SORTSUBJECT: case SORTTO: case SORTCC: + ftflags = FT_NEEDENV + ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL); + } + if (spg) { /* only if a search needs to be done */ + int silent = stream->silent; + stream->silent = T; /* don't pass up mm_searched() events */ + /* search for messages */ + mail_search_full (stream,charset,spg,flags & SE_NOSERVER); + stream->silent = silent; /* restore silence state */ + } + /* initialize progress counters */ + pgm->nmsgs = pgm->progress.cached = 0; + /* pass 1: count messages to sort */ + for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i) + if ((elt = mail_elt (stream,i))->searched) { + pgm->nmsgs++; + if (ftflags ? !elt->private.msg.env : !elt->day) { + if (s) { /* continuing a sequence */ + if (i == last + 1) last = i; + else { /* end of range */ + if (last != start) sprintf (t,":%lu,%lu",last,i); + else sprintf (t,",%lu",i); + start = last = i; /* begin a new range */ + if ((len - (j = ((t += strlen (t)) - s)) < 20)) { + fs_resize ((void **) &s,len += MAILTMPLEN); + t = s + j; /* relocate current pointer */ + } + } + } + else { /* first time, start new buffer */ + s = (char *) fs_get (len = MAILTMPLEN); + sprintf (s,"%lu",start = last = i); + t = s + strlen (s); /* end of buffer */ + } + } + } + /* last sequence */ + if (last != start) sprintf (t,":%lu",last); + if (s) { /* load cache for all messages being sorted */ + imap_fetch (stream,s,ftflags); + fs_give ((void **) &s); + } + if (pgm->nmsgs) { /* pass 2: sort cache */ + sortresults_t sr = (sortresults_t) + mail_parameters (NIL,GET_SORTRESULTS,NIL); + sc = mail_sort_loadcache (stream,pgm); + /* pass 3: sort messages */ + if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags); + fs_give ((void **) &sc); /* don't need sort vector any more */ + /* also return via callback if requested */ + if (sr) (*sr) (stream,ret,pgm->nmsgs); + } + } + return ret; +} + +/* IMAP thread messages + * Accepts: mail stream + * thread type + * character set + * search program + * option flags + * Returns: thread node tree or NIL if error + */ + +THREADNODE *imap_thread (MAILSTREAM *stream,char *type,char *charset, + SEARCHPGM *spg,long flags) +{ + THREADER *thr; + if (!(flags & SE_NOSERVER) && + (!spg || (LEVELWITHIN (stream) || !(spg->older || spg->younger)))) + /* does server have this threader type? */ + for (thr = LOCAL->cap.threader; thr; thr = thr->next) + if (!compare_cstring (thr->name,type)) + return imap_thread_work (stream,type,charset,spg,flags); + /* server doesn't support it, do locally */ + return (flags & SE_NOLOCAL) ? NIL: + mail_thread_msgs (stream,type,charset,spg,flags | SE_NOSERVER,imap_sort); +} + +/* IMAP thread messages worker routine + * Accepts: mail stream + * thread type + * character set + * search program + * option flags + * Returns: thread node tree + */ + +THREADNODE *imap_thread_work (MAILSTREAM *stream,char *type,char *charset, + SEARCHPGM *spg,long flags) +{ + unsigned long i,start,last; + char *cmd = (flags & SE_UID) ? "UID THREAD" : "THREAD"; + IMAPARG *args[4],apgm,achs,aspg; + IMAPPARSEDREPLY *reply; + THREADNODE *ret = NIL; + SEARCHSET *ss = NIL; + SEARCHPGM *tsp = NIL; + apgm.type = ATOM; apgm.text = (void *) type; + achs.type = ASTRING; + achs.text = (void *) (charset ? charset : "US-ASCII"); + aspg.type = SEARCHPROGRAM; + /* did he provide a searchpgm? */ + if (!(aspg.text = (void *) spg)) { + for (i = 1,start = last = 0; i <= stream->nmsgs; ++i) + if (mail_elt (stream,i)->searched) { + if (ss) { /* continuing a sequence */ + if (i == last + 1) last = i; + else { /* end of range */ + if (last != start) ss->last = last; + (ss = ss->next = mail_newsearchset ())->first = i; + start = last =i; /* begin a new range */ + } + } + else { /* first time, start new searchpgm */ + (tsp = mail_newsearchpgm ())->msgno = ss = mail_newsearchset (); + ss->first = start = last = i; + } + } + /* nothing to sort if no messages */ + if (!(aspg.text = (void *) tsp)) return NIL; + /* else install last sequence */ + if (last != start) ss->last = last; + } + + args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL; + /* ask server to do it */ + reply = imap_send (stream,cmd,args); + if (tsp) { /* was there a temporary searchpgm? */ + aspg.text = NIL; /* yes, flush it */ + mail_free_searchpgm (&tsp); + /* did server barf with that searchpgm? */ + if (!(flags & SE_UID) && !strcmp (reply->key,"BAD")) { + LOCAL->filter = T; /* retry, filtering SORT/THREAD results */ + reply = imap_send (stream,cmd,args); + LOCAL->filter = NIL; /* turn off filtering */ + } + } + /* do locally if server barfs */ + if (!strcmp (reply->key,"BAD")) + ret = (flags & SE_NOLOCAL) ? NIL: + mail_thread_msgs (stream,type,charset,spg,flags | SE_NOSERVER,imap_sort); + /* server threaded OK? */ + else if (imap_OK (stream,reply)) { + ret = LOCAL->threaddata; + LOCAL->threaddata = NIL; /* mail program is responsible for flushing */ + } + else mm_log (reply->text,ERROR); + return ret; +} + +/* IMAP ping mailbox + * Accepts: MAIL stream + * Returns: T if stream still alive, else NIL + */ + +long imap_ping (MAILSTREAM *stream) +{ + return (LOCAL->netstream && /* send "NOOP" */ + imap_OK (stream,imap_send (stream,"NOOP",NIL))) ? T : NIL; +} + + +/* IMAP check mailbox + * Accepts: MAIL stream + */ + +void imap_check (MAILSTREAM *stream) +{ + /* send "CHECK" */ + IMAPPARSEDREPLY *reply = imap_send (stream,"CHECK",NIL); + mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR); +} + +/* IMAP expunge mailbox + * Accepts: MAIL stream + * sequence to expunge if non-NIL + * expunge options + * Returns: T if success, NIL if failure + */ + +long imap_expunge (MAILSTREAM *stream,char *sequence,long options) +{ + long ret = NIL; + IMAPPARSEDREPLY *reply = NIL; + if (sequence) { /* wants selective expunging? */ + if (options & EX_UID) { /* UID EXPUNGE form? */ + if (LEVELUIDPLUS (stream)) {/* server support UIDPLUS? */ + IMAPARG *args[2],aseq; + aseq.type = SEQUENCE; aseq.text = (void *) sequence; + args[0] = &aseq; args[1] = NIL; + ret = imap_OK (stream,reply = imap_send (stream,"UID EXPUNGE",args)); + } + else mm_log ("[NOTUIDPLUS] Can't do UID EXPUNGE with this server",ERROR); + } + /* otherwise try to make into UID EXPUNGE */ + else if (mail_sequence (stream,sequence)) { + unsigned long i,j; + char *t = (char *) fs_get (IMAPTMPLEN); + char *s = t; + /* search through mailbox */ + for (*s = '\0', i = 1; i <= stream->nmsgs; ++i) + if (mail_elt (stream,i)->sequence) { + if (t[0]) *s++ = ','; /* prepend with comma if not first time */ + sprintf (s,"%lu",mail_uid (stream,j = i)); + s += strlen (s); /* point at end of string */ + /* search for possible end of range */ + while ((i < stream->nmsgs) && mail_elt (stream,i+1)->sequence) i++; + if (i != j) { /* output end of range */ + sprintf (s,":%lu",mail_uid (stream,i)); + s += strlen (s); /* point at end of string */ + } + if ((s - t) > (IMAPTMPLEN - 50)) { + mm_log ("Excessively complex sequence",ERROR); + return NIL; + } + } + /* now do as UID EXPUNGE */ + ret = imap_expunge (stream,t,EX_UID); + fs_give ((void **) &t); + } + } + /* ordinary EXPUNGE */ + else ret = imap_OK (stream,reply = imap_send (stream,"EXPUNGE",NIL)); + if (reply) mm_log (reply->text,ret ? (long) NIL : ERROR); + return ret; +} + +/* IMAP copy message(s) + * Accepts: MAIL stream + * sequence + * destination mailbox + * option flags + * Returns: T if successful else NIL + */ + +long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long flags) +{ + char *cmd = (LEVELIMAP4 (stream) && (flags & CP_UID)) ? "UID COPY" : "COPY"; + char *s; + long ret = NIL; + IMAPPARSEDREPLY *reply; + IMAPARG *args[3],aseq,ambx; + imapreferral_t ir = + (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL); + mailproxycopy_t pc = + (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL); + if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence, + flags & CP_UID); + aseq.type = SEQUENCE; aseq.text = (void *) sequence; + ambx.type = ASTRING; ambx.text = (void *) mailbox; + args[0] = &aseq; args[1] = &ambx; args[2] = NIL; + /* note mailbox in case APPENDUID */ + LOCAL->appendmailbox = mailbox; + /* send "COPY sequence mailbox" */ + ret = imap_OK (stream,reply = imap_send (stream,cmd,args)); + LOCAL->appendmailbox = NIL; /* no longer appending */ + if (ret) { /* success, delete messages if move */ + if (flags & CP_MOVE) imap_flag (stream,sequence,"\\Deleted", + ST_SET + ((flags&CP_UID) ? ST_UID : NIL)); + } + /* failed, do referral action if any */ + else if (ir && pc && LOCAL->referral && mail_sequence (stream,sequence) && + (s = (*ir) (stream,LOCAL->referral,REFCOPY))) + ret = (*pc) (stream,sequence,s,flags | (stream->debug ? CP_DEBUG : NIL)); + /* otherwise issue error message */ + else mm_log (reply->text,ERROR); + return ret; +} + +/* IMAP mail append message from stringstruct + * Accepts: MAIL stream + * destination mailbox + * append callback + * data for callback + * Returns: T if append successful, else NIL + */ + +long imap_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + MAILSTREAM *st = stream; + IMAPARG *args[3],ambx,amap; + IMAPPARSEDREPLY *reply = NIL; + APPENDDATA map; + char tmp[MAILTMPLEN]; + long debug = stream ? stream->debug : NIL; + long ret = NIL; + imapreferral_t ir = + (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL); + /* mailbox must be good */ + if (mail_valid_net (mailbox,&imapdriver,NIL,tmp)) { + /* create a stream if given one no good */ + if ((stream && LOCAL && LOCAL->netstream) || + (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT | + (debug ? OP_DEBUG : NIL)))) { + /* note mailbox in case APPENDUID */ + LOCAL->appendmailbox = mailbox; + /* use multi-append? */ + if (LEVELMULTIAPPEND (stream)) { + ambx.type = ASTRING; ambx.text = (void *) tmp; + amap.type = MULTIAPPEND; amap.text = (void *) ↦ + map.af = af; map.data = data; + args[0] = &ambx; args[1] = &amap; args[2] = NIL; + /* success if OK */ + ret = imap_OK (stream,reply = imap_send (stream,"APPEND",args)); + LOCAL->appendmailbox = NIL; + } + /* do succession of single appends */ + else while ((*af) (stream,data,&map.flags,&map.date,&map.message) && + map.message && + (ret = imap_OK (stream,reply = + imap_append_single (stream,tmp,map.flags, + map.date,map.message)))); + LOCAL->appendmailbox = NIL; + /* don't do referrals if success or no reply */ + if (ret || !reply) mailbox = NIL; + /* otherwise generate referral */ + else if (!(mailbox = (ir && LOCAL->referral) ? + (*ir) (stream,LOCAL->referral,REFAPPEND) : NIL)) + mm_log (reply->text,ERROR); + /* close temporary stream */ + if (st != stream) stream = mail_close (stream); + if (mailbox) /* chase referral if any */ + ret = imap_append_referral (mailbox,tmp,af,data,map.flags,map.date, + map.message,&map,debug); + } + else mm_log ("Can't access server for append",ERROR); + } + return ret; /* return */ +} + +/* IMAP mail append message referral retry + * Accepts: destination mailbox + * temporary buffer + * append callback + * data for callback + * flags from previous attempt + * date from previous attempt + * message stringstruct from previous attempt + * options (currently non-zero to set OP_DEBUG) + * Returns: T if append successful, else NIL + */ + +long imap_append_referral (char *mailbox,char *tmp,append_t af,void *data, + char *flags,char *date,STRING *message, + APPENDDATA *map,long options) +{ + MAILSTREAM *stream; + IMAPARG *args[3],ambx,amap; + IMAPPARSEDREPLY *reply; + imapreferral_t ir = + (imapreferral_t) mail_parameters (NIL,GET_IMAPREFERRAL,NIL); + /* barf if bad mailbox */ + while (mailbox && mail_valid_net (mailbox,&imapdriver,NIL,tmp)) { + /* create a stream if given one no good */ + if (!(stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT | + (options ? OP_DEBUG : NIL)))) { + sprintf (tmp,"Can't access referral server: %.80s",mailbox); + mm_log (tmp,ERROR); + return NIL; + } + /* got referral server, use multi-append? */ + if (LEVELMULTIAPPEND (stream)) { + ambx.type = ASTRING; ambx.text = (void *) tmp; + amap.type = MULTIAPPENDREDO; amap.text = (void *) map; + args[0] = &ambx; args[1] = &amap; args[2] = NIL; + /* do multiappend on referral site */ + if (imap_OK (stream,reply = imap_send (stream,"APPEND",args))) { + mail_close (stream); /* multiappend OK, close stream */ + return LONGT; /* all done */ + } + } + /* do multiple single appends */ + else while (imap_OK (stream,reply = + imap_append_single (stream,tmp,flags,date,message))) + if (!((*af) (stream,data,&flags,&date,&message) && message)) { + mail_close (stream); /* last message, close stream */ + return LONGT; /* all done */ + } + /* generate error if no nested referral */ + if (!(mailbox = (ir && LOCAL->referral) ? + (*ir) (stream,LOCAL->referral,REFAPPEND) : NIL)) + mm_log (reply->text,ERROR); + mail_close (stream); /* close previous referral stream */ + } + return NIL; /* bogus mailbox */ +} + +/* IMAP append single message + * Accepts: mail stream + * destination mailbox + * initial flags + * internal date + * stringstruct of message to append + * Returns: reply from append + */ + +IMAPPARSEDREPLY *imap_append_single (MAILSTREAM *stream,char *mailbox, + char *flags,char *date,STRING *message) +{ + MESSAGECACHE elt; + IMAPARG *args[5],ambx,aflg,adat,amsg; + IMAPPARSEDREPLY *reply; + char tmp[MAILTMPLEN]; + int i; + ambx.type = ASTRING; ambx.text = (void *) mailbox; + args[i = 0] = &ambx; + if (flags) { + aflg.type = FLAGS; aflg.text = (void *) flags; + args[++i] = &aflg; + } + if (date) { /* ensure date in INTERNALDATE format */ + if (!mail_parse_date (&elt,date)) { + /* flush previous reply */ + if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line); + /* build new fake reply */ + LOCAL->reply.tag = LOCAL->reply.line = cpystr ("*"); + LOCAL->reply.key = "BAD"; + LOCAL->reply.text = "Bad date in append"; + return &LOCAL->reply; + } + adat.type = ASTRING; + adat.text = (void *) (date = mail_date (tmp,&elt)); + args[++i] = &adat; + } + amsg.type = LITERAL; amsg.text = (void *) message; + args[++i] = &amsg; + args[++i] = NIL; + /* easy if IMAP4[rev1] */ + if (LEVELIMAP4 (stream)) reply = imap_send (stream,"APPEND",args); + else { /* try the IMAP2bis way */ + args[1] = &amsg; args[2] = NIL; + reply = imap_send (stream,"APPEND",args); + } + return reply; +} + +/* IMAP garbage collect stream + * Accepts: Mail stream + * garbage collection flags + */ + +void imap_gc (MAILSTREAM *stream,long gcflags) +{ + unsigned long i; + MESSAGECACHE *elt; + mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); + /* make sure the cache is large enough */ + (*mc) (stream,stream->nmsgs,CH_SIZE); + if (gcflags & GC_TEXTS) { /* garbage collect texts? */ + if (!stream->scache) for (i = 1; i <= stream->nmsgs; ++i) + if (elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) + imap_gc_body (elt->private.msg.body); + imap_gc_body (stream->body); + } + /* gc cache if requested and unlocked */ + if (gcflags & GC_ELT) for (i = 1; i <= stream->nmsgs; ++i) + if ((elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) && + (elt->lockcount == 1)) (*mc) (stream,i,CH_FREE); +} + +/* IMAP garbage collect body texts + * Accepts: body to GC + */ + +void imap_gc_body (BODY *body) +{ + PART *part; + if (body) { /* have a body? */ + if (body->mime.text.data) /* flush MIME data */ + fs_give ((void **) &body->mime.text.data); + /* flush text contents */ + if (body->contents.text.data) + fs_give ((void **) &body->contents.text.data); + body->mime.text.size = body->contents.text.size = 0; + /* multipart? */ + if (body->type == TYPEMULTIPART) + for (part = body->nested.part; part; part = part->next) + imap_gc_body (&part->body); + /* MESSAGE/RFC822? */ + else if ((body->type == TYPEMESSAGE) && !strcmp (body->subtype,"RFC822")) { + imap_gc_body (body->nested.msg->body); + if (body->nested.msg->full.text.data) + fs_give ((void **) &body->nested.msg->full.text.data); + if (body->nested.msg->header.text.data) + fs_give ((void **) &body->nested.msg->header.text.data); + if (body->nested.msg->text.text.data) + fs_give ((void **) &body->nested.msg->text.text.data); + body->nested.msg->full.text.size = body->nested.msg->header.text.size = + body->nested.msg->text.text.size = 0; + } + } +} + +/* IMAP get capabilities + * Accepts: mail stream + */ + +void imap_capability (MAILSTREAM *stream) +{ + THREADER *thr,*t; + LOCAL->gotcapability = NIL; /* flush any previous capabilities */ + /* request new capabilities */ + imap_send (stream,"CAPABILITY",NIL); + if (!LOCAL->gotcapability) { /* did server get any? */ + /* no, flush threaders just in case */ + if (thr = LOCAL->cap.threader) while (t = thr) { + fs_give ((void **) &t->name); + thr = t->next; + fs_give ((void **) &t); + } + /* zap most capabilities */ + memset (&LOCAL->cap,0,sizeof (LOCAL->cap)); + /* assume IMAP2bis server if failure */ + LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T; + } +} + +/* IMAP set ACL + * Accepts: mail stream + * mailbox name + * authentication identifer + * new access rights + * Returns: T on success, NIL on failure + */ + +long imap_setacl (MAILSTREAM *stream,char *mailbox,char *id,char *rights) +{ + IMAPARG *args[4],ambx,aid,art; + ambx.type = aid.type = art.type = ASTRING; + ambx.text = (void *) mailbox; aid.text = (void *) id; + art.text = (void *) rights; + args[0] = &ambx; args[1] = &aid; args[2] = &art; args[3] = NIL; + return imap_acl_work (stream,"SETACL",args); +} + + +/* IMAP delete ACL + * Accepts: mail stream + * mailbox name + * authentication identifer + * Returns: T on success, NIL on failure + */ + +long imap_deleteacl (MAILSTREAM *stream,char *mailbox,char *id) +{ + IMAPARG *args[3],ambx,aid; + ambx.type = aid.type = ASTRING; + ambx.text = (void *) mailbox; aid.text = (void *) id; + args[0] = &ambx; args[1] = &aid; args[2] = NIL; + return imap_acl_work (stream,"DELETEACL",args); +} + + +/* IMAP get ACL + * Accepts: mail stream + * mailbox name + * Returns: T on success with data returned via callback, NIL on failure + */ + +long imap_getacl (MAILSTREAM *stream,char *mailbox) +{ + IMAPARG *args[2],ambx; + ambx.type = ASTRING; ambx.text = (void *) mailbox; + args[0] = &ambx; args[1] = NIL; + return imap_acl_work (stream,"GETACL",args); +} + +/* IMAP list rights + * Accepts: mail stream + * mailbox name + * authentication identifer + * Returns: T on success with data returned via callback, NIL on failure + */ + +long imap_listrights (MAILSTREAM *stream,char *mailbox,char *id) +{ + IMAPARG *args[3],ambx,aid; + ambx.type = aid.type = ASTRING; + ambx.text = (void *) mailbox; aid.text = (void *) id; + args[0] = &ambx; args[1] = &aid; args[2] = NIL; + return imap_acl_work (stream,"LISTRIGHTS",args); +} + + +/* IMAP my rights + * Accepts: mail stream + * mailbox name + * Returns: T on success with data returned via callback, NIL on failure + */ + +long imap_myrights (MAILSTREAM *stream,char *mailbox) +{ + IMAPARG *args[2],ambx; + ambx.type = ASTRING; ambx.text = (void *) mailbox; + args[0] = &ambx; args[1] = NIL; + return imap_acl_work (stream,"MYRIGHTS",args); +} + + +/* IMAP ACL worker routine + * Accepts: mail stream + * command + * command arguments + * Returns: T on success, NIL on failure + */ + +long imap_acl_work (MAILSTREAM *stream,char *command,IMAPARG *args[]) +{ + long ret = NIL; + if (LEVELACL (stream)) { /* send command */ + IMAPPARSEDREPLY *reply; + if (imap_OK (stream,reply = imap_send (stream,command,args))) + ret = LONGT; + else mm_log (reply->text,ERROR); + } + else mm_log ("ACL not available on this IMAP server",ERROR); + return ret; +} + +/* IMAP set quota + * Accepts: mail stream + * quota root name + * resource limit list as a stringlist + * Returns: T on success with data returned via callback, NIL on failure + */ + +long imap_setquota (MAILSTREAM *stream,char *qroot,STRINGLIST *limits) +{ + long ret = NIL; + if (LEVELQUOTA (stream)) { /* send "SETQUOTA" */ + IMAPPARSEDREPLY *reply; + IMAPARG *args[3],aqrt,alim; + aqrt.type = ASTRING; aqrt.text = (void *) qroot; + alim.type = SNLIST; alim.text = (void *) limits; + args[0] = &aqrt; args[1] = &alim; args[2] = NIL; + if (imap_OK (stream,reply = imap_send (stream,"SETQUOTA",args))) + ret = LONGT; + else mm_log (reply->text,ERROR); + } + else mm_log ("Quota not available on this IMAP server",ERROR); + return ret; +} + +/* IMAP get quota + * Accepts: mail stream + * quota root name + * Returns: T on success with data returned via callback, NIL on failure + */ + +long imap_getquota (MAILSTREAM *stream,char *qroot) +{ + long ret = NIL; + if (LEVELQUOTA (stream)) { /* send "GETQUOTA" */ + IMAPPARSEDREPLY *reply; + IMAPARG *args[2],aqrt; + aqrt.type = ASTRING; aqrt.text = (void *) qroot; + args[0] = &aqrt; args[1] = NIL; + if (imap_OK (stream,reply = imap_send (stream,"GETQUOTA",args))) + ret = LONGT; + else mm_log (reply->text,ERROR); + } + else mm_log ("Quota not available on this IMAP server",ERROR); + return ret; +} + + +/* IMAP get quota root + * Accepts: mail stream + * mailbox name + * Returns: T on success with data returned via callback, NIL on failure + */ + +long imap_getquotaroot (MAILSTREAM *stream,char *mailbox) +{ + long ret = NIL; + if (LEVELQUOTA (stream)) { /* send "GETQUOTAROOT" */ + IMAPPARSEDREPLY *reply; + IMAPARG *args[2],ambx; + ambx.type = ASTRING; ambx.text = (void *) mailbox; + args[0] = &ambx; args[1] = NIL; + if (imap_OK (stream,reply = imap_send (stream,"GETQUOTAROOT",args))) + ret = LONGT; + else mm_log (reply->text,ERROR); + } + else mm_log ("Quota not available on this IMAP server",ERROR); + return ret; +} + +/* Internal routines */ + + +/* IMAP send command + * Accepts: MAIL stream + * command + * argument list + * Returns: parsed reply + */ + +#define CMDBASE LOCAL->tmp /* command base */ + +IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,IMAPARG *args[]) +{ + IMAPPARSEDREPLY *reply; + IMAPARG *arg,**arglst; + SORTPGM *spg; + STRINGLIST *list; + SIZEDTEXT st; + APPENDDATA *map; + sendcommand_t sc = (sendcommand_t) mail_parameters (NIL,GET_SENDCOMMAND,NIL); + size_t i; + void *a; + char c,*s,*t,tag[10]; + stream->unhealthy = NIL; /* make stream healthy again */ + /* gensym a new tag */ + sprintf (tag,"%08lx",0xffffffff & (stream->gensym++)); + if (!LOCAL->netstream) /* make sure have a session */ + return imap_fake (stream,tag,"[CLOSED] IMAP connection lost"); + mail_lock (stream); /* lock up the stream */ + if (sc) /* tell client sending a command */ + (*sc) (stream,cmd,((compare_cstring (cmd,"FETCH") && + compare_cstring (cmd,"STORE") && + compare_cstring (cmd,"SEARCH")) ? + NIL : SC_EXPUNGEDEFERRED)); + /* ignore referral from previous command */ + if (LOCAL->referral) fs_give ((void **) &LOCAL->referral); + sprintf (CMDBASE,"%s %s",tag,cmd); + s = CMDBASE + strlen (CMDBASE); + if (arglst = args) while (arg = *arglst++) { + *s++ = ' '; /* delimit argument with space */ + switch (arg->type) { + case ATOM: /* atom */ + for (t = (char *) arg->text; *t; *s++ = *t++); + break; + case NUMBER: /* number */ + sprintf (s,"%lu",(unsigned long) arg->text); + s += strlen (s); + break; + case FLAGS: /* flag list as a single string */ + if (*(t = (char *) arg->text) != '(') { + *s++ = '('; /* wrap parens around string */ + while (*t) *s++ = *t++; + *s++ = ')'; /* wrap parens around string */ + } + else while (*t) *s++ = *t++; + break; + case ASTRING: /* atom or string, must be literal? */ + st.size = strlen ((char *) (st.data = (unsigned char *) arg->text)); + if (reply = imap_send_astring (stream,tag,&s,&st,NIL,CMDBASE+MAXCOMMAND)) + return reply; + break; + case LITERAL: /* literal, as a stringstruct */ + if (reply = imap_send_literal (stream,tag,&s,arg->text)) return reply; + break; + + case LIST: /* list of strings */ + list = (STRINGLIST *) arg->text; + c = '('; /* open paren */ + do { /* for each list item */ + *s++ = c; /* write prefix character */ + if (reply = imap_send_astring (stream,tag,&s,&list->text,NIL, + CMDBASE+MAXCOMMAND)) return reply; + c = ' '; /* prefix character for subsequent strings */ + } + while (list = list->next); + *s++ = ')'; /* close list */ + break; + case SEARCHPROGRAM: /* search program */ + if (reply = imap_send_spgm (stream,tag,CMDBASE,&s,arg->text, + CMDBASE+MAXCOMMAND)) + return reply; + break; + case SORTPROGRAM: /* search program */ + c = '('; /* open paren */ + for (spg = (SORTPGM *) arg->text; spg; spg = spg->next) { + *s++ = c; /* write prefix */ + if (spg->reverse) for (t = "REVERSE "; *t; *s++ = *t++); + switch (spg->function) { + case SORTDATE: + for (t = "DATE"; *t; *s++ = *t++); + break; + case SORTARRIVAL: + for (t = "ARRIVAL"; *t; *s++ = *t++); + break; + case SORTFROM: + for (t = "FROM"; *t; *s++ = *t++); + break; + case SORTSUBJECT: + for (t = "SUBJECT"; *t; *s++ = *t++); + break; + case SORTTO: + for (t = "TO"; *t; *s++ = *t++); + break; + case SORTCC: + for (t = "CC"; *t; *s++ = *t++); + break; + case SORTSIZE: + for (t = "SIZE"; *t; *s++ = *t++); + break; + default: + fatal ("Unknown sort program function in imap_send()!"); + } + c = ' '; /* prefix character for subsequent items */ + } + *s++ = ')'; /* close list */ + break; + + case BODYTEXT: /* body section */ + for (t = "BODY["; *t; *s++ = *t++); + for (t = (char *) arg->text; *t; *s++ = *t++); + break; + case BODYPEEK: /* body section */ + for (t = "BODY.PEEK["; *t; *s++ = *t++); + for (t = (char *) arg->text; *t; *s++ = *t++); + break; + case BODYCLOSE: /* close bracket and possible length */ + s[-1] = ']'; /* no leading space */ + for (t = (char *) arg->text; *t; *s++ = *t++); + break; + case SEQUENCE: /* sequence */ + if ((i = strlen (t = (char *) arg->text)) <= (size_t) MAXCOMMAND) + while (*t) *s++ = *t++; /* easy case */ + else { + mail_unlock (stream); /* unlock stream */ + a = arg->text; /* save original sequence pointer */ + arg->type = ATOM; /* make recursive call be faster */ + do { /* break up into multiple commands */ + if (i <= MAXCOMMAND) {/* final part? */ + reply = imap_send (stream,cmd,args); + i = 0; /* and mark as done */ + } + else { /* still needs to be split further */ + if (!(t = strchr (t + MAXCOMMAND - 30,',')) || + ((t - (char *) arg->text) > MAXCOMMAND)) + fatal ("impossible over-long sequence"); + *t = '\0'; /* tie off sequence at point of split*/ + /* recurse to do this part */ + reply = imap_send (stream,cmd,args); + *t++ = ','; /* restore the comma in case something cares */ + /* punt if error */ + if (!imap_OK (stream,reply)) break; + /* calculate size of remaining sequence */ + i -= (t - (char *) arg->text); + /* point to new remaining sequence */ + arg->text = (void *) t; + } + } while (i); + arg->type = SEQUENCE; /* restore in case something cares */ + arg->text = a; + return reply; /* return result */ + } + break; + case LISTMAILBOX: /* astring with wildcards */ + st.size = strlen ((char *) (st.data = (unsigned char *) arg->text)); + if (reply = imap_send_astring (stream,tag,&s,&st,T,CMDBASE+MAXCOMMAND)) + return reply; + break; + + case MULTIAPPEND: /* append multiple messages */ + /* get package pointer */ + map = (APPENDDATA *) arg->text; + if (!(*map->af) (stream,map->data,&map->flags,&map->date,&map->message)|| + !map->message) { + STRING es; + INIT (&es,mail_string,"",0); + return (reply = imap_send_literal (stream,tag,&s,&es)) ? + reply : imap_fake (stream,tag,"Server zero-length literal error"); + } + case MULTIAPPENDREDO: /* redo multiappend */ + /* get package pointer */ + map = (APPENDDATA *) arg->text; + do { /* make sure date valid if given */ + char datetmp[MAILTMPLEN]; + MESSAGECACHE elt; + STRING es; + if (!map->date || mail_parse_date (&elt,map->date)) { + if (t = map->flags) { /* flags given? */ + if (*t != '(') { + *s++ = '('; /* wrap parens around string */ + while (*t) *s++ = *t++; + *s++ = ')'; /* wrap parens around string */ + } + else while (*t) *s++ = *t++; + *s++ = ' '; /* delimit with space */ + } + if (map->date) { /* date given? */ + st.size = strlen ((char *) (st.data = (unsigned char *) + mail_date (datetmp,&elt))); + if (reply = imap_send_astring (stream,tag,&s,&st,NIL, + CMDBASE+MAXCOMMAND)) return reply; + *s++ = ' '; /* delimit with space */ + } + if (reply = imap_send_literal (stream,tag,&s,map->message)) + return reply; + /* get next message */ + if ((*map->af) (stream,map->data,&map->flags,&map->date, + &map->message)) { + /* have a message, delete next in command */ + if (map->message) *s++ = ' '; + continue; /* loop back for next message */ + } + } + /* bad date or need to abort */ + INIT (&es,mail_string,"",0); + return (reply = imap_send_literal (stream,tag,&s,&es)) ? + reply : imap_fake (stream,tag,"Server zero-length literal error"); + break; /* exit the loop */ + } while (map->message); + break; + + case SNLIST: /* list of string/number pairs */ + list = (STRINGLIST *) arg->text; + c = '('; /* open paren */ + do { /* for each list item */ + *s++ = c; /* write prefix character */ + if (list) { /* sigh, QUOTA has bizarre syntax! */ + for (t = (char *) list->text.data; *t; *s++ = *t++); + sprintf (s," %lu",list->text.size); + s += strlen (s); + c = ' '; /* prefix character for subsequent strings */ + } + } + while (list = list->next); + *s++ = ')'; /* close list */ + break; + default: + fatal ("Unknown argument type in imap_send()!"); + } + } + /* send the command */ + reply = imap_sout (stream,tag,CMDBASE,&s); + mail_unlock (stream); /* unlock stream */ + return reply; +} + +/* IMAP send atom-string + * Accepts: MAIL stream + * reply tag + * pointer to current position pointer of output bigbuf + * atom-string to output + * flag if list_wildcards allowed + * maximum to write as atom or qstring + * Returns: error reply or NIL if success + */ + +IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s, + SIZEDTEXT *as,long wildok,char *limit) +{ + unsigned long j; + char c; + STRING st; + /* default to atom unless empty or loser */ + int qflag = (as->size && !LOCAL->loser) ? NIL : T; + /* in case needed */ + INIT (&st,mail_string,(void *) as->data,as->size); + /* always write literal if no space */ + if ((*s + as->size) > limit) return imap_send_literal (stream,tag,s,&st); + for (j = 0; j < as->size; j++) switch (c = as->data[j]) { + default: /* all other characters */ + if (!(c & 0x80)) { /* must not be 8bit */ + if (c <= ' ') qflag = T; /* must quote if a CTL */ + break; + } + case '\0': /* not a CHAR */ + case '\012': case '\015': /* not a TEXT-CHAR */ + case '"': case '\\': /* quoted-specials (IMAP2 required this) */ + return imap_send_literal (stream,tag,s,&st); + case '*': case '%': /* list_wildcards */ + if (wildok) break; /* allowed if doing the wild thing */ + /* atom_specials */ + case '(': case ')': case '{': case ' ': case 0x7f: +#if 0 + case '"': case '\\': /* quoted-specials (could work in IMAP4) */ +#endif + qflag = T; /* must use quoted string format */ + break; + } + if (qflag) *(*s)++ = '"'; /* write open quote */ + for (j = 0; j < as->size; j++) *(*s)++ = as->data[j]; + if (qflag) *(*s)++ = '"'; /* write close quote */ + return NIL; +} + +/* IMAP send literal + * Accepts: MAIL stream + * reply tag + * pointer to current position pointer of output bigbuf + * literal to output as stringstruct + * Returns: error reply or NIL if success + */ + +IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s, + STRING *st) +{ + IMAPPARSEDREPLY *reply; + unsigned long i = SIZE (st); + unsigned long j; + sprintf (*s,"{%lu}",i); /* write literal count */ + *s += strlen (*s); /* size of literal count */ + /* send the command */ + reply = imap_sout (stream,tag,CMDBASE,s); + if (strcmp (reply->tag,"+")) {/* prompt for more data? */ + mail_unlock (stream); /* no, give up */ + return reply; + } + while (i) { /* dump the text */ + if (st->cursize) { /* if text to do in this chunk */ + /* RFC 3501 technically forbids NULs in literals. Normally, the + * delivering MTA would take care of MIME converting the message text + * so that it is NUL-free. If it doesn't, then we have the choice of + * either violating IMAP by sending NULs, corrupting the data, or going + * to lots of work to do MIME conversion in the IMAP server. + * + * No current stringstruct driver objects to having its buffer patched. + * If this ever changes, it will be necessary to change this kludge. + */ + /* patch NULs to C1 control */ + for (j = 0; j < st->cursize; ++j) + if (!st->curpos[j]) st->curpos[j] = 0x80; + if (!net_sout (LOCAL->netstream,st->curpos,st->cursize)) { + mail_unlock (stream); + return imap_fake (stream,tag,"[CLOSED] IMAP connection broken (data)"); + } + i -= st->cursize; /* note that we wrote out this much */ + st->curpos += (st->cursize - 1); + st->cursize = 0; + } + (*st->dtb->next) (st); /* advance to next buffer's worth */ + } + return NIL; /* success */ +} + +/* IMAP send search program + * Accepts: MAIL stream + * reply tag + * base pointer if trimming needed + * pointer to current position pointer of output bigbuf + * search program to output + * pointer to limit guideline + * Returns: error reply or NIL if success + */ + + +IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char *base, + char **s,SEARCHPGM *pgm,char *limit) +{ + IMAPPARSEDREPLY *reply; + SEARCHHEADER *hdr; + SEARCHOR *pgo; + SEARCHPGMLIST *pgl; + char *t; + /* trim if called recursively */ + if (base) *s = imap_send_spgm_trim (base,*s,NIL); + base = *s; /* this is the new base */ + /* default searchpgm */ + for (t = "ALL"; *t; *(*s)++ = *t++); + if (!pgm) return NIL; /* done if NIL searchpgm */ + if ((pgm->msgno && /* message sequences */ + (pgm->msgno->next || /* trim away first:last */ + (pgm->msgno->first != 1) || (pgm->msgno->last != stream->nmsgs)) && + (reply = imap_send_sset (stream,tag,base,s,pgm->msgno," ",limit))) || + (pgm->uid && + (reply = imap_send_sset (stream,tag,base,s,pgm->uid," UID ",limit)))) + return reply; + /* message sizes */ + if (pgm->larger) { + sprintf (*s," LARGER %lu",pgm->larger); + *s += strlen (*s); + } + if (pgm->smaller) { + sprintf (*s," SMALLER %lu",pgm->smaller); + *s += strlen (*s); + } + + /* message flags */ + if (pgm->answered) for (t = " ANSWERED"; *t; *(*s)++ = *t++); + if (pgm->unanswered) for (t =" UNANSWERED"; *t; *(*s)++ = *t++); + if (pgm->deleted) for (t =" DELETED"; *t; *(*s)++ = *t++); + if (pgm->undeleted) for (t =" UNDELETED"; *t; *(*s)++ = *t++); + if (pgm->draft) for (t =" DRAFT"; *t; *(*s)++ = *t++); + if (pgm->undraft) for (t =" UNDRAFT"; *t; *(*s)++ = *t++); + if (pgm->flagged) for (t =" FLAGGED"; *t; *(*s)++ = *t++); + if (pgm->unflagged) for (t =" UNFLAGGED"; *t; *(*s)++ = *t++); + if (pgm->recent) for (t =" RECENT"; *t; *(*s)++ = *t++); + if (pgm->old) for (t =" OLD"; *t; *(*s)++ = *t++); + if (pgm->seen) for (t =" SEEN"; *t; *(*s)++ = *t++); + if (pgm->unseen) for (t =" UNSEEN"; *t; *(*s)++ = *t++); + if ((pgm->keyword && /* keywords */ + (reply = imap_send_slist (stream,tag,base,s," KEYWORD ",pgm->keyword, + limit))) || + (pgm->unkeyword && + (reply = imap_send_slist (stream,tag,base,s," UNKEYWORD ", + pgm->unkeyword,limit)))) + return reply; + /* sent date ranges */ + if (pgm->sentbefore) imap_send_sdate (s,"SENTBEFORE",pgm->sentbefore); + if (pgm->senton) imap_send_sdate (s,"SENTON",pgm->senton); + if (pgm->sentsince) imap_send_sdate (s,"SENTSINCE",pgm->sentsince); + /* internal date ranges */ + if (pgm->before) imap_send_sdate (s,"BEFORE",pgm->before); + if (pgm->on) imap_send_sdate (s,"ON",pgm->on); + if (pgm->since) imap_send_sdate (s,"SINCE",pgm->since); + if (pgm->older) { + sprintf (*s," OLDER %lu",pgm->older); + *s += strlen (*s); + } + if (pgm->younger) { + sprintf (*s," YOUNGER %lu",pgm->younger); + *s += strlen (*s); + } + /* search texts */ + if ((pgm->bcc && (reply = imap_send_slist (stream,tag,base,s," BCC ", + pgm->bcc,limit))) || + (pgm->cc && (reply = imap_send_slist (stream,tag,base,s," CC ",pgm->cc, + limit))) || + (pgm->from && (reply = imap_send_slist (stream,tag,base,s," FROM ", + pgm->from,limit))) || + (pgm->to && (reply = imap_send_slist (stream,tag,base,s," TO ",pgm->to, + limit)))) + return reply; + if ((pgm->subject && (reply = imap_send_slist (stream,tag,base,s," SUBJECT ", + pgm->subject,limit))) || + (pgm->body && (reply = imap_send_slist (stream,tag,base,s," BODY ", + pgm->body,limit))) || + (pgm->text && (reply = imap_send_slist (stream,tag,base,s," TEXT ", + pgm->text,limit)))) + return reply; + + /* Note that these criteria are not supported by IMAP and have to be + emulated */ + if ((pgm->return_path && + (reply = imap_send_slist (stream,tag,base,s," HEADER Return-Path ", + pgm->return_path,limit))) || + (pgm->sender && + (reply = imap_send_slist (stream,tag,base,s," HEADER Sender ", + pgm->sender,limit))) || + (pgm->reply_to && + (reply = imap_send_slist (stream,tag,base,s," HEADER Reply-To ", + pgm->reply_to,limit))) || + (pgm->in_reply_to && + (reply = imap_send_slist (stream,tag,base,s," HEADER In-Reply-To ", + pgm->in_reply_to,limit))) || + (pgm->message_id && + (reply = imap_send_slist (stream,tag,base,s," HEADER Message-ID ", + pgm->message_id,limit))) || + (pgm->newsgroups && + (reply = imap_send_slist (stream,tag,base,s," HEADER Newsgroups ", + pgm->newsgroups,limit))) || + (pgm->followup_to && + (reply = imap_send_slist (stream,tag,base,s," HEADER Followup-To ", + pgm->followup_to,limit))) || + (pgm->references && + (reply = imap_send_slist (stream,tag,base,s," HEADER References ", + pgm->references,limit)))) return reply; + + /* all other headers */ + if (hdr = pgm->header) do { + *s = imap_send_spgm_trim (base,*s," HEADER "); + if (reply = imap_send_astring (stream,tag,s,&hdr->line,NIL,limit)) + return reply; + *(*s)++ = ' '; + if (reply = imap_send_astring (stream,tag,s,&hdr->text,NIL,limit)) + return reply; + } while (hdr = hdr->next); + for (pgo = pgm->or; pgo; pgo = pgo->next) { + *s = imap_send_spgm_trim (base,*s," OR ("); + if (reply = imap_send_spgm (stream,tag,base,s,pgo->first,limit)) + return reply; + for (t = ") ("; *t; *(*s)++ = *t++); + if (reply = imap_send_spgm (stream,tag,base,s,pgo->second,limit)) + return reply; + *(*s)++ = ')'; + } + for (pgl = pgm->not; pgl; pgl = pgl->next) { + *s = imap_send_spgm_trim (base,*s," NOT ("); + if (reply = imap_send_spgm (stream,tag,base,s,pgl->pgm,limit)) + return reply; + *(*s)++ = ')'; + } + /* trim if needed */ + *s = imap_send_spgm_trim (base,*s,NIL); + return NIL; /* search program written OK */ +} + + +/* Write new text and trim extraneous "ALL" from searchpgm + * Accepts: pointer to start of searchpgm or NIL + * current end pointer + * new text to write or NIL + * Returns: new end pointer, trimmed if needed + */ + +char *imap_send_spgm_trim (char *base,char *s,char *text) +{ + char *t; + /* write new text */ + if (text) for (t = text; *t; *s++ = *t++); + /* need to trim? */ + if (base && (s > (t = (base + 4))) && (*base == 'A') && (base[1] == 'L') && + (base[2] == 'L') && (base[3] == ' ')) { + memmove (base,t,s - t); /* yes, blat down remaining text */ + s -= 4; /* and reduce current pointer */ + } + return s; /* return new end pointer */ +} + +/* IMAP send search set + * Accepts: MAIL stream + * current command tag + * base pointer if trimming needed + * pointer to current position pointer of output bigbuf + * search set to output + * message prefix + * maximum output pointer + * Returns: NIL if success, error reply if error + */ + +IMAPPARSEDREPLY *imap_send_sset (MAILSTREAM *stream,char *tag,char *base, + char **s,SEARCHSET *set,char *prefix, + char *limit) +{ + IMAPPARSEDREPLY *reply; + STRING st; + char c,*t; + char *start = *s; + /* trim and write prefix */ + *s = imap_send_spgm_trim (base,*s,prefix); + /* run down search list */ + for (c = NIL; set && (*s < limit); set = set->next, c = ',') { + if (c) *(*s)++ = c; /* write delimiter and first value */ + if (set->first == 0xffffffff) *(*s)++ = '*'; + else { + sprintf (*s,"%lu",set->first); + *s += strlen (*s); + } + /* have a second value? */ + if (set->last && (set->first != set->last)) { + *(*s)++ = ':'; /* write delimiter and second value */ + if (set->last == 0xffffffff) *(*s)++ = '*'; + else { + sprintf (*s,"%lu",set->last); + *s += strlen (*s); + } + } + } + if (set) { /* insert "OR" in front of incomplete set */ + memmove (start + 3,start,*s - start); + memcpy (start," OR",3); + *s += 3; /* point to end of buffer */ + /* write glue that is equivalent to ALL */ + for (t =" ((OR BCC FOO NOT BCC "; *t; *(*s)++ = *t++); + /* but broken by a literal */ + INIT (&st,mail_string,(void *) "FOO",3); + if (reply = imap_send_literal (stream,tag,s,&st)) return reply; + *(*s)++ = ')'; /* close glue */ + if (reply = imap_send_sset (stream,tag,NIL,s,set,prefix,limit)) + return reply; + *(*s)++ = ')'; /* close second OR argument */ + } + return NIL; +} + +/* IMAP send search list + * Accepts: MAIL stream + * reply tag + * base pointer if trimming needed + * pointer to current position pointer of output bigbuf + * name of search list + * search list to output + * maximum output pointer + * Returns: NIL if success, error reply if error + */ + +IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char *base, + char **s,char *name,STRINGLIST *list, + char *limit) +{ + IMAPPARSEDREPLY *reply; + do { + *s = imap_send_spgm_trim (base,*s,name); + base = NIL; /* no longer need trimming */ + reply = imap_send_astring (stream,tag,s,&list->text,NIL,limit); + } + while (!reply && (list = list->next)); + return reply; +} + + +/* IMAP send search date + * Accepts: pointer to current position pointer of output bigbuf + * field name + * search date to output + */ + +void imap_send_sdate (char **s,char *name,unsigned short date) +{ + sprintf (*s," %s %d-%s-%d",name,date & 0x1f, + months[((date >> 5) & 0xf) - 1],BASEYEAR + (date >> 9)); + *s += strlen (*s); +} + +/* IMAP send buffered command to sender + * Accepts: MAIL stream + * reply tag + * string + * pointer to string tail pointer + * Returns: reply + */ + +IMAPPARSEDREPLY *imap_sout (MAILSTREAM *stream,char *tag,char *base,char **s) +{ + IMAPPARSEDREPLY *reply; + if (stream->debug) { /* output debugging telemetry */ + **s = '\0'; + mail_dlog (base,LOCAL->sensitive); + } + *(*s)++ = '\015'; /* append CRLF */ + *(*s)++ = '\012'; + **s = '\0'; + reply = net_sout (LOCAL->netstream,base,*s - base) ? + imap_reply (stream,tag) : + imap_fake (stream,tag,"[CLOSED] IMAP connection broken (command)"); + *s = base; /* restart buffer */ + return reply; +} + + +/* IMAP send null-terminated string to sender + * Accepts: MAIL stream + * string + * Returns: T if success, else NIL + */ + +long imap_soutr (MAILSTREAM *stream,char *string) +{ + long ret; + unsigned long i; + char *s; + if (stream->debug) mm_dlog (string); + sprintf (s = (char *) fs_get ((i = strlen (string) + 2) + 1), + "%s\015\012",string); + ret = net_sout (LOCAL->netstream,s,i); + fs_give ((void **) &s); + return ret; +} + +/* IMAP get reply + * Accepts: MAIL stream + * tag to search or NIL if want a greeting + * Returns: parsed reply, never NIL + */ + +IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag) +{ + IMAPPARSEDREPLY *reply; + while (LOCAL->netstream) { /* parse reply from server */ + if (reply = imap_parse_reply (stream,net_getline (LOCAL->netstream))) { + /* continuation ready? */ + if (!strcmp (reply->tag,"+")) return reply; + /* untagged data? */ + else if (!strcmp (reply->tag,"*")) { + imap_parse_unsolicited (stream,reply); + if (!tag) return reply; /* return if just wanted greeting */ + } + else { /* tagged data */ + if (tag && !compare_cstring (tag,reply->tag)) return reply; + /* report bogon */ + sprintf (LOCAL->tmp,"Unexpected tagged response: %.80s %.80s %.80s", + (char *) reply->tag,(char *) reply->key,(char *) reply->text); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + } + } + return imap_fake (stream,tag, + "[CLOSED] IMAP connection broken (server response)"); +} + +/* IMAP parse reply + * Accepts: MAIL stream + * text of reply + * Returns: parsed reply, or NIL if can't parse at least a tag and key + */ + + +IMAPPARSEDREPLY *imap_parse_reply (MAILSTREAM *stream,char *text) +{ + char *r; + if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line); + /* init fields in case error */ + LOCAL->reply.key = LOCAL->reply.text = LOCAL->reply.tag = NIL; + if (!(LOCAL->reply.line = text)) { + /* NIL text means the stream died */ + if (LOCAL->netstream) net_close (LOCAL->netstream); + LOCAL->netstream = NIL; + return NIL; + } + if (stream->debug) mm_dlog (LOCAL->reply.line); + if (!(LOCAL->reply.tag = strtok_r (LOCAL->reply.line," ",&r))) { + mm_notify (stream,"IMAP server sent a blank line",WARN); + stream->unhealthy = T; + return NIL; + } + /* non-continuation replies */ + if (strcmp (LOCAL->reply.tag,"+")) { + /* parse key */ + if (!(LOCAL->reply.key = strtok_r (NIL," ",&r))) { + /* determine what is missing */ + sprintf (LOCAL->tmp,"Missing IMAP reply key: %.80s", + (char *) LOCAL->reply.tag); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + return NIL; /* can't parse this text */ + } + ucase (LOCAL->reply.key); /* canonicalize key to upper */ + /* get text as well, allow empty text */ + if (!(LOCAL->reply.text = strtok_r (NIL,"\n",&r))) + LOCAL->reply.text = LOCAL->reply.key + strlen (LOCAL->reply.key); + } + else { /* special handling of continuation */ + LOCAL->reply.key = "BAD"; /* so it barfs if not expecting continuation */ + if (!(LOCAL->reply.text = strtok_r (NIL,"\n",&r))) + LOCAL->reply.text = ""; + } + return &LOCAL->reply; /* return parsed reply */ +} + +/* IMAP fake reply when stream determined to be dead + * Accepts: MAIL stream + * tag + * text of fake reply (must start with "[CLOSED]") + * Returns: parsed reply + */ + +IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text) +{ + mm_notify (stream,text,BYE); /* send bye alert */ + if (LOCAL->netstream) net_close (LOCAL->netstream); + LOCAL->netstream = NIL; /* farewell, dear NET stream... */ + /* flush previous reply */ + if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line); + /* build new fake reply */ + LOCAL->reply.tag = LOCAL->reply.line = cpystr (tag ? tag : "*"); + LOCAL->reply.key = "NO"; + LOCAL->reply.text = text; + return &LOCAL->reply; /* return parsed reply */ +} + + +/* IMAP check for OK response in tagged reply + * Accepts: MAIL stream + * parsed reply + * Returns: T if OK else NIL + */ + +long imap_OK (MAILSTREAM *stream,IMAPPARSEDREPLY *reply) +{ + long ret = NIL; + /* OK - operation succeeded */ + if (!strcmp (reply->key,"OK")) { + imap_parse_response (stream,reply->text,NIL,NIL); + ret = T; + } + /* NO - operation failed */ + else if (!strcmp (reply->key,"NO")) + imap_parse_response (stream,reply->text,WARN,NIL); + else { /* BAD - operation rejected */ + if (!strcmp (reply->key,"BAD")) { + imap_parse_response (stream,reply->text,ERROR,NIL); + sprintf (LOCAL->tmp,"IMAP protocol error: %.80s",(char *) reply->text); + } + /* bad protocol received */ + else sprintf (LOCAL->tmp,"Unexpected IMAP response: %.80s %.80s", + (char *) reply->key,(char *) reply->text); + mm_log (LOCAL->tmp,ERROR); /* either way, this is not good */ + } + return ret; +} + +/* IMAP parse and act upon unsolicited reply + * Accepts: MAIL stream + * parsed reply + */ + +void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply) +{ + unsigned long i = 0; + unsigned long j,msgno; + unsigned char *s,*t; + char *r; + /* see if key is a number */ + if (isdigit (*reply->key)) { + msgno = strtoul (reply->key,(char **) &s,10); + if (*s) { /* better be nothing after number */ + sprintf (LOCAL->tmp,"Unexpected untagged message: %.80s", + (char *) reply->key); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + return; + } + if (!reply->text) { /* better be some data */ + mm_notify (stream,"Missing message data",WARN); + stream->unhealthy = T; + return; + } + /* get message data type, canonicalize upper */ + s = ucase (strtok_r (reply->text," ",&r)); + /* and locate the text after it */ + t = strtok_r (NIL,"\n",&r); + /* now take the action */ + /* change in size of mailbox */ + if (!strcmp (s,"EXISTS") && (msgno >= stream->nmsgs)) + mail_exists (stream,msgno); + else if (!strcmp (s,"RECENT") && (msgno <= stream->nmsgs)) + mail_recent (stream,msgno); + else if (!strcmp (s,"EXPUNGE") && msgno && (msgno <= stream->nmsgs)) { + mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); + MESSAGECACHE *elt = (MESSAGECACHE *) (*mc) (stream,msgno,CH_ELT); + if (elt) imap_gc_body (elt->private.msg.body); + /* notify upper level */ + mail_expunged (stream,msgno); + } + + else if ((!strcmp (s,"FETCH") || !strcmp (s,"STORE")) && + msgno && (msgno <= stream->nmsgs)) { + char *prop; + GETS_DATA md; + ENVELOPE **e; + MESSAGECACHE *elt = mail_elt (stream,msgno); + ENVELOPE *env = NIL; + imapenvelope_t ie = + (imapenvelope_t) mail_parameters (stream,GET_IMAPENVELOPE,NIL); + ++t; /* skip past open parenthesis */ + /* parse Lisp-form property list */ + while (prop = (strtok_r (t," )",&r))) { + t = strtok_r (NIL,"\n",&r); + INIT_GETS (md,stream,elt->msgno,NIL,0,0); + e = NIL; /* not pointing at any envelope yet */ + /* canonicalize property, parse it */ + if (!strcmp (ucase (prop),"FLAGS")) imap_parse_flags (stream,elt,&t); + else if (!strcmp (prop,"INTERNALDATE") && + (s = imap_parse_string (stream,&t,reply,NIL,NIL,LONGT))) { + if (!mail_parse_date (elt,s)) { + sprintf (LOCAL->tmp,"Bogus date: %.80s",(char *) s); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + /* slam in default so we don't try again */ + mail_parse_date (elt,"01-Jan-1970 00:00:00 +0000"); + } + fs_give ((void **) &s); + } + /* unique identifier */ + else if (!strcmp (prop,"UID")) { + LOCAL->lastuid.uid = elt->private.uid = strtoul (t,(char **) &t,10); + LOCAL->lastuid.msgno = elt->msgno; + } + else if (!strcmp (prop,"ENVELOPE")) { + if (stream->scache) { /* short cache, flush old stuff */ + mail_free_body (&stream->body); + stream->msgno = elt->msgno; + e = &stream->env; /* get pointer to envelope */ + } + else e = &elt->private.msg.env; + imap_parse_envelope (stream,e,&t,reply); + } + else if (!strncmp (prop,"BODY",4)) { + if (!prop[4] || !strcmp (prop+4,"STRUCTURE")) { + BODY **body; + if (stream->scache){/* short cache, flush old stuff */ + if (stream->msgno != msgno) { + mail_free_envelope (&stream->env); + sprintf (LOCAL->tmp,"Body received for %lu but current is %lu", + msgno,stream->msgno); + stream->msgno = msgno; + } + /* get pointer to body */ + body = &stream->body; + } + else body = &elt->private.msg.body; + /* flush any prior body */ + mail_free_body (body); + /* instantiate and parse a new body */ + imap_parse_body_structure (stream,*body = mail_newbody(),&t,reply); + } + + else if (prop[4] == '[') { + STRINGLIST *stl = NIL; + SIZEDTEXT text; + /* will want to return envelope data */ + if (!strcmp (md.what = cpystr (prop + 5),"HEADER]") || + !strcmp (md.what,"0]")) + e = stream->scache ? &stream->env : &elt->private.msg.env; + LOCAL->tmp[0] ='\0';/* no errors yet */ + /* found end of section? */ + if (!(s = strchr (md.what,']'))) { + /* skip leading nesting */ + for (s = md.what; *s && (isdigit (*s) || (*s == '.')); s++); + /* better be one of these */ + if (strncmp (s,"HEADER.FIELDS",13) && + (!s[13] || strcmp (s+13,".NOT"))) + sprintf (LOCAL->tmp,"Unterminated section: %.80s",md.what); + /* get list of headers */ + else if (!(stl = imap_parse_stringlist (stream,&t,reply))) + sprintf (LOCAL->tmp,"Bogus header field list: %.80s", + (char *) t); + else if (*t != ']') + sprintf (LOCAL->tmp,"Unterminated header section: %.80s", + (char *) t); + /* point after the text */ + else if (t = strchr (s = t,' ')) *t++ = '\0'; + } + if (s && !LOCAL->tmp[0]) { + *s++ = '\0'; /* tie off section specifier */ + if (*s == '<') { /* partial specifier? */ + md.first = strtoul (s+1,(char **) &s,10) + 1; + if (*s++ != '>') /* make sure properly terminated */ + sprintf (LOCAL->tmp,"Unterminated partial data: %.80s", + (char *) s-1); + } + if (!LOCAL->tmp[0] && *s) + sprintf (LOCAL->tmp,"Junk after section: %.80s",(char *) s); + } + if (LOCAL->tmp[0]) { /* got any errors? */ + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + mail_free_stringlist (&stl); + } + else { /* parse text from server */ + text.data = (unsigned char *) + imap_parse_string (stream,&t,reply, + ((md.what[0] && (md.what[0] != 'H')) || + md.first || md.last) ? &md : NIL, + &text.size,NIL); + /* all done if partial */ + if (md.first || md.last) mail_free_stringlist (&stl); + /* otherwise register it in the cache */ + else imap_cache (stream,msgno,md.what,stl,&text); + } + fs_give ((void **) &md.what); + } + else { + sprintf (LOCAL->tmp,"Unknown body message property: %.80s",prop); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + } + + /* one of the RFC822 props? */ + else if (!strncmp (prop,"RFC822",6) && (!prop[6] || (prop[6] == '.'))){ + SIZEDTEXT text; + if (!prop[6]) { /* cache full message */ + md.what = ""; + text.data = (unsigned char *) + imap_parse_string (stream,&t,reply,&md,&text.size,NIL); + imap_cache (stream,msgno,md.what,NIL,&text); + } + else if (!strcmp (prop+7,"SIZE")) + elt->rfc822_size = strtoul (t,(char **) &t,10); + /* legacy properties */ + else if (!strcmp (prop+7,"HEADER")) { + text.data = (unsigned char *) + imap_parse_string (stream,&t,reply,NIL,&text.size,NIL); + imap_cache (stream,msgno,"HEADER",NIL,&text); + e = stream->scache ? &stream->env : &elt->private.msg.env; + } + else if (!strcmp (prop+7,"TEXT")) { + md.what = "TEXT"; + text.data = (unsigned char *) + imap_parse_string (stream,&t,reply,&md,&text.size,NIL); + imap_cache (stream,msgno,md.what,NIL,&text); + } + else { + sprintf (LOCAL->tmp,"Unknown RFC822 message property: %.80s",prop); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + } + else { + sprintf (LOCAL->tmp,"Unknown message property: %.80s",prop); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + if (e && *e) env = *e; /* note envelope if we got one */ + } + /* do callback if requested */ + if (ie && env) (*ie) (stream,msgno,env); + } + /* obsolete response to COPY */ + else if (strcmp (s,"COPY")) { + sprintf (LOCAL->tmp,"Unknown message data: %lu %.80s",msgno,(char *) s); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + } + + else if (!strcmp (reply->key,"FLAGS") && reply->text && + (*reply->text == '(') && + (s = strtok_r (reply->text+1," )",&r))) + do if (*s != '\\') { + for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i] && + compare_cstring (s,stream->user_flags[i]); i++); + if (i > NUSERFLAGS) { + sprintf (LOCAL->tmp,"Too many server flags, discarding: %.80s", + (char *) s); + mm_notify (stream,LOCAL->tmp,WARN); + } + else if (!stream->user_flags[i]) stream->user_flags[i++] = cpystr (s); + } + while (s = strtok_r (NIL," )",&r)); + else if (!strcmp (reply->key,"SEARCH")) { + /* only do something if have text */ + if (reply->text && (t = strtok_r (reply->text," ",&r))) do + if (i = strtoul (t,NIL,10)) { + /* UIDs always passed to main program */ + if (LOCAL->uidsearch) mm_searched (stream,i); + /* should be a msgno then */ + else if ((i <= stream->nmsgs) && + (!LOCAL->filter || mail_elt (stream,i)->private.filter)) { + mail_elt (stream,i)->searched = T; + if (!stream->silent) mm_searched (stream,i); + } + } while (t = strtok_r (NIL," ",&r)); + } + else if (!strcmp (reply->key,"SORT")) { + sortresults_t sr = (sortresults_t) + mail_parameters (NIL,GET_SORTRESULTS,NIL); + LOCAL->sortsize = 0; /* initialize sort data */ + if (LOCAL->sortdata) fs_give ((void **) &LOCAL->sortdata); + LOCAL->sortdata = (unsigned long *) + fs_get ((stream->nmsgs + 1) * sizeof (unsigned long)); + /* only do something if have text */ + if (reply->text && (t = strtok_r (reply->text," ",&r))) { + do if ((i = atol (t)) && (LOCAL->filter ? + mail_elt (stream,i)->searched : T)) + LOCAL->sortdata[LOCAL->sortsize++] = i; + while ((t = strtok_r (NIL," ",&r)) && (LOCAL->sortsize < stream->nmsgs)); + } + LOCAL->sortdata[LOCAL->sortsize] = 0; + /* also return via callback if requested */ + if (sr) (*sr) (stream,LOCAL->sortdata,LOCAL->sortsize); + } + else if (!strcmp (reply->key,"THREAD")) { + threadresults_t tr = (threadresults_t) + mail_parameters (NIL,GET_THREADRESULTS,NIL); + if (LOCAL->threaddata) mail_free_threadnode (&LOCAL->threaddata); + if (s = reply->text) { + LOCAL->threaddata = imap_parse_thread (stream,&s); + if (tr) (*tr) (stream,LOCAL->threaddata); + if (s && *s) { + sprintf (LOCAL->tmp,"Junk at end of thread: %.80s",(char *) s); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + } + } + + else if (!strcmp (reply->key,"STATUS") && reply->text) { + MAILSTATUS status; + unsigned char *txt = reply->text; + if ((t = imap_parse_astring (stream,&txt,reply,&j)) && txt && + (*txt++ == ' ') && (*txt++ == '(') && (s = strchr (txt,')')) && + (s - txt) && !s[1]) { + *s = '\0'; /* tie off status data */ + /* initialize data block */ + status.flags = status.messages = status.recent = status.unseen = + status.uidnext = status.uidvalidity = 0; + while (*txt && (s = strchr (txt,' '))) { + *s++ = '\0'; /* tie off status attribute name */ + /* get attribute value */ + i = strtoul (s,(char **) &s,10); + if (!compare_cstring (txt,"MESSAGES")) { + status.flags |= SA_MESSAGES; + status.messages = i; + } + else if (!compare_cstring (txt,"RECENT")) { + status.flags |= SA_RECENT; + status.recent = i; + } + else if (!compare_cstring (txt,"UNSEEN")) { + status.flags |= SA_UNSEEN; + status.unseen = i; + } + else if (!compare_cstring (txt,"UIDNEXT")) { + status.flags |= SA_UIDNEXT; + status.uidnext = i; + } + else if (!compare_cstring (txt,"UIDVALIDITY")) { + status.flags |= SA_UIDVALIDITY; + status.uidvalidity = i; + } + /* next attribute */ + txt = (*s == ' ') ? s + 1 : s; + } + if (((i = 1 + strchr (stream->mailbox,'}') - stream->mailbox) + j) < + IMAPTMPLEN) { + strcpy (strncpy (LOCAL->tmp,stream->mailbox,i) + i,t); + /* pass status to main program */ + mm_status (stream,LOCAL->tmp,&status); + } + } + if (t) fs_give ((void **) &t); + } + + else if ((!strcmp (reply->key,"LIST") || !strcmp (reply->key,"LSUB")) && + reply->text && (*reply->text == '(') && + (s = strchr (reply->text,')')) && (s[1] == ' ')) { + char delimiter = '\0'; + *s++ = '\0'; /* tie off attribute list */ + /* parse attribute list */ + if (t = strtok_r (reply->text+1," ",&r)) do { + if (!compare_cstring (t,"\\NoInferiors")) i |= LATT_NOINFERIORS; + else if (!compare_cstring (t,"\\NoSelect")) i |= LATT_NOSELECT; + else if (!compare_cstring (t,"\\Marked")) i |= LATT_MARKED; + else if (!compare_cstring (t,"\\Unmarked")) i |= LATT_UNMARKED; + else if (!compare_cstring (t,"\\HasChildren")) i |= LATT_HASCHILDREN; + else if (!compare_cstring (t,"\\HasNoChildren")) i |= LATT_HASNOCHILDREN; + /* ignore extension flags */ + } + while (t = strtok_r (NIL," ",&r)); + switch (*++s) { /* process delimiter */ + case 'N': /* NIL */ + case 'n': + s += 4; /* skip over NIL<space> */ + break; + case '"': /* have a delimiter */ + delimiter = (*++s == '\\') ? *++s : *s; + s += 3; /* skip over <delimiter><quote><space> */ + } + /* parse the mailbox name */ + if (t = imap_parse_astring (stream,&s,reply,&j)) { + /* prepend prefix if requested */ + if (LOCAL->prefix && ((strlen (LOCAL->prefix) + j) < IMAPTMPLEN)) + sprintf (s = LOCAL->tmp,"%s%s",LOCAL->prefix,(char *) t); + else s = t; /* otherwise just mailbox name */ + /* pass data to main program */ + if (reply->key[1] == 'S') mm_lsub (stream,delimiter,s,i); + else mm_list (stream,delimiter,s,i); + fs_give ((void **) &t); /* flush mailbox name */ + } + } + else if (!strcmp (reply->key,"NAMESPACE")) { + if (LOCAL->namespace) { + mail_free_namespace (&LOCAL->namespace[0]); + mail_free_namespace (&LOCAL->namespace[1]); + mail_free_namespace (&LOCAL->namespace[2]); + } + else LOCAL->namespace = (NAMESPACE **) fs_get (3 * sizeof (NAMESPACE *)); + if (s = reply->text) { /* parse namespace results */ + LOCAL->namespace[0] = imap_parse_namespace (stream,&s,reply); + LOCAL->namespace[1] = imap_parse_namespace (stream,&s,reply); + LOCAL->namespace[2] = imap_parse_namespace (stream,&s,reply); + if (s && *s) { + sprintf (LOCAL->tmp,"Junk after namespace list: %.80s",(char *) s); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + } + else { + mm_notify (stream,"Missing namespace list",WARN); + stream->unhealthy = T; + } + } + + else if (!strcmp (reply->key,"ACL") && (s = reply->text) && + (t = imap_parse_astring (stream,&s,reply,NIL))) { + getacl_t ar = (getacl_t) mail_parameters (NIL,GET_ACL,NIL); + if (s && (*s++ == ' ')) { + ACLLIST *al = mail_newacllist (); + ACLLIST *ac = al; + do if ((ac->identifier = imap_parse_astring (stream,&s,reply,NIL)) && + s && (*s++ == ' ')) + ac->rights = imap_parse_astring (stream,&s,reply,NIL); + while (ac->rights && s && (*s == ' ') && s++ && + (ac = ac->next = mail_newacllist ())); + if (!ac->rights || (s && *s)) { + sprintf (LOCAL->tmp,"Invalid ACL identifer/rights for %.80s", + (char *) t); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + else if (ar) (*ar) (stream,t,al); + mail_free_acllist (&al); /* clean up */ + } + /* no optional rights */ + else if (ar) (*ar) (stream,t,NIL); + fs_give ((void **) &t); /* free mailbox name */ + } + + else if (!strcmp (reply->key,"LISTRIGHTS") && (s = reply->text) && + (t = imap_parse_astring (stream,&s,reply,NIL))) { + listrights_t lr = (listrights_t) mail_parameters (NIL,GET_LISTRIGHTS,NIL); + char *id,*r; + if (s && (*s++ == ' ') && (id = imap_parse_astring (stream,&s,reply,NIL))){ + if (s && (*s++ == ' ') && + (r = imap_parse_astring (stream,&s,reply,NIL))) { + if (s && (*s++ == ' ')) { + STRINGLIST *rl = mail_newstringlist (); + STRINGLIST *rc = rl; + do rc->text.data = (unsigned char *) + imap_parse_astring (stream,&s,reply,&rc->text.size); + while (rc->text.data && s && (*s == ' ') && s++ && + (rc = rc->next = mail_newstringlist ())); + if (!rc->text.data || (s && *s)) { + sprintf (LOCAL->tmp,"Invalid optional LISTRIGHTS for %.80s", + (char *) t); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + else if (lr) (*lr) (stream,t,id,r,rl); + /* clean up */ + mail_free_stringlist (&rl); + } + /* no optional rights */ + else if (lr) (*lr) (stream,t,id,r,NIL); + fs_give ((void **) &r); /* free rights */ + } + else { + sprintf (LOCAL->tmp,"Missing LISTRIGHTS rights for %.80s",(char *) t); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + fs_give ((void **) &id); /* free identifier */ + } + else { + sprintf (LOCAL->tmp,"Missing LISTRIGHTS identifer for %.80s",(char *) t); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + fs_give ((void **) &t); /* free mailbox name */ + } + + else if (!strcmp (reply->key,"MYRIGHTS") && (s = reply->text) && + (t = imap_parse_astring (stream,&s,reply,NIL))) { + myrights_t mr = (myrights_t) mail_parameters (NIL,GET_MYRIGHTS,NIL); + char *r; + if (s && (*s++ == ' ') && (r = imap_parse_astring (stream,&s,reply,NIL))) { + if (s && *s) { + sprintf (LOCAL->tmp,"Junk after MYRIGHTS for %.80s",(char *) t); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + else if (mr) (*mr) (stream,t,r); + fs_give ((void **) &r); /* free rights */ + } + else { + sprintf (LOCAL->tmp,"Missing MYRIGHTS for %.80s",(char *) t); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + fs_give ((void **) &t); /* free mailbox name */ + } + + /* this response has a bizarre syntax! */ + else if (!strcmp (reply->key,"QUOTA") && (s = reply->text) && + (t = imap_parse_astring (stream,&s,reply,NIL))) { + /* in case error */ + sprintf (LOCAL->tmp,"Bad quota resource list for %.80s",(char *) t); + if (s && (*s++ == ' ') && (*s++ == '(') && *s && ((*s != ')') || !s[1])) { + quota_t qt = (quota_t) mail_parameters (NIL,GET_QUOTA,NIL); + QUOTALIST *ql = NIL; + QUOTALIST *qc; + /* parse non-empty quota resource list */ + if (*s != ')') for (ql = qc = mail_newquotalist (); T; + qc = qc->next = mail_newquotalist ()) { + if ((qc->name = imap_parse_astring (stream,&s,reply,NIL)) && s && + (*s++ == ' ') && (isdigit (*s) || (LOCAL->loser && (*s == '-')))) { + if (isdigit (*s)) qc->usage = strtoul (s,(char **) &s,10); + else if (t = strchr (s,' ')) t = s; + if ((*s++ == ' ') && (isdigit (*s) || (LOCAL->loser &&(*s == '-')))){ + if (isdigit (*s)) qc->limit = strtoul (s,(char **) &s,10); + else if (t = strpbrk (s," )")) t = s; + /* another resource follows? */ + if (*s == ' ') continue; + /* end of resource list? */ + if ((*s == ')') && !s[1]) { + if (qt) (*qt) (stream,t,ql); + break; /* all done */ + } + } + } + /* something bad happened */ + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + break; /* parse failed */ + } + /* all done with quota resource list now */ + if (ql) mail_free_quotalist (&ql); + } + else { + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + fs_give ((void **) &t); /* free root name */ + } + else if (!strcmp (reply->key,"QUOTAROOT") && (s = reply->text) && + (t = imap_parse_astring (stream,&s,reply,NIL))) { + sprintf (LOCAL->tmp,"Bad quota root list for %.80s",(char *) t); + if (s && (*s++ == ' ')) { + quotaroot_t qr = (quotaroot_t) mail_parameters (NIL,GET_QUOTAROOT,NIL); + STRINGLIST *rl = mail_newstringlist (); + STRINGLIST *rc = rl; + do rc->text.data = (unsigned char *) + imap_parse_astring (stream,&s,reply,&rc->text.size); + while (rc->text.data && *s && (*s++ == ' ') && + (rc = rc->next = mail_newstringlist ())); + if (!rc->text.data || (s && *s)) { + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + else if (qr) (*qr) (stream,t,rl); + /* clean up */ + mail_free_stringlist (&rl); + } + else { + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + fs_give ((void **) &t); + } + + else if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH")) + imap_parse_response (stream,reply->text,NIL,T); + else if (!strcmp (reply->key,"NO")) + imap_parse_response (stream,reply->text,WARN,T); + else if (!strcmp (reply->key,"BAD")) + imap_parse_response (stream,reply->text,ERROR,T); + else if (!strcmp (reply->key,"BYE")) { + LOCAL->byeseen = T; /* note that a BYE seen */ + imap_parse_response (stream,reply->text,BYE,T); + } + else if (!strcmp (reply->key,"CAPABILITY") && reply->text) + imap_parse_capabilities (stream,reply->text); + else if (!strcmp (reply->key,"MAILBOX") && reply->text) { + if (LOCAL->prefix && + ((strlen (LOCAL->prefix) + strlen (reply->text)) < IMAPTMPLEN)) + sprintf (t = LOCAL->tmp,"%s%s",LOCAL->prefix,(char *) reply->text); + else t = reply->text; + mm_list (stream,NIL,t,NIL); + } + else { + sprintf (LOCAL->tmp,"Unexpected untagged message: %.80s", + (char *) reply->key); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } +} + +/* Parse human-readable response text + * Accepts: mail stream + * text + * error level for mm_notify() + * non-NIL if want mm_notify() event even if no response code + */ + +void imap_parse_response (MAILSTREAM *stream,char *text,long errflg,long ntfy) +{ + char *s,*t,*r; + size_t i; + unsigned long j; + MESSAGECACHE *elt; + copyuid_t cu; + appenduid_t au; + SEARCHSET *source = NIL; + SEARCHSET *dest = NIL; + if (text && (*text == '[') && (t = strchr (s = text + 1,']')) && + ((i = t - s) < IMAPTMPLEN)) { + LOCAL->tmp[i] = '\0'; /* make mungable copy of text code */ + if (s = strchr (strncpy (t = LOCAL->tmp,s,i),' ')) *s++ = '\0'; + if (s) { /* have argument? */ + ntfy = NIL; /* suppress mm_notify if normal SELECT data */ + if (!compare_cstring (t,"UIDVALIDITY") && + ((j = strtoul (s,NIL,10)) != stream->uid_validity)) { + mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); + stream->uid_validity = j; + /* purge any UIDs in cache */ + for (j = 1; j <= stream->nmsgs; j++) + if (elt = (MESSAGECACHE *) (*mc) (stream,j,CH_ELT)) + elt->private.uid = 0; + } + else if (!compare_cstring (t,"UIDNEXT")) + stream->uid_last = strtoul (s,NIL,10) - 1; + else if (!compare_cstring (t,"PERMANENTFLAGS") && (*s == '(') && + (t[i-1] == ')')) { + t[i-1] = '\0'; /* tie off flags */ + stream->perm_seen = stream->perm_deleted = stream->perm_answered = + stream->perm_draft = stream->kwd_create = NIL; + stream->perm_user_flags = NIL; + if (s = strtok_r (s+1," ",&r)) do { + if (*s == '\\') { /* system flags */ + if (!compare_cstring (s,"\\Seen")) stream->perm_seen = T; + else if (!compare_cstring (s,"\\Deleted")) + stream->perm_deleted = T; + else if (!compare_cstring (s,"\\Flagged")) + stream->perm_flagged = T; + else if (!compare_cstring (s,"\\Answered")) + stream->perm_answered = T; + else if (!compare_cstring (s,"\\Draft")) stream->perm_draft = T; + else if (!strcmp (s,"\\*")) stream->kwd_create = T; + } + else stream->perm_user_flags |= imap_parse_user_flag (stream,s); + } + while (s = strtok_r (NIL," ",&r)); + } + + else if (!compare_cstring (t,"CAPABILITY")) + imap_parse_capabilities (stream,s); + else if ((j = LEVELUIDPLUS (stream) && LOCAL->appendmailbox) && + !compare_cstring (t,"COPYUID") && + (cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL)) && + isdigit (*s) && (j = strtoul (s,&s,10)) && (*s++ == ' ') && + (source = mail_parse_set (s,&s)) && (*s++ == ' ') && + (dest = mail_parse_set (s,&s)) && !*s) + (*cu) (stream,LOCAL->appendmailbox,j,source,dest); + else if (j && !compare_cstring (t,"APPENDUID") && + (au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL)) && + isdigit (*s) && (j = strtoul (s,&s,10)) && (*s++ == ' ') && + (dest = mail_parse_set (s,&s)) && !*s) + (*au) (LOCAL->appendmailbox,j,dest); + else { /* all other response code events */ + ntfy = T; /* must mm_notify() */ + if (!compare_cstring (t,"REFERRAL")) + LOCAL->referral = cpystr (t + 9); + } + mail_free_searchset (&source); + mail_free_searchset (&dest); + } + else { /* no arguments */ + if (!compare_cstring (t,"UIDNOTSTICKY")) { + ntfy = NIL; + stream->uid_nosticky = T; + } + else if (!compare_cstring (t,"READ-ONLY")) stream->rdonly = T; + else if (!compare_cstring (t,"READ-WRITE")) + stream->rdonly = NIL; + else if (!compare_cstring (t,"PARSE") && !errflg) + errflg = PARSE; + } + } + /* give event to main program */ + if (ntfy && !stream->silent) mm_notify (stream,text ? text : "",errflg); +} + +/* Parse a namespace + * Accepts: mail stream + * current text pointer + * parsed reply + * Returns: namespace list, text pointer updated + */ + +NAMESPACE *imap_parse_namespace (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply) +{ + NAMESPACE *ret = NIL; + NAMESPACE *nam = NIL; + NAMESPACE *prev = NIL; + PARAMETER *par = NIL; + if (*txtptr) { /* only if argument given */ + /* ignore leading space */ + while (**txtptr == ' ') ++*txtptr; + switch (**txtptr) { + case 'N': /* if NIL */ + case 'n': + ++*txtptr; /* bump past "N" */ + ++*txtptr; /* bump past "I" */ + ++*txtptr; /* bump past "L" */ + break; + case '(': + ++*txtptr; /* skip past open paren */ + while (**txtptr == '(') { + ++*txtptr; /* skip past open paren */ + prev = nam; /* note previous if any */ + nam = (NAMESPACE *) memset (fs_get (sizeof (NAMESPACE)),0, + sizeof (NAMESPACE)); + if (!ret) ret = nam; /* if first time note first namespace */ + /* if previous link new block to it */ + if (prev) prev->next = nam; + nam->name = imap_parse_string (stream,txtptr,reply,NIL,NIL,NIL); + /* ignore whitespace */ + while (**txtptr == ' ') ++*txtptr; + switch (**txtptr) { /* parse delimiter */ + case 'N': + case 'n': + *txtptr += 3; /* bump past "NIL" */ + break; + case '"': + if (*++*txtptr == '\\') nam->delimiter = *++*txtptr; + else nam->delimiter = **txtptr; + *txtptr += 2; /* bump past character and closing quote */ + break; + default: + sprintf (LOCAL->tmp,"Missing delimiter in namespace: %.80s", + (char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + *txtptr = NIL; /* stop parse */ + return ret; + } + + while (**txtptr == ' '){/* append new parameter to tail */ + if (nam->param) par = par->next = mail_newbody_parameter (); + else nam->param = par = mail_newbody_parameter (); + if (!(par->attribute = imap_parse_string (stream,txtptr,reply,NIL, + NIL,NIL))) { + mm_notify (stream,"Missing namespace extension attribute",WARN); + stream->unhealthy = T; + par->attribute = cpystr ("UNKNOWN"); + } + /* skip space */ + while (**txtptr == ' ') ++*txtptr; + if (**txtptr == '(') {/* have value list? */ + char *att = par->attribute; + ++*txtptr; /* yes */ + do { /* parse each value */ + if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL, + NIL,LONGT))) { + sprintf (LOCAL->tmp, + "Missing value for namespace attribute %.80s",att); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + par->value = cpystr ("UNKNOWN"); + } + /* is there another value? */ + if (**txtptr == ' ') par = par->next = mail_newbody_parameter (); + } while (!par->value); + } + else { + sprintf (LOCAL->tmp,"Missing values for namespace attribute %.80s", + par->attribute); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + par->value = cpystr ("UNKNOWN"); + } + } + if (**txtptr == ')') ++*txtptr; + else { /* missing trailing paren */ + sprintf (LOCAL->tmp,"Junk at end of namespace: %.80s", + (char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + return ret; + } + } + if (**txtptr == ')') { /* expected trailing paren? */ + ++*txtptr; /* got it! */ + break; + } + default: + sprintf (LOCAL->tmp,"Not a namespace: %.80s",(char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + *txtptr = NIL; /* stop parse now */ + break; + } + } + return ret; +} + +/* Parse a thread node list + * Accepts: mail stream + * current text pointer + * Returns: thread node list, text pointer updated + */ + +THREADNODE *imap_parse_thread (MAILSTREAM *stream,unsigned char **txtptr) +{ + char *s; + THREADNODE *ret = NIL; /* returned tree */ + THREADNODE *last = NIL; /* last branch in this tree */ + THREADNODE *parent = NIL; /* parent of current node */ + THREADNODE *cur; /* current node */ + while (**txtptr == '(') { /* see a thread? */ + ++*txtptr; /* skip past open paren */ + while (**txtptr != ')') { /* parse thread */ + if (**txtptr == '(') { /* thread branch */ + cur = imap_parse_thread (stream,txtptr); + /* add to parent */ + if (parent) parent = parent->next = cur; + else { /* no parent, create dummy */ + if (last) last = last->branch = mail_newthreadnode (NIL); + /* new tree */ + else ret = last = mail_newthreadnode (NIL); + /* add to dummy parent */ + last->next = parent = cur; + } + } + /* threaded message number */ + else if (isdigit (*(s = *txtptr)) && + ((cur = mail_newthreadnode (NIL))->num = + strtoul (*txtptr,(char **) txtptr,10))) { + if (LOCAL->filter && !mail_elt (stream,cur->num)->searched) + cur->num = NIL; /* make dummy if filtering and not searched */ + /* add to parent */ + if (parent) parent = parent->next = cur; + /* no parent, start new thread */ + else if (last) last = last->branch = parent = cur; + /* create new tree */ + else ret = last = parent = cur; + } + else { /* anything else is a bogon */ + char tmp[MAILTMPLEN]; + sprintf (tmp,"Bogus thread member: %.80s",s); + mm_notify (stream,tmp,WARN); + stream->unhealthy = T; + return ret; + } + /* skip past any space */ + if (**txtptr == ' ') ++*txtptr; + } + ++*txtptr; /* skip pase end of thread */ + parent = NIL; /* close this thread */ + } + return ret; /* return parsed thread */ +} + +/* Parse RFC822 message header + * Accepts: MAIL stream + * envelope to parse into + * header as sized text + * stringlist if partial header + */ + +void imap_parse_header (MAILSTREAM *stream,ENVELOPE **env,SIZEDTEXT *hdr, + STRINGLIST *stl) +{ + ENVELOPE *nenv; + /* parse what we can from this header */ + rfc822_parse_msg (&nenv,NIL,(char *) hdr->data,hdr->size,NIL, + net_host (LOCAL->netstream),stream->dtb->flags); + if (*env) { /* need to merge this header into envelope? */ + if (!(*env)->newsgroups) { /* need Newsgroups? */ + (*env)->newsgroups = nenv->newsgroups; + (*env)->ngpathexists = nenv->ngpathexists; + nenv->newsgroups = NIL; + } + if (!(*env)->followup_to) { /* need Followup-To? */ + (*env)->followup_to = nenv->followup_to; + nenv->followup_to = NIL; + } + if (!(*env)->references) { /* need References? */ + (*env)->references = nenv->references; + nenv->references = NIL; + } + if (!(*env)->sparep) { /* need spare pointer? */ + (*env)->sparep = nenv->sparep; + nenv->sparep = NIL; + } + mail_free_envelope (&nenv); + (*env)->imapenvonly = NIL; /* have complete envelope now */ + } + /* otherwise set it to this envelope */ + else (*env = nenv)->incomplete = stl ? T : NIL; +} + +/* IMAP parse envelope + * Accepts: MAIL stream + * pointer to envelope pointer + * current text pointer + * parsed reply + * + * Updates text pointer + */ + +void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env, + unsigned char **txtptr,IMAPPARSEDREPLY *reply) +{ + ENVELOPE *oenv = *env; + char c = *((*txtptr)++); /* grab first character */ + /* ignore leading spaces */ + while (c == ' ') c = *((*txtptr)++); + switch (c) { /* dispatch on first character */ + case '(': /* if envelope S-expression */ + *env = mail_newenvelope (); /* parse the new envelope */ + (*env)->date = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); + (*env)->subject = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); + (*env)->from = imap_parse_adrlist (stream,txtptr,reply); + (*env)->sender = imap_parse_adrlist (stream,txtptr,reply); + (*env)->reply_to = imap_parse_adrlist (stream,txtptr,reply); + (*env)->to = imap_parse_adrlist (stream,txtptr,reply); + (*env)->cc = imap_parse_adrlist (stream,txtptr,reply); + (*env)->bcc = imap_parse_adrlist (stream,txtptr,reply); + (*env)->in_reply_to = imap_parse_string (stream,txtptr,reply,NIL,NIL, + LONGT); + (*env)->message_id = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); + if (oenv) { /* need to merge old envelope? */ + (*env)->newsgroups = oenv->newsgroups; + oenv->newsgroups = NIL; + (*env)->ngpathexists = oenv->ngpathexists; + (*env)->followup_to = oenv->followup_to; + oenv->followup_to = NIL; + (*env)->references = oenv->references; + oenv->references = NIL; + mail_free_envelope(&oenv);/* free old envelope */ + } + /* have IMAP envelope components only */ + else (*env)->imapenvonly = T; + if (**txtptr != ')') { + sprintf (LOCAL->tmp,"Junk at end of envelope: %.80s",(char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + else ++*txtptr; /* skip past delimiter */ + break; + case 'N': /* if NIL */ + case 'n': + ++*txtptr; /* bump past "I" */ + ++*txtptr; /* bump past "L" */ + break; + default: + sprintf (LOCAL->tmp,"Not an envelope: %.80s",(char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + break; + } +} + +/* IMAP parse address list + * Accepts: MAIL stream + * current text pointer + * parsed reply + * Returns: address list, NIL on failure + * + * Updates text pointer + */ + +ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply) +{ + ADDRESS *adr = NIL; + char c = **txtptr; /* sniff at first character */ + /* ignore leading spaces */ + while (c == ' ') c = *++*txtptr; + ++*txtptr; /* skip past open paren */ + switch (c) { + case '(': /* if envelope S-expression */ + adr = imap_parse_address (stream,txtptr,reply); + if (**txtptr != ')') { + sprintf (LOCAL->tmp,"Junk at end of address list: %.80s", + (char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + else ++*txtptr; /* skip past delimiter */ + break; + case 'N': /* if NIL */ + case 'n': + ++*txtptr; /* bump past "I" */ + ++*txtptr; /* bump past "L" */ + break; + default: + sprintf (LOCAL->tmp,"Not an address: %.80s",(char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + break; + } + return adr; +} + +/* IMAP parse address + * Accepts: MAIL stream + * current text pointer + * parsed reply + * Returns: address, NIL on failure + * + * Updates text pointer + */ + +ADDRESS *imap_parse_address (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply) +{ + long ingroup = 0; + ADDRESS *adr = NIL; + ADDRESS *ret = NIL; + ADDRESS *prev = NIL; + char c = **txtptr; /* sniff at first address character */ + switch (c) { + case '(': /* if envelope S-expression */ + while (c == '(') { /* recursion dies on small stack machines */ + ++*txtptr; /* skip past open paren */ + if (adr) prev = adr; /* note previous if any */ + adr = mail_newaddr (); /* instantiate address and parse its fields */ + adr->personal = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); + adr->adl = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); + adr->mailbox = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); + adr->host = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); + if (**txtptr != ')') { /* handle trailing paren */ + sprintf (LOCAL->tmp,"Junk at end of address: %.80s",(char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + else ++*txtptr; /* skip past close paren */ + c = **txtptr; /* set up for while test */ + /* ignore leading spaces in front of next */ + while (c == ' ') c = *++*txtptr; + + if (!adr->mailbox) { /* end of group? */ + /* decrement group if all looks well */ + if (ingroup && !(adr->personal || adr->adl || adr->host)) --ingroup; + else { + if (ingroup) { /* in a group? */ + sprintf (LOCAL->tmp,/* yes, must be bad syntax */ + "Junk in end of group: pn=%.80s al=%.80s dn=%.80s", + adr->personal ? adr->personal : "", + adr->adl ? adr->adl : "", + adr->host ? adr->host : ""); + mm_notify (stream,LOCAL->tmp,WARN); + } + else mm_notify (stream,"End of group encountered when not in group", + WARN); + stream->unhealthy = T; + mail_free_address (&adr); + adr = prev; + prev = NIL; + } + } + else if (!adr->host) { /* start of group? */ + if (adr->personal || adr->adl) { + sprintf (LOCAL->tmp,"Junk in start of group: pn=%.80s al=%.80s", + adr->personal ? adr->personal : "", + adr->adl ? adr->adl : ""); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + mail_free_address (&adr); + adr = prev; + prev = NIL; + } + else ++ingroup; /* in a group now */ + } + if (adr) { /* good address */ + if (!ret) ret = adr; /* if first time note first adr */ + /* if previous link new block to it */ + if (prev) prev->next = adr; + /* flush bogus personal name */ + if (LOCAL->loser && adr->personal && strchr (adr->personal,'@')) + fs_give ((void **) &adr->personal); + } + } + break; + case 'N': /* if NIL */ + case 'n': + *txtptr += 3; /* bump past NIL */ + break; + default: + sprintf (LOCAL->tmp,"Not an address: %.80s",(char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + break; + } + return ret; +} + +/* IMAP parse flags + * Accepts: current message cache + * current text pointer + * + * Updates text pointer + */ + +void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt, + unsigned char **txtptr) +{ + char *flag; + char c = '\0'; + struct { /* old flags */ + unsigned int valid : 1; + unsigned int seen : 1; + unsigned int deleted : 1; + unsigned int flagged : 1; + unsigned int answered : 1; + unsigned int draft : 1; + unsigned long user_flags; + } old; + old.valid = elt->valid; old.seen = elt->seen; old.deleted = elt->deleted; + old.flagged = elt->flagged; old.answered = elt->answered; + old.draft = elt->draft; old.user_flags = elt->user_flags; + elt->valid = T; /* mark have valid flags now */ + elt->user_flags = NIL; /* zap old flag values */ + elt->seen = elt->deleted = elt->flagged = elt->answered = elt->draft = + elt->recent = NIL; + while (c != ')') { /* parse list of flags */ + /* point at a flag */ + while (*(flag = ++*txtptr) == ' '); + /* scan for end of flag */ + while (**txtptr != ' ' && **txtptr != ')') ++*txtptr; + c = **txtptr; /* save delimiter */ + **txtptr = '\0'; /* tie off flag */ + if (!*flag) break; /* null flag */ + /* if starts with \ must be sys flag */ + else if (*flag == '\\') { + if (!compare_cstring (flag,"\\Seen")) elt->seen = T; + else if (!compare_cstring (flag,"\\Deleted")) elt->deleted = T; + else if (!compare_cstring (flag,"\\Flagged")) elt->flagged = T; + else if (!compare_cstring (flag,"\\Answered")) elt->answered = T; + else if (!compare_cstring (flag,"\\Recent")) elt->recent = T; + else if (!compare_cstring (flag,"\\Draft")) elt->draft = T; + } + /* otherwise user flag */ + else elt->user_flags |= imap_parse_user_flag (stream,flag); + } + ++*txtptr; /* bump past delimiter */ + if (!old.valid || (old.seen != elt->seen) || + (old.deleted != elt->deleted) || (old.flagged != elt->flagged) || + (old.answered != elt->answered) || (old.draft != elt->draft) || + (old.user_flags != elt->user_flags)) mm_flags (stream,elt->msgno); +} + + +/* IMAP parse user flag + * Accepts: MAIL stream + * flag name + * Returns: flag bit position + */ + +unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag) +{ + long i; + /* sniff through all user flags */ + for (i = 0; i < NUSERFLAGS; ++i) if (stream->user_flags[i]) + if (!compare_cstring (flag,stream->user_flags[i])) return (1 << i); + return (unsigned long) 0; /* not found */ +} + +/* IMAP parse atom-string + * Accepts: MAIL stream + * current text pointer + * parsed reply + * returned string length + * Returns: string + * + * Updates text pointer + */ + +unsigned char *imap_parse_astring (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply,unsigned long *len) +{ + unsigned long i; + unsigned char c,*s,*ret; + /* ignore leading spaces */ + for (c = **txtptr; c == ' '; c = *++*txtptr); + switch (c) { + case '"': /* quoted string? */ + case '{': /* literal? */ + ret = imap_parse_string (stream,txtptr,reply,NIL,len,NIL); + break; + default: /* must be atom */ + for (c = *(s = *txtptr); /* find end of atom */ + c && (c > ' ') && (c != '(') && (c != ')') && (c != '{') && + (c != '%') && (c != '*') && (c != '"') && (c != '\\') && (c < 0x80); + c = *++*txtptr); + if (i = *txtptr - s) { /* atom ends at atom_special */ + if (len) *len = i; /* return length of atom */ + ret = strncpy ((char *) fs_get (i + 1),s,i); + ret[i] = '\0'; /* tie off string */ + } + else { /* no atom found */ + sprintf (LOCAL->tmp,"Not an atom: %.80s",(char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + if (len) *len = 0; + ret = NIL; + } + break; + } + return ret; +} + +/* IMAP parse string + * Accepts: MAIL stream + * current text pointer + * parsed reply + * mailgets data + * returned string length + * filter newline flag + * Returns: string + * + * Updates text pointer + */ + +unsigned char *imap_parse_string (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply,GETS_DATA *md, + unsigned long *len,long flags) +{ + char *st; + char *string = NIL; + unsigned long i,j,k; + int bogon = NIL; + unsigned char c = **txtptr; /* sniff at first character */ + mailgets_t mg = (mailgets_t) mail_parameters (NIL,GET_GETS,NIL); + readprogress_t rp = + (readprogress_t) mail_parameters (NIL,GET_READPROGRESS,NIL); + /* ignore leading spaces */ + while (c == ' ') c = *++*txtptr; + st = ++*txtptr; /* remember start of string */ + switch (c) { + case '"': /* if quoted string */ + i = 0; /* initial byte count */ + /* search for end of string */ + for (c = **txtptr; c != '"'; ++i,c = *++*txtptr) { + /* backslash quotes next character */ + if (c == '\\') c = *++*txtptr; + /* CHAR8 not permitted in quoted string */ + if (!bogon && (bogon = (c & 0x80))) { + sprintf (LOCAL->tmp,"Invalid CHAR in quoted string: %x", + (unsigned int) c); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + else if (!c) { /* NUL not permitted either */ + mm_notify (stream,"Unterminated quoted string",WARN); + stream->unhealthy = T; + if (len) *len = 0; /* punt, since may be at end of string */ + return NIL; + } + } + ++*txtptr; /* bump past delimiter */ + string = (char *) fs_get ((size_t) i + 1); + for (j = 0; j < i; j++) { /* copy the string */ + if (*st == '\\') ++st; /* quoted character */ + string[j] = *st++; + } + string[j] = '\0'; /* tie off string */ + if (len) *len = i; /* set return value too */ + if (md && mg) { /* have special routine to slurp string? */ + STRING bs; + if (md->first) { /* partial fetch? */ + md->first--; /* restore origin octet */ + md->last = i; /* number of octets that we got */ + } + INIT (&bs,mail_string,string,i); + (*mg) (mail_read,&bs,i,md); + } + break; + + case 'N': /* if NIL */ + case 'n': + ++*txtptr; /* bump past "I" */ + ++*txtptr; /* bump past "L" */ + if (len) *len = 0; + break; + case '{': /* if literal string */ + /* get size of string */ + if ((i = strtoul (*txtptr,(char **) txtptr,10)) > MAXSERVERLIT) { + sprintf (LOCAL->tmp,"Absurd server literal length %lu",i); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; /* read and discard */ + do net_getbuffer (LOCAL->netstream,j = min (i,(long) IMAPTMPLEN - 1), + LOCAL->tmp); + while (i -= j); + } + if (len) *len = i; /* set return value */ + if (md && mg) { /* have special routine to slurp string? */ + if (md->first) { /* partial fetch? */ + md->first--; /* restore origin octet */ + md->last = i; /* number of octets that we got */ + } + else md->flags |= MG_COPY;/* otherwise flag need to copy */ + string = (*mg) (net_getbuffer,LOCAL->netstream,i,md); + } + else { /* must slurp into free storage */ + string = (char *) fs_get ((size_t) i + 1); + *string = '\0'; /* init in case getbuffer fails */ + /* get the literal */ + if (rp) for (k = 0; j = min ((long) MAILTMPLEN,(long) i); i -= j) { + net_getbuffer (LOCAL->netstream,j,string + k); + (*rp) (md,k += j); + } + else net_getbuffer (LOCAL->netstream,i,string); + } + fs_give ((void **) &reply->line); + if (flags && string) /* need to filter newlines? */ + for (st = string; st = strpbrk (st,"\015\012\011"); *st++ = ' '); + /* get new reply text line */ + if (!(reply->line = net_getline (LOCAL->netstream))) + reply->line = cpystr (""); + if (stream->debug) mm_dlog (reply->line); + *txtptr = reply->line; /* set text pointer to point at it */ + break; + default: + sprintf (LOCAL->tmp,"Not a string: %c%.80s",c,(char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + if (len) *len = 0; + break; + } + return (unsigned char *) string; +} + +/* Register text in IMAP cache + * Accepts: MAIL stream + * message number + * IMAP segment specifier + * header string list (if a HEADER section specifier) + * sized text to register + * Returns: non-zero if cache non-empty + */ + +long imap_cache (MAILSTREAM *stream,unsigned long msgno,char *seg, + STRINGLIST *stl,SIZEDTEXT *text) +{ + char *t,tmp[MAILTMPLEN]; + unsigned long i; + BODY *b; + SIZEDTEXT *ret; + STRINGLIST *stc; + MESSAGECACHE *elt = mail_elt (stream,msgno); + /* top-level header never does mailgets */ + if (!strcmp (seg,"HEADER") || !strcmp (seg,"0") || + !strcmp (seg,"HEADER.FIELDS") || !strcmp (seg,"HEADER.FIELDS.NOT")) { + ret = &elt->private.msg.header.text; + if (text) { /* don't do this if no text */ + if (ret->data) fs_give ((void **) &ret->data); + mail_free_stringlist (&elt->private.msg.lines); + elt->private.msg.lines = stl; + /* prevent cache reuse of .NOT */ + if ((seg[0] == 'H') && (seg[6] == '.') && (seg[13] == '.')) + for (stc = stl; stc; stc = stc->next) stc->text.size = 0; + if (stream->scache) { /* short caching puts it in the stream */ + if (stream->msgno != msgno) { + /* flush old stuff */ + mail_free_envelope (&stream->env); + mail_free_body (&stream->body); + stream->msgno = msgno; + } + imap_parse_header (stream,&stream->env,text,stl); + } + /* regular caching */ + else imap_parse_header (stream,&elt->private.msg.env,text,stl); + } + } + /* top level text */ + else if (!strcmp (seg,"TEXT")) { + ret = &elt->private.msg.text.text; + if (text && ret->data) fs_give ((void **) &ret->data); + } + else if (!*seg) { /* full message */ + ret = &elt->private.msg.full.text; + if (text && ret->data) fs_give ((void **) &ret->data); + } + + else { /* nested, find non-contents specifier */ + for (t = seg; *t && !((*t == '.') && (isalpha(t[1]) || !atol (t+1))); t++); + if (*t) *t++ = '\0'; /* tie off section from data specifier */ + if (!(b = mail_body (stream,msgno,seg))) { + sprintf (tmp,"Unknown section number: %.80s",seg); + mm_notify (stream,tmp,WARN); + stream->unhealthy = T; + return NIL; + } + if (*t) { /* if a non-numberic subpart */ + if ((i = (b->type == TYPEMESSAGE) && (!strcmp (b->subtype,"RFC822"))) && + (!strcmp (t,"HEADER") || !strcmp (t,"0") || + !strcmp (t,"HEADER.FIELDS") || !strcmp (t,"HEADER.FIELDS.NOT"))) { + ret = &b->nested.msg->header.text; + if (text) { + if (ret->data) fs_give ((void **) &ret->data); + mail_free_stringlist (&b->nested.msg->lines); + b->nested.msg->lines = stl; + /* prevent cache reuse of .NOT */ + if ((t[0] == 'H') && (t[6] == '.') && (t[13] == '.')) + for (stc = stl; stc; stc = stc->next) stc->text.size = 0; + imap_parse_header (stream,&b->nested.msg->env,text,stl); + } + } + else if (i && !strcmp (t,"TEXT")) { + ret = &b->nested.msg->text.text; + if (text && ret->data) fs_give ((void **) &ret->data); + } + /* otherwise it must be MIME */ + else if (!strcmp (t,"MIME")) { + ret = &b->mime.text; + if (text && ret->data) fs_give ((void **) &ret->data); + } + else { + sprintf (tmp,"Unknown section specifier: %.80s.%.80s",seg,t); + mm_notify (stream,tmp,WARN); + stream->unhealthy = T; + return NIL; + } + } + else { /* ordinary contents */ + ret = &b->contents.text; + if (text && ret->data) fs_give ((void **) &ret->data); + } + } + if (text) { /* update cache if requested */ + ret->data = text->data; + ret->size = text->size; + } + return ret->data ? LONGT : NIL; +} + +/* IMAP parse body structure + * Accepts: MAIL stream + * body structure to write into + * current text pointer + * parsed reply + * + * Updates text pointer + */ + +void imap_parse_body_structure (MAILSTREAM *stream,BODY *body, + unsigned char **txtptr,IMAPPARSEDREPLY *reply) +{ + int i; + char *s; + PART *part = NIL; + char c = *((*txtptr)++); /* grab first character */ + /* ignore leading spaces */ + while (c == ' ') c = *((*txtptr)++); + switch (c) { /* dispatch on first character */ + case '(': /* body structure list */ + if (**txtptr == '(') { /* multipart body? */ + body->type= TYPEMULTIPART;/* yes, set its type */ + do { /* instantiate new body part */ + if (part) part = part->next = mail_newbody_part (); + else body->nested.part = part = mail_newbody_part (); + /* parse it */ + imap_parse_body_structure (stream,&part->body,txtptr,reply); + } while (**txtptr == '(');/* for each body part */ + if (body->subtype = imap_parse_string(stream,txtptr,reply,NIL,NIL,LONGT)) + ucase (body->subtype); + else { + mm_notify (stream,"Missing multipart subtype",WARN); + stream->unhealthy = T; + body->subtype = cpystr (rfc822_default_subtype (body->type)); + } + if (**txtptr == ' ') /* multipart parameters */ + body->parameter = imap_parse_body_parameter (stream,txtptr,reply); + if (**txtptr == ' ') { /* disposition */ + imap_parse_disposition (stream,body,txtptr,reply); + if (LOCAL->cap.extlevel < BODYEXTDSP) LOCAL->cap.extlevel = BODYEXTDSP; + } + if (**txtptr == ' ') { /* language */ + body->language = imap_parse_language (stream,txtptr,reply); + if (LOCAL->cap.extlevel < BODYEXTLANG) + LOCAL->cap.extlevel = BODYEXTLANG; + } + if (**txtptr == ' ') { /* location */ + body->location = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); + if (LOCAL->cap.extlevel < BODYEXTLOC) LOCAL->cap.extlevel = BODYEXTLOC; + } + while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply); + if (**txtptr != ')') { /* validate ending */ + sprintf (LOCAL->tmp,"Junk at end of multipart body: %.80s", + (char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + else ++*txtptr; /* skip past delimiter */ + } + + else { /* not multipart, parse type name */ + if (**txtptr == ')') { /* empty body? */ + ++*txtptr; /* bump past it */ + break; /* and punt */ + } + body->type = TYPEOTHER; /* assume unknown type */ + body->encoding = ENCOTHER;/* and unknown encoding */ + /* parse type */ + if (s = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT)) { + ucase (s); /* application always gets uppercase form */ + for (i = 0; /* look in existing table */ + (i <= TYPEMAX) && body_types[i] && strcmp (s,body_types[i]); i++); + if (i <= TYPEMAX) { /* only if found a slot */ + body->type = i; /* set body type */ + if (body_types[i]) fs_give ((void **) &s); + else body_types[i]=s; /* assign empty slot */ + } + } + if (body->subtype = imap_parse_string(stream,txtptr,reply,NIL,NIL,LONGT)) + ucase (body->subtype); /* parse subtype */ + else { + mm_notify (stream,"Missing body subtype",WARN); + stream->unhealthy = T; + body->subtype = cpystr (rfc822_default_subtype (body->type)); + } + body->parameter = imap_parse_body_parameter (stream,txtptr,reply); + body->id = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); + body->description = imap_parse_string (stream,txtptr,reply,NIL,NIL, + LONGT); + if (s = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT)) { + ucase (s); /* application always gets uppercase form */ + for (i = 0; /* search for body encoding */ + (i <= ENCMAX) && body_encodings[i] && strcmp(s,body_encodings[i]); + i++); + if (i > ENCMAX) body->encoding = ENCOTHER; + else { /* only if found a slot */ + body->encoding = i; /* set body encoding */ + if (body_encodings[i]) fs_give ((void **) &s); + /* assign empty slot */ + else body_encodings[i] = s; + } + } + /* parse size of contents in bytes */ + body->size.bytes = strtoul (*txtptr,(char **) txtptr,10); + switch (body->type) { /* possible extra stuff */ + case TYPEMESSAGE: /* message envelope and body */ + /* non MESSAGE/RFC822 is basic type */ + if (strcmp (body->subtype,"RFC822")) break; + { /* make certain server sends an envelope */ + ENVELOPE *env = NIL; + imap_parse_envelope (stream,&env,txtptr,reply); + if (!env) { + mm_notify (stream,"Missing body message envelope",WARN); + stream->unhealthy = T; + body->subtype = cpystr ("RFC822_MISSING_ENVELOPE"); + break; + } + (body->nested.msg = mail_newmsg ())->env = env; + } + body->nested.msg->body = mail_newbody (); + imap_parse_body_structure (stream,body->nested.msg->body,txtptr,reply); + /* drop into text case */ + case TYPETEXT: /* size in lines */ + body->size.lines = strtoul (*txtptr,(char **) txtptr,10); + break; + default: /* otherwise nothing special */ + break; + } + + if (**txtptr == ' ') { /* extension data - md5 */ + body->md5 = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); + if (LOCAL->cap.extlevel < BODYEXTMD5) LOCAL->cap.extlevel = BODYEXTMD5; + } + if (**txtptr == ' ') { /* disposition */ + imap_parse_disposition (stream,body,txtptr,reply); + if (LOCAL->cap.extlevel < BODYEXTDSP) LOCAL->cap.extlevel = BODYEXTDSP; + } + if (**txtptr == ' ') { /* language */ + body->language = imap_parse_language (stream,txtptr,reply); + if (LOCAL->cap.extlevel < BODYEXTLANG) + LOCAL->cap.extlevel = BODYEXTLANG; + } + if (**txtptr == ' ') { /* location */ + body->location = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT); + if (LOCAL->cap.extlevel < BODYEXTLOC) LOCAL->cap.extlevel = BODYEXTLOC; + } + while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply); + if (**txtptr != ')') { /* validate ending */ + sprintf (LOCAL->tmp,"Junk at end of body part: %.80s", + (char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + else ++*txtptr; /* skip past delimiter */ + } + break; + case 'N': /* if NIL */ + case 'n': + ++*txtptr; /* bump past "I" */ + ++*txtptr; /* bump past "L" */ + break; + default: /* otherwise quite bogus */ + sprintf (LOCAL->tmp,"Bogus body structure: %.80s",(char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + break; + } +} + +/* IMAP parse body parameter + * Accepts: MAIL stream + * current text pointer + * parsed reply + * Returns: body parameter + * Updates text pointer + */ + +PARAMETER *imap_parse_body_parameter (MAILSTREAM *stream, + unsigned char **txtptr, + IMAPPARSEDREPLY *reply) +{ + PARAMETER *ret = NIL; + PARAMETER *par = NIL; + char c,*s; + /* ignore leading spaces */ + while ((c = *(*txtptr)++) == ' '); + /* parse parameter list */ + if (c == '(') while (c != ')') { + /* append new parameter to tail */ + if (ret) par = par->next = mail_newbody_parameter (); + else ret = par = mail_newbody_parameter (); + if(!(par->attribute=imap_parse_string (stream,txtptr,reply,NIL,NIL, + LONGT))) { + mm_notify (stream,"Missing parameter attribute",WARN); + stream->unhealthy = T; + par->attribute = cpystr ("UNKNOWN"); + } + if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT))){ + sprintf (LOCAL->tmp,"Missing value for parameter %.80s",par->attribute); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + par->value = cpystr ("UNKNOWN"); + } + switch (c = **txtptr) { /* see what comes after */ + case ' ': /* flush whitespace */ + while ((c = *++*txtptr) == ' '); + break; + case ')': /* end of attribute/value pairs */ + ++*txtptr; /* skip past closing paren */ + break; + default: + sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",(char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + break; + } + } + /* empty parameter, must be NIL */ + else if (((c == 'N') || (c == 'n')) && + ((*(s = *txtptr) == 'I') || (*s == 'i')) && + ((s[1] == 'L') || (s[1] == 'l'))) *txtptr += 2; + else { + sprintf (LOCAL->tmp,"Bogus body parameter: %c%.80s",c, + (char *) (*txtptr) - 1); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + return ret; +} + +/* IMAP parse body disposition + * Accepts: MAIL stream + * body structure to write into + * current text pointer + * parsed reply + */ + +void imap_parse_disposition (MAILSTREAM *stream,BODY *body, + unsigned char **txtptr,IMAPPARSEDREPLY *reply) +{ + switch (*++*txtptr) { + case '(': + ++*txtptr; /* skip open paren */ + body->disposition.type = imap_parse_string (stream,txtptr,reply,NIL,NIL, + LONGT); + body->disposition.parameter = + imap_parse_body_parameter (stream,txtptr,reply); + if (**txtptr != ')') { /* validate ending */ + sprintf (LOCAL->tmp,"Junk at end of disposition: %.80s", + (char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + } + else ++*txtptr; /* skip past delimiter */ + break; + case 'N': /* if NIL */ + case 'n': + ++*txtptr; /* bump past "N" */ + ++*txtptr; /* bump past "I" */ + ++*txtptr; /* bump past "L" */ + break; + default: + sprintf (LOCAL->tmp,"Unknown body disposition: %.80s",(char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + /* try to skip to next space */ + while ((*++*txtptr != ' ') && (**txtptr != ')') && **txtptr); + break; + } +} + +/* IMAP parse body language + * Accepts: MAIL stream + * current text pointer + * parsed reply + * Returns: string list or NIL if empty or error + */ + +STRINGLIST *imap_parse_language (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply) +{ + unsigned long i; + char *s; + STRINGLIST *ret = NIL; + /* language is a list */ + if (*++*txtptr == '(') ret = imap_parse_stringlist (stream,txtptr,reply); + else if (s = imap_parse_string (stream,txtptr,reply,NIL,&i,LONGT)) { + (ret = mail_newstringlist ())->text.data = (unsigned char *) s; + ret->text.size = i; + } + return ret; +} + +/* IMAP parse string list + * Accepts: MAIL stream + * current text pointer + * parsed reply + * Returns: string list or NIL if empty or error + */ + +STRINGLIST *imap_parse_stringlist (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply) +{ + STRINGLIST *stl = NIL; + STRINGLIST *stc = NIL; + unsigned char *t = *txtptr; + /* parse the list */ + if (*t++ == '(') while (*t != ')') { + if (stl) stc = stc->next = mail_newstringlist (); + else stc = stl = mail_newstringlist (); + /* parse astring */ + if (!(stc->text.data = + imap_parse_astring (stream,&t,reply,&stc->text.size))) { + sprintf (LOCAL->tmp,"Bogus string list member: %.80s",(char *) t); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + mail_free_stringlist (&stl); + break; + } + else if (*t == ' ') ++t; /* another token follows */ + } + if (stl) *txtptr = ++t; /* update return string */ + return stl; +} + +/* IMAP parse unknown body extension data + * Accepts: MAIL stream + * current text pointer + * parsed reply + * + * Updates text pointer + */ + +void imap_parse_extension (MAILSTREAM *stream,unsigned char **txtptr, + IMAPPARSEDREPLY *reply) +{ + unsigned long i,j; + switch (*++*txtptr) { /* action depends upon first character */ + case '(': + while (**txtptr != ')') imap_parse_extension (stream,txtptr,reply); + ++*txtptr; /* bump past closing parenthesis */ + break; + case '"': /* if quoted string */ + while (*++*txtptr != '"') if (**txtptr == '\\') ++*txtptr; + ++*txtptr; /* bump past closing quote */ + break; + case 'N': /* if NIL */ + case 'n': + ++*txtptr; /* bump past "N" */ + ++*txtptr; /* bump past "I" */ + ++*txtptr; /* bump past "L" */ + break; + case '{': /* get size of literal */ + ++*txtptr; /* bump past open squiggle */ + if (i = strtoul (*txtptr,(char **) txtptr,10)) do + net_getbuffer (LOCAL->netstream,j = min (i,(long) IMAPTMPLEN - 1), + LOCAL->tmp); + while (i -= j); + /* get new reply text line */ + if (!(reply->line = net_getline (LOCAL->netstream))) + reply->line = cpystr (""); + if (stream->debug) mm_dlog (reply->line); + *txtptr = reply->line; /* set text pointer to point at it */ + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + strtoul (*txtptr,(char **) txtptr,10); + break; + default: + sprintf (LOCAL->tmp,"Unknown extension token: %.80s",(char *) *txtptr); + mm_notify (stream,LOCAL->tmp,WARN); + stream->unhealthy = T; + /* try to skip to next space */ + while ((*++*txtptr != ' ') && (**txtptr != ')') && **txtptr); + break; + } +} + +/* IMAP parse capabilities + * Accepts: MAIL stream + * capability list + */ + +void imap_parse_capabilities (MAILSTREAM *stream,char *t) +{ + char *s,*r; + unsigned long i; + THREADER *thr,*th; + if (!LOCAL->gotcapability) { /* need to save previous capabilities? */ + /* no, flush threaders */ + if (thr = LOCAL->cap.threader) while (th = thr) { + fs_give ((void **) &th->name); + thr = th->next; + fs_give ((void **) &th); + } + /* zap capabilities */ + memset (&LOCAL->cap,0,sizeof (LOCAL->cap)); + LOCAL->gotcapability = T; /* flag that capabilities arrived */ + } + for (t = strtok_r (t," ",&r); t; t = strtok_r (NIL," ",&r)) { + if (!compare_cstring (t,"IMAP4")) + LOCAL->cap.imap4 = LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T; + else if (!compare_cstring (t,"IMAP4rev1")) + LOCAL->cap.imap4rev1 = LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T; + else if (!compare_cstring (t,"IMAP2")) LOCAL->cap.rfc1176 = T; + else if (!compare_cstring (t,"IMAP2bis")) + LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T; + else if (!compare_cstring (t,"ACL")) LOCAL->cap.acl = T; + else if (!compare_cstring (t,"QUOTA")) LOCAL->cap.quota = T; + else if (!compare_cstring (t,"LITERAL+")) LOCAL->cap.litplus = T; + else if (!compare_cstring (t,"IDLE")) LOCAL->cap.idle = T; + else if (!compare_cstring (t,"MAILBOX-REFERRALS")) LOCAL->cap.mbx_ref = T; + else if (!compare_cstring (t,"LOGIN-REFERRALS")) LOCAL->cap.log_ref = T; + else if (!compare_cstring (t,"NAMESPACE")) LOCAL->cap.namespace = T; + else if (!compare_cstring (t,"UIDPLUS")) LOCAL->cap.uidplus = T; + else if (!compare_cstring (t,"STARTTLS")) LOCAL->cap.starttls = T; + else if (!compare_cstring (t,"LOGINDISABLED"))LOCAL->cap.logindisabled = T; + else if (!compare_cstring (t,"ID")) LOCAL->cap.id = T; + else if (!compare_cstring (t,"CHILDREN")) LOCAL->cap.children = T; + else if (!compare_cstring (t,"MULTIAPPEND")) LOCAL->cap.multiappend = T; + else if (!compare_cstring (t,"BINARY")) LOCAL->cap.binary = T; + else if (!compare_cstring (t,"UNSELECT")) LOCAL->cap.unselect = T; + else if (!compare_cstring (t,"SASL-IR")) LOCAL->cap.sasl_ir = T; + else if (!compare_cstring (t,"SCAN")) LOCAL->cap.scan = T; + else if (!compare_cstring (t,"URLAUTH")) LOCAL->cap.urlauth = T; + else if (!compare_cstring (t,"CATENATE")) LOCAL->cap.catenate = T; + else if (!compare_cstring (t,"CONDSTORE")) LOCAL->cap.condstore = T; + else if (!compare_cstring (t,"ESEARCH")) LOCAL->cap.esearch = T; + else if (((t[0] == 'S') || (t[0] == 's')) && + ((t[1] == 'O') || (t[1] == 'o')) && + ((t[2] == 'R') || (t[2] == 'r')) && + ((t[3] == 'T') || (t[3] == 't'))) LOCAL->cap.sort = T; + /* capability with value? */ + else if (s = strchr (t,'=')) { + *s++ = '\0'; /* separate token from value */ + if (!compare_cstring (t,"THREAD") && !LOCAL->loser) { + THREADER *thread = (THREADER *) fs_get (sizeof (THREADER)); + thread->name = cpystr (s); + thread->dispatch = NIL; + thread->next = LOCAL->cap.threader; + LOCAL->cap.threader = thread; + } + else if (!compare_cstring (t,"AUTH")) { + if ((i = mail_lookup_auth_name (s,LOCAL->authflags)) && + (--i < MAXAUTHENTICATORS)) LOCAL->cap.auth |= (1 << i); + else if (!compare_cstring (s,"ANONYMOUS")) LOCAL->cap.authanon = T; + } + } + /* ignore other capabilities */ + } + /* disable LOGIN if PLAIN also advertised */ + if ((i = mail_lookup_auth_name ("PLAIN",NIL)) && (--i < MAXAUTHENTICATORS) && + (LOCAL->cap.auth & (1 << i)) && + (i = mail_lookup_auth_name ("LOGIN",NIL)) && (--i < MAXAUTHENTICATORS)) + LOCAL->cap.auth &= ~(1 << i); +} + +/* IMAP load cache + * Accepts: MAIL stream + * sequence + * flags + * Returns: parsed reply from fetch + */ + +IMAPPARSEDREPLY *imap_fetch (MAILSTREAM *stream,char *sequence,long flags) +{ + int i = 2; + char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? + "UID FETCH" : "FETCH"; + IMAPARG *args[9],aseq,aarg,aenv,ahhr,axtr,ahtr,abdy,atrl; + if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence, + flags & FT_UID); + args[0] = &aseq; aseq.type = SEQUENCE; aseq.text = (void *) sequence; + args[1] = &aarg; aarg.type = ATOM; + aenv.type = ATOM; aenv.text = (void *) "ENVELOPE"; + ahhr.type = ATOM; ahhr.text = (void *) hdrheader[LOCAL->cap.extlevel]; + axtr.type = ATOM; axtr.text = (void *) imap_extrahdrs; + ahtr.type = ATOM; ahtr.text = (void *) hdrtrailer; + abdy.type = ATOM; abdy.text = (void *) "BODYSTRUCTURE"; + atrl.type = ATOM; atrl.text = (void *) "INTERNALDATE RFC822.SIZE FLAGS)"; + if (LEVELIMAP4 (stream)) { /* include UID if IMAP4 or IMAP4rev1 */ + aarg.text = (void *) "(UID"; + if (flags & FT_NEEDENV) { /* if need envelopes */ + args[i++] = &aenv; /* include envelope */ + /* extra header poop if IMAP4rev1 */ + if (!(flags & FT_NOHDRS) && LEVELIMAP4rev1 (stream)) { + args[i++] = &ahhr; /* header header */ + if (axtr.text) args[i++] = &axtr; + args[i++] = &ahtr; /* header trailer */ + } + /* fetch body if requested */ + if (flags & FT_NEEDBODY) args[i++] = &abdy; + } + args[i++] = &atrl; /* fetch trailer */ + } + /* easy if IMAP2 */ + else aarg.text = (void *) (flags & FT_NEEDENV) ? + ((flags & FT_NEEDBODY) ? + "(RFC822.HEADER BODY INTERNALDATE RFC822.SIZE FLAGS)" : + "(RFC822.HEADER INTERNALDATE RFC822.SIZE FLAGS)") : "FAST"; + args[i] = NIL; /* tie off command */ + return imap_send (stream,cmd,args); +} + +/* Reform sequence for losing server that doesn't handle ranges right + * Accepts: MAIL stream + * sequence + * non-zero if UID + * Returns: sequence + */ + +char *imap_reform_sequence (MAILSTREAM *stream,char *sequence,long flags) +{ + unsigned long i,j,star; + char *s,*t,*tl,*rs; + /* can't win if empty */ + if (!stream->nmsgs) return sequence; + /* get highest possible range value */ + star = flags ? mail_uid (stream,stream->nmsgs) : stream->nmsgs; + /* flush old reformed sequence */ + if (LOCAL->reform) fs_give ((void **) &LOCAL->reform); + rs = LOCAL->reform = (char *) fs_get (1+ strlen (sequence)); + for (s = sequence; t = strpbrk (s,",:"); ) switch (*t++) { + case ',': /* single message */ + strncpy (rs,s,i = t - s); /* copy string up to that point */ + rs += i; /* advance destination pointer */ + s += i; /* and source */ + break; + case ':': /* message range */ + i = (*s == '*') ? star : strtoul (s,NIL,10); + if (*t == '*') { /* range ends with star */ + j = star; + tl = t+1; + } + else { /* numeric range end */ + j = strtoul (t,(char **) &tl,10); + if (!tl) tl = t + strlen (t); + } + if (i <= j) { /* if first less than second */ + if (*tl) tl++; /* skip past end of range if present */ + strncpy (rs,s,i = tl - s);/* copy string up to that point */ + rs += i; /* advance destination and source pointers */ + s += i; + } + else { /* here's the workaround for losing servers */ + strncpy (rs,t,i = tl - t);/* swap the order */ + rs[i] = ':'; /* delimit */ + strncpy (rs+i+1,s,j = (t-1) - s); + rs += i + 1 + j; /* advance destination pointer */ + if (*tl) *rs++ = *tl++; /* write trailing delimiter if present */ + s = tl; /* advance source pointer */ + } + } + if (*s) strcpy (rs,s); /* write remainder of sequence */ + else *rs = '\0'; /* tie off string */ + return LOCAL->reform; +} + +/* IMAP return host name + * Accepts: MAIL stream + * Returns: host name + */ + +char *imap_host (MAILSTREAM *stream) +{ + if (stream->dtb != &imapdriver) + fatal ("imap_host called on non-IMAP stream!"); + /* return host name on stream if open */ + return (LOCAL && LOCAL->netstream) ? net_host (LOCAL->netstream) : + ".NO-IMAP-CONNECTION."; +} + + +/* IMAP return IMAP capability structure + * Accepts: MAIL stream + * Returns: IMAP capability structure + */ + +IMAPCAP *imap_cap (MAILSTREAM *stream) +{ + if (stream->dtb != &imapdriver) + fatal ("imap_cap called on non-IMAP stream!"); + return &LOCAL->cap; /* return capability structure */ +} diff --git a/imap/src/c-client/imap4r1.h b/imap/src/c-client/imap4r1.h new file mode 100644 index 00000000..8ee8a186 --- /dev/null +++ b/imap/src/c-client/imap4r1.h @@ -0,0 +1,281 @@ +/* ======================================================================== + * Copyright 1988-2007 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Interactive Mail Access Protocol 4rev1 (IMAP4R1) routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 14 October 1988 + * Last Edited: 5 September 2007 + */ + + +/* This include file is provided for applications which need to look under + * the covers at the IMAP driver and in particular want to do different + * operations depending upon the IMAP server's protocol level and + * capabilities. It is NOT included in the normal c-client.h application + * export, and most applications do NOT need the definitions in this file. + * + * As of October 15, 2003, it is believed that: + * + * Version RFC Status Known Implementations + * ------- --- ------ --------------------- + * IMAP1 none extinct experimental TOPS-20 server + * IMAP2 1064 extinct old TOPS-20, SUMEX servers + * IMAP2 1176 rare TOPS-20, old UW servers + * IMAP2bis expired I-D uncommon old UW, Cyrus servers + * IMAP3 1203 extinct none (never implemented) + * IMAP4 1730 rare old UW, Cyrus, Netscape servers + * IMAP4rev1 2060, 3501 ubiquitous UW, Cyrus, and many others + * + * Most client implementations will only interoperate with an IMAP4rev1 + * server. c-client based client implementations can interoperate with IMAP2, + * IMAP2bis, IMAP4, and IMAP4rev1 servers, but only if they are very careful. + * + * The LEVELxxx() macros in this file enable the client to determine the + * server protocol level and capabilities. This file also contains a few + * backdoor calls into the IMAP driver. + */ + +/* Server protocol level and capabilities */ + +typedef struct imap_cap { + unsigned int rfc1176 : 1; /* server is RFC-1176 IMAP2 */ + unsigned int imap2bis : 1; /* server is IMAP2bis */ + unsigned int imap4 : 1; /* server is IMAP4 (RFC 1730) */ + unsigned int imap4rev1 : 1; /* server is IMAP4rev1 */ + unsigned int acl : 1; /* server has ACL (RFC 2086) */ + unsigned int quota : 1; /* server has QUOTA (RFC 2087) */ + unsigned int litplus : 1; /* server has LITERAL+ (RFC 2088) */ + unsigned int idle : 1; /* server has IDLE (RFC 2177) */ + unsigned int mbx_ref : 1; /* server has mailbox referrals (RFC 2193) */ + unsigned int log_ref : 1; /* server has login referrals (RFC 2221) */ + unsigned int authanon : 1; /* server has anonymous SASL (RFC 2245) */ + unsigned int namespace :1; /* server has NAMESPACE (RFC 2342) */ + unsigned int uidplus : 1; /* server has UIDPLUS (RFC 2359) */ + unsigned int starttls : 1; /* server has STARTTLS (RFC 2595) */ + /* server disallows LOGIN command (RFC 2595) */ + unsigned int logindisabled : 1; + unsigned int id : 1; /* server has ID (RFC 2971) */ + unsigned int children : 1; /* server has CHILDREN (RFC 3348) */ + unsigned int multiappend : 1; /* server has multi-APPEND (RFC 3502) ;*/ + unsigned int binary : 1; /* server has BINARY (RFC 3516) */ + unsigned int unselect : 1; /* server has UNSELECT */ + unsigned int sasl_ir : 1; /* server has SASL-IR initial response */ + unsigned int sort : 1; /* server has SORT */ + unsigned int scan : 1; /* server has SCAN */ + unsigned int urlauth : 1; /* server has URLAUTH (RFC 4467) */ + unsigned int catenate : 1; /* server has CATENATE (RFC 4469) */ + unsigned int condstore : 1; /* server has CONDSTORE (RFC 4551) */ + unsigned int esearch : 1; /* server has ESEARCH (RFC 4731) */ + unsigned int within : 1; /* server has WITHIN (RFC 5032) */ + unsigned int extlevel; /* extension data level supported by server */ + /* supported authenticators */ + unsigned int auth : MAXAUTHENTICATORS; + THREADER *threader; /* list of threaders */ +} IMAPCAP; + +/* IMAP4rev1 level or better */ + +#define LEVELIMAP4rev1(stream) imap_cap (stream)->imap4rev1 + +#define LEVELSTATUS LEVELIMAP4rev1 + + +/* IMAP4 level or better (not including RFC 1730 design mistakes) */ + +#define LEVELIMAP4(stream) (imap_cap (stream)->imap4rev1 || \ + imap_cap (stream)->imap4) + + +/* IMAP4 RFC-1730 level */ + +#define LEVEL1730(stream) imap_cap (stream)->imap4 + + +/* IMAP2bis level or better */ + +#define LEVELIMAP2bis(stream) imap_cap (stream)->imap2bis + + +/* IMAP2 RFC-1176 level or better */ + +#define LEVEL1176(stream) imap_cap (stream)->rfc1176 + + +/* IMAP2 RFC-1064 or better */ + +#define LEVEL1064(stream) 1 + +/* Has ACL extension */ + +#define LEVELACL(stream) imap_cap (stream)->acl + + +/* Has QUOTA extension */ + +#define LEVELQUOTA(stream) imap_cap (stream)->quota + + +/* Has LITERALPLUS extension */ + +#define LEVELLITERALPLUS(stream) imap_cap (stream)->litplus + + +/* Has IDLE extension */ + +#define LEVELIDLE(stream) imap_cap (stream)->idle + + +/* Has mailbox referrals */ + +#define LEVELMBX_REF(stream) imap_cap (stream)->mbx_ref + + +/* Has login referrals */ + +#define LEVELLOG_REF(stream) imap_cap (stream)->log_ref + + +/* Has AUTH=ANONYMOUS extension */ + +#define LEVELANONYMOUS(stream) imap_cap (stream)->authanon + + +/* Has NAMESPACE extension */ + +#define LEVELNAMESPACE(stream) imap_cap (stream)->namespace + + +/* Has UIDPLUS extension */ + +#define LEVELUIDPLUS(stream) imap_cap (stream)->uidplus + + +/* Has STARTTLS extension */ + +#define LEVELSTARTTLS(stream) imap_cap (stream)->starttls + + +/* Has LOGINDISABLED extension */ + +#define LEVELLOGINDISABLED(stream) imap_cap (stream)->logindisabled + +/* Has ID extension */ + +#define LEVELID(stream) imap_cap (stream)->id + + +/* Has CHILDREN extension */ + +#define LEVELCHILDREN(stream) imap_cap (stream)->children + + +/* Has MULTIAPPEND extension */ + +#define LEVELMULTIAPPEND(stream) imap_cap (stream)->multiappend + + +/* Has BINARY extension */ + +#define LEVELBINARY(stream) imap_cap (stream)->binary + + +/* Has UNSELECT extension */ + +#define LEVELUNSELECT(stream) imap_cap (stream)->unselect + + +/* Has SASL initial response extension */ + +#define LEVELSASLIR(stream) imap_cap (stream)->sasl_ir + + +/* Has SORT extension */ + +#define LEVELSORT(stream) imap_cap (stream)->sort + + +/* Has at least one THREAD extension */ + +#define LEVELTHREAD(stream) ((imap_cap (stream)->threader) ? T : NIL) + + +/* Has SCAN extension */ + +#define LEVELSCAN(stream) imap_cap (stream)->scan + + +/* Has URLAUTH extension */ + +#define LEVELURLAUTH(stream) imap_cap (stream)->urlauth + + +/* Has CATENATE extension */ + +#define LEVELCATENATE(stream) imap_cap (stream)->catenate + + +/* Has CONDSTORE extension */ + +#define LEVELCONDSTORE(stream) imap_cap (stream)->condstore + + +/* Has ESEARCH extension */ + +#define LEVELESEARCH(stream) imap_cap (stream)->esearch + + +/* Has WITHIN extension */ + +#define LEVELWITHIN(stream) imap_cap (stream)->within + +/* Body structure extension levels */ + +/* These are in BODYSTRUCTURE order. Note that multipart bodies do not have + * body-fld-md5. This is alright, since all subsequent body structure + * extensions are in both singlepart and multipart bodies. If that ever + * changes, this will have to be split. + */ + +#define BODYEXTMD5 1 /* body-fld-md5 */ +#define BODYEXTDSP 2 /* body-fld-dsp */ +#define BODYEXTLANG 3 /* body-fld-lang */ +#define BODYEXTLOC 4 /* body-fld-loc */ + + +/* Function prototypes */ + +IMAPCAP *imap_cap (MAILSTREAM *stream); +char *imap_host (MAILSTREAM *stream); +long imap_cache (MAILSTREAM *stream,unsigned long msgno,char *seg, + STRINGLIST *stl,SIZEDTEXT *text); + + +/* Temporary */ + +long imap_setacl (MAILSTREAM *stream,char *mailbox,char *id,char *rights); +long imap_deleteacl (MAILSTREAM *stream,char *mailbox,char *id); +long imap_getacl (MAILSTREAM *stream,char *mailbox); +long imap_listrights (MAILSTREAM *stream,char *mailbox,char *id); +long imap_myrights (MAILSTREAM *stream,char *mailbox); +long imap_setquota (MAILSTREAM *stream,char *qroot,STRINGLIST *limits); +long imap_getquota (MAILSTREAM *stream,char *qroot); +long imap_getquotaroot (MAILSTREAM *stream,char *mailbox); diff --git a/imap/src/c-client/mail.c b/imap/src/c-client/mail.c new file mode 100644 index 00000000..d80a01f6 --- /dev/null +++ b/imap/src/c-client/mail.c @@ -0,0 +1,6337 @@ +/* ======================================================================== + * Copyright 1988-2008 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Mailbox Access routines + * + * Author: Mark Crispin + * UW Technology + * University of Washington + * Seattle, WA 98195 + * Internet: MRC@Washington.EDU + * + * Date: 22 November 1989 + * Last Edited: 15 April 2008 + */ + + +#include <ctype.h> +#include <stdio.h> +#include <time.h> +#include "c-client.h" + +char *UW_copyright = "Copyright 1988-2007 University of Washington\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n"; + +/* c-client global data */ + + /* version of this library */ +static char *mailcclientversion = CCLIENTVERSION; + /* list of mail drivers */ +static DRIVER *maildrivers = NIL; + /* list of authenticators */ +static AUTHENTICATOR *mailauthenticators = NIL; + /* SSL driver pointer */ +static NETDRIVER *mailssldriver = NIL; + /* pointer to alternate gets function */ +static mailgets_t mailgets = NIL; + /* pointer to read progress function */ +static readprogress_t mailreadprogress = NIL; + /* mail cache manipulation function */ +static mailcache_t mailcache = mm_cache; + /* RFC-822 output generator */ +static rfc822out_t mail822out = NIL; + /* RFC-822 output generator (new style) */ +static rfc822outfull_t mail822outfull = NIL; + /* SMTP verbose callback */ +static smtpverbose_t mailsmtpverbose = mm_dlog; + /* proxy copy routine */ +static mailproxycopy_t mailproxycopy = NIL; + /* RFC-822 external line parse */ +static parseline_t mailparseline = NIL; + /* RFC-822 external phrase parser */ +static parsephrase_t mailparsephrase = NIL; +static kinit_t mailkinit = NIL; /* application kinit callback */ + /* note network sent command */ +static sendcommand_t mailsendcommand = NIL; + /* newsrc file name decision function */ +static newsrcquery_t mailnewsrcquery = NIL; + /* ACL results callback */ +static getacl_t mailaclresults = NIL; + /* list rights results callback */ +static listrights_t maillistrightsresults = NIL; + /* my rights results callback */ +static myrights_t mailmyrightsresults = NIL; + /* quota results callback */ +static quota_t mailquotaresults = NIL; + /* quota root results callback */ +static quotaroot_t mailquotarootresults = NIL; + /* sorted results callback */ +static sortresults_t mailsortresults = NIL; + /* threaded results callback */ +static threadresults_t mailthreadresults = NIL; + /* COPY UID results */ +static copyuid_t mailcopyuid = NIL; + /* APPEND UID results */ +static appenduid_t mailappenduid = NIL; + /* free elt extra stuff callback */ +static freeeltsparep_t mailfreeeltsparep = NIL; + /* free envelope extra stuff callback */ +static freeenvelopesparep_t mailfreeenvelopesparep = NIL; + /* free body extra stuff callback */ +static freebodysparep_t mailfreebodysparep = NIL; + /* free stream extra stuff callback */ +static freestreamsparep_t mailfreestreamsparep = NIL; + /* SSL start routine */ +static sslstart_t mailsslstart = NIL; + /* SSL certificate query */ +static sslcertificatequery_t mailsslcertificatequery = NIL; + /* SSL client certificate */ +static sslclientcert_t mailsslclientcert = NIL; + /* SSL client private key */ +static sslclientkey_t mailsslclientkey = NIL; + /* SSL failure notify */ +static sslfailure_t mailsslfailure = NIL; + /* snarf interval */ +static long mailsnarfinterval = 60; + /* snarf preservation */ +static long mailsnarfpreserve = NIL; + /* newsrc name uses canonical host */ +static long mailnewsrccanon = LONGT; + + /* supported threaders */ +static THREADER mailthreadordsub = { + "ORDEREDSUBJECT",mail_thread_orderedsubject,NIL +}; +static THREADER mailthreadlist = { + "REFERENCES",mail_thread_references,&mailthreadordsub +}; + + /* server name */ +static char *servicename = "unknown"; + /* server externally-set authentication ID */ +static char *externalauthid = NIL; +static int expungeatping = T; /* mail_ping() may call mm_expunged() */ +static int trysslfirst = NIL; /* always try SSL first */ +static int notimezones = NIL; /* write timezones in "From " header */ +static int trustdns = T; /* do DNS canonicalization */ +static int saslusesptrname = T; /* SASL uses name from DNS PTR lookup */ + /* trustdns also must be set */ +static int debugsensitive = NIL;/* debug telemetry includes sensitive data */ + +/* Default mail cache handler + * Accepts: pointer to cache handle + * message number + * caching function + * Returns: cache data + */ + +void *mm_cache (MAILSTREAM *stream,unsigned long msgno,long op) +{ + size_t n; + void *ret = NIL; + unsigned long i; + switch ((int) op) { /* what function? */ + case CH_INIT: /* initialize cache */ + if (stream->cache) { /* flush old cache contents */ + while (stream->cachesize) { + mm_cache (stream,stream->cachesize,CH_FREE); + mm_cache (stream,stream->cachesize--,CH_FREESORTCACHE); + } + fs_give ((void **) &stream->cache); + fs_give ((void **) &stream->sc); + stream->nmsgs = 0; /* can't have any messages now */ + } + break; + case CH_SIZE: /* (re-)size the cache */ + if (!stream->cache) { /* have a cache already? */ + /* no, create new cache */ + n = (stream->cachesize = msgno + CACHEINCREMENT) * sizeof (void *); + stream->cache = (MESSAGECACHE **) memset (fs_get (n),0,n); + stream->sc = (SORTCACHE **) memset (fs_get (n),0,n); + } + /* is existing cache size large neough */ + else if (msgno > stream->cachesize) { + i = stream->cachesize; /* remember old size */ + n = (stream->cachesize = msgno + CACHEINCREMENT) * sizeof (void *); + fs_resize ((void **) &stream->cache,n); + fs_resize ((void **) &stream->sc,n); + while (i < stream->cachesize) { + stream->cache[i] = NIL; + stream->sc[i++] = NIL; + } + } + break; + + case CH_MAKEELT: /* return elt, make if necessary */ + if (!stream->cache[msgno - 1]) + stream->cache[msgno - 1] = mail_new_cache_elt (msgno); + /* falls through */ + case CH_ELT: /* return elt */ + ret = (void *) stream->cache[msgno - 1]; + break; + case CH_SORTCACHE: /* return sortcache entry, make if needed */ + if (!stream->sc[msgno - 1]) stream->sc[msgno - 1] = + (SORTCACHE *) memset (fs_get (sizeof (SORTCACHE)),0,sizeof (SORTCACHE)); + ret = (void *) stream->sc[msgno - 1]; + break; + case CH_FREE: /* free elt */ + mail_free_elt (&stream->cache[msgno - 1]); + break; + case CH_FREESORTCACHE: + if (stream->sc[msgno - 1]) { + if (stream->sc[msgno - 1]->from) + fs_give ((void **) &stream->sc[msgno - 1]->from); + if (stream->sc[msgno - 1]->to) + fs_give ((void **) &stream->sc[msgno - 1]->to); + if (stream->sc[msgno - 1]->cc) + fs_give ((void **) &stream->sc[msgno - 1]->cc); + if (stream->sc[msgno - 1]->subject) + fs_give ((void **) &stream->sc[msgno - 1]->subject); + if (stream->sc[msgno - 1]->unique && + (stream->sc[msgno - 1]->unique != stream->sc[msgno - 1]->message_id)) + fs_give ((void **) &stream->sc[msgno - 1]->unique); + if (stream->sc[msgno - 1]->message_id) + fs_give ((void **) &stream->sc[msgno - 1]->message_id); + if (stream->sc[msgno - 1]->references) + mail_free_stringlist (&stream->sc[msgno - 1]->references); + fs_give ((void **) &stream->sc[msgno - 1]); + } + break; + case CH_EXPUNGE: /* expunge cache slot */ + for (i = msgno - 1; msgno < stream->nmsgs; i++,msgno++) { + if (stream->cache[i] = stream->cache[msgno]) + stream->cache[i]->msgno = msgno; + stream->sc[i] = stream->sc[msgno]; + } + stream->cache[i] = NIL; /* top of cache goes away */ + stream->sc[i] = NIL; + break; + default: + fatal ("Bad mm_cache op"); + break; + } + return ret; +} + +/* Dummy string driver for complete in-memory strings */ + +static void mail_string_init (STRING *s,void *data,unsigned long size); +static char mail_string_next (STRING *s); +static void mail_string_setpos (STRING *s,unsigned long i); + +STRINGDRIVER mail_string = { + mail_string_init, /* initialize string structure */ + mail_string_next, /* get next byte in string structure */ + mail_string_setpos /* set position in string structure */ +}; + + +/* Initialize mail string structure for in-memory string + * Accepts: string structure + * pointer to string + * size of string + */ + +static void mail_string_init (STRING *s,void *data,unsigned long size) +{ + /* set initial string pointers */ + s->chunk = s->curpos = (char *) (s->data = data); + /* and sizes */ + s->size = s->chunksize = s->cursize = size; + s->data1 = s->offset = 0; /* never any offset */ +} + + +/* Get next character from string + * Accepts: string structure + * Returns: character, string structure chunk refreshed + */ + +static char mail_string_next (STRING *s) +{ + return *s->curpos++; /* return the last byte */ +} + + +/* Set string pointer position + * Accepts: string structure + * new position + */ + +static void mail_string_setpos (STRING *s,unsigned long i) +{ + s->curpos = s->chunk + i; /* set new position */ + s->cursize = s->chunksize - i;/* and new size */ +} + +/* Mail routines + * + * mail_xxx routines are the interface between this module and the outside + * world. Only these routines should be referenced by external callers. + * + * Note that there is an important difference between a "sequence" and a + * "message #" (msgno). A sequence is a string representing a sequence in + * {"n", "n:m", or combination separated by commas} format, whereas a msgno + * is a single integer. + * + */ + +/* Mail version check + * Accepts: version + */ + +void mail_versioncheck (char *version) +{ + /* attempt to protect again wrong .h */ + if (strcmp (version,mailcclientversion)) { + char tmp[MAILTMPLEN]; + sprintf (tmp,"c-client library version skew, app=%.100s library=%.100s", + version,mailcclientversion); + fatal (tmp); + } +} + + +/* Mail link driver + * Accepts: driver to add to list + */ + +void mail_link (DRIVER *driver) +{ + DRIVER **d = &maildrivers; + while (*d) d = &(*d)->next; /* find end of list of drivers */ + *d = driver; /* put driver at the end */ + driver->next = NIL; /* this driver is the end of the list */ +} + +/* Mail manipulate driver parameters + * Accepts: mail stream + * function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *mail_parameters (MAILSTREAM *stream,long function,void *value) +{ + void *r,*ret = NIL; + DRIVER *d; + AUTHENTICATOR *a; + switch ((int) function) { + case SET_INBOXPATH: + fatal ("SET_INBOXPATH not permitted"); + case GET_INBOXPATH: + if ((stream || (stream = mail_open (NIL,"INBOX",OP_PROTOTYPE))) && + stream->dtb) ret = (*stream->dtb->parameters) (function,value); + break; + case SET_THREADERS: + fatal ("SET_THREADERS not permitted"); + case GET_THREADERS: /* use stream dtb instead of global */ + ret = (stream && stream->dtb) ? + /* KLUDGE ALERT: note stream passed as value */ + (*stream->dtb->parameters) (function,stream) : (void *) &mailthreadlist; + break; + case SET_NAMESPACE: + fatal ("SET_NAMESPACE not permitted"); + break; + case SET_NEWSRC: /* too late on open stream */ + if (stream && stream->dtb && (stream != ((*stream->dtb->open) (NIL)))) + fatal ("SET_NEWSRC not permitted"); + else ret = env_parameters (function,value); + break; + case GET_NAMESPACE: + case GET_NEWSRC: /* use stream dtb instead of environment */ + ret = (stream && stream->dtb) ? + /* KLUDGE ALERT: note stream passed as value */ + (*stream->dtb->parameters) (function,stream) : + env_parameters (function,value); + break; + case ENABLE_DEBUG: + fatal ("ENABLE_DEBUG not permitted"); + case DISABLE_DEBUG: + fatal ("DISABLE_DEBUG not permitted"); + case SET_DIRFMTTEST: + fatal ("SET_DIRFMTTEST not permitted"); + case GET_DIRFMTTEST: + if (!(stream && stream->dtb && + (ret = (*stream->dtb->parameters) (function,NIL)))) + fatal ("GET_DIRFMTTEST not permitted"); + break; + + case SET_DRIVERS: + fatal ("SET_DRIVERS not permitted"); + case GET_DRIVERS: /* always return global */ + ret = (void *) maildrivers; + break; + case SET_DRIVER: + fatal ("SET_DRIVER not permitted"); + case GET_DRIVER: + for (d = maildrivers; d && compare_cstring (d->name,(char *) value); + d = d->next); + ret = (void *) d; + break; + case ENABLE_DRIVER: + for (d = maildrivers; d && compare_cstring (d->name,(char *) value); + d = d->next); + if (ret = (void *) d) d->flags &= ~DR_DISABLE; + break; + case DISABLE_DRIVER: + for (d = maildrivers; d && compare_cstring (d->name,(char *) value); + d = d->next); + if (ret = (void *) d) d->flags |= DR_DISABLE; + break; + case ENABLE_AUTHENTICATOR: + for (a = mailauthenticators;/* scan authenticators */ + a && compare_cstring (a->name,(char *) value); a = a->next); + if (ret = (void *) a) a->flags &= ~AU_DISABLE; + break; + case DISABLE_AUTHENTICATOR: + for (a = mailauthenticators;/* scan authenticators */ + a && compare_cstring (a->name,(char *) value); a = a->next); + if (ret = (void *) a) a->flags |= AU_DISABLE; + break; + case UNHIDE_AUTHENTICATOR: + for (a = mailauthenticators;/* scan authenticators */ + a && compare_cstring (a->name,(char *) value); a = a->next); + if (ret = (void *) a) a->flags &= ~AU_HIDE; + break; + case HIDE_AUTHENTICATOR: + for (a = mailauthenticators;/* scan authenticators */ + a && compare_cstring (a->name,(char *) value); a = a->next); + if (ret = (void *) a) a->flags |= AU_HIDE; + break; + case SET_EXTERNALAUTHID: + if (value) { /* setting external authentication ID */ + externalauthid = cpystr ((char *) value); + mail_parameters (NIL,UNHIDE_AUTHENTICATOR,"EXTERNAL"); + } + else { /* clearing external authentication ID */ + if (externalauthid) fs_give ((void **) &externalauthid); + mail_parameters (NIL,HIDE_AUTHENTICATOR,"EXTERNAL"); + } + case GET_EXTERNALAUTHID: + ret = (void *) externalauthid; + break; + + case SET_GETS: + mailgets = (mailgets_t) value; + case GET_GETS: + ret = (void *) mailgets; + break; + case SET_READPROGRESS: + mailreadprogress = (readprogress_t) value; + case GET_READPROGRESS: + ret = (void *) mailreadprogress; + break; + case SET_CACHE: + mailcache = (mailcache_t) value; + case GET_CACHE: + ret = (void *) mailcache; + break; + case SET_RFC822OUTPUT: + mail822out = (rfc822out_t) value; + case GET_RFC822OUTPUT: + ret = (void *) mail822out; + break; + case SET_RFC822OUTPUTFULL: + mail822outfull = (rfc822outfull_t) value; + case GET_RFC822OUTPUTFULL: + ret = (void *) mail822outfull; + break; + case SET_SMTPVERBOSE: + mailsmtpverbose = (smtpverbose_t) value; + case GET_SMTPVERBOSE: + ret = (void *) mailsmtpverbose; + break; + case SET_MAILPROXYCOPY: + mailproxycopy = (mailproxycopy_t) value; + case GET_MAILPROXYCOPY: + ret = (void *) mailproxycopy; + break; + case SET_PARSELINE: + mailparseline = (parseline_t) value; + case GET_PARSELINE: + ret = (void *) mailparseline; + break; + case SET_PARSEPHRASE: + mailparsephrase = (parsephrase_t) value; + case GET_PARSEPHRASE: + ret = (void *) mailparsephrase; + break; + case SET_NEWSRCQUERY: + mailnewsrcquery = (newsrcquery_t) value; + case GET_NEWSRCQUERY: + ret = (void *) mailnewsrcquery; + break; + case SET_NEWSRCCANONHOST: + mailnewsrccanon = (long) value; + case GET_NEWSRCCANONHOST: + ret = (void *) mailnewsrccanon; + break; + + case SET_COPYUID: + mailcopyuid = (copyuid_t) value; + case GET_COPYUID: + ret = (void *) mailcopyuid; + break; + case SET_APPENDUID: + mailappenduid = (appenduid_t) value; + case GET_APPENDUID: + ret = (void *) mailappenduid; + break; + case SET_FREEENVELOPESPAREP: + mailfreeenvelopesparep = (freeenvelopesparep_t) value; + case GET_FREEENVELOPESPAREP: + ret = (void *) mailfreeenvelopesparep; + break; + case SET_FREEELTSPAREP: + mailfreeeltsparep = (freeeltsparep_t) value; + case GET_FREEELTSPAREP: + ret = (void *) mailfreeeltsparep; + break; + case SET_FREESTREAMSPAREP: + mailfreestreamsparep = (freestreamsparep_t) value; + case GET_FREESTREAMSPAREP: + ret = (void *) mailfreestreamsparep; + break; + case SET_FREEBODYSPAREP: + mailfreebodysparep = (freebodysparep_t) value; + case GET_FREEBODYSPAREP: + ret = (void *) mailfreebodysparep; + break; + + case SET_SSLSTART: + mailsslstart = (sslstart_t) value; + case GET_SSLSTART: + ret = (void *) mailsslstart; + break; + case SET_SSLCERTIFICATEQUERY: + mailsslcertificatequery = (sslcertificatequery_t) value; + case GET_SSLCERTIFICATEQUERY: + ret = (void *) mailsslcertificatequery; + break; + case SET_SSLCLIENTCERT: + mailsslclientcert = (sslclientcert_t) value; + case GET_SSLCLIENTCERT: + ret = (void *) mailsslclientcert; + break; + case SET_SSLCLIENTKEY: + mailsslclientkey = (sslclientkey_t) value; + case GET_SSLCLIENTKEY: + ret = (void *) mailsslclientkey; + break; + case SET_SSLFAILURE: + mailsslfailure = (sslfailure_t) value; + case GET_SSLFAILURE: + ret = (void *) mailsslfailure; + break; + case SET_KINIT: + mailkinit = (kinit_t) value; + case GET_KINIT: + ret = (void *) mailkinit; + break; + case SET_SENDCOMMAND: + mailsendcommand = (sendcommand_t) value; + case GET_SENDCOMMAND: + ret = (void *) mailsendcommand; + break; + + case SET_SERVICENAME: + servicename = (char *) value; + case GET_SERVICENAME: + ret = (void *) servicename; + break; + case SET_EXPUNGEATPING: + expungeatping = (value ? T : NIL); + case GET_EXPUNGEATPING: + ret = (void *) (expungeatping ? VOIDT : NIL); + break; + case SET_SORTRESULTS: + mailsortresults = (sortresults_t) value; + case GET_SORTRESULTS: + ret = (void *) mailsortresults; + break; + case SET_THREADRESULTS: + mailthreadresults = (threadresults_t) value; + case GET_THREADRESULTS: + ret = (void *) mailthreadresults; + break; + case SET_SSLDRIVER: + mailssldriver = (NETDRIVER *) value; + case GET_SSLDRIVER: + ret = (void *) mailssldriver; + break; + case SET_TRYSSLFIRST: + trysslfirst = (value ? T : NIL); + case GET_TRYSSLFIRST: + ret = (void *) (trysslfirst ? VOIDT : NIL); + break; + case SET_NOTIMEZONES: + notimezones = (value ? T : NIL); + case GET_NOTIMEZONES: + ret = (void *) (notimezones ? VOIDT : NIL); + break; + case SET_TRUSTDNS: + trustdns = (value ? T : NIL); + case GET_TRUSTDNS: + ret = (void *) (trustdns ? VOIDT : NIL); + break; + case SET_SASLUSESPTRNAME: + saslusesptrname = (value ? T : NIL); + case GET_SASLUSESPTRNAME: + ret = (void *) (saslusesptrname ? VOIDT : NIL); + break; + case SET_DEBUGSENSITIVE: + debugsensitive = (value ? T : NIL); + case GET_DEBUGSENSITIVE: + ret = (void *) (debugsensitive ? VOIDT : NIL); + break; + + case SET_ACL: + mailaclresults = (getacl_t) value; + case GET_ACL: + ret = (void *) mailaclresults; + break; + case SET_LISTRIGHTS: + maillistrightsresults = (listrights_t) value; + case GET_LISTRIGHTS: + ret = (void *) maillistrightsresults; + break; + case SET_MYRIGHTS: + mailmyrightsresults = (myrights_t) value; + case GET_MYRIGHTS: + ret = (void *) mailmyrightsresults; + break; + case SET_QUOTA: + mailquotaresults = (quota_t) value; + case GET_QUOTA: + ret = (void *) mailquotaresults; + break; + case SET_QUOTAROOT: + mailquotarootresults = (quotaroot_t) value; + case GET_QUOTAROOT: + ret = (void *) mailquotarootresults; + break; + case SET_SNARFINTERVAL: + mailsnarfinterval = (long) value; + case GET_SNARFINTERVAL: + ret = (void *) mailsnarfinterval; + break; + case SET_SNARFPRESERVE: + mailsnarfpreserve = (long) value; + case GET_SNARFPRESERVE: + ret = (void *) mailsnarfpreserve; + break; + case SET_SNARFMAILBOXNAME: + if (stream) { /* have a stream? */ + if (stream->snarf.name) fs_give ((void **) &stream->snarf.name); + stream->snarf.name = cpystr ((char *) value); + } + else fatal ("SET_SNARFMAILBOXNAME with no stream"); + case GET_SNARFMAILBOXNAME: + if (stream) ret = (void *) stream->snarf.name; + break; + default: + if (r = smtp_parameters (function,value)) ret = r; + if (r = env_parameters (function,value)) ret = r; + if (r = tcp_parameters (function,value)) ret = r; + if (stream && stream->dtb) {/* if have stream, do for its driver only */ + if (r = (*stream->dtb->parameters) (function,value)) ret = r; + } + /* else do all drivers */ + else for (d = maildrivers; d; d = d->next) + if (r = (d->parameters) (function,value)) ret = r; + break; + } + return ret; +} + +/* Mail validate mailbox name + * Accepts: MAIL stream + * mailbox name + * purpose string for error message + * Return: driver factory on success, NIL on failure + */ + +DRIVER *mail_valid (MAILSTREAM *stream,char *mailbox,char *purpose) +{ + char tmp[MAILTMPLEN]; + DRIVER *factory = NIL; + /* never allow names with newlines */ + if (strpbrk (mailbox,"\015\012")) { + if (purpose) { /* if want an error message */ + sprintf (tmp,"Can't %s with such a name",purpose); + MM_LOG (tmp,ERROR); + } + return NIL; + } + /* validate name, find driver factory */ + if (strlen (mailbox) < (NETMAXHOST+(NETMAXUSER*2)+NETMAXMBX+NETMAXSRV+50)) + for (factory = maildrivers; factory && + ((factory->flags & DR_DISABLE) || + ((factory->flags & DR_LOCAL) && (*mailbox == '{')) || + !(*factory->valid) (mailbox)); + factory = factory->next); + /* validate factory against non-dummy stream */ + if (factory && stream && stream->dtb && (stream->dtb != factory) && + strcmp (stream->dtb->name,"dummy")) + /* factory invalid; if dummy, use stream */ + factory = strcmp (factory->name,"dummy") ? NIL : stream->dtb; + if (!factory && purpose) { /* if want an error message */ + sprintf (tmp,"Can't %s %.80s: %s",purpose,mailbox,(*mailbox == '{') ? + "invalid remote specification" : "no such mailbox"); + MM_LOG (tmp,ERROR); + } + return factory; /* return driver factory */ +} + +/* Mail validate network mailbox name + * Accepts: mailbox name + * mailbox driver to validate against + * pointer to where to return host name if non-NIL + * pointer to where to return mailbox name if non-NIL + * Returns: driver on success, NIL on failure + */ + +DRIVER *mail_valid_net (char *name,DRIVER *drv,char *host,char *mailbox) +{ + NETMBX mb; + if (!mail_valid_net_parse (name,&mb) || strcmp (mb.service,drv->name)) + return NIL; + if (host) strcpy (host,mb.host); + if (mailbox) strcpy (mailbox,mb.mailbox); + return drv; +} + + +/* Mail validate network mailbox name + * Accepts: mailbox name + * NETMBX structure to return values + * Returns: T on success, NIL on failure + */ + +long mail_valid_net_parse (char *name,NETMBX *mb) +{ + return mail_valid_net_parse_work (name,mb,"imap"); +} + +/* Mail validate network mailbox name worker routine + * Accepts: mailbox name + * NETMBX structure to return values + * default service + * Returns: T on success, NIL on failure + */ + +long mail_valid_net_parse_work (char *name,NETMBX *mb,char *service) +{ + int i,j; + char c,*s,*t,*v,tmp[MAILTMPLEN],arg[MAILTMPLEN]; + /* initialize structure */ + memset (mb,'\0',sizeof (NETMBX)); + /* must have host specification */ + if (*name++ != '{') return NIL; + if (*name == '[') { /* if domain literal, find its ending */ + if (!((v = strpbrk (name,"]}")) && (*v++ == ']'))) return NIL; + } + /* find end of host name */ + else if (!(v = strpbrk (name,"/:}"))) return NIL; + /* validate length, find mailbox part */ + if (!((i = v - name) && (i < NETMAXHOST) && (t = strchr (v,'}')) && + ((j = t - v) < MAILTMPLEN) && (strlen (t+1) < (size_t) NETMAXMBX))) + return NIL; /* invalid mailbox */ + strncpy (mb->host,name,i); /* set host name */ + strncpy (mb->orighost,name,i); + mb->host[i] = mb->orighost[i] = '\0'; + strcpy (mb->mailbox,t+1); /* set mailbox name */ + if (t - v) { /* any switches or port specification? */ + strncpy (t = tmp,v,j); /* copy it */ + tmp[j] = '\0'; /* tie it off */ + c = *t++; /* get first delimiter */ + do switch (c) { /* act based upon the character */ + case ':': /* port specification */ + if (mb->port || !(mb->port = strtoul (t,&t,10))) return NIL; + c = t ? *t++ : '\0'; /* get delimiter, advance pointer */ + break; + case '/': /* switch */ + /* find delimiter */ + if (t = strpbrk (s = t,"/:=")) { + c = *t; /* remember delimiter for later */ + *t++ = '\0'; /* tie off switch name */ + } + else c = '\0'; /* no delimiter */ + if (c == '=') { /* parse switches which take arguments */ + if (*t == '"') { /* quoted string? */ + for (v = arg,i = 0,++t; (c = *t++) != '"';) { + if (!c) return NIL; /* unterminated string */ + /* quote next character */ + if (c == '\\') c = *t++; + if (!c) return NIL; /* can't quote NUL either */ + arg[i++] = c; + } + c = *t++; /* remember delimiter for later */ + arg[i] = '\0'; /* tie off argument */ + } + else { /* non-quoted argument */ + if (t = strpbrk (v = t,"/:")) { + c = *t; /* remember delimiter for later */ + *t++ = '\0'; /* tie off switch name */ + } + else c = '\0'; /* no delimiter */ + i = strlen (v); /* length of argument */ + } + if (!compare_cstring (s,"service") && (i < NETMAXSRV) && !*mb->service) + lcase (strcpy (mb->service,v)); + else if (!compare_cstring (s,"user") && (i < NETMAXUSER) && !*mb->user) + strcpy (mb->user,v); + else if (!compare_cstring (s,"authuser") && (i < NETMAXUSER) && + !*mb->authuser) strcpy (mb->authuser,v); + else return NIL; + } + + else { /* non-argument switch */ + if (!compare_cstring (s,"anonymous")) mb->anoflag = T; + else if (!compare_cstring (s,"debug")) mb->dbgflag = T; + else if (!compare_cstring (s,"readonly")) mb->readonlyflag = T; + else if (!compare_cstring (s,"secure")) mb->secflag = T; + else if (!compare_cstring (s,"norsh")) mb->norsh = T; + else if (!compare_cstring (s,"loser")) mb->loser = T; + else if (!compare_cstring (s,"tls") && !mb->notlsflag) + mb->tlsflag = T; + else if (!compare_cstring (s,"tls-sslv23") && !mb->notlsflag) + mb->tlssslv23 = mb->tlsflag = T; + else if (!compare_cstring (s,"notls") && !mb->tlsflag) + mb->notlsflag = T; + else if (!compare_cstring (s,"tryssl")) + mb->trysslflag = mailssldriver? T : NIL; + else if (mailssldriver && !compare_cstring (s,"ssl") && !mb->tlsflag) + mb->sslflag = mb->notlsflag = T; + else if (mailssldriver && !compare_cstring (s,"novalidate-cert")) + mb->novalidate = T; + /* hack for compatibility with the past */ + else if (mailssldriver && !compare_cstring (s,"validate-cert")); + /* service switches below here */ + else if (*mb->service) return NIL; + else if (!compare_cstring (s,"imap") || + !compare_cstring (s,"nntp") || + !compare_cstring (s,"pop3") || + !compare_cstring (s,"smtp") || + !compare_cstring (s,"submit")) + lcase (strcpy (mb->service,s)); + else if (!compare_cstring (s,"imap2") || + !compare_cstring (s,"imap2bis") || + !compare_cstring (s,"imap4") || + !compare_cstring (s,"imap4rev1")) + strcpy (mb->service,"imap"); + else if (!compare_cstring (s,"pop")) + strcpy (mb->service,"pop3"); + else return NIL; /* invalid non-argument switch */ + } + break; + default: /* anything else is bogus */ + return NIL; + } while (c); /* see if anything more to parse */ + } + /* default mailbox name */ + if (!*mb->mailbox) strcpy (mb->mailbox,"INBOX"); + /* default service name */ + if (!*mb->service) strcpy (mb->service,service); + /* /norsh only valid if imap */ + if (mb->norsh && strcmp (mb->service,"imap")) return NIL; + return T; +} + +/* Mail scan mailboxes for string + * Accepts: mail stream + * reference + * pattern to search + * contents to search + */ + +void mail_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) +{ + int remote = ((*pat == '{') || (ref && *ref == '{')); + DRIVER *d; + if (ref && (strlen (ref) > NETMAXMBX)) { + char tmp[MAILTMPLEN]; + sprintf (tmp,"Invalid LIST reference specification: %.80s",ref); + MM_LOG (tmp,ERROR); + return; + } + if (strlen (pat) > NETMAXMBX) { + char tmp[MAILTMPLEN]; + sprintf (tmp,"Invalid LIST pattern specification: %.80s",pat); + MM_LOG (tmp,ERROR); + return; + } + if (*pat == '{') ref = NIL; /* ignore reference if pattern is remote */ + if (stream) { /* if have a stream, do it for that stream */ + if ((d = stream->dtb) && d->scan && + !(((d->flags & DR_LOCAL) && remote))) + (*d->scan) (stream,ref,pat,contents); + } + /* otherwise do for all DTB's */ + else for (d = maildrivers; d; d = d->next) + if (d->scan && !((d->flags & DR_DISABLE) || + ((d->flags & DR_LOCAL) && remote))) + (d->scan) (NIL,ref,pat,contents); +} + +/* Mail list mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void mail_list (MAILSTREAM *stream,char *ref,char *pat) +{ + int remote = ((*pat == '{') || (ref && *ref == '{')); + DRIVER *d = maildrivers; + if (ref && (strlen (ref) > NETMAXMBX)) { + char tmp[MAILTMPLEN]; + sprintf (tmp,"Invalid LIST reference specification: %.80s",ref); + MM_LOG (tmp,ERROR); + return; + } + if (strlen (pat) > NETMAXMBX) { + char tmp[MAILTMPLEN]; + sprintf (tmp,"Invalid LIST pattern specification: %.80s",pat); + MM_LOG (tmp,ERROR); + return; + } + if (*pat == '{') ref = NIL; /* ignore reference if pattern is remote */ + if (stream && stream->dtb) { /* if have a stream, do it for that stream */ + if (!(((d = stream->dtb)->flags & DR_LOCAL) && remote)) + (*d->list) (stream,ref,pat); + } + /* otherwise do for all DTB's */ + else do if (!((d->flags & DR_DISABLE) || + ((d->flags & DR_LOCAL) && remote))) + (d->list) (NIL,ref,pat); + while (d = d->next); /* until at the end */ +} + +/* Mail list subscribed mailboxes + * Accepts: mail stream + * pattern to search + */ + +void mail_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + int remote = ((*pat == '{') || (ref && *ref == '{')); + DRIVER *d = maildrivers; + if (ref && (strlen (ref) > NETMAXMBX)) { + char tmp[MAILTMPLEN]; + sprintf (tmp,"Invalid LSUB reference specification: %.80s",ref); + MM_LOG (tmp,ERROR); + return; + } + if (strlen (pat) > NETMAXMBX) { + char tmp[MAILTMPLEN]; + sprintf (tmp,"Invalid LSUB pattern specification: %.80s",pat); + MM_LOG (tmp,ERROR); + return; + } + if (*pat == '{') ref = NIL; /* ignore reference if pattern is remote */ + if (stream && stream->dtb) { /* if have a stream, do it for that stream */ + if (!(((d = stream->dtb)->flags & DR_LOCAL) && remote)) + (*d->lsub) (stream,ref,pat); + } + /* otherwise do for all DTB's */ + else do if (!((d->flags & DR_DISABLE) || + ((d->flags & DR_LOCAL) && remote))) + (d->lsub) (NIL,ref,pat); + while (d = d->next); /* until at the end */ +} + +/* Mail subscribe to mailbox + * Accepts: mail stream + * mailbox to add to subscription list + * Returns: T on success, NIL on failure + */ + +long mail_subscribe (MAILSTREAM *stream,char *mailbox) +{ + DRIVER *factory = mail_valid (stream,mailbox,"subscribe to mailbox"); + return factory ? + (factory->subscribe ? + (*factory->subscribe) (stream,mailbox) : sm_subscribe (mailbox)) : NIL; +} + + +/* Mail unsubscribe to mailbox + * Accepts: mail stream + * mailbox to delete from subscription list + * Returns: T on success, NIL on failure + */ + +long mail_unsubscribe (MAILSTREAM *stream,char *mailbox) +{ + DRIVER *factory = mail_valid (stream,mailbox,NIL); + return (factory && factory->unsubscribe) ? + (*factory->unsubscribe) (stream,mailbox) : sm_unsubscribe (mailbox); +} + +/* Mail create mailbox + * Accepts: mail stream + * mailbox name to create + * Returns: T on success, NIL on failure + */ + +long mail_create (MAILSTREAM *stream,char *mailbox) +{ + MAILSTREAM *ts; + char *s,*t,tmp[MAILTMPLEN]; + size_t i; + DRIVER *d; + /* never allow names with newlines */ + if (s = strpbrk (mailbox,"\015\012")) { + MM_LOG ("Can't create mailbox with such a name",ERROR); + return NIL; + } + if (strlen (mailbox) >= (NETMAXHOST+(NETMAXUSER*2)+NETMAXMBX+NETMAXSRV+50)) { + sprintf (tmp,"Can't create %.80s: %s",mailbox,(*mailbox == '{') ? + "invalid remote specification" : "no such mailbox"); + MM_LOG (tmp,ERROR); + return NIL; + } + /* create of INBOX invalid */ + if (!compare_cstring (mailbox,"INBOX")) { + MM_LOG ("Can't create INBOX",ERROR); + return NIL; + } + /* validate name */ + if (s = mail_utf7_valid (mailbox)) { + sprintf (tmp,"Can't create %s: %.80s",s,mailbox); + MM_LOG (tmp,ERROR); + return NIL; + } + + /* see if special driver hack */ + if ((mailbox[0] == '#') && ((mailbox[1] == 'd') || (mailbox[1] == 'D')) && + ((mailbox[2] == 'r') || (mailbox[2] == 'R')) && + ((mailbox[3] == 'i') || (mailbox[3] == 'I')) && + ((mailbox[4] == 'v') || (mailbox[4] == 'V')) && + ((mailbox[5] == 'e') || (mailbox[5] == 'E')) && + ((mailbox[6] == 'r') || (mailbox[6] == 'R')) && (mailbox[7] == '.')) { + /* copy driver until likely delimiter */ + if ((s = strpbrk (t = mailbox+8,"/\\:")) && (i = s - t)) { + strncpy (tmp,t,i); + tmp[i] = '\0'; + } + else { + sprintf (tmp,"Can't create mailbox %.80s: bad driver syntax",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + } + for (d = maildrivers; d && strcmp (d->name,tmp); d = d->next); + if (d) mailbox = ++s; /* skip past driver specification */ + else { + sprintf (tmp,"Can't create mailbox %.80s: unknown driver",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + } + } + /* use stream if one given or deterministic */ + else if ((stream && stream->dtb) || + (((*mailbox == '{') || (*mailbox == '#')) && + (stream = mail_open (NIL,mailbox,OP_PROTOTYPE | OP_SILENT)))) + d = stream->dtb; + else if ((*mailbox != '{') && (ts = default_proto (NIL))) d = ts->dtb; + else { /* failed utterly */ + sprintf (tmp,"Can't create mailbox %.80s: indeterminate format",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + } + return (*d->create) (stream,mailbox); +} + +/* Mail delete mailbox + * Accepts: mail stream + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long mail_delete (MAILSTREAM *stream,char *mailbox) +{ + DRIVER *dtb = mail_valid (stream,mailbox,"delete mailbox"); + if (!dtb) return NIL; + if (((mailbox[0] == 'I') || (mailbox[0] == 'i')) && + ((mailbox[1] == 'N') || (mailbox[1] == 'n')) && + ((mailbox[2] == 'B') || (mailbox[2] == 'b')) && + ((mailbox[3] == 'O') || (mailbox[3] == 'o')) && + ((mailbox[4] == 'X') || (mailbox[4] == 'x')) && !mailbox[5]) { + MM_LOG ("Can't delete INBOX",ERROR); + return NIL; + } + return SAFE_DELETE (dtb,stream,mailbox); +} + + +/* Mail rename mailbox + * Accepts: mail stream + * old mailbox name + * new mailbox name + * Returns: T on success, NIL on failure + */ + +long mail_rename (MAILSTREAM *stream,char *old,char *newname) +{ + char *s,tmp[MAILTMPLEN]; + DRIVER *dtb = mail_valid (stream,old,"rename mailbox"); + if (!dtb) return NIL; + /* validate name */ + if (s = mail_utf7_valid (newname)) { + sprintf (tmp,"Can't rename to %s: %.80s",s,newname); + MM_LOG (tmp,ERROR); + return NIL; + } + if ((*old != '{') && (*old != '#') && mail_valid (NIL,newname,NIL)) { + sprintf (tmp,"Can't rename %.80s: mailbox %.80s already exists", + old,newname); + MM_LOG (tmp,ERROR); + return NIL; + } + return SAFE_RENAME (dtb,stream,old,newname); +} + +/* Validate mailbox as Modified UTF-7 + * Accepts: candidate mailbox name + * Returns: error string if error, NIL if valid + */ + +char *mail_utf7_valid (char *mailbox) +{ + char *s; + for (s = mailbox; *s; s++) { /* make sure valid name */ + /* reserved for future use with UTF-8 */ + if (*s & 0x80) return "mailbox name with 8-bit octet"; + /* validate modified UTF-7 */ + else if (*s == '&') while (*++s != '-') switch (*s) { + case '\0': + return "unterminated modified UTF-7 name"; + case '+': /* valid modified BASE64 */ + case ',': + break; /* all OK so far */ + default: /* must be alphanumeric */ + if (!isalnum (*s)) return "invalid modified UTF-7 name"; + break; + } + } + return NIL; /* all OK */ +} + +/* Mail status of mailbox + * Accepts: mail stream if open on this mailbox + * mailbox name + * status flags + * Returns: T on success, NIL on failure + */ + +long mail_status (MAILSTREAM *stream,char *mbx,long flags) +{ + DRIVER *dtb = mail_valid (stream,mbx,"get status of mailbox"); + if (!dtb) return NIL; /* only if valid */ + if (stream && ((dtb != stream->dtb) || + ((dtb->flags & DR_LOCAL) && strcmp (mbx,stream->mailbox) && + strcmp (mbx,stream->original_mailbox)))) + stream = NIL; /* stream not suitable */ + return SAFE_STATUS (dtb,stream,mbx,flags); +} + + +/* Mail status of mailbox default handler + * Accepts: mail stream + * mailbox name + * status flags + * Returns: T on success, NIL on failure + */ + +long mail_status_default (MAILSTREAM *stream,char *mbx,long flags) +{ + MAILSTATUS status; + unsigned long i; + MAILSTREAM *tstream = NIL; + /* make temporary stream (unless this mbx) */ + if (!stream && !(stream = tstream = + mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL; + status.flags = flags; /* return status values */ + status.messages = stream->nmsgs; + status.recent = stream->recent; + if (flags & SA_UNSEEN) /* must search to get unseen messages */ + for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++) + if (!mail_elt (stream,i)->seen) status.unseen++; + status.uidnext = stream->uid_last + 1; + status.uidvalidity = stream->uid_validity; + MM_STATUS(stream,mbx,&status);/* pass status to main program */ + if (tstream) mail_close (tstream); + return T; /* success */ +} + +/* Mail open + * Accepts: candidate stream for recycling + * mailbox name + * open options + * Returns: stream to use on success, NIL on failure + */ + +MAILSTREAM *mail_open (MAILSTREAM *stream,char *name,long options) +{ + int i; + char c,*s,tmp[MAILTMPLEN]; + NETMBX mb; + DRIVER *d; + switch (name[0]) { /* see if special handling */ + case '#': /* possible special hacks */ + if (((name[1] == 'M') || (name[1] == 'm')) && + ((name[2] == 'O') || (name[2] == 'o')) && + ((name[3] == 'V') || (name[3] == 'v')) && + ((name[4] == 'E') || (name[4] == 'e')) && (c = name[5]) && + (s = strchr (name+6,c)) && (i = s - (name + 6)) && (i < MAILTMPLEN)) { + if (stream = mail_open (stream,s+1,options)) { + strncpy (tmp,name+6,i); /* copy snarf mailbox name */ + tmp[i] = '\0'; /* tie off name */ + mail_parameters (stream,SET_SNARFMAILBOXNAME,(void *) tmp); + stream->snarf.options = options; + mail_ping (stream); /* do initial snarf */ + /* punt if can't do initial snarf */ + if (!stream->snarf.time) stream = mail_close (stream); + } + return stream; + } + /* special POP hack */ + else if (((name[1] == 'P') || (name[1] == 'p')) && + ((name[2] == 'O') || (name[2] == 'o')) && + ((name[3] == 'P') || (name[3] == 'p')) && + mail_valid_net_parse_work (name+4,&mb,"pop3") && + !strcmp (mb.service,"pop3") && !mb.anoflag && !mb.readonlyflag) { + if (stream = mail_open (stream,mb.mailbox,options)) { + sprintf (tmp,"{%.255s",mb.host); + if (mb.port) sprintf (tmp + strlen (tmp),":%lu",mb.port); + if (mb.user[0]) sprintf (tmp + strlen (tmp),"/user=%.64s",mb.user); + if (mb.dbgflag) strcat (tmp,"/debug"); + if (mb.secflag) strcat (tmp,"/secure"); + if (mb.tlsflag) strcat (tmp,"/tls"); + if (mb.notlsflag) strcat (tmp,"/notls"); + if (mb.sslflag) strcat (tmp,"/ssl"); + if (mb.trysslflag) strcat (tmp,"/tryssl"); + if (mb.novalidate) strcat (tmp,"/novalidate-cert"); + strcat (tmp,"/pop3/loser}"); + mail_parameters (stream,SET_SNARFMAILBOXNAME,(void *) tmp); + mail_ping (stream); /* do initial snarf */ + } + return stream; /* return local mailbox stream */ + } + + else if ((options & OP_PROTOTYPE) && + ((name[1] == 'D') || (name[1] == 'd')) && + ((name[2] == 'R') || (name[2] == 'r')) && + ((name[3] == 'I') || (name[3] == 'i')) && + ((name[4] == 'V') || (name[4] == 'v')) && + ((name[5] == 'E') || (name[5] == 'e')) && + ((name[6] == 'R') || (name[6] == 'r')) && (name[7] == '.')) { + sprintf (tmp,"%.80s",name+8); + /* tie off name at likely delimiter */ + if (s = strpbrk (tmp,"/\\:")) *s++ = '\0'; + else { + sprintf (tmp,"Can't resolve mailbox %.80s: bad driver syntax",name); + MM_LOG (tmp,ERROR); + return mail_close (stream); + } + for (d = maildrivers; d && compare_cstring (d->name,tmp); d = d->next); + if (d) return (*d->open) (NIL); + sprintf (tmp,"Can't resolve mailbox %.80s: unknown driver",name); + MM_LOG (tmp,ERROR); + return mail_close (stream); + } + /* fall through to default case */ + default: /* not special hack (but could be # name */ + d = mail_valid (NIL,name,(options & OP_SILENT) ? + (char *) NIL : "open mailbox"); + } + return d ? mail_open_work (d,stream,name,options) : stream; +} + +/* Mail open worker routine + * Accepts: factory + * candidate stream for recycling + * mailbox name + * open options + * Returns: stream to use on success, NIL on failure + */ + +MAILSTREAM *mail_open_work (DRIVER *d,MAILSTREAM *stream,char *name, + long options) +{ + int i; + char tmp[MAILTMPLEN]; + NETMBX mb; + if (options & OP_PROTOTYPE) return (*d->open) (NIL); + /* name is copied here in case the caller does a re-open using + * stream->mailbox or stream->original_mailbox as the argument. + */ + name = cpystr (name); /* make copy of name */ + if (stream) { /* recycling requested? */ + if ((stream->dtb == d) && (d->flags & DR_RECYCLE) && + ((d->flags & DR_HALFOPEN) || !(options & OP_HALFOPEN)) && + mail_usable_network_stream (stream,name)) { + /* yes, checkpoint if needed */ + if (d->flags & DR_XPOINT) mail_check (stream); + mail_free_cache (stream); /* clean up stream */ + if (stream->mailbox) fs_give ((void **) &stream->mailbox); + if (stream->original_mailbox) + fs_give ((void **) &stream->original_mailbox); + /* flush user flags */ + for (i = 0; i < NUSERFLAGS; i++) + if (stream->user_flags[i]) fs_give ((void **) &stream->user_flags[i]); + } + else { /* stream not recycleable, babble if net */ + if (!stream->silent && stream->dtb && !(stream->dtb->flags&DR_LOCAL) && + mail_valid_net_parse (stream->mailbox,&mb)) { + sprintf (tmp,"Closing connection to %.80s",mb.host); + MM_LOG (tmp,(long) NIL); + } + /* flush the old stream */ + stream = mail_close (stream); + } + } + /* check if driver does not support halfopen */ + else if ((options & OP_HALFOPEN) && !(d->flags & DR_HALFOPEN)) { + fs_give ((void **) &name); + return NIL; + } + + /* instantiate new stream if not recycling */ + if (!stream) (*mailcache) (stream = (MAILSTREAM *) + memset (fs_get (sizeof (MAILSTREAM)),0, + sizeof (MAILSTREAM)),(long) 0,CH_INIT); + stream->dtb = d; /* set dispatch */ + /* set mailbox name */ + stream->mailbox = cpystr (stream->original_mailbox = name); + /* initialize stream flags */ + stream->inbox = stream->lock = NIL; + stream->debug = (options & OP_DEBUG) ? T : NIL; + stream->rdonly = (options & OP_READONLY) ? T : NIL; + stream->anonymous = (options & OP_ANONYMOUS) ? T : NIL; + stream->scache = (options & OP_SHORTCACHE) ? T : NIL; + stream->silent = (options & OP_SILENT) ? T : NIL; + stream->halfopen = (options & OP_HALFOPEN) ? T : NIL; + stream->secure = (options & OP_SECURE) ? T : NIL; + stream->tryssl = (options & OP_TRYSSL) ? T : NIL; + stream->mulnewsrc = (options & OP_MULNEWSRC) ? T : NIL; + stream->nokod = (options & OP_NOKOD) ? T : NIL; + stream->sniff = (options & OP_SNIFF) ? T : NIL; + stream->perm_seen = stream->perm_deleted = stream->perm_flagged = + stream->perm_answered = stream->perm_draft = stream->kwd_create = NIL; + stream->uid_nosticky = (d->flags & DR_NOSTICKY) ? T : NIL; + stream->uid_last = 0; /* default UID validity */ + stream->uid_validity = (unsigned long) time (0); + /* have driver open, flush if failed */ + return ((*d->open) (stream)) ? stream : mail_close (stream); +} + +/* Mail close + * Accepts: mail stream + * close options + * Returns: NIL, always + */ + +MAILSTREAM *mail_close_full (MAILSTREAM *stream,long options) +{ + int i; + if (stream) { /* make sure argument given */ + /* do the driver's close action */ + if (stream->dtb) (*stream->dtb->close) (stream,options); + stream->dtb = NIL; /* resign driver */ + if (stream->mailbox) fs_give ((void **) &stream->mailbox); + if (stream->original_mailbox) + fs_give ((void **) &stream->original_mailbox); + if (stream->snarf.name) fs_give ((void **) &stream->snarf.name); + stream->sequence++; /* invalidate sequence */ + /* flush user flags */ + for (i = 0; i < NUSERFLAGS; i++) + if (stream->user_flags[i]) fs_give ((void **) &stream->user_flags[i]); + mail_free_cache (stream); /* finally free the stream's storage */ + if (mailfreestreamsparep && stream->sparep) + (*mailfreestreamsparep) (&stream->sparep); + if (!stream->use) fs_give ((void **) &stream); + } + return NIL; +} + +/* Mail make handle + * Accepts: mail stream + * Returns: handle + * + * Handles provide a way to have multiple pointers to a stream yet allow the + * stream's owner to nuke it or recycle it. + */ + +MAILHANDLE *mail_makehandle (MAILSTREAM *stream) +{ + MAILHANDLE *handle = (MAILHANDLE *) fs_get (sizeof (MAILHANDLE)); + handle->stream = stream; /* copy stream */ + /* and its sequence */ + handle->sequence = stream->sequence; + stream->use++; /* let stream know another handle exists */ + return handle; +} + + +/* Mail release handle + * Accepts: Mail handle + */ + +void mail_free_handle (MAILHANDLE **handle) +{ + MAILSTREAM *s; + if (*handle) { /* only free if exists */ + /* resign stream, flush unreferenced zombies */ + if ((!--(s = (*handle)->stream)->use) && !s->dtb) fs_give ((void **) &s); + fs_give ((void **) handle); /* now flush the handle */ + } +} + + +/* Mail get stream handle + * Accepts: Mail handle + * Returns: mail stream or NIL if stream gone + */ + +MAILSTREAM *mail_stream (MAILHANDLE *handle) +{ + MAILSTREAM *s = handle->stream; + return (s->dtb && (handle->sequence == s->sequence)) ? s : NIL; +} + +/* Mail fetch cache element + * Accepts: mail stream + * message # to fetch + * Returns: cache element of this message + * Can also be used to create cache elements for new messages. + */ + +MESSAGECACHE *mail_elt (MAILSTREAM *stream,unsigned long msgno) +{ + if (msgno < 1 || msgno > stream->nmsgs) { + char tmp[MAILTMPLEN]; + sprintf (tmp,"Bad msgno %lu in mail_elt, nmsgs = %lu, mbx=%.80s", + msgno,stream->nmsgs,stream->mailbox ? stream->mailbox : "???"); + fatal (tmp); + } + return (MESSAGECACHE *) (*mailcache) (stream,msgno,CH_MAKEELT); +} + + +/* Mail fetch fast information + * Accepts: mail stream + * sequence + * option flags + * + * Generally, mail_fetch_structure is preferred + */ + +void mail_fetch_fast (MAILSTREAM *stream,char *sequence,long flags) +{ + /* do the driver's action */ + if (stream->dtb && stream->dtb->fast) + (*stream->dtb->fast) (stream,sequence,flags); +} + + +/* Mail fetch flags + * Accepts: mail stream + * sequence + * option flags + */ + +void mail_fetch_flags (MAILSTREAM *stream,char *sequence,long flags) +{ + /* do the driver's action */ + if (stream->dtb && stream->dtb->msgflags) + (*stream->dtb->msgflags) (stream,sequence,flags); +} + +/* Mail fetch message overview + * Accepts: mail stream + * UID sequence to fetch + * pointer to overview return function + */ + +void mail_fetch_overview (MAILSTREAM *stream,char *sequence,overview_t ofn) +{ + if (stream->dtb && mail_uid_sequence (stream,sequence) && + !(stream->dtb->overview && (*stream->dtb->overview) (stream,ofn)) && + mail_ping (stream)) + mail_fetch_overview_default (stream,ofn); +} + + +/* Mail fetch message overview using sequence numbers instead of UIDs + * Accepts: mail stream + * sequence to fetch + * pointer to overview return function + */ + +void mail_fetch_overview_sequence (MAILSTREAM *stream,char *sequence, + overview_t ofn) +{ + if (stream->dtb && mail_sequence (stream,sequence) && + !(stream->dtb->overview && (*stream->dtb->overview) (stream,ofn)) && + mail_ping (stream)) + mail_fetch_overview_default (stream,ofn); +} + + +/* Mail fetch message overview default handler + * Accepts: mail stream with sequence bits lit + * pointer to overview return function + */ + +void mail_fetch_overview_default (MAILSTREAM *stream,overview_t ofn) +{ + MESSAGECACHE *elt; + ENVELOPE *env; + OVERVIEW ov; + unsigned long i; + ov.optional.lines = 0; + ov.optional.xref = NIL; + for (i = 1; i <= stream->nmsgs; i++) + if (((elt = mail_elt (stream,i))->sequence) && + (env = mail_fetch_structure (stream,i,NIL,NIL)) && ofn) { + ov.subject = env->subject; + ov.from = env->from; + ov.date = env->date; + ov.message_id = env->message_id; + ov.references = env->references; + ov.optional.octets = elt->rfc822_size; + (*ofn) (stream,mail_uid (stream,i),&ov,i); + } +} + +/* Mail fetch message structure + * Accepts: mail stream + * message # to fetch + * pointer to return body + * option flags + * Returns: envelope of this message, body returned in body value + * + * Fetches the "fast" information as well + */ + +ENVELOPE *mail_fetch_structure (MAILSTREAM *stream,unsigned long msgno, + BODY **body,long flags) +{ + ENVELOPE **env; + BODY **b; + MESSAGECACHE *elt; + char c,*s,*hdr; + unsigned long hdrsize; + STRING bs; + /* do the driver's action if specified */ + if (stream->dtb && stream->dtb->structure) + return (*stream->dtb->structure) (stream,msgno,body,flags); + if (flags & FT_UID) { /* UID form of call */ + if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID; + else return NIL; /* must get UID/msgno map first */ + } + elt = mail_elt (stream,msgno);/* get elt for real message number */ + if (stream->scache) { /* short caching */ + if (msgno != stream->msgno){/* garbage collect if not same message */ + mail_gc (stream,GC_ENV | GC_TEXTS); + stream->msgno = msgno; /* this is the current message now */ + } + env = &stream->env; /* get pointers to envelope and body */ + b = &stream->body; + } + else { /* get pointers to elt envelope and body */ + env = &elt->private.msg.env; + b = &elt->private.msg.body; + } + + if (stream->dtb && ((body && !*b) || !*env || (*env)->incomplete)) { + mail_free_envelope (env); /* flush old envelope and body */ + mail_free_body (b); + /* see if need to fetch the whole thing */ + if (body || !elt->rfc822_size) { + s = (*stream->dtb->header) (stream,msgno,&hdrsize,flags & ~FT_INTERNAL); + /* make copy in case body fetch smashes it */ + hdr = (char *) memcpy (fs_get ((size_t) hdrsize+1),s,(size_t) hdrsize); + hdr[hdrsize] = '\0'; /* tie off header */ + (*stream->dtb->text) (stream,msgno,&bs,(flags & ~FT_INTERNAL) | FT_PEEK); + if (!elt->rfc822_size) elt->rfc822_size = hdrsize + SIZE (&bs); + if (body) /* only parse body if requested */ + rfc822_parse_msg (env,b,hdr,hdrsize,&bs,BADHOST,stream->dtb->flags); + else + rfc822_parse_msg (env,NIL,hdr,hdrsize,NIL,BADHOST,stream->dtb->flags); + fs_give ((void **) &hdr); /* flush header */ + } + else { /* can save memory doing it this way */ + hdr = (*stream->dtb->header) (stream,msgno,&hdrsize,flags | FT_INTERNAL); + if (hdrsize) { /* in case null header */ + c = hdr[hdrsize]; /* preserve what's there */ + hdr[hdrsize] = '\0'; /* tie off header */ + rfc822_parse_msg (env,NIL,hdr,hdrsize,NIL,BADHOST,stream->dtb->flags); + hdr[hdrsize] = c; /* restore in case cached data */ + } + else *env = mail_newenvelope (); + } + } + /* if need date, have date in envelope? */ + if (!elt->day && *env && (*env)->date) mail_parse_date (elt,(*env)->date); + /* sigh, fill in bogus default */ + if (!elt->day) elt->day = elt->month = 1; + if (body) *body = *b; /* return the body */ + return *env; /* return the envelope */ +} + +/* Mail mark single message (internal use only) + * Accepts: mail stream + * elt to mark + * fetch flags + */ + +static void markseen (MAILSTREAM *stream,MESSAGECACHE *elt,long flags) +{ + unsigned long i; + char sequence[20]; + MESSAGECACHE *e; + /* non-peeking and needs to set \Seen? */ + if (!(flags & FT_PEEK) && !elt->seen) { + if (stream->dtb->flagmsg){ /* driver wants per-message call? */ + elt->valid = NIL; /* do pre-alteration driver call */ + (*stream->dtb->flagmsg) (stream,elt); + /* set seen, do post-alteration driver call */ + elt->seen = elt->valid = T; + (*stream->dtb->flagmsg) (stream,elt); + } + if (stream->dtb->flag) { /* driver wants one-time call? */ + /* better safe than sorry, save seq bits */ + for (i = 1; i <= stream->nmsgs; i++) { + e = mail_elt (stream,i); + e->private.sequence = e->sequence; + } + /* call driver to set the message */ + sprintf (sequence,"%lu",elt->msgno); + (*stream->dtb->flag) (stream,sequence,"\\Seen",ST_SET); + /* restore sequence bits */ + for (i = 1; i <= stream->nmsgs; i++) { + e = mail_elt (stream,i); + e->sequence = e->private.sequence; + } + } + /* notify mail program of flag change */ + MM_FLAGS (stream,elt->msgno); + } +} + +/* Mail fetch message + * Accepts: mail stream + * message # to fetch + * pointer to returned length + * flags + * Returns: message text + */ + +char *mail_fetch_message (MAILSTREAM *stream,unsigned long msgno, + unsigned long *len,long flags) +{ + GETS_DATA md; + SIZEDTEXT *t; + STRING bs; + MESSAGECACHE *elt; + char *s,*u; + unsigned long i,j; + if (len) *len = 0; /* default return size */ + if (flags & FT_UID) { /* UID form of call */ + if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID; + else return ""; /* must get UID/msgno map first */ + } + /* initialize message data identifier */ + INIT_GETS (md,stream,msgno,"",0,0); + /* is data already cached? */ + if ((t = &(elt = mail_elt (stream,msgno))->private.msg.full.text)->data) { + markseen (stream,elt,flags);/* mark message seen */ + return mail_fetch_text_return (&md,t,len); + } + if (!stream->dtb) return ""; /* not in cache, must have live driver */ + if (stream->dtb->msgdata) return + ((*stream->dtb->msgdata) (stream,msgno,"",0,0,NIL,flags) && t->data) ? + mail_fetch_text_return (&md,t,len) : ""; + /* ugh, have to do this the crufty way */ + u = mail_fetch_header (stream,msgno,NIL,NIL,&i,flags); + /* copy in case text method stomps on it */ + s = (char *) memcpy (fs_get ((size_t) i),u,(size_t) i); + if ((*stream->dtb->text) (stream,msgno,&bs,flags)) { + t = &stream->text; /* build combined copy */ + if (t->data) fs_give ((void **) &t->data); + t->data = (unsigned char *) fs_get ((t->size = i + SIZE (&bs)) + 1); + if (!elt->rfc822_size) elt->rfc822_size = t->size; + else if (elt->rfc822_size != t->size) { + char tmp[MAILTMPLEN]; + sprintf (tmp,"Calculated RFC822.SIZE (%lu) != reported size (%lu)", + t->size,elt->rfc822_size); + mm_log (tmp,WARN); /* bug trap */ + } + memcpy (t->data,s,(size_t) i); + for (u = (char *) t->data + i, j = SIZE (&bs); j;) { + memcpy (u,bs.curpos,bs.cursize); + u += bs.cursize; /* update text */ + j -= bs.cursize; + bs.curpos += (bs.cursize -1); + bs.cursize = 0; + (*bs.dtb->next) (&bs); /* advance to next buffer's worth */ + } + *u = '\0'; /* tie off data */ + u = mail_fetch_text_return (&md,t,len); + } + else u = ""; + fs_give ((void **) &s); /* finished with copy of header */ + return u; +} + +/* Mail fetch message header + * Accepts: mail stream + * message # to fetch + * MIME section specifier (#.#.#...#) + * list of lines to fetch + * pointer to returned length + * flags + * Returns: message header in RFC822 format + * + * Note: never calls a mailgets routine + */ + +char *mail_fetch_header (MAILSTREAM *stream,unsigned long msgno,char *section, + STRINGLIST *lines,unsigned long *len,long flags) +{ + STRING bs; + BODY *b = NIL; + SIZEDTEXT *t = NIL,rt; + MESSAGE *m = NIL; + MESSAGECACHE *elt; + char tmp[MAILTMPLEN]; + if (len) *len = 0; /* default return size */ + if (section && (strlen (section) > (MAILTMPLEN - 20))) return ""; + if (flags & FT_UID) { /* UID form of call */ + if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID; + else return ""; /* must get UID/msgno map first */ + } + elt = mail_elt (stream,msgno);/* get cache data */ + if (section && *section) { /* nested body header wanted? */ + if (!((b = mail_body (stream,msgno,section)) && + (b->type == TYPEMESSAGE) && !strcmp (b->subtype,"RFC822"))) + return ""; /* lose if no body or not MESSAGE/RFC822 */ + m = b->nested.msg; /* point to nested message */ + } + /* else top-level message header wanted */ + else m = &elt->private.msg; + if (m->header.text.data && mail_match_lines (lines,m->lines,flags)) { + if (lines) textcpy (t = &stream->text,&m->header.text); + else t = &m->header.text; /* in cache, and cache is valid */ + markseen (stream,elt,flags);/* mark message seen */ + } + + else if (stream->dtb) { /* not in cache, has live driver? */ + if (stream->dtb->msgdata) { /* has driver section fetch? */ + /* build driver section specifier */ + if (section && *section) sprintf (tmp,"%s.HEADER",section); + else strcpy (tmp,"HEADER"); + if ((*stream->dtb->msgdata) (stream,msgno,tmp,0,0,lines,flags)) { + t = &m->header.text; /* fetch data */ + /* don't need to postprocess lines */ + if (m->lines) lines = NIL; + else if (lines) textcpy (t = &stream->text,&m->header.text); + } + } + else if (b) { /* nested body wanted? */ + if (stream->private.search.text) { + rt.data = (unsigned char *) stream->private.search.text + + b->nested.msg->header.offset; + rt.size = b->nested.msg->header.text.size; + t = &rt; + } + else if ((*stream->dtb->text) (stream,msgno,&bs,flags & ~FT_INTERNAL)) { + if ((bs.dtb->next == mail_string_next) && !lines) { + rt.data = (unsigned char *) bs.curpos + b->nested.msg->header.offset; + rt.size = b->nested.msg->header.text.size; + if (stream->private.search.string) + stream->private.search.text = bs.curpos; + t = &rt; /* special hack to avoid extra copy */ + } + else textcpyoffstring (t = &stream->text,&bs, + b->nested.msg->header.offset, + b->nested.msg->header.text.size); + } + } + else { /* top-level header fetch */ + /* mark message seen */ + markseen (stream,elt,flags); + if (rt.data = (unsigned char *) + (*stream->dtb->header) (stream,msgno,&rt.size,flags)) { + /* make a safe copy if need to filter */ + if (lines) textcpy (t = &stream->text,&rt); + else t = &rt; /* top level header */ + } + } + } + if (!t || !t->data) return "";/* error if no string */ + /* filter headers if requested */ + if (lines) t->size = mail_filter ((char *) t->data,t->size,lines,flags); + if (len) *len = t->size; /* return size if requested */ + return (char *) t->data; /* and text */ +} + +/* Mail fetch message text + * Accepts: mail stream + * message # to fetch + * MIME section specifier (#.#.#...#) + * pointer to returned length + * flags + * Returns: message text + */ + +char *mail_fetch_text (MAILSTREAM *stream,unsigned long msgno,char *section, + unsigned long *len,long flags) +{ + GETS_DATA md; + PARTTEXT *p; + STRING bs; + MESSAGECACHE *elt; + BODY *b = NIL; + char tmp[MAILTMPLEN]; + unsigned long i; + if (len) *len = 0; /* default return size */ + memset (&stream->private.string,NIL,sizeof (STRING)); + if (section && (strlen (section) > (MAILTMPLEN - 20))) return ""; + if (flags & FT_UID) { /* UID form of call */ + if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID; + else return ""; /* must get UID/msgno map first */ + } + elt = mail_elt (stream,msgno);/* get cache data */ + if (section && *section) { /* nested body text wanted? */ + if (!((b = mail_body (stream,msgno,section)) && + (b->type == TYPEMESSAGE) && !strcmp (b->subtype,"RFC822"))) + return ""; /* lose if no body or not MESSAGE/RFC822 */ + p = &b->nested.msg->text; /* point at nested message */ + /* build IMAP-format section specifier */ + sprintf (tmp,"%s.TEXT",section); + flags &= ~FT_INTERNAL; /* can't win with this set */ + } + else { /* top-level message text wanted */ + p = &elt->private.msg.text; + strcpy (tmp,"TEXT"); + } + /* initialize message data identifier */ + INIT_GETS (md,stream,msgno,section,0,0); + if (p->text.data) { /* is data already cached? */ + markseen (stream,elt,flags);/* mark message seen */ + return mail_fetch_text_return (&md,&p->text,len); + } + if (!stream->dtb) return ""; /* not in cache, must have live driver */ + if (stream->dtb->msgdata) return + ((*stream->dtb->msgdata) (stream,msgno,tmp,0,0,NIL,flags) && p->text.data)? + mail_fetch_text_return (&md,&p->text,len) : ""; + if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) return ""; + if (section && *section) { /* nested is more complex */ + SETPOS (&bs,p->offset); + i = p->text.size; /* just want this much */ + } + else i = SIZE (&bs); /* want entire text */ + return mail_fetch_string_return (&md,&bs,i,len,flags); +} + +/* Mail fetch message body part MIME headers + * Accepts: mail stream + * message # to fetch + * MIME section specifier (#.#.#...#) + * pointer to returned length + * flags + * Returns: message text + */ + +char *mail_fetch_mime (MAILSTREAM *stream,unsigned long msgno,char *section, + unsigned long *len,long flags) +{ + PARTTEXT *p; + STRING bs; + BODY *b; + char tmp[MAILTMPLEN]; + if (len) *len = 0; /* default return size */ + if (section && (strlen (section) > (MAILTMPLEN - 20))) return ""; + if (flags & FT_UID) { /* UID form of call */ + if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID; + else return ""; /* must get UID/msgno map first */ + } + flags &= ~FT_INTERNAL; /* can't win with this set */ + if (!(section && *section && (b = mail_body (stream,msgno,section)))) + return ""; /* not valid section */ + /* in cache? */ + if ((p = &b->mime)->text.data) { + /* mark message seen */ + markseen (stream,mail_elt (stream,msgno),flags); + if (len) *len = p->text.size; + return (char *) p->text.data; + } + if (!stream->dtb) return ""; /* not in cache, must have live driver */ + if (stream->dtb->msgdata) { /* has driver fetch? */ + /* build driver section specifier */ + sprintf (tmp,"%s.MIME",section); + if ((*stream->dtb->msgdata) (stream,msgno,tmp,0,0,NIL,flags) && + p->text.data) { + if (len) *len = p->text.size; + return (char *) p->text.data; + } + else return ""; + } + if (len) *len = b->mime.text.size; + if (!b->mime.text.size) { /* empty MIME header -- mark seen anyway */ + markseen (stream,mail_elt (stream,msgno),flags); + return ""; + } + /* have to get it from offset */ + if (stream->private.search.text) + return stream->private.search.text + b->mime.offset; + if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) { + if (len) *len = 0; + return ""; + } + if (bs.dtb->next == mail_string_next) { + if (stream->private.search.string) stream->private.search.text = bs.curpos; + return bs.curpos + b->mime.offset; + } + return textcpyoffstring (&stream->text,&bs,b->mime.offset,b->mime.text.size); +} + +/* Mail fetch message body part + * Accepts: mail stream + * message # to fetch + * MIME section specifier (#.#.#...#) + * pointer to returned length + * flags + * Returns: message body + */ + +char *mail_fetch_body (MAILSTREAM *stream,unsigned long msgno,char *section, + unsigned long *len,long flags) +{ + GETS_DATA md; + PARTTEXT *p; + STRING bs; + BODY *b; + SIZEDTEXT *t; + char *s,tmp[MAILTMPLEN]; + memset (&stream->private.string,NIL,sizeof (STRING)); + if (!(section && *section)) /* top-level text wanted? */ + return mail_fetch_message (stream,msgno,len,flags); + else if (strlen (section) > (MAILTMPLEN - 20)) return ""; + flags &= ~FT_INTERNAL; /* can't win with this set */ + /* initialize message data identifier */ + INIT_GETS (md,stream,msgno,section,0,0); + /* kludge for old section 0 header */ + if (!strcmp (s = strcpy (tmp,section),"0") || + ((s = strstr (tmp,".0")) && !s[2])) { + SIZEDTEXT ht; + *s = '\0'; /* tie off section */ + /* this silly way so it does mailgets */ + ht.data = (unsigned char *) mail_fetch_header (stream,msgno, + tmp[0] ? tmp : NIL,NIL, + &ht.size,flags); + /* may have UIDs here */ + md.flags = (flags & FT_UID) ? MG_UID : NIL; + return mail_fetch_text_return (&md,&ht,len); + } + if (len) *len = 0; /* default return size */ + if (flags & FT_UID) { /* UID form of call */ + if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID; + else return ""; /* must get UID/msgno map first */ + } + /* must have body */ + if (!(b = mail_body (stream,msgno,section))) return ""; + /* have cached text? */ + if ((t = &(p = &b->contents)->text)->data) { + /* mark message seen */ + markseen (stream,mail_elt (stream,msgno),flags); + return mail_fetch_text_return (&md,t,len); + } + if (!stream->dtb) return ""; /* not in cache, must have live driver */ + if (stream->dtb->msgdata) return + ((*stream->dtb->msgdata)(stream,msgno,section,0,0,NIL,flags) && t->data) ? + mail_fetch_text_return (&md,t,len) : ""; + if (len) *len = t->size; + if (!t->size) { /* empty body part -- mark seen anyway */ + markseen (stream,mail_elt (stream,msgno),flags); + return ""; + } + /* copy body from stringstruct offset */ + if (stream->private.search.text) + return stream->private.search.text + p->offset; + if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) { + if (len) *len = 0; + return ""; + } + if (bs.dtb->next == mail_string_next) { + if (stream->private.search.string) stream->private.search.text = bs.curpos; + return bs.curpos + p->offset; + } + SETPOS (&bs,p->offset); + return mail_fetch_string_return (&md,&bs,t->size,len,flags); +} + +/* Mail fetch partial message text + * Accepts: mail stream + * message # to fetch + * MIME section specifier (#.#.#...#) + * offset of first designed byte or 0 to start at beginning + * maximum number of bytes or 0 for all bytes + * flags + * Returns: T if successful, else NIL + */ + +long mail_partial_text (MAILSTREAM *stream,unsigned long msgno,char *section, + unsigned long first,unsigned long last,long flags) +{ + GETS_DATA md; + PARTTEXT *p = NIL; + MESSAGECACHE *elt; + STRING bs; + BODY *b; + char tmp[MAILTMPLEN]; + unsigned long i; + if (!mailgets) fatal ("mail_partial_text() called without a mailgets!"); + if (section && (strlen (section) > (MAILTMPLEN - 20))) return NIL; + if (flags & FT_UID) { /* UID form of call */ + if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID; + else return NIL; /* must get UID/msgno map first */ + } + elt = mail_elt (stream,msgno);/* get cache data */ + flags &= ~FT_INTERNAL; /* bogus if this is set */ + if (section && *section) { /* nested body text wanted? */ + if (!((b = mail_body (stream,msgno,section)) && + (b->type == TYPEMESSAGE) && !strcmp (b->subtype,"RFC822"))) + return NIL; /* lose if no body or not MESSAGE/RFC822 */ + p = &b->nested.msg->text; /* point at nested message */ + /* build IMAP-format section specifier */ + sprintf (tmp,"%s.TEXT",section); + } + else { /* else top-level message text wanted */ + p = &elt->private.msg.text; + strcpy (tmp,"TEXT"); + } + + /* initialize message data identifier */ + INIT_GETS (md,stream,msgno,tmp,first,last); + if (p->text.data) { /* is data already cached? */ + INIT (&bs,mail_string,p->text.data,i = p->text.size); + markseen (stream,elt,flags);/* mark message seen */ + } + else { /* else get data from driver */ + if (!stream->dtb) return NIL; + if (stream->dtb->msgdata) /* driver will handle this */ + return (*stream->dtb->msgdata) (stream,msgno,tmp,first,last,NIL,flags); + if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) return NIL; + if (section && *section) { /* nexted if more complex */ + SETPOS (&bs,p->offset); /* offset stringstruct to data */ + i = p->text.size; /* maximum size of data */ + } + else i = SIZE (&bs); /* just want this much */ + } + if (i <= first) i = first = 0;/* first byte is beyond end of text */ + /* truncate as needed */ + else { /* offset and truncate */ + SETPOS (&bs,first + GETPOS (&bs)); + i -= first; /* reduced size */ + if (last && (i > last)) i = last; + } + /* do the mailgets thing */ + (*mailgets) (mail_read,&bs,i,&md); + return T; /* success */ +} + +/* Mail fetch partial message body part + * Accepts: mail stream + * message # to fetch + * MIME section specifier (#.#.#...#) + * offset of first designed byte or 0 to start at beginning + * maximum number of bytes or 0 for all bytes + * flags + * Returns: T if successful, else NIL + */ + +long mail_partial_body (MAILSTREAM *stream,unsigned long msgno,char *section, + unsigned long first,unsigned long last,long flags) +{ + GETS_DATA md; + PARTTEXT *p; + STRING bs; + BODY *b; + SIZEDTEXT *t; + unsigned long i; + if (!(section && *section)) /* top-level text wanted? */ + return mail_partial_text (stream,msgno,NIL,first,last,flags); + if (!mailgets) fatal ("mail_partial_body() called without a mailgets!"); + if (flags & FT_UID) { /* UID form of call */ + if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID; + else return NIL; /* must get UID/msgno map first */ + } + /* must have body */ + if (!(b = mail_body (stream,msgno,section))) return NIL; + flags &= ~FT_INTERNAL; /* bogus if this is set */ + + /* initialize message data identifier */ + INIT_GETS (md,stream,msgno,section,first,last); + /* have cached text? */ + if ((t = &(p = &b->contents)->text)->data) { + /* mark message seen */ + markseen (stream,mail_elt (stream,msgno),flags); + INIT (&bs,mail_string,t->data,i = t->size); + } + else { /* else get data from driver */ + if (!stream->dtb) return NIL; + if (stream->dtb->msgdata) /* driver will handle this */ + return (*stream->dtb->msgdata) (stream,msgno,section,first,last,NIL, + flags); + if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) return NIL; + if (section && *section) { /* nexted if more complex */ + SETPOS (&bs,p->offset); /* offset stringstruct to data */ + i = t->size; /* maximum size of data */ + } + else i = SIZE (&bs); /* just want this much */ + } + if (i <= first) i = first = 0;/* first byte is beyond end of text */ + else { /* offset and truncate */ + SETPOS (&bs,first + GETPOS (&bs)); + i -= first; /* reduced size */ + if (last && (i > last)) i = last; + } + /* do the mailgets thing */ + (*mailgets) (mail_read,&bs,i,&md); + return T; /* success */ +} + +/* Mail return message text + * Accepts: identifier data + * sized text + * pointer to returned length + * Returns: text + */ + +char *mail_fetch_text_return (GETS_DATA *md,SIZEDTEXT *t,unsigned long *len) +{ + STRING bs; + if (len) *len = t->size; /* return size */ + if (t->size && mailgets) { /* have to do the mailgets thing? */ + /* silly but do it anyway for consistency */ + INIT (&bs,mail_string,t->data,t->size); + return (*mailgets) (mail_read,&bs,t->size,md); + } + return t->size ? (char *) t->data : ""; +} + + +/* Mail return message string + * Accepts: identifier data + * stringstruct + * text length + * pointer to returned length + * flags + * Returns: text, or NIL if stringstruct returned + */ + +char *mail_fetch_string_return (GETS_DATA *md,STRING *bs,unsigned long i, + unsigned long *len,long flags) +{ + char *ret = NIL; + if (len) *len = i; /* return size */ + /* return stringstruct hack */ + if (flags & FT_RETURNSTRINGSTRUCT) { + memcpy (&md->stream->private.string,bs,sizeof (STRING)); + SETPOS (&md->stream->private.string,GETPOS (&md->stream->private.string)); + } + /* have to do the mailgets thing? */ + else if (mailgets) ret = (*mailgets) (mail_read,bs,i,md); + /* special hack to avoid extra copy */ + else if (bs->dtb->next == mail_string_next) ret = bs->curpos; + /* make string copy in memory */ + else ret = textcpyoffstring (&md->stream->text,bs,GETPOS (bs),i); + return ret; +} + +/* Read data from stringstruct + * Accepts: stringstruct + * size of data to read + * buffer to read into + * Returns: T, always, stringstruct updated + */ + +long mail_read (void *stream,unsigned long size,char *buffer) +{ + unsigned long i; + STRING *s = (STRING *) stream; + while (size) { /* until satisfied */ + memcpy (buffer,s->curpos,i = min (s->cursize,size)); + buffer += i; /* update buffer */ + size -= i; /* note that we read this much */ + s->curpos += --i; /* advance that many spaces minus 1 */ + s->cursize -= i; + SNX (s); /* now use SNX to advance the last byte */ + } + return T; +} + +/* Mail fetch UID + * Accepts: mail stream + * message number + * Returns: UID or zero if dead stream + */ + +unsigned long mail_uid (MAILSTREAM *stream,unsigned long msgno) +{ + unsigned long uid = mail_elt (stream,msgno)->private.uid; + return uid ? uid : + (stream->dtb && stream->dtb->uid) ? (*stream->dtb->uid) (stream,msgno) : 0; +} + + +/* Mail fetch msgno from UID + * Accepts: mail stream + * UID + * Returns: msgno or zero if failed + */ + +unsigned long mail_msgno (MAILSTREAM *stream,unsigned long uid) +{ + unsigned long msgno,delta,first,firstuid,last,lastuid,middle,miduid; + if (stream->dtb) { /* active stream? */ + if (stream->dtb->msgno) /* direct way */ + return (*stream->dtb->msgno) (stream,uid); + else if (stream->dtb->uid) {/* indirect way */ + /* Placeholder for now, since currently there are no drivers which + * have a uid method but not a msgno method + */ + for (msgno = 1; msgno <= stream->nmsgs; msgno++) + if ((*stream->dtb->uid) (stream,msgno) == uid) return msgno; + } + /* binary search since have full map */ + else for (first = 1,last = stream->nmsgs, delta = (first <= last) ? 1 : 0; + delta && + (uid >= (firstuid = mail_elt (stream,first)->private.uid)) && + (uid <= (lastuid = mail_elt (stream,last)->private.uid));) { + /* done if match at an endpoint */ + if (uid == firstuid) return first; + if (uid == lastuid) return last; + /* have anything between endpoints? */ + if (delta = ((last - first) / 2)) { + if ((miduid = mail_elt (stream,middle = first + delta)->private.uid) + == uid) + return middle; /* found match in middle */ + else if (uid < miduid) last = middle - 1; + else first = middle + 1; + } + } + } + else { /* dead stream, do linear search for UID */ + for (msgno = 1; msgno <= stream->nmsgs; msgno++) + if (mail_elt (stream,msgno)->private.uid == uid) return msgno; + } + return 0; /* didn't find the UID anywhere */ +} + +/* Mail fetch From string for menu + * Accepts: destination string + * mail stream + * message # to fetch + * desired string length + * Returns: string of requested length + */ + +void mail_fetchfrom (char *s,MAILSTREAM *stream,unsigned long msgno, + long length) +{ + char *t; + char tmp[MAILTMPLEN]; + ENVELOPE *env = mail_fetchenvelope (stream,msgno); + ADDRESS *adr = env ? env->from : NIL; + memset (s,' ',(size_t)length);/* fill it with spaces */ + s[length] = '\0'; /* tie off with null */ + /* get first from address from envelope */ + while (adr && !adr->host) adr = adr->next; + if (adr) { /* if a personal name exists use it */ + if (!(t = adr->personal)) + sprintf (t = tmp,"%.256s@%.256s",adr->mailbox,adr->host); + memcpy (s,t,(size_t) min (length,(long) strlen (t))); + } +} + + +/* Mail fetch Subject string for menu + * Accepts: destination string + * mail stream + * message # to fetch + * desired string length + * Returns: string of no more than requested length + */ + +void mail_fetchsubject (char *s,MAILSTREAM *stream,unsigned long msgno, + long length) +{ + ENVELOPE *env = mail_fetchenvelope (stream,msgno); + memset (s,'\0',(size_t) length+1); + /* copy subject from envelope */ + if (env && env->subject) strncpy (s,env->subject,(size_t) length); + else *s = ' '; /* if no subject then just a space */ +} + +/* Mail modify flags + * Accepts: mail stream + * sequence + * flag(s) + * option flags + */ + +void mail_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags) +{ + MESSAGECACHE *elt; + unsigned long i,uf; + long f; + short nf; + if (!stream->dtb) return; /* no-op if no stream */ + if ((stream->dtb->flagmsg || !stream->dtb->flag) && + ((flags & ST_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence)) && + ((f = mail_parse_flags (stream,flag,&uf)) || uf)) + for (i = 1,nf = (flags & ST_SET) ? T : NIL; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->sequence) { + struct { /* old flags */ + unsigned int valid : 1; + unsigned int seen : 1; + unsigned int deleted : 1; + unsigned int flagged : 1; + unsigned int answered : 1; + unsigned int draft : 1; + unsigned long user_flags; + } old; + old.valid = elt->valid; old.seen = elt->seen; + old.deleted = elt->deleted; old.flagged = elt->flagged; + old.answered = elt->answered; old.draft = elt->draft; + old.user_flags = elt->user_flags; + elt->valid = NIL; /* prepare for flag alteration */ + if (stream->dtb->flagmsg) (*stream->dtb->flagmsg) (stream,elt); + if (f&fSEEN) elt->seen = nf; + if (f&fDELETED) elt->deleted = nf; + if (f&fFLAGGED) elt->flagged = nf; + if (f&fANSWERED) elt->answered = nf; + if (f&fDRAFT) elt->draft = nf; + /* user flags */ + if (flags & ST_SET) elt->user_flags |= uf; + else elt->user_flags &= ~uf; + elt->valid = T; /* flags now altered */ + if ((old.valid != elt->valid) || (old.seen != elt->seen) || + (old.deleted != elt->deleted) || (old.flagged != elt->flagged) || + (old.answered != elt->answered) || (old.draft != elt->draft) || + (old.user_flags != elt->user_flags)) + MM_FLAGS (stream,elt->msgno); + if (stream->dtb->flagmsg) (*stream->dtb->flagmsg) (stream,elt); + } + /* call driver once */ + if (stream->dtb->flag) (*stream->dtb->flag) (stream,sequence,flag,flags); +} + +/* Mail search for messages + * Accepts: mail stream + * character set + * search program + * option flags + * Returns: T if successful, NIL if dead stream, NIL searchpgm or bad charset + */ + +long mail_search_full (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm, + long flags) +{ + unsigned long i; + long ret = NIL; + if (!(flags & SE_RETAIN)) /* clear search vector unless retaining */ + for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = NIL; + if (pgm && stream->dtb) /* must have a search program and driver */ + ret = (*(stream->dtb->search ? stream->dtb->search : mail_search_default)) + (stream,charset,pgm,flags); + /* flush search program if requested */ + if (flags & SE_FREE) mail_free_searchpgm (&pgm); + return ret; +} + + +/* Mail search for messages default handler + * Accepts: mail stream + * character set + * search program + * option flags + * Returns: T if successful, NIL if bad charset + */ + +long mail_search_default (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm, + long flags) +{ + unsigned long i; + char *msg; + /* make sure that charset is good */ + if (msg = utf8_badcharset (charset)) { + MM_LOG (msg,ERROR); /* output error */ + fs_give ((void **) &msg); + return NIL; + } + utf8_searchpgm (pgm,charset); + for (i = 1; i <= stream->nmsgs; ++i) + if (mail_search_msg (stream,i,NIL,pgm)) { + if (flags & SE_UID) mm_searched (stream,mail_uid (stream,i)); + else { /* mark as searched, notify mail program */ + mail_elt (stream,i)->searched = T; + if (!stream->silent) mm_searched (stream,i); + } + } + return LONGT; /* search completed */ +} + +/* Mail ping mailbox + * Accepts: mail stream + * Returns: stream if still open else NIL + */ + +long mail_ping (MAILSTREAM *stream) +{ + unsigned long i,n,uf,len; + char *s,*f,tmp[MAILTMPLEN],flags[MAILTMPLEN]; + MAILSTREAM *snarf; + MESSAGECACHE *elt; + STRING bs; + long ret; + /* do driver action */ + if ((ret = ((stream && stream->dtb) ? (stream->dtb->ping) (stream) : NIL)) && + stream->snarf.name && /* time to snarf? */ + /* prohibit faster than once/min */ + (time (0) > (time_t) (stream->snarf.time + min(60,mailsnarfinterval))) && + (snarf = mail_open (NIL,stream->snarf.name, + stream->snarf.options | OP_SILENT))) { + if ((n = snarf->nmsgs) && /* yes, have messages to snarf? */ + mail_search_full (snarf,NIL,mail_criteria ("UNDELETED"),SE_FREE)) { + for (i = 1; ret && (i <= n); i++) /* for each message */ + if ((elt = mail_elt (snarf,i))->searched && + (s = mail_fetch_message (snarf,i,&len,FT_PEEK)) && len) { + INIT (&bs,mail_string,s,len); + if (mailsnarfpreserve) { + /* yes, make sure have fast data */ + if (!elt->valid || !elt->day) { + sprintf (tmp,"%lu",n); + mail_fetch_fast (snarf,tmp,NIL); + } + /* initialize flag string */ + memset (flags,0,MAILTMPLEN); + /* output system flags except \Deleted */ + if (elt->seen) strcat (flags," \\Seen"); + if (elt->flagged) strcat (flags," \\Flagged"); + if (elt->answered) strcat (flags," \\Answered"); + if (elt->draft) strcat (flags," \\Draft"); + /* any user flags? */ + for (uf = elt->user_flags,s = flags + strlen (flags); + uf && (f = stream->user_flags[find_rightmost_bit (&uf)]) && + ((MAILTMPLEN - (s - tmp)) > (long) (2 + strlen (f))); + s += strlen (s)) sprintf (s," %s",f); + ret = mail_append_full (stream,stream->mailbox,flags + 1, + mail_date (tmp,elt),&bs); + } + else ret = mail_append (stream,stream->mailbox,&bs); + + if (ret) { /* did snarf succeed? */ + /* driver has per-message (or no) flag call */ + if (snarf->dtb->flagmsg || !snarf->dtb->flag) { + elt->valid = NIL; /* prepare for flag alteration */ + if (snarf->dtb->flagmsg) (*snarf->dtb->flagmsg) (snarf,elt); + /* flags now altered */ + elt->deleted = elt->seen = elt->valid = T; + if (snarf->dtb->flagmsg) (*snarf->dtb->flagmsg) (snarf,elt); + } + /* driver has one-time flag call */ + if (snarf->dtb->flag) { + sprintf (tmp,"%lu",i); + (*snarf->dtb->flag) (snarf,tmp,"\\Deleted \\Seen",ST_SET); + } + } + else { /* copy failed */ + sprintf (tmp,"Unable to move message %lu from %s mailbox", + i,snarf->dtb->name); + mm_log (tmp,WARN); + } + } + } + /* expunge the messages */ + mail_close_full (snarf,n ? CL_EXPUNGE : NIL); + stream->snarf.time = (unsigned long) time (0); + /* Even if the snarf failed, we don't want to return NIL if the stream + * is still alive. Or at least that's what we currently think. + */ + /* redo the driver's action */ + ret = stream->dtb ? (*stream->dtb->ping) (stream) : NIL; + } + return ret; +} + +/* Mail check mailbox + * Accepts: mail stream + */ + +void mail_check (MAILSTREAM *stream) +{ + /* do the driver's action */ + if (stream->dtb) (*stream->dtb->check) (stream); +} + + +/* Mail expunge mailbox + * Accepts: mail stream + * sequence to expunge if non-NIL + * expunge options + * Returns: T on success, NIL on failure + */ + +long mail_expunge_full (MAILSTREAM *stream,char *sequence,long options) +{ + /* do the driver's action */ + return stream->dtb ? (*stream->dtb->expunge) (stream,sequence,options) : NIL; +} + + +/* Mail copy message(s) + * Accepts: mail stream + * sequence + * destination mailbox + * flags + */ + +long mail_copy_full (MAILSTREAM *stream,char *sequence,char *mailbox, + long options) +{ + return stream->dtb ? + SAFE_COPY (stream->dtb,stream,sequence,mailbox,options) : NIL; +} + +/* Append data package to use for old single-message mail_append() interface */ + +typedef struct mail_append_package { + char *flags; /* initial flags */ + char *date; /* message internal date */ + STRING *message; /* stringstruct of message */ +} APPENDPACKAGE; + + +/* Single append message string + * Accepts: mail stream + * package pointer (cast as a void *) + * pointer to return initial flags + * pointer to return message internal date + * pointer to return stringstruct of message to append + * Returns: T, always + */ + +static long mail_append_single (MAILSTREAM *stream,void *data,char **flags, + char **date,STRING **message) +{ + APPENDPACKAGE *ap = (APPENDPACKAGE *) data; + *flags = ap->flags; /* get desired data from the package */ + *date = ap->date; + *message = ap->message; + ap->message = NIL; /* so next callback puts a stop to it */ + return LONGT; /* always return success */ +} + + +/* Mail append message string + * Accepts: mail stream + * destination mailbox + * initial flags + * message internal date + * stringstruct of message to append + * Returns: T on success, NIL on failure + */ + +long mail_append_full (MAILSTREAM *stream,char *mailbox,char *flags,char *date, + STRING *message) +{ + APPENDPACKAGE ap; + ap.flags = flags; /* load append package */ + ap.date = date; + ap.message = message; + return mail_append_multiple (stream,mailbox,mail_append_single,(void *) &ap); +} + +/* Mail append message(s) + * Accepts: mail stream + * destination mailbox + * append data callback + * arbitrary data for callback use + * Returns: T on success, NIL on failure + */ + +long mail_append_multiple (MAILSTREAM *stream,char *mailbox,append_t af, + void *data) +{ + char *s,tmp[MAILTMPLEN]; + DRIVER *d = NIL; + long ret = NIL; + /* never allow names with newlines */ + if (strpbrk (mailbox,"\015\012")) + MM_LOG ("Can't append to mailbox with such a name",ERROR); + else if (strlen (mailbox) >= + (NETMAXHOST+(NETMAXUSER*2)+NETMAXMBX+NETMAXSRV+50)) { + sprintf (tmp,"Can't append %.80s: %s",mailbox,(*mailbox == '{') ? + "invalid remote specification" : "no such mailbox"); + MM_LOG (tmp,ERROR); + } + /* special driver hack? */ + else if (!strncmp (lcase (strcpy (tmp,mailbox)),"#driver.",8)) { + /* yes, tie off name at likely delimiter */ + if (!(s = strpbrk (tmp+8,"/\\:"))) { + sprintf (tmp,"Can't append to mailbox %.80s: bad driver syntax",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + } + *s++ = '\0'; /* tie off at delimiter */ + if (!(d = (DRIVER *) mail_parameters (NIL,GET_DRIVER,tmp+8))) { + sprintf (tmp,"Can't append to mailbox %.80s: unknown driver",mailbox); + MM_LOG (tmp,ERROR); + } + else ret = SAFE_APPEND (d,stream,mailbox + (s - tmp),af,data); + } + else if (d = mail_valid (stream,mailbox,NIL)) + ret = SAFE_APPEND (d,stream,mailbox,af,data); + /* No driver, try for TRYCREATE if no stream. Note that we use the + * createProto here, not the appendProto, since the dummy driver already + * took care of the appendProto case. Otherwise, if appendProto is set to + * NIL, we won't get a TRYCREATE. + */ + else if (!stream && (stream = default_proto (NIL)) && stream->dtb && + SAFE_APPEND (stream->dtb,stream,mailbox,af,data)) + /* timing race? */ + MM_NOTIFY (stream,"Append validity confusion",WARN); + /* generate error message */ + else mail_valid (stream,mailbox,"append to mailbox"); + return ret; +} + +/* Mail garbage collect stream + * Accepts: mail stream + * garbage collection flags + */ + +void mail_gc (MAILSTREAM *stream,long gcflags) +{ + MESSAGECACHE *elt; + unsigned long i; + /* do the driver's action first */ + if (stream->dtb && stream->dtb->gc) (*stream->dtb->gc) (stream,gcflags); + stream->msgno = 0; /* nothing cached now */ + if (gcflags & GC_ENV) { /* garbage collect envelopes? */ + if (stream->env) mail_free_envelope (&stream->env); + if (stream->body) mail_free_body (&stream->body); + } + if (gcflags & GC_TEXTS) { /* free texts */ + if (stream->text.data) fs_give ((void **) &stream->text.data); + stream->text.size = 0; + } + /* garbage collect per-message stuff */ + for (i = 1; i <= stream->nmsgs; i++) + if (elt = (MESSAGECACHE *) (*mailcache) (stream,i,CH_ELT)) + mail_gc_msg (&elt->private.msg,gcflags); +} + + +/* Mail garbage collect message + * Accepts: message structure + * garbage collection flags + */ + +void mail_gc_msg (MESSAGE *msg,long gcflags) +{ + if (gcflags & GC_ENV) { /* garbage collect envelopes? */ + mail_free_envelope (&msg->env); + mail_free_body (&msg->body); + } + if (gcflags & GC_TEXTS) { /* garbage collect texts */ + if (msg->full.text.data) fs_give ((void **) &msg->full.text.data); + if (msg->header.text.data) { + mail_free_stringlist (&msg->lines); + fs_give ((void **) &msg->header.text.data); + } + if (msg->text.text.data) fs_give ((void **) &msg->text.text.data); + /* now GC all body components */ + if (msg->body) mail_gc_body (msg->body); + } +} + +/* Mail garbage collect texts in BODY structure + * Accepts: BODY structure + */ + +void mail_gc_body (BODY *body) +{ + PART *part; + switch (body->type) { /* free contents */ + case TYPEMULTIPART: /* multiple part */ + for (part = body->nested.part; part; part = part->next) + mail_gc_body (&part->body); + break; + case TYPEMESSAGE: /* encapsulated message */ + if (body->subtype && !strcmp (body->subtype,"RFC822")) { + mail_free_stringlist (&body->nested.msg->lines); + mail_gc_msg (body->nested.msg,GC_TEXTS); + } + break; + default: + break; + } + if (body->mime.text.data) fs_give ((void **) &body->mime.text.data); + if (body->contents.text.data) fs_give ((void **) &body->contents.text.data); +} + +/* Mail get body part + * Accepts: mail stream + * message number + * section specifier + * Returns: pointer to body + */ + +BODY *mail_body (MAILSTREAM *stream,unsigned long msgno,unsigned char *section) +{ + BODY *b = NIL; + PART *pt; + unsigned long i; + /* make sure have a body */ + if (section && *section && mail_fetchstructure (stream,msgno,&b) && b) + while (*section) { /* find desired section */ + if (isdigit (*section)) { /* get section specifier */ + /* make sure what follows is valid */ + if (!(i = strtoul (section,(char **) §ion,10)) || + (*section && ((*section++ != '.') || !*section))) return NIL; + /* multipart content? */ + if (b->type == TYPEMULTIPART) { + /* yes, find desired part */ + if (pt = b->nested.part) while (--i && (pt = pt->next)); + if (!pt) return NIL; /* bad specifier */ + b = &pt->body; /* note new body */ + } + /* otherwise must be section 1 */ + else if (i != 1) return NIL; + /* need to go down further? */ + if (*section) switch (b->type) { + case TYPEMULTIPART: /* multipart */ + break; + case TYPEMESSAGE: /* embedded message */ + if (!strcmp (b->subtype,"RFC822")) { + b = b->nested.msg->body; + break; + } + default: /* bogus subpart specification */ + return NIL; + } + } + else return NIL; /* unknown section specifier */ + } + return b; +} + +/* Mail output date from elt fields + * Accepts: character string to write into + * elt to get data data from + * Returns: the character string + */ + +const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + +const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + +char *mail_date (char *string,MESSAGECACHE *elt) +{ + sprintf (string,"%2d-%s-%d %02d:%02d:%02d %c%02d%02d", + elt->day ? elt->day : 1, + months[elt->month ? (elt->month - 1) : 0], + elt->year + BASEYEAR,elt->hours,elt->minutes,elt->seconds, + elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes); + return string; +} + + +/* Mail output extended-ctime format date from elt fields + * Accepts: character string to write into + * elt to get data data from + * Returns: the character string + */ + +char *mail_cdate (char *string,MESSAGECACHE *elt) +{ + char *fmt = "%s %s %2d %02d:%02d:%02d %4d %s%02d%02d\n"; + int d = elt->day ? elt->day : 1; + int m = elt->month ? (elt->month - 1) : 0; + int y = elt->year + BASEYEAR; + const char *s = months[m]; + if (m < 2) { /* if before March, */ + m += 10; /* January = month 10 of previous year */ + y--; + } + else m -= 2; /* March is month 0 */ + sprintf (string,fmt,days[(int) (d + 2 + ((7 + 31 * m) / 12) +#ifndef USEJULIANCALENDAR +#ifndef USEORTHODOXCALENDAR /* Gregorian calendar */ + + (y / 400) +#ifdef Y4KBUGFIX + - (y / 4000) +#endif +#else /* Orthodox calendar */ + + (2 * (y / 900)) + ((y % 900) >= 200) + + ((y % 900) >= 600) +#endif + - (y / 100) +#endif + + y + (y / 4)) % 7], + s,d,elt->hours,elt->minutes,elt->seconds,elt->year + BASEYEAR, + elt->zoccident ? "-" : "+",elt->zhours,elt->zminutes); + return string; +} + +/* Mail parse date into elt fields + * Accepts: elt to write into + * date string to parse + * Returns: T if parse successful, else NIL + * This routine parses dates as follows: + * . leading three alphas followed by comma and space are ignored + * . date accepted in format: mm/dd/yy, mm/dd/yyyy, dd-mmm-yy, dd-mmm-yyyy, + * dd mmm yy, dd mmm yyyy, yyyy-mm-dd, yyyymmdd + * . two and three digit years interpreted according to RFC 2822 rules + * . mandatory end of string if yyyy-mm-dd or yyyymmdd; otherwise optional + * space followed by time: + * . time accepted in format hh:mm:ss or hh:mm + * . end of string accepted + * . timezone accepted: hyphen followed by symbolic timezone, or space + * followed by signed numeric timezone or symbolic timezone + * Examples of normal input: + * . IMAP date-only (SEARCH): + * dd-mmm-yyyy + * . IMAP date-time (INTERNALDATE): + * dd-mmm-yyyy hh:mm:ss +zzzz + * . RFC-822: + * www, dd mmm yy hh:mm:ss zzz + * . RFC-2822: + * www, dd mmm yyyy hh:mm:ss +zzzz + */ + +long mail_parse_date (MESSAGECACHE *elt,unsigned char *s) +{ + unsigned long d,m,y; + int mi,ms; + struct tm *t; + time_t tn; + char tmp[MAILTMPLEN]; + static unsigned long maxyear = 0; + if (!maxyear) { /* know the end of time yet? */ + MESSAGECACHE tmpelt; + memset (&tmpelt,0xff,sizeof (MESSAGECACHE)); + maxyear = BASEYEAR + tmpelt.year; + } + /* clear elt */ + elt->zoccident = elt->zhours = elt->zminutes = + elt->hours = elt->minutes = elt->seconds = + elt->day = elt->month = elt->year = 0; + /* make a writeable uppercase copy */ + if (s && *s && (strlen (s) < (size_t)MAILTMPLEN)) s = ucase (strcpy (tmp,s)); + else return NIL; + /* skip over possible day of week */ + if (isalpha (*s) && isalpha (s[1]) && isalpha (s[2]) && (s[3] == ',') && + (s[4] == ' ')) s += 5; + while (*s == ' ') s++; /* parse first number (probable month) */ + if (!(m = strtoul (s,(char **) &s,10))) return NIL; + + switch (*s) { /* different parse based on delimiter */ + case '/': /* mm/dd/yy format */ + if (isdigit (*++s) && (d = strtoul (s,(char **) &s,10)) && + (*s == '/') && isdigit (*++s)) { + y = strtoul (s,(char **) &s,10); + if (*s == '\0') break; /* must end here */ + } + return NIL; /* bogon */ + case ' ': /* dd mmm yy format */ + while (s[1] == ' ') s++; /* slurp extra whitespace */ + case '-': + if (isdigit (s[1])) { /* possible ISO 8601 date format? */ + y = m; /* yes, first number is year */ + /* get month and day */ + if ((m = strtoul (s+1,(char **) &s,10)) && (*s++ == '-') && + (d = strtoul (s,(char **) &s,10)) && !*s) break; + return NIL; /* syntax error or time present */ + } + d = m; /* dd-mmm-yy[yy], so first number is a day */ + /* make sure string long enough! */ + if (strlen (s) < (size_t) 5) return NIL; + /* Some compilers don't allow `<<' and/or longs in case statements. */ + /* slurp up the month string */ + ms = ((s[1] - 'A') * 1024) + ((s[2] - 'A') * 32) + (s[3] - 'A'); + switch (ms) { /* determine the month */ + case (('J'-'A') * 1024) + (('A'-'A') * 32) + ('N'-'A'): m = 1; break; + case (('F'-'A') * 1024) + (('E'-'A') * 32) + ('B'-'A'): m = 2; break; + case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('R'-'A'): m = 3; break; + case (('A'-'A') * 1024) + (('P'-'A') * 32) + ('R'-'A'): m = 4; break; + case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('Y'-'A'): m = 5; break; + case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('N'-'A'): m = 6; break; + case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('L'-'A'): m = 7; break; + case (('A'-'A') * 1024) + (('U'-'A') * 32) + ('G'-'A'): m = 8; break; + case (('S'-'A') * 1024) + (('E'-'A') * 32) + ('P'-'A'): m = 9; break; + case (('O'-'A') * 1024) + (('C'-'A') * 32) + ('T'-'A'): m = 10; break; + case (('N'-'A') * 1024) + (('O'-'A') * 32) + ('V'-'A'): m = 11; break; + case (('D'-'A') * 1024) + (('E'-'A') * 32) + ('C'-'A'): m = 12; break; + default: return NIL; /* unknown month */ + } + if (s[4] == *s) s += 5; /* advance to year */ + else { /* first three were OK, possibly full name */ + mi = *s; /* note delimiter, skip alphas */ + for (s += 4; isalpha (*s); s++); + /* error if delimiter not here */ + if (mi != *s++) return NIL; + } + while (*s == ' ') s++; /* parse year */ + if (isdigit (*s)) { /* must be a digit here */ + y = strtoul (s,(char **) &s,10); + if (*s == '\0' || *s == ' ') break; + } + case '\0': /* ISO 8601 compact date */ + if (m < (BASEYEAR * 10000)) return NIL; + y = m / 10000; /* get year */ + d = (m %= 10000) % 100; /* get day */ + m /= 100; /* and month */ + break; + default: + return NIL; /* unknown date format */ + } + + /* minimal validity check of date */ + if ((d > 31) || (m > 12)) return NIL; + if (y < 49) y += 2000; /* RFC 2282 rules for two digit years 00-49 */ + else if (y < 999) y += 1900; /* 2-digit years 50-99 and 3-digit years */ + /* reject prehistoric and far future years */ + if ((y < BASEYEAR) || (y > maxyear)) return NIL; + /* set values in elt */ + elt->day = d; elt->month = m; elt->year = y - BASEYEAR; + ms = '\0'; /* initially no time zone string */ + if (*s) { /* time specification present? */ + /* parse time */ + d = strtoul (s+1,(char **) &s,10); + if (*s != ':') return NIL; + m = strtoul (++s,(char **) &s,10); + y = (*s == ':') ? strtoul (++s,(char **) &s,10) : 0; + /* validity check time */ + if ((d > 23) || (m > 59) || (y > 60)) return NIL; + /* set values in elt */ + elt->hours = d; elt->minutes = m; elt->seconds = y; + switch (*s) { /* time zone specifier? */ + case ' ': /* numeric time zone */ + while (s[1] == ' ') s++; /* slurp extra whitespace */ + if (!isalpha (s[1])) { /* treat as '-' case if alphabetic */ + /* test for sign character */ + if ((elt->zoccident = (*++s == '-')) || (*s == '+')) s++; + /* validate proper timezone */ + if (isdigit(*s) && isdigit(s[1]) && isdigit(s[2]) && (s[2] < '6') && + isdigit(s[3])) { + elt->zhours = (*s - '0') * 10 + (s[1] - '0'); + elt->zminutes = (s[2] - '0') * 10 + (s[3] - '0'); + } + return T; /* all done! */ + } + /* falls through */ + case '-': /* symbolic time zone */ + if (!(ms = *++s)) ms = 'Z'; + else if (*++s) { /* multi-character? */ + ms -= 'A'; ms *= 1024; /* yes, make compressed three-byte form */ + ms += ((*s++ - 'A') * 32); + if (*s) ms += *s++ - 'A'; + if (*s) ms = '\0'; /* more than three characters */ + } + default: /* ignore anything else */ + break; + } + } + + /* This is not intended to be a comprehensive list of all possible + * timezone strings. Such a list would be impractical. Rather, this + * listing is intended to incorporate all military, North American, and + * a few special cases such as Japan and the major European zone names, + * such as what might be expected to be found in a Tenex format mailbox + * and spewed from an IMAP server. The trend is to migrate to numeric + * timezones which lack the flavor but also the ambiguity of the names. + * + * RFC-822 only recognizes UT, GMT, 1-letter military timezones, and the + * 4 CONUS timezones and their summer time variants. [Sorry, Canadian + * Atlantic Provinces, Alaska, and Hawaii.] + */ + switch (ms) { /* determine the timezone */ + /* Universal */ + case (('U'-'A')*1024)+(('T'-'A')*32): +#ifndef STRICT_RFC822_TIMEZONES + case (('U'-'A')*1024)+(('T'-'A')*32)+'C'-'A': +#endif + /* Greenwich */ + case (('G'-'A')*1024)+(('M'-'A')*32)+'T'-'A': + case 'Z': elt->zhours = 0; break; + + /* oriental (from Greenwich) timezones */ +#ifndef STRICT_RFC822_TIMEZONES + /* Middle Europe */ + case (('M'-'A')*1024)+(('E'-'A')*32)+'T'-'A': +#endif +#ifdef BRITISH_SUMMER_TIME + /* British Summer */ + case (('B'-'A')*1024)+(('S'-'A')*32)+'T'-'A': +#endif + case 'A': elt->zhours = 1; break; +#ifndef STRICT_RFC822_TIMEZONES + /* Eastern Europe */ + case (('E'-'A')*1024)+(('E'-'A')*32)+'T'-'A': +#endif + case 'B': elt->zhours = 2; break; + case 'C': elt->zhours = 3; break; + case 'D': elt->zhours = 4; break; + case 'E': elt->zhours = 5; break; + case 'F': elt->zhours = 6; break; + case 'G': elt->zhours = 7; break; + case 'H': elt->zhours = 8; break; +#ifndef STRICT_RFC822_TIMEZONES + /* Japan */ + case (('J'-'A')*1024)+(('S'-'A')*32)+'T'-'A': +#endif + case 'I': elt->zhours = 9; break; + case 'K': elt->zhours = 10; break; + case 'L': elt->zhours = 11; break; + case 'M': elt->zhours = 12; break; + + /* occidental (from Greenwich) timezones */ + case 'N': elt->zoccident = 1; elt->zhours = 1; break; + case 'O': elt->zoccident = 1; elt->zhours = 2; break; +#ifndef STRICT_RFC822_TIMEZONES + case (('A'-'A')*1024)+(('D'-'A')*32)+'T'-'A': +#endif + case 'P': elt->zoccident = 1; elt->zhours = 3; break; +#ifdef NEWFOUNDLAND_STANDARD_TIME + /* Newfoundland */ + case (('N'-'A')*1024)+(('S'-'A')*32)+'T'-'A': + elt->zoccident = 1; elt->zhours = 3; elt->zminutes = 30; break; +#endif +#ifndef STRICT_RFC822_TIMEZONES + /* Atlantic */ + case (('A'-'A')*1024)+(('S'-'A')*32)+'T'-'A': +#endif + /* CONUS */ + case (('E'-'A')*1024)+(('D'-'A')*32)+'T'-'A': + case 'Q': elt->zoccident = 1; elt->zhours = 4; break; + /* Eastern */ + case (('E'-'A')*1024)+(('S'-'A')*32)+'T'-'A': + case (('C'-'A')*1024)+(('D'-'A')*32)+'T'-'A': + case 'R': elt->zoccident = 1; elt->zhours = 5; break; + /* Central */ + case (('C'-'A')*1024)+(('S'-'A')*32)+'T'-'A': + case (('M'-'A')*1024)+(('D'-'A')*32)+'T'-'A': + case 'S': elt->zoccident = 1; elt->zhours = 6; break; + /* Mountain */ + case (('M'-'A')*1024)+(('S'-'A')*32)+'T'-'A': + case (('P'-'A')*1024)+(('D'-'A')*32)+'T'-'A': + case 'T': elt->zoccident = 1; elt->zhours = 7; break; + /* Pacific */ + case (('P'-'A')*1024)+(('S'-'A')*32)+'T'-'A': +#ifndef STRICT_RFC822_TIMEZONES + case (('Y'-'A')*1024)+(('D'-'A')*32)+'T'-'A': +#endif + case 'U': elt->zoccident = 1; elt->zhours = 8; break; +#ifndef STRICT_RFC822_TIMEZONES + /* Yukon */ + case (('Y'-'A')*1024)+(('S'-'A')*32)+'T'-'A': +#endif + case 'V': elt->zoccident = 1; elt->zhours = 9; break; +#ifndef STRICT_RFC822_TIMEZONES + /* Hawaii */ + case (('H'-'A')*1024)+(('S'-'A')*32)+'T'-'A': +#endif + case 'W': elt->zoccident = 1; elt->zhours = 10; break; + /* Nome/Bering/Samoa */ +#ifdef NOME_STANDARD_TIME + case (('N'-'A')*1024)+(('S'-'A')*32)+'T'-'A': +#endif +#ifdef BERING_STANDARD_TIME + case (('B'-'A')*1024)+(('S'-'A')*32)+'T'-'A': +#endif +#ifdef SAMOA_STANDARD_TIME + case (('S'-'A')*1024)+(('S'-'A')*32)+'T'-'A': +#endif + case 'X': elt->zoccident = 1; elt->zhours = 11; break; + case 'Y': elt->zoccident = 1; elt->zhours = 12; break; + + default: /* unknown time zones treated as local */ + tn = time (0); /* time now... */ + t = localtime (&tn); /* get local minutes since midnight */ + mi = t->tm_hour * 60 + t->tm_min; + ms = t->tm_yday; /* note Julian day */ + if (t = gmtime (&tn)) { /* minus UTC minutes since midnight */ + mi -= t->tm_hour * 60 + t->tm_min; + /* ms can be one of: + * 36x local time is December 31, UTC is January 1, offset -24 hours + * 1 local time is 1 day ahead of UTC, offset +24 hours + * 0 local time is same day as UTC, no offset + * -1 local time is 1 day behind UTC, offset -24 hours + * -36x local time is January 1, UTC is December 31, offset +24 hours + */ + if (ms -= t->tm_yday) /* correct offset if different Julian day */ + mi += ((ms < 0) == (abs (ms) == 1)) ? -24*60 : 24*60; + if (mi < 0) { /* occidental? */ + mi = abs (mi); /* yup, make positive number */ + elt->zoccident = 1; /* and note west of UTC */ + } + elt->zhours = mi / 60; /* now break into hours and minutes */ + elt->zminutes = mi % 60; + } + break; + } + return T; +} + +/* Mail n messages exist + * Accepts: mail stream + * number of messages + */ + +void mail_exists (MAILSTREAM *stream,unsigned long nmsgs) +{ + char tmp[MAILTMPLEN]; + if (nmsgs > MAXMESSAGES) { + sprintf (tmp,"Mailbox has more messages (%lu) exist than maximum (%lu)", + nmsgs,MAXMESSAGES); + mm_log (tmp,ERROR); + nmsgs = MAXMESSAGES; /* cap to maximum */ + /* probably will crash in mail_elt() soon enough... */ + } + /* make sure cache is large enough */ + (*mailcache) (stream,nmsgs,CH_SIZE); + stream->nmsgs = nmsgs; /* update stream status */ + /* notify main program of change */ + if (!stream->silent) MM_EXISTS (stream,nmsgs); +} + + +/* Mail n messages are recent + * Accepts: mail stream + * number of recent messages + */ + +void mail_recent (MAILSTREAM *stream,unsigned long recent) +{ + char tmp[MAILTMPLEN]; + if (recent <= stream->nmsgs) stream->recent = recent; + else { + sprintf (tmp,"Non-existent recent message(s) %lu, nmsgs=%lu", + recent,stream->nmsgs); + mm_log (tmp,ERROR); + } +} + + +/* Mail message n is expunged + * Accepts: mail stream + * message # + */ + +void mail_expunged (MAILSTREAM *stream,unsigned long msgno) +{ + char tmp[MAILTMPLEN]; + MESSAGECACHE *elt; + if (msgno > stream->nmsgs) { + sprintf (tmp,"Expunge of non-existent message %lu, nmsgs=%lu", + msgno,stream->nmsgs); + mm_log (tmp,ERROR); + } + else { + elt = (MESSAGECACHE *) (*mailcache) (stream,msgno,CH_ELT); + /* notify main program of change */ + if (!stream->silent) MM_EXPUNGED (stream,msgno); + if (elt) { /* if an element is there */ + elt->msgno = 0; /* invalidate its message number and free */ + (*mailcache) (stream,msgno,CH_FREE); + (*mailcache) (stream,msgno,CH_FREESORTCACHE); + } + /* expunge the slot */ + (*mailcache) (stream,msgno,CH_EXPUNGE); + --stream->nmsgs; /* update stream status */ + if (stream->msgno) { /* have stream pointers? */ + /* make sure the short cache is nuked */ + if (stream->scache) mail_gc (stream,GC_ENV | GC_TEXTS); + else stream->msgno = 0; /* make sure invalidated in any case */ + } + } +} + +/* Mail stream status routines */ + + +/* Mail lock stream + * Accepts: mail stream + */ + +void mail_lock (MAILSTREAM *stream) +{ + if (stream->lock) { + char tmp[MAILTMPLEN]; + sprintf (tmp,"Lock when already locked, mbx=%.80s", + stream->mailbox ? stream->mailbox : "???"); + fatal (tmp); + } + else stream->lock = T; /* lock stream */ +} + + +/* Mail unlock stream + * Accepts: mail stream + */ + +void mail_unlock (MAILSTREAM *stream) +{ + if (!stream->lock) fatal ("Unlock when not locked"); + else stream->lock = NIL; /* unlock stream */ +} + + +/* Mail turn on debugging telemetry + * Accepts: mail stream + */ + +void mail_debug (MAILSTREAM *stream) +{ + stream->debug = T; /* turn on debugging telemetry */ + if (stream->dtb) (*stream->dtb->parameters) (ENABLE_DEBUG,stream); +} + + +/* Mail turn off debugging telemetry + * Accepts: mail stream + */ + +void mail_nodebug (MAILSTREAM *stream) +{ + stream->debug = NIL; /* turn off debugging telemetry */ + if (stream->dtb) (*stream->dtb->parameters) (DISABLE_DEBUG,stream); +} + + +/* Mail log to debugging telemetry + * Accepts: message + * flag that data is "sensitive" + */ + +void mail_dlog (char *string,long flag) +{ + mm_dlog ((debugsensitive || !flag) ? string : "<suppressed>"); +} + +/* Mail parse UID sequence + * Accepts: mail stream + * sequence to parse + * Returns: T if parse successful, else NIL + */ + +long mail_uid_sequence (MAILSTREAM *stream,unsigned char *sequence) +{ + unsigned long i,j,k,x,y; + for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->sequence = NIL; + while (sequence && *sequence){/* while there is something to parse */ + if (*sequence == '*') { /* maximum message */ + i = stream->nmsgs ? mail_uid (stream,stream->nmsgs) : stream->uid_last; + sequence++; /* skip past * */ + } + /* parse and validate message number */ + /* parse and validate message number */ + else if (!isdigit (*sequence)) { + MM_LOG ("Syntax error in sequence",ERROR); + return NIL; + } + else if (!(i = strtoul (sequence,(char **) &sequence,10))) { + MM_LOG ("UID may not be zero",ERROR); + return NIL; + } + switch (*sequence) { /* see what the delimiter is */ + case ':': /* sequence range */ + if (*++sequence == '*') { /* maximum message */ + j = stream->nmsgs ? mail_uid (stream,stream->nmsgs) : stream->uid_last; + sequence++; /* skip past * */ + } + /* parse end of range */ + else if (!(j = strtoul (sequence,(char **) &sequence,10))) { + MM_LOG ("UID sequence range invalid",ERROR); + return NIL; + } + if (*sequence && *sequence++ != ',') { + MM_LOG ("UID sequence range syntax error",ERROR); + return NIL; + } + if (i > j) { /* swap the range if backwards */ + x = i; i = j; j = x; + } + x = mail_msgno (stream,i);/* get msgnos */ + y = mail_msgno (stream,j);/* for both UIDS (don't && it) */ + /* easy if both UIDs valid */ + if (x && y) while (x <= y) mail_elt (stream,x++)->sequence = T; + /* start UID valid, end is not */ + else if (x) while ((x <= stream->nmsgs) && (mail_uid (stream,x) <= j)) + mail_elt (stream,x++)->sequence = T; + /* end UID valid, start is not */ + else if (y) for (x = 1; x <= y; x++) { + if (mail_uid (stream,x) >= i) mail_elt (stream,x)->sequence = T; + } + /* neither is valid, ugh */ + else for (x = 1; x <= stream->nmsgs; x++) + if (((k = mail_uid (stream,x)) >= i) && (k <= j)) + mail_elt (stream,x)->sequence = T; + break; + case ',': /* single message */ + ++sequence; /* skip the delimiter, fall into end case */ + case '\0': /* end of sequence, mark this message */ + if (x = mail_msgno (stream,i)) mail_elt (stream,x)->sequence = T; + break; + default: /* anything else is a syntax error! */ + MM_LOG ("UID sequence syntax error",ERROR); + return NIL; + } + } + return T; /* successfully parsed sequence */ +} + +/* Mail see if line list matches that in cache + * Accepts: candidate line list + * cached line list + * matching flags + * Returns: T if match, NIL if no match + */ + +long mail_match_lines (STRINGLIST *lines,STRINGLIST *msglines,long flags) +{ + unsigned long i; + unsigned char *s,*t; + STRINGLIST *m; + if (!msglines) return T; /* full header is in cache */ + /* need full header but filtered in cache */ + if ((flags & FT_NOT) || !lines) return NIL; + do { /* make sure all present & accounted for */ + for (m = msglines; m; m = m->next) if (lines->text.size == m->text.size) { + for (s = lines->text.data,t = m->text.data,i = lines->text.size; + i && !compare_uchar (*s,*t); s++,t++,i--); + if (!i) break; /* this line matches */ + } + if (!m) return NIL; /* didn't find in the list */ + } + while (lines = lines->next); + return T; /* all lines found */ +} + +/* Mail filter text by header lines + * Accepts: text to filter, with trailing null + * length of text + * list of lines + * fetch flags + * Returns: new text size, text overwritten + */ + +unsigned long mail_filter (char *text,unsigned long len,STRINGLIST *lines, + long flags) +{ + STRINGLIST *hdrs; + int notfound; + unsigned long i; + char c,*s,*e,*t,tmp[MAILTMPLEN]; + char *src = text; + char *dst = src; + char *end = text + len; + text[len] = '\012'; /* guard against running off buffer */ + while (src < end) { /* process header */ + /* slurp header line name */ + for (s = src,e = s + MAILTMPLEN - 1,e = (e < end ? e : end),t = tmp; + (s < e) && ((c = (*s ? *s : (*s = ' '))) != ':') && + ((c > ' ') || + ((c != ' ') && (c != '\t') && (c != '\015') && (c != '\012'))); + *t++ = *s++); + *t = '\0'; /* tie off */ + notfound = T; /* not found yet */ + if (i = t - tmp) /* see if found in header */ + for (hdrs = lines; hdrs && notfound; hdrs = hdrs->next) + if ((hdrs->text.size == i) && !compare_csizedtext (tmp,&hdrs->text)) + notfound = NIL; + /* skip header line if not wanted */ + if (i && ((flags & FT_NOT) ? !notfound : notfound)) + while (((*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') && + (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') && + (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') && + (*src++ != '\012')) || ((*src == ' ') || (*src == '\t'))); + else if (src == dst) { /* copy to self */ + while (((*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') && + (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') && + (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') && + (*src++ != '\012')) || ((*src == ' ') || (*src == '\t'))); + dst = src; /* update destination */ + } + else { /* copy line and any continuation line */ + while ((((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') && + ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') && + ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') && + ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') && + ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012'))|| + ((*src == ' ') || (*src == '\t'))); + /* in case hit the guard LF */ + if (src > end) dst -= (src - end); + } + } + *dst = '\0'; /* tie off destination */ + return dst - text; +} + +/* Local mail search message + * Accepts: MAIL stream + * message number + * optional section specification + * search program + * Returns: T if found, NIL otherwise + */ + +long mail_search_msg (MAILSTREAM *stream,unsigned long msgno,char *section, + SEARCHPGM *pgm) +{ + unsigned short d; + char tmp[MAILTMPLEN]; + MESSAGECACHE *elt = mail_elt (stream,msgno); + SEARCHHEADER *hdr; + SEARCHOR *or; + SEARCHPGMLIST *not; + unsigned long now = (unsigned long) time (0); + if (pgm->msgno || pgm->uid) { /* message set searches */ + SEARCHSET *set; + /* message sequences */ + if (pgm->msgno) { /* inside this message sequence set */ + for (set = pgm->msgno; set; set = set->next) + if (set->last ? ((set->first <= set->last) ? + ((msgno >= set->first) && (msgno <= set->last)) : + ((msgno >= set->last) && (msgno <= set->first))) : + msgno == set->first) break; + if (!set) return NIL; /* not found within sequence */ + } + if (pgm->uid) { /* inside this unique identifier set */ + unsigned long uid = mail_uid (stream,msgno); + for (set = pgm->uid; set; set = set->next) + if (set->last ? ((set->first <= set->last) ? + ((uid >= set->first) && (uid <= set->last)) : + ((uid >= set->last) && (uid <= set->first))) : + uid == set->first) break; + if (!set) return NIL; /* not found within sequence */ + } + } + + /* Fast data searches */ + /* need to fetch fast data? */ + if ((!elt->rfc822_size && (pgm->larger || pgm->smaller)) || + (!elt->year && (pgm->before || pgm->on || pgm->since || + pgm->older || pgm->younger)) || + (!elt->valid && (pgm->answered || pgm->unanswered || + pgm->deleted || pgm->undeleted || + pgm->draft || pgm->undraft || + pgm->flagged || pgm->unflagged || + pgm->recent || pgm->old || + pgm->seen || pgm->unseen || + pgm->keyword || pgm->unkeyword))) { + unsigned long i; + MESSAGECACHE *ielt; + for (i = elt->msgno; /* find last unloaded message in range */ + (i < stream->nmsgs) && (ielt = mail_elt (stream,i+1)) && + ((!ielt->rfc822_size && (pgm->larger || pgm->smaller)) || + (!ielt->year && (pgm->before || pgm->on || pgm->since || + pgm->older || pgm->younger)) || + (!ielt->valid && (pgm->answered || pgm->unanswered || + pgm->deleted || pgm->undeleted || + pgm->draft || pgm->undraft || + pgm->flagged || pgm->unflagged || + pgm->recent || pgm->old || + pgm->seen || pgm->unseen || + pgm->keyword || pgm->unkeyword))); ++i); + if (i == elt->msgno) sprintf (tmp,"%lu",elt->msgno); + else sprintf (tmp,"%lu:%lu",elt->msgno,i); + mail_fetch_fast (stream,tmp,NIL); + } + /* size ranges */ + if ((pgm->larger && (elt->rfc822_size <= pgm->larger)) || + (pgm->smaller && (elt->rfc822_size >= pgm->smaller))) return NIL; + /* message flags */ + if ((pgm->answered && !elt->answered) || + (pgm->unanswered && elt->answered) || + (pgm->deleted && !elt->deleted) || + (pgm->undeleted && elt->deleted) || + (pgm->draft && !elt->draft) || + (pgm->undraft && elt->draft) || + (pgm->flagged && !elt->flagged) || + (pgm->unflagged && elt->flagged) || + (pgm->recent && !elt->recent) || + (pgm->old && elt->recent) || + (pgm->seen && !elt->seen) || + (pgm->unseen && elt->seen)) return NIL; + /* keywords */ + if ((pgm->keyword && !mail_search_keyword (stream,elt,pgm->keyword,LONGT)) || + (pgm->unkeyword && !mail_search_keyword (stream,elt,pgm->unkeyword,NIL))) + return NIL; + /* internal date ranges */ + if (pgm->before || pgm->on || pgm->since) { + d = mail_shortdate (elt->year,elt->month,elt->day); + if (pgm->before && (d >= pgm->before)) return NIL; + if (pgm->on && (d != pgm->on)) return NIL; + if (pgm->since && (d < pgm->since)) return NIL; + } + if (pgm->older || pgm->younger) { + unsigned long msgd = mail_longdate (elt); + if (pgm->older && msgd > (now - pgm->older)) return NIL; + if (pgm->younger && msgd < (now - pgm->younger)) return NIL; + } + + /* envelope searches */ + if (pgm->sentbefore || pgm->senton || pgm->sentsince || + pgm->bcc || pgm->cc || pgm->from || pgm->to || pgm->subject || + pgm->return_path || pgm->sender || pgm->reply_to || pgm->in_reply_to || + pgm->message_id || pgm->newsgroups || pgm->followup_to || + pgm->references) { + ENVELOPE *env; + MESSAGECACHE delt; + if (section) { /* use body part envelope */ + BODY *body = mail_body (stream,msgno,section); + env = (body && (body->type == TYPEMESSAGE) && body->subtype && + !strcmp (body->subtype,"RFC822")) ? body->nested.msg->env : NIL; + } + else { /* use top level envelope if no section */ + if (pgm->header && !stream->scache && !(stream->dtb->flags & DR_LOCAL)) + mail_fetch_header(stream,msgno,NIL,NIL,NIL,FT_PEEK|FT_SEARCHLOOKAHEAD); + env = mail_fetchenvelope (stream,msgno); + } + if (!env) return NIL; /* no envelope obtained */ + /* sent date ranges */ + if ((pgm->sentbefore || pgm->senton || pgm->sentsince) && + (!mail_parse_date (&delt,env->date) || + !(d = mail_shortdate (delt.year,delt.month,delt.day)) || + (pgm->sentbefore && (d >= pgm->sentbefore)) || + (pgm->senton && (d != pgm->senton)) || + (pgm->sentsince && (d < pgm->sentsince)))) return NIL; + /* search headers */ + if ((pgm->bcc && !mail_search_addr (env->bcc,pgm->bcc)) || + (pgm->cc && !mail_search_addr (env->cc,pgm->cc)) || + (pgm->from && !mail_search_addr (env->from,pgm->from)) || + (pgm->to && !mail_search_addr (env->to,pgm->to)) || + (pgm->subject && !mail_search_header_text (env->subject,pgm->subject))) + return NIL; + /* These criteria are not supported by IMAP and have to be emulated */ + if ((pgm->return_path && + !mail_search_addr (env->return_path,pgm->return_path)) || + (pgm->sender && !mail_search_addr (env->sender,pgm->sender)) || + (pgm->reply_to && !mail_search_addr (env->reply_to,pgm->reply_to)) || + (pgm->in_reply_to && + !mail_search_header_text (env->in_reply_to,pgm->in_reply_to)) || + (pgm->message_id && + !mail_search_header_text (env->message_id,pgm->message_id)) || + (pgm->newsgroups && + !mail_search_header_text (env->newsgroups,pgm->newsgroups)) || + (pgm->followup_to && + !mail_search_header_text (env->followup_to,pgm->followup_to)) || + (pgm->references && + !mail_search_header_text (env->references,pgm->references))) + return NIL; + } + + /* search header lines */ + for (hdr = pgm->header; hdr; hdr = hdr->next) { + char *t,*e,*v; + SIZEDTEXT s; + STRINGLIST sth,stc; + sth.next = stc.next = NIL; /* only one at a time */ + sth.text.data = hdr->line.data; + sth.text.size = hdr->line.size; + /* get the header text */ + if ((t = mail_fetch_header (stream,msgno,NIL,&sth,&s.size, + FT_INTERNAL | FT_PEEK | + (section ? NIL : FT_SEARCHLOOKAHEAD))) && + strchr (t,':')) { + if (hdr->text.size) { /* anything matches empty search string */ + /* non-empty, copy field data */ + s.data = (unsigned char *) fs_get (s.size + 1); + /* for each line */ + for (v = (char *) s.data, e = t + s.size; t < e;) switch (*t) { + default: /* non-continuation, skip leading field name */ + while ((t < e) && (*t++ != ':')); + if ((t < e) && (*t == ':')) t++; + case '\t': case ' ': /* copy field data */ + while ((t < e) && (*t != '\015') && (*t != '\012')) *v++ = *t++; + *v++ = '\n'; /* tie off line */ + while (((*t == '\015') || (*t == '\012')) && (t < e)) t++; + } + /* calculate true size */ + s.size = v - (char *) s.data; + *v = '\0'; /* tie off results */ + stc.text.data = hdr->text.data; + stc.text.size = hdr->text.size; + /* search header */ + if (mail_search_header (&s,&stc)) fs_give ((void **) &s.data); + else { /* search failed */ + fs_give ((void **) &s.data); + return NIL; + } + } + } + else return NIL; /* no matching header text */ + } + /* search strings */ + if ((pgm->text && !mail_search_text (stream,msgno,section,pgm->text,LONGT))|| + (pgm->body && !mail_search_text (stream,msgno,section,pgm->body,NIL))) + return NIL; + /* logical conditions */ + for (or = pgm->or; or; or = or->next) + if (!(mail_search_msg (stream,msgno,section,or->first) || + mail_search_msg (stream,msgno,section,or->second))) return NIL; + for (not = pgm->not; not; not = not->next) + if (mail_search_msg (stream,msgno,section,not->pgm)) return NIL; + return T; +} + +/* Mail search message header null-terminated text + * Accepts: header text + * strings to search + * Returns: T if search found a match + */ + +long mail_search_header_text (char *s,STRINGLIST *st) +{ + SIZEDTEXT h; + /* have any text? */ + if (h.data = (unsigned char *) s) { + h.size = strlen (s); /* yes, get its size */ + return mail_search_header (&h,st); + } + return NIL; +} + + +/* Mail search message header + * Accepts: header as sized text + * strings to search + * Returns: T if search found a match + */ + +long mail_search_header (SIZEDTEXT *hdr,STRINGLIST *st) +{ + SIZEDTEXT h; + long ret = LONGT; + /* make UTF-8 version of header */ + utf8_mime2text (hdr,&h,U8T_CANONICAL); + while (h.size && ((h.data[h.size-1]=='\015') || (h.data[h.size-1]=='\012'))) + --h.size; /* slice off trailing newlines */ + do if (h.size ? /* search non-empty string */ + !ssearch (h.data,h.size,st->text.data,st->text.size) : st->text.size) + ret = NIL; + while (ret && (st = st->next)); + if (h.data != hdr->data) fs_give ((void **) &h.data); + return ret; +} + +/* Mail search message body + * Accepts: MAIL stream + * message number + * optional section specification + * string list + * flags + * Returns: T if search found a match + */ + +long mail_search_text (MAILSTREAM *stream,unsigned long msgno,char *section, + STRINGLIST *st,long flags) +{ + BODY *body; + long ret = NIL; + STRINGLIST *s = mail_newstringlist (); + mailgets_t omg = mailgets; + if (stream->dtb->flags & DR_LOWMEM) mailgets = mail_search_gets; + /* strings to search */ + for (stream->private.search.string = s; st;) { + s->text.data = st->text.data; + s->text.size = st->text.size; + if (st = st->next) s = s->next = mail_newstringlist (); + } + stream->private.search.text = NIL; + if (flags) { /* want header? */ + SIZEDTEXT s,t; + s.data = (unsigned char *) + mail_fetch_header (stream,msgno,section,NIL,&s.size,FT_INTERNAL|FT_PEEK); + utf8_mime2text (&s,&t,U8T_CANONICAL); + ret = mail_search_string_work (&t,&stream->private.search.string); + if (t.data != s.data) fs_give ((void **) &t.data); + } + if (!ret) { /* still looking for match? */ + /* no section, get top-level body */ + if (!section) mail_fetchstructure (stream,msgno,&body); + /* get body of nested message */ + else if ((body = mail_body (stream,msgno,section)) && + (body->type == TYPEMULTIPART) && body->subtype && + !strcmp (body->subtype,"RFC822")) body = body->nested.msg->body; + if (body) ret = mail_search_body (stream,msgno,body,NIL,1,flags); + } + mailgets = omg; /* restore former gets routine */ + /* clear searching */ + for (s = stream->private.search.string; s; s = s->next) s->text.data = NIL; + mail_free_stringlist (&stream->private.search.string); + stream->private.search.text = NIL; + return ret; +} + +/* Mail search message body text parts + * Accepts: MAIL stream + * message number + * current body pointer + * hierarchical level prefix + * position at current hierarchical level + * string list + * flags + * Returns: T if search found a match + */ + +long mail_search_body (MAILSTREAM *stream,unsigned long msgno,BODY *body, + char *prefix,unsigned long section,long flags) +{ + long ret = NIL; + unsigned long i; + char *s,*t,sect[MAILTMPLEN]; + SIZEDTEXT st,h; + PART *part; + PARAMETER *param; + if (prefix && (strlen (prefix) > (MAILTMPLEN - 20))) return NIL; + sprintf (sect,"%s%lu",prefix ? prefix : "",section++); + if (flags && prefix) { /* want to search MIME header too? */ + st.data = (unsigned char *) mail_fetch_mime (stream,msgno,sect,&st.size, + FT_INTERNAL | FT_PEEK); + if (stream->dtb->flags & DR_LOWMEM) ret = stream->private.search.result; + else { + /* make UTF-8 version of header */ + utf8_mime2text (&st,&h,U8T_CANONICAL); + ret = mail_search_string_work (&h,&stream->private.search.string); + if (h.data != st.data) fs_give ((void **) &h.data); + } + } + if (!ret) switch (body->type) { + case TYPEMULTIPART: + /* extend prefix if not first time */ + s = prefix ? strcat (sect,".") : ""; + for (i = 1,part = body->nested.part; part && !ret; i++,part = part->next) + ret = mail_search_body (stream,msgno,&part->body,s,i,flags); + break; + case TYPEMESSAGE: + if (!strcmp (body->subtype,"RFC822")) { + if (flags) { /* want to search nested message header? */ + st.data = (unsigned char *) + mail_fetch_header (stream,msgno,sect,NIL,&st.size, + FT_INTERNAL | FT_PEEK); + if (stream->dtb->flags & DR_LOWMEM) ret =stream->private.search.result; + else { + /* make UTF-8 version of header */ + utf8_mime2text (&st,&h,U8T_CANONICAL); + ret = mail_search_string_work (&h,&stream->private.search.string); + if (h.data != st.data) fs_give ((void **) &h.data); + } + } + if (body = body->nested.msg->body) + ret = (body->type == TYPEMULTIPART) ? + mail_search_body (stream,msgno,body,(prefix ? prefix : ""), + section - 1,flags) : + mail_search_body (stream,msgno,body,strcat (sect,"."),1,flags); + break; + } + /* non-MESSAGE/RFC822 falls into text case */ + + case TYPETEXT: + s = mail_fetch_body (stream,msgno,sect,&i,FT_INTERNAL | FT_PEEK); + if (stream->dtb->flags & DR_LOWMEM) ret = stream->private.search.result; + else { + for (t = NIL,param = body->parameter; param && !t; param = param->next) + if (!strcmp (param->attribute,"CHARSET")) t = param->value; + switch (body->encoding) { /* what encoding? */ + case ENCBASE64: + if (st.data = (unsigned char *) + rfc822_base64 ((unsigned char *) s,i,&st.size)) { + ret = mail_search_string (&st,t,&stream->private.search.string); + fs_give ((void **) &st.data); + } + break; + case ENCQUOTEDPRINTABLE: + if (st.data = rfc822_qprint ((unsigned char *) s,i,&st.size)) { + ret = mail_search_string (&st,t,&stream->private.search.string); + fs_give ((void **) &st.data); + } + break; + default: + st.data = (unsigned char *) s; + st.size = i; + ret = mail_search_string (&st,t,&stream->private.search.string); + break; + } + } + break; + } + return ret; +} + +/* Mail search text + * Accepts: sized text to search + * character set of sized text + * string list of search keys + * Returns: T if search found a match + */ + +long mail_search_string (SIZEDTEXT *s,char *charset,STRINGLIST **st) +{ + SIZEDTEXT u; + long ret; + STRINGLIST **sc = st; + /* convert to UTF-8 as best we can */ + if (!utf8_text (s,charset,&u,U8T_CANONICAL)) + utf8_text (s,NIL,&u,U8T_CANONICAL); + ret = mail_search_string_work (&u,st); + if (u.data != s->data) fs_give ((void **) &u.data); + return ret; +} + + +/* Mail search text worker routine + * Accepts: sized text to search + * string list of search keys + * Returns: T if search found a match + */ + +long mail_search_string_work (SIZEDTEXT *s,STRINGLIST **st) +{ + void *t; + STRINGLIST **sc = st; + while (*sc) { /* run down criteria list */ + if (ssearch (s->data,s->size,(*sc)->text.data,(*sc)->text.size)) { + t = (void *) (*sc); /* found one, need to flush this */ + *sc = (*sc)->next; /* remove it from the list */ + fs_give (&t); /* flush the buffer */ + } + else sc = &(*sc)->next; /* move to next in list */ + } + return *st ? NIL : LONGT; +} + + +/* Mail search keyword + * Accepts: MAIL stream + * elt to get flags from + * keyword list + * T for keyword search, NIL for unkeyword search + * Returns: T if search found a match + */ + +long mail_search_keyword (MAILSTREAM *stream,MESSAGECACHE *elt,STRINGLIST *st, + long flag) +{ + int i,j; + unsigned long f = 0; + unsigned long tf; + do { + for (i = 0; (j = (i < NUSERFLAGS) && stream->user_flags[i]); ++i) + if (!compare_csizedtext (stream->user_flags[i],&st->text)) { + f |= (1 << i); + break; + } + if (flag && !j) return NIL; + } while (st = st->next); + tf = elt->user_flags & f; /* get set flags which match */ + return flag ? (f == tf) : !tf; +} + +/* Mail search an address list + * Accepts: address list + * string list + * Returns: T if search found a match + */ + +#define SEARCHBUFLEN (size_t) 2000 +#define SEARCHBUFSLOP (size_t) 5 + +long mail_search_addr (ADDRESS *adr,STRINGLIST *st) +{ + ADDRESS *a,tadr; + SIZEDTEXT txt; + char tmp[SENDBUFLEN + 1]; + size_t i = SEARCHBUFLEN; + size_t k; + long ret = NIL; + if (adr) { + txt.data = (unsigned char *) fs_get (i + SEARCHBUFSLOP); + /* never an error or next */ + tadr.error = NIL,tadr.next = NIL; + /* write address list */ + for (txt.size = 0,a = adr; a; a = a->next) { + k = (tadr.mailbox = a->mailbox) ? 4 + 2*strlen (a->mailbox) : 3; + if (tadr.personal = a->personal) k += 3 + 2*strlen (a->personal); + if (tadr.adl = a->adl) k += 3 + 2*strlen (a->adl); + if (tadr.host = a->host) k += 3 + 2*strlen (a->host); + if (tadr.personal || tadr.adl) k += 2; + if (k < (SENDBUFLEN-10)) {/* ignore ridiculous addresses */ + tmp[0] = '\0'; + rfc822_write_address (tmp,&tadr); + /* resize buffer if necessary */ + if (((k = strlen (tmp)) + txt.size) > i) + fs_resize ((void **) &txt.data,SEARCHBUFSLOP + (i += SEARCHBUFLEN)); + /* add new address */ + memcpy (txt.data + txt.size,tmp,k); + txt.size += k; + /* another address follows */ + if (a->next) txt.data[txt.size++] = ','; + } + } + txt.data[txt.size] = '\0'; /* tie off string */ + ret = mail_search_header (&txt,st); + fs_give ((void **) &txt.data); + } + return ret; +} + +/* Get string for low-memory searching + * Accepts: readin function pointer + * stream to use + * number of bytes + * gets data packet + + * mail stream + * message number + * descriptor string + * option flags + * Returns: NIL, always + */ + +#define SEARCHSLOP 128 + +char *mail_search_gets (readfn_t f,void *stream,unsigned long size, + GETS_DATA *md) +{ + unsigned long i; + char tmp[MAILTMPLEN+SEARCHSLOP+1]; + SIZEDTEXT st; + /* better not be called unless searching */ + if (!md->stream->private.search.string) { + sprintf (tmp,"Search botch, mbx = %.80s, %s = %lu[%.80s]", + md->stream->mailbox, + (md->flags & FT_UID) ? "UID" : "msg",md->msgno,md->what); + fatal (tmp); + } + /* initially no match for search */ + md->stream->private.search.result = NIL; + /* make sure buffer clear */ + memset (st.data = (unsigned char *) tmp,'\0', + (size_t) MAILTMPLEN+SEARCHSLOP+1); + /* read first buffer */ + (*f) (stream,st.size = i = min (size,(long) MAILTMPLEN),tmp); + /* search for text */ + if (mail_search_string (&st,NIL,&md->stream->private.search.string)) + md->stream->private.search.result = T; + else if (size -= i) { /* more to do, blat slop down */ + memmove (tmp,tmp+MAILTMPLEN-SEARCHSLOP,(size_t) SEARCHSLOP); + do { /* read subsequent buffers one at a time */ + (*f) (stream,i = min (size,(long) MAILTMPLEN),tmp+SEARCHSLOP); + st.size = i + SEARCHSLOP; + if (mail_search_string (&st,NIL,&md->stream->private.search.string)) + md->stream->private.search.result = T; + else memmove (tmp,tmp+MAILTMPLEN,(size_t) SEARCHSLOP); + } + while ((size -= i) && !md->stream->private.search.result); + } + if (size) { /* toss out everything after that */ + do (*f) (stream,i = min (size,(long) MAILTMPLEN),tmp); + while (size -= i); + } + return NIL; +} + +/* Mail parse search criteria + * Accepts: criteria + * Returns: search program if parse successful, else NIL + */ + +SEARCHPGM *mail_criteria (char *criteria) +{ + SEARCHPGM *pgm = NIL; + char *criterion,*r,tmp[MAILTMPLEN]; + int f; + if (criteria) { /* only if criteria defined */ + /* make writeable copy of criteria */ + criteria = cpystr (criteria); + /* for each criterion */ + for (pgm = mail_newsearchpgm (), criterion = strtok_r (criteria," ",&r); + criterion; (criterion = strtok_r (NIL," ",&r))) { + f = NIL; /* init then scan the criterion */ + switch (*ucase (criterion)) { + case 'A': /* possible ALL, ANSWERED */ + if (!strcmp (criterion+1,"LL")) f = T; + else if (!strcmp (criterion+1,"NSWERED")) f = pgm->answered = T; + break; + case 'B': /* possible BCC, BEFORE, BODY */ + if (!strcmp (criterion+1,"CC")) + f = mail_criteria_string (&pgm->bcc,&r); + else if (!strcmp (criterion+1,"EFORE")) + f = mail_criteria_date (&pgm->before,&r); + else if (!strcmp (criterion+1,"ODY")) + f = mail_criteria_string (&pgm->body,&r); + break; + case 'C': /* possible CC */ + if (!strcmp (criterion+1,"C")) f = mail_criteria_string (&pgm->cc,&r); + break; + case 'D': /* possible DELETED */ + if (!strcmp (criterion+1,"ELETED")) f = pgm->deleted = T; + break; + case 'F': /* possible FLAGGED, FROM */ + if (!strcmp (criterion+1,"LAGGED")) f = pgm->flagged = T; + else if (!strcmp (criterion+1,"ROM")) + f = mail_criteria_string (&pgm->from,&r); + break; + case 'K': /* possible KEYWORD */ + if (!strcmp (criterion+1,"EYWORD")) + f = mail_criteria_string (&pgm->keyword,&r); + break; + + case 'N': /* possible NEW */ + if (!strcmp (criterion+1,"EW")) f = pgm->recent = pgm->unseen = T; + break; + case 'O': /* possible OLD, ON */ + if (!strcmp (criterion+1,"LD")) f = pgm->old = T; + else if (!strcmp (criterion+1,"N")) + f = mail_criteria_date (&pgm->on,&r); + break; + case 'R': /* possible RECENT */ + if (!strcmp (criterion+1,"ECENT")) f = pgm->recent = T; + break; + case 'S': /* possible SEEN, SINCE, SUBJECT */ + if (!strcmp (criterion+1,"EEN")) f = pgm->seen = T; + else if (!strcmp (criterion+1,"INCE")) + f = mail_criteria_date (&pgm->since,&r); + else if (!strcmp (criterion+1,"UBJECT")) + f = mail_criteria_string (&pgm->subject,&r); + break; + case 'T': /* possible TEXT, TO */ + if (!strcmp (criterion+1,"EXT")) + f = mail_criteria_string (&pgm->text,&r); + else if (!strcmp (criterion+1,"O")) + f = mail_criteria_string (&pgm->to,&r); + break; + case 'U': /* possible UN* */ + if (criterion[1] == 'N') { + if (!strcmp (criterion+2,"ANSWERED")) f = pgm->unanswered = T; + else if (!strcmp (criterion+2,"DELETED")) f = pgm->undeleted = T; + else if (!strcmp (criterion+2,"FLAGGED")) f = pgm->unflagged = T; + else if (!strcmp (criterion+2,"KEYWORD")) + f = mail_criteria_string (&pgm->unkeyword,&r); + else if (!strcmp (criterion+2,"SEEN")) f = pgm->unseen = T; + } + break; + default: /* we will barf below */ + break; + } + if (!f) { /* if can't identify criterion */ + sprintf (tmp,"Unknown search criterion: %.30s",criterion); + MM_LOG (tmp,ERROR); + mail_free_searchpgm (&pgm); + break; + } + } + /* no longer need copy of criteria */ + fs_give ((void **) &criteria); + } + return pgm; +} + +/* Parse a date + * Accepts: pointer to date integer to return + * pointer to strtok state + * Returns: T if successful, else NIL + */ + +int mail_criteria_date (unsigned short *date,char **r) +{ + STRINGLIST *s = NIL; + MESSAGECACHE elt; + /* parse the date and return fn if OK */ + int ret = (mail_criteria_string (&s,r) && + mail_parse_date (&elt,(char *) s->text.data) && + (*date = mail_shortdate (elt.year,elt.month,elt.day))) ? + T : NIL; + if (s) mail_free_stringlist (&s); + return ret; +} + +/* Calculate shortdate from elt values + * Accepts: year (0 = BASEYEAR) + * month (1 = January) + * day + * Returns: shortdate + */ + +unsigned short mail_shortdate (unsigned int year,unsigned int month, + unsigned int day) +{ + return (year << 9) + (month << 5) + day; +} + +/* Parse a string + * Accepts: pointer to stringlist + * pointer to strtok state + * Returns: T if successful, else NIL + */ + +int mail_criteria_string (STRINGLIST **s,char **r) +{ + unsigned long n; + char e,*d,*end = " ",*c = strtok_r (NIL,"",r); + if (!c) return NIL; /* missing argument */ + switch (*c) { /* see what the argument is */ + case '{': /* literal string */ + n = strtoul (c+1,&d,10); /* get its length */ + if ((*d++ == '}') && (*d++ == '\015') && (*d++ == '\012') && + (!(*(c = d + n)) || (*c == ' '))) { + e = *--c; /* store old delimiter */ + *c = '\377'; /* make sure not a space */ + strtok_r (c," ",r); /* reset the strtok mechanism */ + *c = e; /* put character back */ + break; + } + case '\0': /* catch bogons */ + case ' ': + return NIL; + case '"': /* quoted string */ + if (strchr (c+1,'"')) end = "\""; + else return NIL; /* falls through */ + default: /* atomic string */ + if (d = strtok_r (c,end,r)) n = strlen (d); + else return NIL; + break; + } + while (*s) s = &(*s)->next; /* find tail of list */ + *s = mail_newstringlist (); /* make new entry */ + /* return the data */ + (*s)->text.data = (unsigned char *) cpystr (d); + (*s)->text.size = n; + return T; +} + +/* Mail parse set from string + * Accepts: string to parse + * pointer to updated string pointer for return + * Returns: set with pointer updated, or NIL if error + */ + +SEARCHSET *mail_parse_set (char *s,char **ret) +{ + SEARCHSET *cur; + SEARCHSET *set = NIL; + while (isdigit (*s)) { + if (!set) cur = set = mail_newsearchset (); + else cur = cur->next = mail_newsearchset (); + /* parse value */ + if (!(cur->first = strtoul (s,&s,10)) || + ((*s == ':') && !(isdigit (*++s) && (cur->last = strtoul (s,&s,10))))) + break; /* bad value or range */ + if (*s == ',') ++s; /* point to next value if more */ + else { /* end of set */ + *ret = s; /* set return pointer */ + return set; /* return set */ + } + } + mail_free_searchset (&set); /* failure, punt partial set */ + return NIL; +} + + +/* Mail append to set + * Accepts: head of search set or NIL to do nothing + * message to add + * Returns: tail of search set or NIL if did nothing + */ + +SEARCHSET *mail_append_set (SEARCHSET *set,unsigned long msgno) +{ + if (set) { /* find tail */ + while (set->next) set = set->next; + /* start of set if no first member */ + if (!set->first) set->first = msgno; + else if (msgno == (set->last ? set->last : set->first) + 1) + set->last = msgno; /* extend range if 1 past current */ + else (set = set->next = mail_newsearchset ())->first = msgno; + } + return set; +} + +/* Mail sort messages + * Accepts: mail stream + * character set + * search program + * sort program + * option flags + * Returns: vector of sorted message sequences or NIL if error + */ + +unsigned long *mail_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, + SORTPGM *pgm,long flags) +{ + unsigned long *ret = NIL; + if (stream->dtb) /* do the driver's action */ + ret = (*(stream->dtb->sort ? stream->dtb->sort : mail_sort_msgs)) + (stream,charset,spg,pgm,flags); + /* flush search/sort programs if requested */ + if (spg && (flags & SE_FREE)) mail_free_searchpgm (&spg); + if (flags & SO_FREE) mail_free_sortpgm (&pgm); + return ret; +} + +/* Mail sort messages work routine + * Accepts: mail stream + * character set + * search program + * sort program + * option flags + * Returns: vector of sorted message sequences or NIL if error + */ + +unsigned long *mail_sort_msgs (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, + SORTPGM *pgm,long flags) +{ + unsigned long i; + SORTCACHE **sc; + unsigned long *ret = NIL; + if (spg) { /* only if a search needs to be done */ + int silent = stream->silent; + stream->silent = T; /* don't pass up mm_searched() events */ + /* search for messages */ + mail_search_full (stream,charset,spg,NIL); + stream->silent = silent; /* restore silence state */ + } + /* initialize progress counters */ + pgm->nmsgs = pgm->progress.cached = 0; + /* pass 1: count messages to sort */ + for (i = 1; i <= stream->nmsgs; ++i) + if (mail_elt (stream,i)->searched) pgm->nmsgs++; + if (pgm->nmsgs) { /* pass 2: sort cache */ + sc = mail_sort_loadcache (stream,pgm); + /* pass 3: sort messages */ + if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags); + fs_give ((void **) &sc); /* don't need sort vector any more */ + } + /* empty sort results */ + else ret = (unsigned long *) memset (fs_get (sizeof (unsigned long)),0, + sizeof (unsigned long)); + /* also return via callback if requested */ + if (mailsortresults) (*mailsortresults) (stream,ret,pgm->nmsgs); + return ret; /* return sort results */ +} + +/* Mail sort sortcache vector + * Accepts: mail stream + * sort program + * sortcache vector + * option flags + * Returns: vector of sorted message sequences or NIL if error + */ + +unsigned long *mail_sort_cache (MAILSTREAM *stream,SORTPGM *pgm,SORTCACHE **sc, + long flags) +{ + unsigned long i,*ret; + /* pass 3: sort messages */ + qsort ((void *) sc,pgm->nmsgs,sizeof (SORTCACHE *),mail_sort_compare); + /* optional post sorting */ + if (pgm->postsort) (*pgm->postsort) ((void *) sc); + /* pass 4: return results */ + ret = (unsigned long *) fs_get ((pgm->nmsgs+1) * sizeof (unsigned long)); + if (flags & SE_UID) /* UID or msgno? */ + for (i = 0; i < pgm->nmsgs; i++) ret[i] = mail_uid (stream,sc[i]->num); + else for (i = 0; i < pgm->nmsgs; i++) ret[i] = sc[i]->num; + ret[pgm->nmsgs] = 0; /* tie off message list */ + return ret; +} + +/* Mail load sortcache + * Accepts: mail stream, already searched + * sort program + * Returns: vector of sortcache pointers matching search + */ + +static STRINGLIST maildateline = {{(unsigned char *) "date",4},NIL}; +static STRINGLIST mailrnfromline = {{(unsigned char *) ">from",5},NIL}; +static STRINGLIST mailfromline = {{(unsigned char *) "from",4}, + &mailrnfromline}; +static STRINGLIST mailtonline = {{(unsigned char *) "to",2},NIL}; +static STRINGLIST mailccline = {{(unsigned char *) "cc",2},NIL}; +static STRINGLIST mailsubline = {{(unsigned char *) "subject",7},NIL}; + +SORTCACHE **mail_sort_loadcache (MAILSTREAM *stream,SORTPGM *pgm) +{ + char *t,*v,*x,tmp[MAILTMPLEN]; + SORTPGM *pg; + SORTCACHE *s,**sc; + MESSAGECACHE *elt,telt; + ENVELOPE *env; + ADDRESS *adr = NIL; + unsigned long i = (pgm->nmsgs) * sizeof (SORTCACHE *); + sc = (SORTCACHE **) memset (fs_get ((size_t) i),0,(size_t) i); + /* see what needs to be loaded */ + for (i = 1; !pgm->abort && (i <= stream->nmsgs); i++) + if ((elt = mail_elt (stream,i))->searched) { + sc[pgm->progress.cached++] = + s = (SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE); + s->pgm = pgm; /* note sort program */ + s->num = i; + /* get envelope if cached */ + if (stream->scache) env = (i == stream->msgno) ? stream->env : NIL; + else env = elt->private.msg.env; + for (pg = pgm; pg; pg = pg->next) switch (pg->function) { + case SORTARRIVAL: /* sort by arrival date */ + if (!s->arrival) { + /* internal date unknown but can get? */ + if (!elt->day && !(stream->dtb->flags & DR_NOINTDATE)) { + sprintf (tmp,"%lu",i); + mail_fetch_fast (stream,tmp,NIL); + } + /* wrong thing before 3-Jan-1970 */ + s->arrival = elt->day ? mail_longdate (elt) : 1; + s->dirty = T; + } + break; + case SORTSIZE: /* sort by message size */ + if (!s->size) { + if (!elt->rfc822_size) { + sprintf (tmp,"%lu",i); + mail_fetch_fast (stream,tmp,NIL); + } + s->size = elt->rfc822_size ? elt->rfc822_size : 1; + s->dirty = T; + } + break; + + case SORTDATE: /* sort by date */ + if (!s->date) { + if (env) t = env->date; + else if ((t = mail_fetch_header (stream,i,NIL,&maildateline,NIL, + FT_INTERNAL | FT_PEEK)) && + (t = strchr (t,':'))) + for (x = ++t; x = strpbrk (x,"\012\015"); x++) + switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){ + case ' ': /* erase continuation newlines */ + case '\t': + memmove (x,v,strlen (v)); + break; + default: /* tie off extraneous text */ + *x = x[1] = '\0'; + } + /* skip leading whitespace */ + if (t) while ((*t == ' ') || (*t == '\t')) t++; + /* parse date from Date: header */ + if (!(t && mail_parse_date (&telt,t) && + (s->date = mail_longdate (&telt)))) { + /* failed, use internal date */ + if (!(s->date = s->arrival)) { + /* internal date unknown but can get? */ + if (!elt->day && !(stream->dtb->flags & DR_NOINTDATE)) { + sprintf (tmp,"%lu",i); + mail_fetch_fast (stream,tmp,NIL); + } + /* wrong thing before 3-Jan-1970 */ + s->date = (s->arrival = elt->day ? mail_longdate (elt) : 1); + } + } + s->dirty = T; + } + break; + + case SORTFROM: /* sort by first from */ + if (!s->from) { + if (env) s->from = env->from && env->from->mailbox ? + cpystr (env->from->mailbox) : NIL; + else if ((t = mail_fetch_header (stream,i,NIL,&mailfromline,NIL, + FT_INTERNAL | FT_PEEK)) && + (t = strchr (t,':'))) { + for (x = ++t; x = strpbrk (x,"\012\015"); x++) + switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){ + case ' ': /* erase continuation newlines */ + case '\t': + memmove (x,v,strlen (v)); + break; + case 'f': /* continuation but with extra "From:" */ + case 'F': + if (v = strchr (v,':')) { + memmove (x,v+1,strlen (v+1)); + break; + } + default: /* tie off extraneous text */ + *x = x[1] = '\0'; + } + if (adr = rfc822_parse_address (&adr,adr,&t,BADHOST,0)) { + s->from = adr->mailbox; + adr->mailbox = NIL; + mail_free_address (&adr); + } + } + if (!s->from) s->from = cpystr (""); + s->dirty = T; + } + break; + + case SORTTO: /* sort by first to */ + if (!s->to) { + if (env) s->to = env->to && env->to->mailbox ? + cpystr (env->to->mailbox) : NIL; + else if ((t = mail_fetch_header (stream,i,NIL,&mailtonline,NIL, + FT_INTERNAL | FT_PEEK)) && + (t = strchr (t,':'))) { + for (x = ++t; x = strpbrk (x,"\012\015"); x++) + switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){ + case ' ': /* erase continuation newlines */ + case '\t': + memmove (x,v,strlen (v)); + break; + case 't': /* continuation but with extra "To:" */ + case 'T': + if (v = strchr (v,':')) { + memmove (x,v+1,strlen (v+1)); + break; + } + default: /* tie off extraneous text */ + *x = x[1] = '\0'; + } + if (adr = rfc822_parse_address (&adr,adr,&t,BADHOST,0)) { + s->to = adr->mailbox; + adr->mailbox = NIL; + mail_free_address (&adr); + } + } + if (!s->to) s->to = cpystr (""); + s->dirty = T; + } + break; + + case SORTCC: /* sort by first cc */ + if (!s->cc) { + if (env) s->cc = env->cc && env->cc->mailbox ? + cpystr (env->cc->mailbox) : NIL; + else if ((t = mail_fetch_header (stream,i,NIL,&mailccline,NIL, + FT_INTERNAL | FT_PEEK)) && + (t = strchr (t,':'))) { + for (x = ++t; x = strpbrk (x,"\012\015"); x++) + switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){ + case ' ': /* erase continuation newlines */ + case '\t': + memmove (x,v,strlen (v)); + break; + case 't': /* continuation but with extra "To:" */ + case 'T': + if (v = strchr (v,':')) { + memmove (x,v+1,strlen (v+1)); + break; + } + default: /* tie off extraneous text */ + *x = x[1] = '\0'; + } + if (adr = rfc822_parse_address (&adr,adr,&t,BADHOST,0)) { + s->cc = adr->mailbox; + adr->mailbox = NIL; + mail_free_address (&adr); + } + } + if (!s->cc) s->cc = cpystr (""); + s->dirty = T; + } + break; + + case SORTSUBJECT: /* sort by subject */ + if (!s->subject) { + /* get subject from envelope if have one */ + if (env) t = env->subject ? env->subject : ""; + /* otherwise snarf from header text */ + else if ((t = mail_fetch_header (stream,i,NIL,&mailsubline, + NIL,FT_INTERNAL | FT_PEEK)) && + (t = strchr (t,':'))) + for (x = ++t; x = strpbrk (x,"\012\015"); x++) + switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){ + case ' ': /* erase continuation newlines */ + case '\t': + memmove (x,v,strlen (v)); + break; + default: /* tie off extraneous text */ + *x = x[1] = '\0'; + } + else t = ""; /* empty subject */ + /* strip and cache subject */ + s->refwd = mail_strip_subject (t,&s->subject); + s->dirty = T; + } + break; + default: + fatal ("Unknown sort function"); + } + } + return sc; +} + +/* Strip subjects of extra spaces and leading and trailing cruft for sorting + * Accepts: unstripped subject + * pointer to return stripped subject, in cpystr form + * Returns: T if subject had a re/fwd, NIL otherwise + */ + +unsigned int mail_strip_subject (char *t,char **ret) +{ + SIZEDTEXT src,dst; + unsigned long i,slen; + char c,*s,*x; + unsigned int refwd = NIL; + if (src.size = strlen (t)) { /* have non-empty subject? */ + src.data = (unsigned char *) t; + /* Step 1 */ + /* make copy, convert MIME2 if needed */ + *ret = s = (utf8_mime2text (&src,&dst,U8T_CANONICAL) && + (src.data != dst.data)) ? (char *) dst.data : cpystr (t); + /* convert spaces to tab, strip extra spaces */ + for (x = t = s, c = 'x'; *t; t++) { + if (c != ' ') c = *x++ = ((*t == '\t') ? ' ' : *t); + else if ((*t != '\t') && (*t != ' ')) c = *x++ = *t; + } + *x = '\0'; /* tie off string */ + /* Step 2 */ + for (slen = dst.size; s; slen = strlen (s)) { + for (t = s + slen; t > s; ) switch (t[-1]) { + case ' ': case '\t': /* WSP */ + *--t = '\0'; /* just remove it */ + break; + case ')': /* possible "(fwd)" */ + if ((t >= (s + 5)) && (t[-5] == '(') && + ((t[-4] == 'F') || (t[-4] == 'f')) && + ((t[-3] == 'W') || (t[-3] == 'w')) && + ((t[-2] == 'D') || (t[-2] == 'd'))) { + *(t -= 5) = '\0'; /* remove "(fwd)" */ + refwd = T; /* note a re/fwd */ + break; + } + default: /* not a subj-trailer */ + t = s; + break; + } + /* Steps 3-5 */ + for (t = s; t; ) switch (*s) { + case ' ': case '\t': /* WSP */ + s = t = mail_strip_subject_wsp (s + 1); + break; + case 'r': case 'R': /* possible "re" */ + if (((s[1] == 'E') || (s[1] == 'e')) && + (t = mail_strip_subject_wsp (s + 2)) && + (t = mail_strip_subject_blob (t)) && (*t == ':')) { + s = ++t; /* found "re" */ + refwd = T; /* definitely a re/fwd at this point */ + } + else t = NIL; /* found subj-middle */ + break; + case 'f': case 'F': /* possible "fw" or "fwd" */ + if (((s[1] == 'w') || (s[1] == 'W')) && + (((s[2] == 'd') || (s[2] == 'D')) ? + (t = mail_strip_subject_wsp (s + 3)) : + (t = mail_strip_subject_wsp (s + 2))) && + (t = mail_strip_subject_blob (t)) && (*t == ':')) { + s = ++t; /* found "fwd" */ + refwd = T; /* definitely a re/fwd at this point */ + } + else t = NIL; /* found subj-middle */ + break; + case '[': /* possible subj-blob */ + if ((t = mail_strip_subject_blob (s)) && *t) s = t; + else t = NIL; /* found subj-middle */ + break; + default: + t = NIL; /* found subj-middle */ + break; + } + /* Step 6 */ + /* Netscape-style "[Fwd: ...]"? */ + if ((*s == '[') && ((s[1] == 'F') || (s[1] == 'f')) && + ((s[2] == 'W') || (s[2] == 'w')) && + ((s[3] == 'D') || (s[3] == 'd')) && (s[4] == ':') && + (s[i = strlen (s) - 1] == ']')) { + s[i] = '\0'; /* flush closing "]" */ + s += 5; /* and leading "[Fwd:" */ + refwd = T; /* definitely a re/fwd at this point */ + } + else break; /* don't need to loop back to step 2 */ + } + if (s != (t = *ret)) { /* removed leading text? */ + s = *ret = cpystr (s); /* yes, make a fresh return copy */ + fs_give ((void **) &t); /* flush old copy */ + } + } + else *ret = cpystr (""); /* empty subject */ + return refwd; /* return re/fwd state */ +} + +/* Strip subject wsp helper routine + * Accepts: text + * Returns: pointer to text after blob + */ + +char *mail_strip_subject_wsp (char *s) +{ + while ((*s == ' ') || (*s == '\t')) s++; + return s; +} + + +/* Strip subject blob helper routine + * Accepts: text + * Returns: pointer to text after any blob, NIL if blob-like but not blob + */ + +char *mail_strip_subject_blob (char *s) +{ + if (*s != '[') return s; /* not a blob, ignore */ + /* search for end of blob */ + while (*++s != ']') if ((*s == '[') || !*s) return NIL; + return mail_strip_subject_wsp (s + 1); +} + +/* Sort compare messages + * Accept: first message sort cache element + * second message sort cache element + * Returns: -1 if a1 < a2, 0 if a1 == a2, 1 if a1 > a2 + */ + +int mail_sort_compare (const void *a1,const void *a2) +{ + int i = 0; + SORTCACHE *s1 = *(SORTCACHE **) a1; + SORTCACHE *s2 = *(SORTCACHE **) a2; + SORTPGM *pgm = s1->pgm; + if (!s1->sorted) { /* this one sorted yet? */ + s1->sorted = T; + pgm->progress.sorted++; /* another sorted message */ + } + if (!s2->sorted) { /* this one sorted yet? */ + s2->sorted = T; + pgm->progress.sorted++; /* another sorted message */ + } + do { + switch (pgm->function) { /* execute search program */ + case SORTDATE: /* sort by date */ + i = compare_ulong (s1->date,s2->date); + break; + case SORTARRIVAL: /* sort by arrival date */ + i = compare_ulong (s1->arrival,s2->arrival); + break; + case SORTSIZE: /* sort by message size */ + i = compare_ulong (s1->size,s2->size); + break; + case SORTFROM: /* sort by first from */ + i = compare_cstring (s1->from,s2->from); + break; + case SORTTO: /* sort by first to */ + i = compare_cstring (s1->to,s2->to); + break; + case SORTCC: /* sort by first cc */ + i = compare_cstring (s1->cc,s2->cc); + break; + case SORTSUBJECT: /* sort by subject */ + i = compare_cstring (s1->subject,s2->subject); + break; + } + if (pgm->reverse) i = -i; /* flip results if necessary */ + } + while (pgm = i ? NIL : pgm->next); + /* return result, avoid 0 if at all possible */ + return i ? i : compare_ulong (s1->num,s2->num); +} + +/* Return message date as an unsigned long seconds since time began + * Accepts: message cache pointer + * Returns: unsigned long of date + * + * This routine, like most UNIX systems, is clueless about leap seconds. + * Thus, it treats 23:59:60 as equivalent to 00:00:00 the next day. + * + * This routine forces any early hours on 1-Jan-1970 in oriental timezones + * to be 1-Jan-1970 00:00:00 UTC, so as to avoid negative longdates. + */ + +unsigned long mail_longdate (MESSAGECACHE *elt) +{ + unsigned long m = elt->month ? elt->month : 1; + unsigned long yr = elt->year + BASEYEAR; + /* number of days since time began */ + unsigned long ret = (elt->day ? (elt->day - 1) : 0) + + 30 * (m - 1) + ((m + (m > 8)) / 2) +#ifndef USEJULIANCALENDAR +#ifndef USEORTHODOXCALENDAR /* Gregorian calendar */ + + ((yr / 400) - (BASEYEAR / 400)) - ((yr / 100) - (BASEYEAR / 100)) +#ifdef Y4KBUGFIX + - ((yr / 4000) - (BASEYEAR / 4000)) +#endif + - ((m < 3) ? + !(yr % 4) && ((yr % 100) || (!(yr % 400) +#ifdef Y4KBUGFIX + && (yr % 4000) +#endif + )) : 2) +#else /* Orthodox calendar */ + + ((2*(yr / 900)) - (2*(BASEYEAR / 900))) + + (((yr % 900) >= 200) - ((BASEYEAR % 900) >= 200)) + + (((yr % 900) >= 600) - ((BASEYEAR % 900) >= 600)) + - ((yr / 100) - (BASEYEAR / 100)) + - ((m < 3) ? + !(yr % 4) && ((yr % 100) || ((yr % 900) == 200) || ((yr % 900) == 600)) + : 2) +#endif +#endif + + elt->year * 365 + (((unsigned long) (elt->year + (BASEYEAR % 4))) / 4); + ret *= 24; ret += elt->hours; /* date value in hours */ + ret *= 60; ret +=elt->minutes;/* date value in minutes */ + yr = (elt->zhours * 60) + elt->zminutes; + if (elt->zoccident) ret += yr;/* occidental timezone, make UTC */ + else if (ret < yr) return 0; /* still 31-Dec-1969 in UTC */ + else ret -= yr; /* oriental timezone, make UTC */ + ret *= 60; ret += elt->seconds; + return ret; +} + +/* Mail thread messages + * Accepts: mail stream + * thread type + * character set + * search program + * option flags + * Returns: thread node tree or NIL if error + */ + +THREADNODE *mail_thread (MAILSTREAM *stream,char *type,char *charset, + SEARCHPGM *spg,long flags) +{ + THREADNODE *ret = NIL; + if (stream->dtb) /* must have a live driver */ + ret = stream->dtb->thread ? /* do driver's action if available */ + (*stream->dtb->thread) (stream,type,charset,spg,flags) : + mail_thread_msgs (stream,type,charset,spg,flags,mail_sort_msgs); + /* flush search/sort programs if requested */ + if (spg && (flags & SE_FREE)) mail_free_searchpgm (&spg); + return ret; +} + + +/* Mail thread messages + * Accepts: mail stream + * thread type + * character set + * search program + * option flags + * sorter routine + * Returns: thread node tree or NIL if error + */ + +THREADNODE *mail_thread_msgs (MAILSTREAM *stream,char *type,char *charset, + SEARCHPGM *spg,long flags,sorter_t sorter) +{ + THREADER *t; + for (t = &mailthreadlist; t; t = t->next) + if (!compare_cstring (type,t->name)) { + THREADNODE *ret = (*t->dispatch) (stream,charset,spg,flags,sorter); + if (mailthreadresults) (*mailthreadresults) (stream,ret); + return ret; + } + MM_LOG ("No such thread type",ERROR); + return NIL; +} + +/* Mail thread ordered subject + * Accepts: mail stream + * character set + * search program + * option flags + * sorter routine + * Returns: thread node tree + */ + +THREADNODE *mail_thread_orderedsubject (MAILSTREAM *stream,char *charset, + SEARCHPGM *spg,long flags, + sorter_t sorter) +{ + THREADNODE *thr = NIL; + THREADNODE *cur,*top,**tc; + SORTPGM pgm,pgm2; + SORTCACHE *s; + unsigned long i,j,*lst,*ls; + /* sort by subject+date */ + memset (&pgm,0,sizeof (SORTPGM)); + memset (&pgm2,0,sizeof (SORTPGM)); + pgm.function = SORTSUBJECT; + pgm.next = &pgm2; + pgm2.function = SORTDATE; + if (lst = (*sorter) (stream,charset,spg,&pgm,flags & ~(SE_FREE | SE_UID))){ + if (*(ls = lst)) { /* create thread */ + /* note first subject */ + cur = top = thr = mail_newthreadnode + ((SORTCACHE *) (*mailcache) (stream,*ls++,CH_SORTCACHE)); + /* note its number */ + cur->num = (flags & SE_UID) ? mail_uid (stream,*lst) : *lst; + i = 1; /* number of threads */ + while (*ls) { /* build tree */ + /* subjects match? */ + s = (SORTCACHE *) (*mailcache) (stream,*ls++,CH_SORTCACHE); + if (compare_cstring (top->sc->subject,s->subject)) { + i++; /* have a new thread */ + top = top->branch = cur = mail_newthreadnode (s); + } + /* start a child of the top */ + else if (cur == top) cur = cur->next = mail_newthreadnode (s); + /* sibling of child */ + else cur = cur->branch = mail_newthreadnode (s); + /* set to msgno or UID as needed */ + cur->num = (flags & SE_UID) ? mail_uid (stream,s->num) : s->num; + } + /* make threadnode cache */ + tc = (THREADNODE **) fs_get (i * sizeof (THREADNODE *)); + /* load threadnode cache */ + for (j = 0, cur = thr; cur; cur = cur->branch) tc[j++] = cur; + if (i != j) fatal ("Threadnode cache confusion"); + qsort ((void *) tc,i,sizeof (THREADNODE *),mail_thread_compare_date); + for (j = 0, --i; j < i; j++) tc[j]->branch = tc[j+1]; + tc[j]->branch = NIL; /* end of root */ + thr = tc[0]; /* head of data */ + fs_give ((void **) &tc); + } + fs_give ((void **) &lst); + } + return thr; +} + +/* Mail thread references + * Accepts: mail stream + * character set + * search program + * option flags + * sorter routine + * Returns: thread node tree + */ + +#define REFHASHSIZE 1009 /* arbitrary prime for hash table size */ + +/* Reference threading container, as described in Jamie Zawinski's web page + * (http://www.jwz.org/doc/threading.html) for this algorithm. These are + * stored as extended data in the hash table (called "id_table" in JWZ's + * document) and are maintained by the hash table routines. The hash table + * routines implement extended data as additional void* words at the end of + * each bucket, hence these strange macros instead of a struct which would + * have been more straightforward. + */ + +#define THREADLINKS 3 /* number of thread links */ + +#define CACHE(data) ((SORTCACHE *) (data)[0]) +#define PARENT(data) ((container_t) (data)[1]) +#define SETPARENT(data,value) ((container_t) (data[1] = value)) +#define SIBLING(data) ((container_t) (data)[2]) +#define SETSIBLING(data,value) ((container_t) (data[2] = value)) +#define CHILD(data) ((container_t) (data)[3]) +#define SETCHILD(data,value) ((container_t) (data[3] = value)) + +THREADNODE *mail_thread_references (MAILSTREAM *stream,char *charset, + SEARCHPGM *spg,long flags,sorter_t sorter) +{ + MESSAGECACHE *elt,telt; + ENVELOPE *env; + SORTCACHE *s; + STRINGLIST *st; + HASHENT *he; + THREADNODE **tc,*cur,*lst,*nxt,*sis,*msg; + container_t con,nxc,prc,sib; + void **sub; + char *t,tmp[MAILTMPLEN]; + unsigned long j,nmsgs; + unsigned long i = stream->nmsgs * sizeof (SORTCACHE *); + SORTCACHE **sc = (SORTCACHE **) memset (fs_get ((size_t) i),0,(size_t) i); + HASHTAB *ht = hash_create (REFHASHSIZE); + THREADNODE *root = NIL; + if (spg) { /* only if a search needs to be done */ + int silent = stream->silent; + stream->silent = T; /* don't pass up mm_searched() events */ + /* search for messages */ + mail_search_full (stream,charset,spg,NIL); + stream->silent = silent; /* restore silence state */ + } + + /* create SORTCACHE vector of requested msgs */ + for (i = 1, nmsgs = 0; i <= stream->nmsgs; ++i) + if (mail_elt (stream,i)->searched) + (sc[nmsgs++] = (SORTCACHE *)(*mailcache)(stream,i,CH_SORTCACHE))->num =i; + /* separate pass so can do overview fetch lookahead */ + for (i = 0; i < nmsgs; ++i) { /* for each requested message */ + /* is anything missing in its SORTCACHE? */ + if (!((s = sc[i])->date && s->subject && s->message_id && s->references)) { + /* driver has an overview mechanism? */ + if (stream->dtb && stream->dtb->overview) { + /* yes, find following unloaded entries */ + for (j = i + 1; (j < nmsgs) && !sc[j]->references; ++j); + sprintf (tmp,"%lu",mail_uid (stream,s->num)); + if (i != --j) /* end of range different? */ + sprintf (tmp + strlen (tmp),":%lu",mail_uid (stream,sc[j]->num)); + /* load via overview mechanism */ + mail_fetch_overview (stream,tmp,mail_thread_loadcache); + } + /* still missing data? */ + if (!s->date || !s->subject || !s->message_id || !s->references) { + /* try to load data from envelope */ + if (env = mail_fetch_structure (stream,s->num,NIL,NIL)) { + if (!s->date && env->date && mail_parse_date (&telt,env->date)) + s->date = mail_longdate (&telt); + if (!s->subject && env->subject) + s->refwd = + mail_strip_subject (env->subject,&s->subject); + if (!s->message_id && env->message_id && *env->message_id) + s->message_id = mail_thread_parse_msgid (env->message_id,NIL); + if (!s->references && /* use References: or In-Reply-To: */ + !(s->references = + mail_thread_parse_references (env->references,T))) + s->references = mail_thread_parse_references(env->in_reply_to,NIL); + } + /* last resort */ + if (!s->date && !(s->date = s->arrival)) { + /* internal date unknown but can get? */ + if (!(elt = mail_elt (stream,s->num))->day && + !(stream->dtb->flags & DR_NOINTDATE)) { + sprintf (tmp,"%lu",s->num); + mail_fetch_fast (stream,tmp,NIL); + } + /* wrong thing before 3-Jan-1970 */ + s->date = (s->arrival = elt->day ? mail_longdate (elt) : 1); + } + if (!s->subject) s->subject = cpystr (""); + if (!s->references) s->references = mail_newstringlist (); + s->dirty = T; + } + } + + /* Step 1 (preliminary) */ + /* generate unique string */ + sprintf (tmp,"%s.%lx.%lx@%s",stream->mailbox,stream->uid_validity, + mail_uid (stream,s->num),mylocalhost ()); + /* flush old unique string if not message-id */ + if (s->unique && (s->unique != s->message_id)) + fs_give ((void **) &s->unique); + s->unique = s->message_id ? /* don't permit Message ID duplicates */ + (hash_lookup (ht,s->message_id) ? cpystr (tmp) : s->message_id) : + (s->message_id = cpystr (tmp)); + /* add unique string to hash table */ + hash_add (ht,s->unique,s,THREADLINKS); + } + /* Step 1 */ + for (i = 0; i < nmsgs; ++i) { /* for each message in sortcache */ + /* Step 1A */ + if ((st = (s = sc[i])->references) && st->text.data) + for (con = hash_lookup_and_add (ht,(char *) st->text.data,NIL, + THREADLINKS); st = st->next; con = nxc) { + nxc = hash_lookup_and_add (ht,(char *) st->text.data,NIL,THREADLINKS); + /* only if no parent & won't introduce loop */ + if (!PARENT (nxc) && !mail_thread_check_child (con,nxc)) { + SETPARENT (nxc,con); /* establish parent/child link */ + /* other children become sibling of this one */ + SETSIBLING (nxc,CHILD (con)); + SETCHILD (con,nxc); /* set as child of parent */ + } + } + else con = NIL; /* else message has no ancestors */ + /* Step 1B */ + if ((prc = PARENT ((nxc = hash_lookup (ht,s->unique)))) && + (prc != con)) { /* break links if have a different parent */ + SETPARENT (nxc,NIL); /* easy if direct child */ + if (nxc == CHILD (prc)) SETCHILD (prc,SIBLING (nxc)); + else { /* otherwise hunt through sisters */ + for (sib = CHILD (prc); nxc != SIBLING (sib); sib = SIBLING (sib)); + SETSIBLING (sib,SIBLING (nxc)); + } + SETSIBLING (nxc,NIL); /* no more little sisters either */ + prc = NIL; /* no more parent set */ + } + /* need to set parent, and parent is good? */ + if (!prc && !mail_thread_check_child (con,nxc)) { + SETPARENT (nxc,con); /* establish parent/child link */ + if (con) { /* if non-root parent, set parent's child */ + if (CHILD (con)) { /* have a child already */ + /* find youngest daughter */ + for (con = CHILD (con); SIBLING (con); con = SIBLING (con)); + SETSIBLING (con,nxc); /* add new baby sister */ + } + else SETCHILD (con,nxc);/* set as only child */ + } + } + } + fs_give ((void **) &sc); /* finished with sortcache vector */ + + /* Step 2 */ + /* search hash table for parentless messages */ + for (i = 0, prc = con = NIL; i < ht->size; i++) + for (he = ht->table[i]; he; he = he->next) + if (!PARENT ((nxc = he->data))) { + /* sibling of previous parentless message */ + if (con) con = SETSIBLING (con,nxc); + else prc = con = nxc; /* first parentless message */ + } + /* Once the dummy containers are pruned, we no longer need the parent + * information, so we can convert the containers to THREADNODEs. Since + * we don't need the id_table any more either, we can reset the hash table + * and reuse it as a subject_table. Resetting the hash table will also + * destroy the containers. + */ + /* Step 3 */ + /* prune dummies, convert to threadnode */ + root = mail_thread_c2node (stream,mail_thread_prune_dummy (prc,NIL),flags); + /* Step 4 */ + /* make buffer for sorting */ + tc = (THREADNODE **) fs_get (nmsgs * sizeof (THREADNODE *)); + /* load threadcache and count nodes to sort */ + for (i = 0, cur = root; cur ; cur = cur->branch) tc[i++] = cur; + if (i > 1) { /* only if need to sort */ + qsort ((void *) tc,i,sizeof (THREADNODE *),mail_thread_compare_date); + /* relink siblings */ + for (j = 0, --i; j < i; j++) tc[j]->branch = tc[j+1]; + tc[j]->branch = NIL; /* end of root */ + root = tc[0]; /* establish new root */ + } + /* Step 5A */ + hash_reset (ht); /* discard containers, reset ht */ + /* Step 5B */ + for (cur = root; cur; cur = cur->branch) + if ((t = (nxt = (cur->sc ? cur : cur->next))->sc->subject) && *t) { + /* add new subject to hash table */ + if (!(sub = hash_lookup (ht,t))) hash_add (ht,t,cur,0); + /* if one in table not dummy and */ + else if ((s = (lst = (THREADNODE *) sub[0])->sc) && + /* current dummy, or not re/fwd and table is */ + (!cur->sc || (!nxt->sc->refwd && s->refwd))) + sub[0] = (void *) cur; /* replace with this message */ + } + + /* Step 5C */ + for (cur = root, sis = NIL; cur; cur = msg) { + /* do nothing if current message or no sub */ + if (!(t = (cur->sc ? cur : cur->next)->sc->subject) || !*t || + ((lst = (THREADNODE *) (sub = hash_lookup (ht,t))[0]) == cur)) + msg = (sis = cur)->branch; + else if (!lst->sc) { /* is message in the table a dummy? */ + /* find youngest daughter of msg in table */ + for (msg = lst->next; msg->branch; msg = msg->branch); + if (!cur->sc) { /* current message a dummy? */ + msg->branch = cur->next;/* current's daughter now dummy's youngest */ + msg = cur->branch; /* continue scan at younger sister */ + /* now delete this node */ + cur->branch = cur->next = NIL; + mail_free_threadnode (&cur); + } + else { /* current message not a dummy */ + msg->branch = cur; /* append as youngest daughter */ + msg = cur->branch; /* continue scan at younger sister */ + cur->branch = NIL; /* lose our younger sisters */ + } + } + else { /* no dummies, is current re/fwd, table not? */ + if (cur->sc->refwd && !lst->sc->refwd) { + if (lst->next) { /* find youngest daughter of msg in table */ + for (msg = lst->next; msg->branch; msg = msg->branch); + msg->branch = cur; /* append as youngest daughter */ + } + else lst->next = cur; /* no children, so make the eldest daughter */ + } + + else { /* no re/fwd, create a new dummy */ + msg = mail_newthreadnode (NIL); + if (lst == root) { /* msg in table is root? */ + root = lst->branch; /* younger sister becomes new root */ + /* no longer older sister either */ + if (lst == sis) sis = NIL; + } + else { /* find older sister of msg in table */ + for (nxt = root; lst != nxt->branch; nxt = nxt->branch); + /* remove from older sister */ + nxt->branch = lst->branch; + } + msg->next = lst; /* msg in table becomes child */ + lst->branch = cur; /* current now little sister of msg in table */ + if (sis) { /* have an elder sister? */ + if (sis == lst) /* rescan if lost her */ + for (sis = root; cur != sis->branch; sis = sis->branch); + sis->branch = msg; /* make dummy younger sister of big sister */ + } + else root = msg; /* otherwise this is the new root */ + sub[0] = sis = msg; /* set new msg in table and new big sister */ + } + msg = cur->branch; /* continue scan at younger sister */ + cur->branch = NIL; /* lose our younger sisters */ + } + if (sis) sis->branch = msg; /* older sister gets this as younger sister */ + else root = msg; /* otherwise this is the new root */ + } + hash_destroy (&ht); /* finished with hash table */ + /* Step 6 */ + /* sort threads */ + root = mail_thread_sort (root,tc); + fs_give ((void **) &tc); /* finished with sort buffer */ + return root; /* return sorted list */ +} + +/* Fetch overview callback to load sortcache for threading + * Accepts: MAIL stream + * UID of this message + * overview of this message + * msgno of this message + */ + +void mail_thread_loadcache (MAILSTREAM *stream,unsigned long uid,OVERVIEW *ov, + unsigned long msgno) +{ + if (msgno && ov) { /* just in case */ + MESSAGECACHE telt, *elt; + ENVELOPE *env; + SORTCACHE *s = (SORTCACHE *) (*mailcache) (stream,msgno,CH_SORTCACHE); + if (!s->subject && ov->subject) { + s->refwd = mail_strip_subject (ov->subject,&s->subject); + s->dirty = T; + } + if (!s->from && ov->from && ov->from->mailbox) { + s->from = cpystr (ov->from->mailbox); + s->dirty = T; + } + if (!s->date && ov->date && mail_parse_date (&telt,ov->date)) { + s->date = mail_longdate (&telt); + s->dirty = T; + } + if (!s->message_id && ov->message_id) { + s->message_id = mail_thread_parse_msgid (ov->message_id,NIL); + s->dirty = T; + } + if (!s->references && + !(s->references = mail_thread_parse_references (ov->references,T)) + && stream->dtb && !strcmp(stream->dtb->name, "imap") + && (elt = mail_elt (stream, msgno)) != NULL + && (env = elt->private.msg.env) != NULL + && env->in_reply_to + && !(s->references = mail_thread_parse_references(env->in_reply_to, NIL))) { + /* don't do In-Reply-To with NNTP mailboxes */ + s->references = mail_newstringlist (); + s->dirty = T; + } + if (!s->size && ov->optional.octets) { + s->size = ov->optional.octets; + s->dirty = T; + } + } +} + +/* Thread parse Message ID + * Accepts: pointer to purported Message ID + * pointer to return pointer + * Returns: Message ID or NIL, return pointer updated + */ + +char *mail_thread_parse_msgid (char *s,char **ss) +{ + char *ret = NIL; + char *t = NIL; + ADDRESS *adr; + if (s) { /* only for non-NIL strings */ + rfc822_skipws (&s); /* skip whitespace */ + /* ignore phrases */ + if (((*s == '<') || (s = rfc822_parse_phrase (s))) && + (adr = rfc822_parse_routeaddr (s,&t,BADHOST))) { + /* make return msgid */ + if (adr->mailbox && adr->host) + sprintf (ret = (char *) fs_get (strlen (adr->mailbox) + + strlen (adr->host) + 2),"%s@%s", + adr->mailbox,adr->host); + mail_free_address (&adr); /* don't need temporary address */ + } + } + if (ss) *ss = t; /* update return pointer */ + return ret; +} + + +/* Thread parse references + * Accepts: pointer to purported references + * parse multiple references flag + * Returns: references or NIL + */ + +STRINGLIST *mail_thread_parse_references (char *s,long flag) +{ + char *t; + STRINGLIST *ret = NIL; + STRINGLIST *cur; + /* found first reference? */ + if (t = mail_thread_parse_msgid (s,&s)) { + (ret = mail_newstringlist ())->text.data = (unsigned char *) t; + ret->text.size = strlen (t); + if (flag) /* parse subsequent references */ + for (cur = ret; t = mail_thread_parse_msgid (s,&s); cur = cur->next) { + (cur->next = mail_newstringlist ())->text.data = (unsigned char *) t; + cur->next->text.size = strlen (t); + } + } + return ret; +} + +/* Prune dummy messages + * Accepts: candidate container to prune + * older sibling of container, if any + * Returns: container in this position, possibly pruned + * All children and younger siblings are also pruned + */ + +container_t mail_thread_prune_dummy (container_t msg,container_t ane) +{ + /* prune container and children */ + container_t ret = msg ? mail_thread_prune_dummy_work (msg,ane) : NIL; + /* prune all younger sisters */ + if (ret) for (ane = ret; ane && (msg = SIBLING (ane)); ane = msg) + msg = mail_thread_prune_dummy_work (msg,ane); + return ret; +} + + +/* Prune dummy messages worker routine + * Accepts: candidate container to prune + * older sibling of container, if any + * Returns: container in this position, possibly pruned + * All children are also pruned + */ + +container_t mail_thread_prune_dummy_work (container_t msg,container_t ane) +{ + container_t cur; + /* get children, if any */ + container_t nxt = mail_thread_prune_dummy (CHILD (msg),NIL); + /* just update children if container has msg */ + if (CACHE (msg)) SETCHILD (msg,nxt); + else if (!nxt) { /* delete dummy with no children */ + nxt = SIBLING (msg); /* get younger sister */ + if (ane) SETSIBLING (ane,nxt); + /* prune younger sister if exists */ + msg = nxt ? mail_thread_prune_dummy_work (nxt,ane) : NIL; + } + /* not if parent root & multiple children */ + else if ((cur = PARENT (msg)) || !SIBLING (nxt)) { + /* OK to promote, try younger sister of aunt */ + if (ane) SETSIBLING (ane,nxt); + /* otherwise promote to child of grandmother */ + else if (cur) SETCHILD (cur,nxt); + SETPARENT (nxt,cur); /* set parent as well */ + /* look for end of siblings in new container */ + for (cur = nxt; SIBLING (cur); cur = SIBLING (cur)); + /* reattach deleted container's siblings */ + SETSIBLING (cur,SIBLING (msg)); + /* prune and return new container */ + msg = mail_thread_prune_dummy_work (nxt,ane); + } + else SETCHILD (msg,nxt); /* in case child pruned */ + return msg; /* return this message */ +} + +/* Test that purported mother is not a child of purported daughter + * Accepts: mother + * purported daugher + * Returns: T if circular parentage exists, else NIL + */ + +long mail_thread_check_child (container_t mother,container_t daughter) +{ + if (mother) { /* only if mother non-NIL */ + if (mother == daughter) return T; + for (daughter = CHILD (daughter); daughter; daughter = SIBLING (daughter)) + if (mail_thread_check_child (mother,daughter)) return T; + } + return NIL; +} + + +/* Generate threadnodes from containers + * Accepts: Mail stream + * container + * flags + * Return: threadnode list + */ + +THREADNODE *mail_thread_c2node (MAILSTREAM *stream,container_t con,long flags) +{ + THREADNODE *ret,*cur; + SORTCACHE *s; + container_t nxt; + /* for each container */ + for (ret = cur = NIL; con; con = SIBLING (con)) { + s = CACHE (con); /* yes, get its sortcache */ + /* create node for it */ + if (ret) cur = cur->branch = mail_newthreadnode (s); + else ret = cur = mail_newthreadnode (s); + /* attach sequence or UID for non-dummy */ + if (s) cur->num = (flags & SE_UID) ? mail_uid (stream,s->num) : s->num; + /* attach the children */ + if (nxt = CHILD (con)) cur->next = mail_thread_c2node (stream,nxt,flags); + } + return ret; +} + +/* Sort thread tree by date + * Accepts: thread tree to sort + * qsort vector to sort + * Returns: sorted thread tree + */ + +THREADNODE *mail_thread_sort (THREADNODE *thr,THREADNODE **tc) +{ + unsigned long i,j; + THREADNODE *cur; + /* sort children of each thread */ + for (cur = thr; cur; cur = cur->branch) + if (cur->next) cur->next = mail_thread_sort (cur->next,tc); + /* Must do this in a separate pass since recursive call will clobber tc */ + /* load threadcache and count nodes to sort */ + for (i = 0, cur = thr; cur; cur = cur->branch) tc[i++] = cur; + if (i > 1) { /* only if need to sort */ + qsort ((void *) tc,i,sizeof (THREADNODE *),mail_thread_compare_date); + /* relink root siblings */ + for (j = 0, --i; j < i; j++) tc[j]->branch = tc[j+1]; + tc[j]->branch = NIL; /* end of root */ + } + return i ? tc[0] : NIL; /* return new head of list */ +} + + +/* Thread compare date + * Accept: first message sort cache element + * second message sort cache element + * Returns: -1 if a1 < a2, 1 if a1 > a2 + * + * This assumes that a sort cache element is either a message (with a + * sortcache entry) or a dummy with a message (with sortcache entry) child. + * This is true of both the ORDEREDSUBJECT (no dummies) and REFERENCES + * (dummies only at top-level, and with non-dummy children). + * + * If a new algorithm allows a dummy parent to have a dummy child, this + * routine must be changed if it is to be used by that algorithm. + * + * Messages with bogus dates are always sorted at the top. + */ + +int mail_thread_compare_date (const void *a1,const void *a2) +{ + THREADNODE *t1 = *(THREADNODE **) a1; + THREADNODE *t2 = *(THREADNODE **) a2; + SORTCACHE *s1 = t1->sc ? t1->sc : t1->next->sc; + SORTCACHE *s2 = t2->sc ? t2->sc : t2->next->sc; + int ret = compare_ulong (s1->date,s2->date); + /* use number as final tie-breaker */ + return ret ? ret : compare_ulong (s1->num,s2->num); +} + +/* Mail parse sequence + * Accepts: mail stream + * sequence to parse + * Returns: T if parse successful, else NIL + */ + +long mail_sequence (MAILSTREAM *stream,unsigned char *sequence) +{ + unsigned long i,j,x; + for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->sequence = NIL; + while (sequence && *sequence){/* while there is something to parse */ + if (*sequence == '*') { /* maximum message */ + if (stream->nmsgs) i = stream->nmsgs; + else { + MM_LOG ("No messages, so no maximum message number",ERROR); + return NIL; + } + sequence++; /* skip past * */ + } + /* parse and validate message number */ + else if (!isdigit (*sequence)) { + MM_LOG ("Syntax error in sequence",ERROR); + return NIL; + } + else if (!(i = strtoul (sequence,(char **) &sequence,10)) || + (i > stream->nmsgs)) { + MM_LOG ("Sequence out of range",ERROR); + return NIL; + } + switch (*sequence) { /* see what the delimiter is */ + case ':': /* sequence range */ + if (*++sequence == '*') { /* maximum message */ + if (stream->nmsgs) j = stream->nmsgs; + else { + MM_LOG ("No messages, so no maximum message number",ERROR); + return NIL; + } + sequence++; /* skip past * */ + } + /* parse end of range */ + else if (!(j = strtoul (sequence,(char **) &sequence,10)) || + (j > stream->nmsgs)) { + MM_LOG ("Sequence range invalid",ERROR); + return NIL; + } + if (*sequence && *sequence++ != ',') { + MM_LOG ("Sequence range syntax error",ERROR); + return NIL; + } + if (i > j) { /* swap the range if backwards */ + x = i; i = j; j = x; + } + /* mark each item in the sequence */ + while (i <= j) mail_elt (stream,j--)->sequence = T; + break; + case ',': /* single message */ + ++sequence; /* skip the delimiter, fall into end case */ + case '\0': /* end of sequence, mark this message */ + mail_elt (stream,i)->sequence = T; + break; + default: /* anything else is a syntax error! */ + MM_LOG ("Sequence syntax error",ERROR); + return NIL; + } + } + return T; /* successfully parsed sequence */ +} + +/* Parse flag list + * Accepts: MAIL stream + * flag list as a character string + * pointer to user flags to return + * Returns: system flags + */ + +long mail_parse_flags (MAILSTREAM *stream,char *flag,unsigned long *uf) +{ + char *t,*n,*s,tmp[MAILTMPLEN],msg[MAILTMPLEN]; + short f = 0; + long i,j; + *uf = 0; /* initially no user flags */ + if (flag && *flag) { /* no-op if no flag string */ + /* check if a list and make sure valid */ + if (((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) || + (strlen (flag) >= MAILTMPLEN)) { + MM_LOG ("Bad flag list",ERROR); + return NIL; + } + /* copy the flag string w/o list construct */ + strncpy (n = tmp,flag+i,(j = strlen (flag) - (2*i))); + tmp[j] = '\0'; + while ((t = n) && *t) { /* parse the flags */ + /* find end of flag */ + if (n = strchr (t,' ')) *n++ = '\0'; + if (*t == '\\') { /* system flag? */ + if (!compare_cstring (t+1,"SEEN")) f |= fSEEN; + else if (!compare_cstring (t+1,"DELETED")) f |= fDELETED; + else if (!compare_cstring (t+1,"FLAGGED")) f |= fFLAGGED; + else if (!compare_cstring (t+1,"ANSWERED")) f |= fANSWERED; + else if (!compare_cstring (t+1,"DRAFT")) f |= fDRAFT; + else { + sprintf (msg,"Unsupported system flag: %.80s",t); + MM_LOG (msg,WARN); + } + } + + else { /* keyword flag */ + for (i = j = 0; /* user flag, search through table */ + !i && (j < NUSERFLAGS) && (s = stream->user_flags[j]); ++j) + if (!compare_cstring (t,s)) *uf |= i = 1 << j; + if (!i) { /* flag not found, can it be created? */ + if (stream->kwd_create && (j < NUSERFLAGS) && *t && + (strlen (t) <= MAXUSERFLAG)) { + for (s = t; t && *s; s++) switch (*s) { + default: /* all other characters */ + /* SPACE, CTL, or not CHAR */ + if ((*s > ' ') && (*s < 0x7f)) break; + case '*': case '%': /* list_wildcards */ + case '"': case '\\':/* quoted-specials */ + /* atom_specials */ + case '(': case ')': case '{': + case ']': /* resp-specials */ + sprintf (msg,"Invalid flag: %.80s",t); + MM_LOG (msg,WARN); + t = NIL; + } + if (t) { /* only if valid */ + *uf |= 1 << j; /* set the bit */ + stream->user_flags[j] = cpystr (t); + /* if out of user flags */ + if (j == NUSERFLAGS - 1) stream->kwd_create = NIL; + } + } + else { + if (*t) sprintf (msg,"Unknown flag: %.80s",t); + else strcpy (msg,"Empty flag invalid"); + MM_LOG (msg,WARN); + } + } + } + } + } + return f; +} + +/* Mail check network stream for usability with new name + * Accepts: MAIL stream + * candidate new name + * Returns: T if stream can be used, NIL otherwise + */ + +long mail_usable_network_stream (MAILSTREAM *stream,char *name) +{ + NETMBX smb,nmb,omb; + return (stream && stream->dtb && !(stream->dtb->flags & DR_LOCAL) && + mail_valid_net_parse (name,&nmb) && + mail_valid_net_parse (stream->mailbox,&smb) && + mail_valid_net_parse (stream->original_mailbox,&omb) && + ((!compare_cstring (smb.host, + trustdns ? tcp_canonical (nmb.host) : nmb.host)&& + !strcmp (smb.service,nmb.service) && + (!nmb.port || (smb.port == nmb.port)) && + (nmb.anoflag == stream->anonymous) && + (!nmb.user[0] || !strcmp (smb.user,nmb.user))) || + (!compare_cstring (omb.host,nmb.host) && + !strcmp (omb.service,nmb.service) && + (!nmb.port || (omb.port == nmb.port)) && + (nmb.anoflag == stream->anonymous) && + (!nmb.user[0] || !strcmp (omb.user,nmb.user))))) ? LONGT : NIL; +} + +/* Mail data structure instantiation routines */ + + +/* Mail instantiate cache elt + * Accepts: initial message number + * Returns: new cache elt + */ + +MESSAGECACHE *mail_new_cache_elt (unsigned long msgno) +{ + MESSAGECACHE *elt = (MESSAGECACHE *) memset (fs_get (sizeof (MESSAGECACHE)), + 0,sizeof (MESSAGECACHE)); + elt->lockcount = 1; /* initially only cache references it */ + elt->msgno = msgno; /* message number */ + return elt; +} + + +/* Mail instantiate envelope + * Returns: new envelope + */ + +ENVELOPE *mail_newenvelope (void) +{ + return (ENVELOPE *) memset (fs_get (sizeof (ENVELOPE)),0,sizeof (ENVELOPE)); +} + + +/* Mail instantiate address + * Returns: new address + */ + +ADDRESS *mail_newaddr (void) +{ + return (ADDRESS *) memset (fs_get (sizeof (ADDRESS)),0,sizeof (ADDRESS)); +} + +/* Mail instantiate body + * Returns: new body + */ + +BODY *mail_newbody (void) +{ + return mail_initbody ((BODY *) fs_get (sizeof (BODY))); +} + + +/* Mail initialize body + * Accepts: body + * Returns: body + */ + +BODY *mail_initbody (BODY *body) +{ + memset ((void *) body,0,sizeof (BODY)); + body->type = TYPETEXT; /* content type */ + body->encoding = ENC7BIT; /* content encoding */ + return body; +} + + +/* Mail instantiate body parameter + * Returns: new body part + */ + +PARAMETER *mail_newbody_parameter (void) +{ + return (PARAMETER *) memset (fs_get (sizeof(PARAMETER)),0,sizeof(PARAMETER)); +} + + +/* Mail instantiate body part + * Returns: new body part + */ + +PART *mail_newbody_part (void) +{ + PART *part = (PART *) memset (fs_get (sizeof (PART)),0,sizeof (PART)); + mail_initbody (&part->body); /* initialize the body */ + return part; +} + + +/* Mail instantiate body message part + * Returns: new body message part + */ + +MESSAGE *mail_newmsg (void) +{ + return (MESSAGE *) memset (fs_get (sizeof (MESSAGE)),0,sizeof (MESSAGE)); +} + +/* Mail instantiate string list + * Returns: new string list + */ + +STRINGLIST *mail_newstringlist (void) +{ + return (STRINGLIST *) memset (fs_get (sizeof (STRINGLIST)),0, + sizeof (STRINGLIST)); +} + + +/* Mail instantiate new search program + * Returns: new search program + */ + +SEARCHPGM *mail_newsearchpgm (void) +{ + return (SEARCHPGM *) memset (fs_get (sizeof(SEARCHPGM)),0,sizeof(SEARCHPGM)); +} + + +/* Mail instantiate new search program + * Accepts: header line name + * Returns: new search program + */ + +SEARCHHEADER *mail_newsearchheader (char *line,char *text) +{ + SEARCHHEADER *hdr = (SEARCHHEADER *) memset (fs_get (sizeof (SEARCHHEADER)), + 0,sizeof (SEARCHHEADER)); + hdr->line.size = strlen ((char *) (hdr->line.data = + (unsigned char *) cpystr (line))); + hdr->text.size = strlen ((char *) (hdr->text.data = + (unsigned char *) cpystr (text))); + return hdr; +} + + +/* Mail instantiate new search set + * Returns: new search set + */ + +SEARCHSET *mail_newsearchset (void) +{ + return (SEARCHSET *) memset (fs_get (sizeof(SEARCHSET)),0,sizeof(SEARCHSET)); +} + + +/* Mail instantiate new search or + * Returns: new search or + */ + +SEARCHOR *mail_newsearchor (void) +{ + SEARCHOR *or = (SEARCHOR *) memset (fs_get (sizeof (SEARCHOR)),0, + sizeof (SEARCHOR)); + or->first = mail_newsearchpgm (); + or->second = mail_newsearchpgm (); + return or; +} + +/* Mail instantiate new searchpgmlist + * Returns: new searchpgmlist + */ + +SEARCHPGMLIST *mail_newsearchpgmlist (void) +{ + SEARCHPGMLIST *pgl = (SEARCHPGMLIST *) + memset (fs_get (sizeof (SEARCHPGMLIST)),0,sizeof (SEARCHPGMLIST)); + pgl->pgm = mail_newsearchpgm (); + return pgl; +} + + +/* Mail instantiate new sortpgm + * Returns: new sortpgm + */ + +SORTPGM *mail_newsortpgm (void) +{ + return (SORTPGM *) memset (fs_get (sizeof (SORTPGM)),0,sizeof (SORTPGM)); +} + + +/* Mail instantiate new threadnode + * Accepts: sort cache for thread node + * Returns: new threadnode + */ + +THREADNODE *mail_newthreadnode (SORTCACHE *sc) +{ + THREADNODE *thr = (THREADNODE *) memset (fs_get (sizeof (THREADNODE)),0, + sizeof (THREADNODE)); + if (sc) thr->sc = sc; /* initialize sortcache */ + return thr; +} + + +/* Mail instantiate new acllist + * Returns: new acllist + */ + +ACLLIST *mail_newacllist (void) +{ + return (ACLLIST *) memset (fs_get (sizeof (ACLLIST)),0,sizeof (ACLLIST)); +} + + +/* Mail instantiate new quotalist + * Returns: new quotalist + */ + +QUOTALIST *mail_newquotalist (void) +{ + return (QUOTALIST *) memset (fs_get (sizeof (QUOTALIST)),0, + sizeof (QUOTALIST)); +} + +/* Mail garbage collection routines */ + + +/* Mail garbage collect body + * Accepts: pointer to body pointer + */ + +void mail_free_body (BODY **body) +{ + if (*body) { /* only free if exists */ + mail_free_body_data (*body);/* free its data */ + fs_give ((void **) body); /* return body to free storage */ + } +} + + +/* Mail garbage collect body data + * Accepts: body pointer + */ + +void mail_free_body_data (BODY *body) +{ + switch (body->type) { /* free contents */ + case TYPEMULTIPART: /* multiple part */ + mail_free_body_part (&body->nested.part); + break; + case TYPEMESSAGE: /* encapsulated message */ + if (body->subtype && !strcmp (body->subtype,"RFC822")) { + mail_free_stringlist (&body->nested.msg->lines); + mail_gc_msg (body->nested.msg,GC_ENV | GC_TEXTS); + } + if (body->nested.msg) fs_give ((void **) &body->nested.msg); + break; + default: + break; + } + if (body->subtype) fs_give ((void **) &body->subtype); + mail_free_body_parameter (&body->parameter); + if (body->id) fs_give ((void **) &body->id); + if (body->description) fs_give ((void **) &body->description); + if (body->disposition.type) fs_give ((void **) &body->disposition.type); + if (body->disposition.parameter) + mail_free_body_parameter (&body->disposition.parameter); + if (body->language) mail_free_stringlist (&body->language); + if (body->location) fs_give ((void **) &body->location); + if (body->mime.text.data) fs_give ((void **) &body->mime.text.data); + if (body->contents.text.data) fs_give ((void **) &body->contents.text.data); + if (body->md5) fs_give ((void **) &body->md5); + if (mailfreebodysparep && body->sparep) + (*mailfreebodysparep) (&body->sparep); +} + +/* Mail garbage collect body parameter + * Accepts: pointer to body parameter pointer + */ + +void mail_free_body_parameter (PARAMETER **parameter) +{ + if (*parameter) { /* only free if exists */ + if ((*parameter)->attribute) fs_give ((void **) &(*parameter)->attribute); + if ((*parameter)->value) fs_give ((void **) &(*parameter)->value); + /* run down the list as necessary */ + mail_free_body_parameter (&(*parameter)->next); + /* return body part to free storage */ + fs_give ((void **) parameter); + } +} + + +/* Mail garbage collect body part + * Accepts: pointer to body part pointer + */ + +void mail_free_body_part (PART **part) +{ + if (*part) { /* only free if exists */ + mail_free_body_data (&(*part)->body); + /* run down the list as necessary */ + mail_free_body_part (&(*part)->next); + fs_give ((void **) part); /* return body part to free storage */ + } +} + +/* Mail garbage collect message cache + * Accepts: mail stream + * + * The message cache is set to NIL when this function finishes. + */ + +void mail_free_cache (MAILSTREAM *stream) +{ + /* do driver specific stuff first */ + mail_gc (stream,GC_ELT | GC_ENV | GC_TEXTS); + /* flush the cache */ + (*mailcache) (stream,(long) 0,CH_INIT); +} + + +/* Mail garbage collect cache element + * Accepts: pointer to cache element pointer + */ + +void mail_free_elt (MESSAGECACHE **elt) +{ + /* only free if exists and no sharers */ + if (*elt && !--(*elt)->lockcount) { + mail_gc_msg (&(*elt)->private.msg,GC_ENV | GC_TEXTS); + if (mailfreeeltsparep && (*elt)->sparep) + (*mailfreeeltsparep) (&(*elt)->sparep); + fs_give ((void **) elt); + } + else *elt = NIL; /* else simply drop pointer */ +} + +/* Mail garbage collect envelope + * Accepts: pointer to envelope pointer + */ + +void mail_free_envelope (ENVELOPE **env) +{ + if (*env) { /* only free if exists */ + if ((*env)->remail) fs_give ((void **) &(*env)->remail); + mail_free_address (&(*env)->return_path); + if ((*env)->date) fs_give ((void **) &(*env)->date); + mail_free_address (&(*env)->from); + mail_free_address (&(*env)->sender); + mail_free_address (&(*env)->reply_to); + if ((*env)->subject) fs_give ((void **) &(*env)->subject); + mail_free_address (&(*env)->to); + mail_free_address (&(*env)->cc); + mail_free_address (&(*env)->bcc); + if ((*env)->in_reply_to) fs_give ((void **) &(*env)->in_reply_to); + if ((*env)->message_id) fs_give ((void **) &(*env)->message_id); + if ((*env)->newsgroups) fs_give ((void **) &(*env)->newsgroups); + if ((*env)->followup_to) fs_give ((void **) &(*env)->followup_to); + if ((*env)->references) fs_give ((void **) &(*env)->references); + if (mailfreeenvelopesparep && (*env)->sparep) + (*mailfreeenvelopesparep) (&(*env)->sparep); + fs_give ((void **) env); /* return envelope to free storage */ + } +} + + +/* Mail garbage collect address + * Accepts: pointer to address pointer + */ + +void mail_free_address (ADDRESS **address) +{ + if (*address) { /* only free if exists */ + if ((*address)->personal) fs_give ((void **) &(*address)->personal); + if ((*address)->adl) fs_give ((void **) &(*address)->adl); + if ((*address)->mailbox) fs_give ((void **) &(*address)->mailbox); + if ((*address)->host) fs_give ((void **) &(*address)->host); + if ((*address)->error) fs_give ((void **) &(*address)->error); + if ((*address)->orcpt.type) fs_give ((void **) &(*address)->orcpt.type); + if ((*address)->orcpt.addr) fs_give ((void **) &(*address)->orcpt.addr); + mail_free_address (&(*address)->next); + fs_give ((void **) address);/* return address to free storage */ + } +} + + +/* Mail garbage collect stringlist + * Accepts: pointer to stringlist pointer + */ + +void mail_free_stringlist (STRINGLIST **string) +{ + if (*string) { /* only free if exists */ + if ((*string)->text.data) fs_give ((void **) &(*string)->text.data); + mail_free_stringlist (&(*string)->next); + fs_give ((void **) string); /* return string to free storage */ + } +} + +/* Mail garbage collect searchpgm + * Accepts: pointer to searchpgm pointer + */ + +void mail_free_searchpgm (SEARCHPGM **pgm) +{ + if (*pgm) { /* only free if exists */ + mail_free_searchset (&(*pgm)->msgno); + mail_free_searchset (&(*pgm)->uid); + mail_free_searchor (&(*pgm)->or); + mail_free_searchpgmlist (&(*pgm)->not); + mail_free_searchheader (&(*pgm)->header); + mail_free_stringlist (&(*pgm)->bcc); + mail_free_stringlist (&(*pgm)->body); + mail_free_stringlist (&(*pgm)->cc); + mail_free_stringlist (&(*pgm)->from); + mail_free_stringlist (&(*pgm)->keyword); + mail_free_stringlist (&(*pgm)->subject); + mail_free_stringlist (&(*pgm)->text); + mail_free_stringlist (&(*pgm)->to); + fs_give ((void **) pgm); /* return program to free storage */ + } +} + + +/* Mail garbage collect searchheader + * Accepts: pointer to searchheader pointer + */ + +void mail_free_searchheader (SEARCHHEADER **hdr) +{ + if (*hdr) { /* only free if exists */ + if ((*hdr)->line.data) fs_give ((void **) &(*hdr)->line.data); + if ((*hdr)->text.data) fs_give ((void **) &(*hdr)->text.data); + mail_free_searchheader (&(*hdr)->next); + fs_give ((void **) hdr); /* return header to free storage */ + } +} + + +/* Mail garbage collect searchset + * Accepts: pointer to searchset pointer + */ + +void mail_free_searchset (SEARCHSET **set) +{ + if (*set) { /* only free if exists */ + mail_free_searchset (&(*set)->next); + fs_give ((void **) set); /* return set to free storage */ + } +} + +/* Mail garbage collect searchor + * Accepts: pointer to searchor pointer + */ + +void mail_free_searchor (SEARCHOR **orl) +{ + if (*orl) { /* only free if exists */ + mail_free_searchpgm (&(*orl)->first); + mail_free_searchpgm (&(*orl)->second); + mail_free_searchor (&(*orl)->next); + fs_give ((void **) orl); /* return searchor to free storage */ + } +} + + +/* Mail garbage collect search program list + * Accepts: pointer to searchpgmlist pointer + */ + +void mail_free_searchpgmlist (SEARCHPGMLIST **pgl) +{ + if (*pgl) { /* only free if exists */ + mail_free_searchpgm (&(*pgl)->pgm); + mail_free_searchpgmlist (&(*pgl)->next); + fs_give ((void **) pgl); /* return searchpgmlist to free storage */ + } +} + + +/* Mail garbage collect namespace + * Accepts: poiner to namespace + */ + +void mail_free_namespace (NAMESPACE **n) +{ + if (*n) { + fs_give ((void **) &(*n)->name); + mail_free_namespace (&(*n)->next); + mail_free_body_parameter (&(*n)->param); + fs_give ((void **) n); /* return namespace to free storage */ + } +} + +/* Mail garbage collect sort program + * Accepts: pointer to sortpgm pointer + */ + +void mail_free_sortpgm (SORTPGM **pgm) +{ + if (*pgm) { /* only free if exists */ + mail_free_sortpgm (&(*pgm)->next); + fs_give ((void **) pgm); /* return sortpgm to free storage */ + } +} + + +/* Mail garbage collect thread node + * Accepts: pointer to threadnode pointer + */ + +void mail_free_threadnode (THREADNODE **thr) +{ + if (*thr) { /* only free if exists */ + mail_free_threadnode (&(*thr)->branch); + mail_free_threadnode (&(*thr)->next); + fs_give ((void **) thr); /* return threadnode to free storage */ + } +} + + +/* Mail garbage collect acllist + * Accepts: pointer to acllist pointer + */ + +void mail_free_acllist (ACLLIST **al) +{ + if (*al) { /* only free if exists */ + if ((*al)->identifier) fs_give ((void **) &(*al)->identifier); + if ((*al)->rights) fs_give ((void **) &(*al)->rights); + mail_free_acllist (&(*al)->next); + fs_give ((void **) al); /* return acllist to free storage */ + } +} + + +/* Mail garbage collect quotalist + * Accepts: pointer to quotalist pointer + */ + +void mail_free_quotalist (QUOTALIST **ql) +{ + if (*ql) { /* only free if exists */ + if ((*ql)->name) fs_give ((void **) &(*ql)->name); + mail_free_quotalist (&(*ql)->next); + fs_give ((void **) ql); /* return quotalist to free storage */ + } +} + +/* Link authenicator + * Accepts: authenticator to add to list + */ + +void auth_link (AUTHENTICATOR *auth) +{ + if (!auth->valid || (*auth->valid) ()) { + AUTHENTICATOR **a = &mailauthenticators; + while (*a) a = &(*a)->next; /* find end of list of authenticators */ + *a = auth; /* put authenticator at the end */ + auth->next = NIL; /* this authenticator is the end of the list */ + } +} + + +/* Authenticate access + * Accepts: mechanism name + * responder function + * argument count + * argument vector + * Returns: authenticated user name or NIL + */ + +char *mail_auth (char *mechanism,authresponse_t resp,int argc,char *argv[]) +{ + AUTHENTICATOR *auth; + for (auth = mailauthenticators; auth; auth = auth->next) + if (auth->server && !compare_cstring (auth->name,mechanism)) + return (!(auth->flags & AU_DISABLE) && + ((auth->flags & AU_SECURE) || + !mail_parameters (NIL,GET_DISABLEPLAINTEXT,NIL))) ? + (*auth->server) (resp,argc,argv) : NIL; + return NIL; /* no authenticator found */ +} + +/* Lookup authenticator index + * Accepts: authenticator index + * Returns: authenticator, or 0 if not found + */ + +AUTHENTICATOR *mail_lookup_auth (unsigned long i) +{ + AUTHENTICATOR *auth = mailauthenticators; + while (auth && --i) auth = auth->next; + return auth; +} + + +/* Lookup authenticator name + * Accepts: authenticator name + * required authenticator flags + * Returns: index in authenticator chain, or 0 if not found + */ + +unsigned int mail_lookup_auth_name (char *mechanism,long flags) +{ + int i; + AUTHENTICATOR *auth; + for (i = 1, auth = mailauthenticators; auth; i++, auth = auth->next) + if (auth->client && !(flags & ~auth->flags) && + !(auth->flags & AU_DISABLE) && !compare_cstring (auth->name,mechanism)) + return i; + return 0; +} + +/* Standard TCP/IP network driver */ + +static NETDRIVER tcpdriver = { + tcp_open, /* open connection */ + tcp_aopen, /* open preauthenticated connection */ + tcp_getline, /* get a line */ + tcp_getbuffer, /* get a buffer */ + tcp_soutr, /* output pushed data */ + tcp_sout, /* output string */ + tcp_close, /* close connection */ + tcp_host, /* return host name */ + tcp_remotehost, /* return remote host name */ + tcp_port, /* return port number */ + tcp_localhost /* return local host name */ +}; + + +/* Network open + * Accepts: NETMBX specifier to open + * default network driver + * default port + * SSL driver + * SSL service name + * SSL driver port + * Returns: Network stream if success, else NIL + */ + +NETSTREAM *net_open (NETMBX *mb,NETDRIVER *dv,unsigned long port, + NETDRIVER *ssld,char *ssls,unsigned long sslp) +{ + NETSTREAM *stream = NIL; + char tmp[MAILTMPLEN]; + unsigned long flags = mb->novalidate ? NET_NOVALIDATECERT : 0; + if (strlen (mb->host) >= NETMAXHOST) { + sprintf (tmp,"Invalid host name: %.80s",mb->host); + MM_LOG (tmp,ERROR); + } + /* use designated driver if given */ + else if (dv) stream = net_open_work (dv,mb->host,mb->service,port,mb->port, + flags); + else if (mb->sslflag && ssld) /* use ssl if sslflag lit */ + stream = net_open_work (ssld,mb->host,ssls,sslp,mb->port,flags); + /* if trysslfirst and can open ssl... */ + else if ((mb->trysslflag || trysslfirst) && ssld && + (stream = net_open_work (ssld,mb->host,ssls,sslp,mb->port, + flags | NET_SILENT | NET_TRYSSL))) { + if (net_sout (stream,"",0)) mb->sslflag = T; + else { + net_close (stream); /* flush fake SSL stream */ + stream = NIL; + } + } + /* default to TCP driver */ + else stream = net_open_work (&tcpdriver,mb->host,mb->service,port,mb->port, + flags); + return stream; +} + +/* Network open worker routine + * Accepts: network driver + * host name + * service name to look up port + * port number if service name not found + * port number to override service name + * flags (passed on top of port) + * Returns: Network stream if success, else NIL + */ + +NETSTREAM *net_open_work (NETDRIVER *dv,char *host,char *service, + unsigned long port,unsigned long portoverride, + unsigned long flags) +{ + NETSTREAM *stream = NIL; + void *tstream; + if (service && (*service == '*')) { + flags |= NET_NOOPENTIMEOUT; /* mark that no timeout is desired */ + ++service; /* no longer need the no timeout indicator */ + } + if (portoverride) { /* explicit port number? */ + service = NIL; /* yes, override service name */ + port = portoverride; /* use that instead of default port */ + } + if (tstream = (*dv->open) (host,service,port | flags)) { + stream = (NETSTREAM *) fs_get (sizeof (NETSTREAM)); + stream->stream = tstream; + stream->dtb = dv; + } + return stream; +} + + +/* Network authenticated open + * Accepts: network driver + * NETMBX specifier + * service specifier + * return user name buffer + * Returns: Network stream if success else NIL + */ + +NETSTREAM *net_aopen (NETDRIVER *dv,NETMBX *mb,char *service,char *user) +{ + NETSTREAM *stream = NIL; + void *tstream; + if (!dv) dv = &tcpdriver; /* default to TCP driver */ + if (tstream = (*dv->aopen) (mb,service,user)) { + stream = (NETSTREAM *) fs_get (sizeof (NETSTREAM)); + stream->stream = tstream; + stream->dtb = dv; + } + return stream; +} + +/* Network receive line + * Accepts: Network stream + * Returns: text line string or NIL if failure + */ + +char *net_getline (NETSTREAM *stream) +{ + return (*stream->dtb->getline) (stream->stream); +} + + +/* Network receive buffer + * Accepts: Network stream (must be void * for use as readfn_t) + * size in bytes + * buffer to read into + * Returns: T if success, NIL otherwise + */ + +long net_getbuffer (void *st,unsigned long size,char *buffer) +{ + NETSTREAM *stream = (NETSTREAM *) st; + return (*stream->dtb->getbuffer) (stream->stream,size,buffer); +} + + +/* Network send null-terminated string + * Accepts: Network stream + * string pointer + * Returns: T if success else NIL + */ + +long net_soutr (NETSTREAM *stream,char *string) +{ + return (*stream->dtb->soutr) (stream->stream,string); +} + + +/* Network send string + * Accepts: Network stream + * string pointer + * byte count + * Returns: T if success else NIL + */ + +long net_sout (NETSTREAM *stream,char *string,unsigned long size) +{ + return (*stream->dtb->sout) (stream->stream,string,size); +} + +/* Network close + * Accepts: Network stream + */ + +void net_close (NETSTREAM *stream) +{ + if (stream->stream) (*stream->dtb->close) (stream->stream); + fs_give ((void **) &stream); +} + + +/* Network get host name + * Accepts: Network stream + * Returns: host name for this stream + */ + +char *net_host (NETSTREAM *stream) +{ + return (*stream->dtb->host) (stream->stream); +} + + +/* Network get remote host name + * Accepts: Network stream + * Returns: host name for this stream + */ + +char *net_remotehost (NETSTREAM *stream) +{ + return (*stream->dtb->remotehost) (stream->stream); +} + +/* Network return port for this stream + * Accepts: Network stream + * Returns: port number for this stream + */ + +unsigned long net_port (NETSTREAM *stream) +{ + return (*stream->dtb->port) (stream->stream); +} + + +/* Network get local host name + * Accepts: Network stream + * Returns: local host name + */ + +char *net_localhost (NETSTREAM *stream) +{ + return (*stream->dtb->localhost) (stream->stream); +} diff --git a/imap/src/c-client/mail.h b/imap/src/c-client/mail.h new file mode 100644 index 00000000..174993e1 --- /dev/null +++ b/imap/src/c-client/mail.h @@ -0,0 +1,1838 @@ +/* ======================================================================== + * Copyright 1988-2008 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Mailbox Access routines + * + * Author: Mark Crispin + * UW Technology + * University of Washington + * Seattle, WA 98195 + * Internet: MRC@Washington.EDU + * + * Date: 22 November 1989 + * Last Edited: 16 December 2008 + */ + +/* The Version */ + +#define CCLIENTVERSION "2007e" + +/* Build parameters */ + +#define CACHEINCREMENT 250 /* cache growth increments */ +#define MAILTMPLEN 1024 /* size of a temporary buffer */ +#define SENDBUFLEN 16385 /* size of temporary sending buffer, also + * used for SMTP commands and NETMBX generation + * buffer so shouldn't be made smaller than + * MAILTMPLEN. Note that there's a guard byte, + * so this is actually len+1. */ +#define MAXAUTHENTICATORS 8 /* maximum number of SASL authenticators */ + /* maximum number of messages */ +#define MAXMESSAGES (unsigned long) 1000000 +#define MAXLOGINTRIALS 3 /* maximum number of client login attempts */ +#define MAXWILDCARDS 10 /* maximum wildcards allowed in LIST/LSUB */ + + +/* These can't be changed without changing code */ + +#define NUSERFLAGS 30 /* maximum number of user flags */ +#define MAXUSERFLAG 50 /* maximum length of a user flag */ +#define BASEYEAR 1970 /* the year time began on Unix DON'T CHANGE */ + /* default for unqualified addresses */ +#define BADHOST ".MISSING-HOST-NAME." + /* default for syntax errors in addresses */ +#define ERRHOST ".SYNTAX-ERROR." + + +/* Coddle certain compilers' 6-character symbol limitation */ + +#ifdef __COMPILER_KCC__ +#include "shortsym.h" +#endif + + +/* Function status code */ + +#define NIL 0 /* convenient name */ +#define T 1 /* opposite of NIL */ +#define LONGT (long) 1 /* long T to pacify some compilers */ +#define VOIDT (void *) "" /* void T ditto */ + +/* Global and Driver Parameters */ + + /* 0xx: driver and authenticator flags */ +#define ENABLE_DRIVER (long) 1 +#define DISABLE_DRIVER (long) 2 +#define ENABLE_AUTHENTICATOR (long) 3 +#define DISABLE_AUTHENTICATOR (long) 4 +#define ENABLE_DEBUG (long) 5 +#define DISABLE_DEBUG (long) 6 +#define HIDE_AUTHENTICATOR (long) 7 +#define UNHIDE_AUTHENTICATOR (long) 8 + /* 1xx: c-client globals */ +#define GET_DRIVERS (long) 101 +#define SET_DRIVERS (long) 102 +#define GET_GETS (long) 103 +#define SET_GETS (long) 104 +#define GET_CACHE (long) 105 +#define SET_CACHE (long) 106 +#define GET_SMTPVERBOSE (long) 107 +#define SET_SMTPVERBOSE (long) 108 +#define GET_RFC822OUTPUT (long) 109 +#define SET_RFC822OUTPUT (long) 110 +#define GET_READPROGRESS (long) 111 +#define SET_READPROGRESS (long) 112 +#define GET_THREADERS (long) 113 +#define SET_THREADERS (long) 114 +#define GET_NAMESPACE (long) 115 +#define SET_NAMESPACE (long) 116 +#define GET_MAILPROXYCOPY (long) 117 +#define SET_MAILPROXYCOPY (long) 118 +#define GET_SERVICENAME (long) 119 +#define SET_SERVICENAME (long) 120 +#define GET_DRIVER (long) 121 +#define SET_DRIVER (long) 122 +#define GET_EXPUNGEATPING (long) 123 +#define SET_EXPUNGEATPING (long) 124 +#define GET_PARSEPHRASE (long) 125 +#define SET_PARSEPHRASE (long) 126 +#define GET_SSLDRIVER (long) 127 +#define SET_SSLDRIVER (long) 128 +#define GET_TRYSSLFIRST (long) 129 +#define SET_TRYSSLFIRST (long) 130 +#define GET_BLOCKNOTIFY (long) 131 +#define SET_BLOCKNOTIFY (long) 132 +#define GET_SORTRESULTS (long) 133 +#define SET_SORTRESULTS (long) 134 +#define GET_THREADRESULTS (long) 135 +#define SET_THREADRESULTS (long) 136 +#define GET_PARSELINE (long) 137 +#define SET_PARSELINE (long) 138 +#define GET_NEWSRCQUERY (long) 139 +#define SET_NEWSRCQUERY (long) 140 +#define GET_FREEENVELOPESPAREP (long) 141 +#define SET_FREEENVELOPESPAREP (long) 142 +#define GET_FREEELTSPAREP (long) 143 +#define SET_FREEELTSPAREP (long) 144 +#define GET_SSLSTART (long) 145 +#define SET_SSLSTART (long) 146 +#define GET_DEBUGSENSITIVE (long) 147 +#define SET_DEBUGSENSITIVE (long) 148 +#define GET_TCPDEBUG (long) 149 +#define SET_TCPDEBUG (long) 150 +#define GET_FREESTREAMSPAREP (long) 151 +#define SET_FREESTREAMSPAREP (long) 152 +#define GET_FREEBODYSPAREP (long) 153 +#define SET_FREEBODYSPAREP (long) 154 +#define GET_COPYUID (long) 155 +#define SET_COPYUID (long) 156 +#define GET_APPENDUID (long) 157 +#define SET_APPENDUID (long) 158 +#define GET_RFC822OUTPUTFULL (long) 159 +#define SET_RFC822OUTPUTFULL (long) 160 +#define GET_BLOCKENVINIT (long) 161 +#define SET_BLOCKENVINIT (long) 162 + + /* 2xx: environment */ +#define GET_USERNAME (long) 201 +#define SET_USERNAME (long) 202 +#define GET_HOMEDIR (long) 203 +#define SET_HOMEDIR (long) 204 +#define GET_LOCALHOST (long) 205 +#define SET_LOCALHOST (long) 206 +#define GET_SYSINBOX (long) 207 +#define SET_SYSINBOX (long) 208 +#define GET_USERPROMPT (long) 209 +#define SET_USERPROMPT (long) 210 +#define GET_DISABLEPLAINTEXT (long) 211 +#define SET_DISABLEPLAINTEXT (long) 212 +#define GET_CHROOTSERVER (long) 213 +#define SET_CHROOTSERVER (long) 214 +#define GET_ADVERTISETHEWORLD (long) 215 +#define SET_ADVERTISETHEWORLD (long) 216 +#define GET_DISABLEAUTOSHAREDNS (long) 217 +#define SET_DISABLEAUTOSHAREDNS (long) 218 +#define GET_MAILSUBDIR 219 +#define SET_MAILSUBDIR 220 +#define GET_DISABLE822TZTEXT 221 +#define SET_DISABLE822TZTEXT 222 +#define GET_LIMITEDADVERTISE (long) 223 +#define SET_LIMITEDADVERTISE (long) 224 +#define GET_LOGOUTHOOK (long) 225 +#define SET_LOGOUTHOOK (long) 226 +#define GET_LOGOUTDATA (long) 227 +#define SET_LOGOUTDATA (long) 228 +#define GET_EXTERNALAUTHID (long) 229 +#define SET_EXTERNALAUTHID (long) 230 +#define GET_SSLCAPATH (long) 231 +#define SET_SSLCAPATH (long) 232 + + /* 3xx: TCP/IP */ +#define GET_OPENTIMEOUT (long) 300 +#define SET_OPENTIMEOUT (long) 301 +#define GET_READTIMEOUT (long) 302 +#define SET_READTIMEOUT (long) 303 +#define GET_WRITETIMEOUT (long) 304 +#define SET_WRITETIMEOUT (long) 305 +#define GET_CLOSETIMEOUT (long) 306 +#define SET_CLOSETIMEOUT (long) 307 +#define GET_TIMEOUT (long) 308 +#define SET_TIMEOUT (long) 309 +#define GET_RSHTIMEOUT (long) 310 +#define SET_RSHTIMEOUT (long) 311 +#define GET_ALLOWREVERSEDNS (long) 312 +#define SET_ALLOWREVERSEDNS (long) 313 +#define GET_RSHCOMMAND (long) 314 +#define SET_RSHCOMMAND (long) 315 +#define GET_RSHPATH (long) 316 +#define SET_RSHPATH (long) 317 +#define GET_SSHTIMEOUT (long) 318 +#define SET_SSHTIMEOUT (long) 319 +#define GET_SSHCOMMAND (long) 320 +#define SET_SSHCOMMAND (long) 321 +#define GET_SSHPATH (long) 322 +#define SET_SSHPATH (long) 323 +#define GET_SSLCERTIFICATEQUERY (long) 324 +#define SET_SSLCERTIFICATEQUERY (long) 325 +#define GET_SSLFAILURE (long) 326 +#define SET_SSLFAILURE (long) 327 +#define GET_NEWSRCCANONHOST (long) 328 +#define SET_NEWSRCCANONHOST (long) 329 +#define GET_KINIT (long) 330 +#define SET_KINIT (long) 331 +#define GET_SSLCLIENTCERT (long) 332 +#define SET_SSLCLIENTCERT (long) 333 +#define GET_SSLCLIENTKEY (long) 334 +#define SET_SSLCLIENTKEY (long) 335 +#define GET_KERBEROS_CP_SVR_NAME (long) 336 +#define SET_KERBEROS_CP_SVR_NAME (long) 337 + + /* 4xx: network drivers */ +#define GET_MAXLOGINTRIALS (long) 400 +#define SET_MAXLOGINTRIALS (long) 401 +#define GET_LOOKAHEAD (long) 402 +#define SET_LOOKAHEAD (long) 403 +#define GET_IMAPPORT (long) 404 +#define SET_IMAPPORT (long) 405 +#define GET_PREFETCH (long) 406 +#define SET_PREFETCH (long) 407 +#define GET_CLOSEONERROR (long) 408 +#define SET_CLOSEONERROR (long) 409 +#define GET_POP3PORT (long) 410 +#define SET_POP3PORT (long) 411 +#define GET_UIDLOOKAHEAD (long) 412 +#define SET_UIDLOOKAHEAD (long) 413 +#define GET_NNTPPORT (long) 414 +#define SET_NNTPPORT (long) 415 +#define GET_IMAPENVELOPE (long) 416 +#define SET_IMAPENVELOPE (long) 417 +#define GET_IMAPREFERRAL (long) 418 +#define SET_IMAPREFERRAL (long) 419 +#define GET_SSLIMAPPORT (long) 420 +#define SET_SSLIMAPPORT (long) 421 +#define GET_SSLPOPPORT (long) 422 +#define SET_SSLPOPPORT (long) 423 +#define GET_SSLNNTPPORT (long) 424 +#define SET_SSLNNTPPORT (long) 425 +#define GET_SSLSMTPPORT (long) 426 +#define SET_SSLSMTPPORT (long) 427 +#define GET_SMTPPORT (long) 428 +#define SET_SMTPPORT (long) 429 +#define GET_IMAPEXTRAHEADERS (long) 430 +#define SET_IMAPEXTRAHEADERS (long) 431 +#define GET_ACL (long) 432 +#define SET_ACL (long) 433 +#define GET_LISTRIGHTS (long) 434 +#define SET_LISTRIGHTS (long) 435 +#define GET_MYRIGHTS (long) 436 +#define SET_MYRIGHTS (long) 437 +#define GET_QUOTA (long) 438 +#define SET_QUOTA (long) 439 +#define GET_QUOTAROOT (long) 440 +#define SET_QUOTAROOT (long) 441 +#define GET_IMAPTRYSSL (long) 442 +#define SET_IMAPTRYSSL (long) 443 +#define GET_FETCHLOOKAHEAD (long) 444 +#define SET_FETCHLOOKAHEAD (long) 445 +#define GET_NNTPRANGE (long) 446 +#define SET_NNTPRANGE (long) 447 +#define GET_NNTPHIDEPATH (long) 448 +#define SET_NNTPHIDEPATH (long) 449 +#define GET_SENDCOMMAND (long) 450 +#define SET_SENDCOMMAND (long) 451 +#define GET_IDLETIMEOUT (long) 452 +#define SET_IDLETIMEOUT (long) 453 +#define GET_FETCHLOOKAHEADLIMIT (long) 454 +#define SET_FETCHLOOKAHEADLIMIT (long) 455 + + /* 5xx: local file drivers */ +#define GET_MBXPROTECTION (long) 500 +#define SET_MBXPROTECTION (long) 501 +#define GET_DIRPROTECTION (long) 502 +#define SET_DIRPROTECTION (long) 503 +#define GET_LOCKPROTECTION (long) 504 +#define SET_LOCKPROTECTION (long) 505 +#define GET_FROMWIDGET (long) 506 +#define SET_FROMWIDGET (long) 507 +#define GET_NEWSACTIVE (long) 508 +#define SET_NEWSACTIVE (long) 509 +#define GET_NEWSSPOOL (long) 510 +#define SET_NEWSSPOOL (long) 511 +#define GET_NEWSRC (long) 512 +#define SET_NEWSRC (long) 513 +#define GET_EXTENSION (long) 514 +#define SET_EXTENSION (long) 515 +#define GET_DISABLEFCNTLLOCK (long) 516 +#define SET_DISABLEFCNTLLOCK (long) 517 +#define GET_LOCKEACCESERROR (long) 518 +#define SET_LOCKEACCESERROR (long) 519 +#define GET_LISTMAXLEVEL (long) 520 +#define SET_LISTMAXLEVEL (long) 521 +#define GET_ANONYMOUSHOME (long) 522 +#define SET_ANONYMOUSHOME (long) 523 +#define GET_FTPHOME (long) 524 +#define SET_FTPHOME (long) 525 +#define GET_PUBLICHOME (long) 526 +#define SET_PUBLICHOME (long) 527 +#define GET_SHAREDHOME (long) 528 +#define SET_SHAREDHOME (long) 529 +#define GET_MHPROFILE (long) 530 +#define SET_MHPROFILE (long) 531 +#define GET_MHPATH (long) 532 +#define SET_MHPATH (long) 533 +#define GET_ONETIMEEXPUNGEATPING (long) 534 +#define SET_ONETIMEEXPUNGEATPING (long) 535 +#define GET_USERHASNOLIFE (long) 536 +#define SET_USERHASNOLIFE (long) 537 +#define GET_FTPPROTECTION (long) 538 +#define SET_FTPPROTECTION (long) 539 +#define GET_PUBLICPROTECTION (long) 540 +#define SET_PUBLICPROTECTION (long) 541 +#define GET_SHAREDPROTECTION (long) 542 +#define SET_SHAREDPROTECTION (long) 543 +#define GET_LOCKTIMEOUT (long) 544 +#define SET_LOCKTIMEOUT (long) 545 +#define GET_NOTIMEZONES (long) 546 +#define SET_NOTIMEZONES (long) 547 +#define GET_HIDEDOTFILES (long) 548 +#define SET_HIDEDOTFILES (long) 549 +#define GET_FTPDIRPROTECTION (long) 550 +#define SET_FTPDIRPROTECTION (long) 551 +#define GET_PUBLICDIRPROTECTION (long) 552 +#define SET_PUBLICDIRPROTECTION (long) 553 +#define GET_SHAREDDIRPROTECTION (long) 554 +#define SET_SHAREDDIRPROTECTION (long) 555 +#define GET_TRUSTDNS (long) 556 +#define SET_TRUSTDNS (long) 557 +#define GET_SASLUSESPTRNAME (long) 558 +#define SET_SASLUSESPTRNAME (long) 559 +#define GET_NETFSSTATBUG (long) 560 +#define SET_NETFSSTATBUG (long) 561 +#define GET_SNARFMAILBOXNAME (long) 562 +#define SET_SNARFMAILBOXNAME (long) 563 +#define GET_SNARFINTERVAL (long) 564 +#define SET_SNARFINTERVAL (long) 565 +#define GET_SNARFPRESERVE (long) 566 +#define SET_SNARFPRESERVE (long) 567 +#define GET_INBOXPATH (long) 568 +#define SET_INBOXPATH (long) 569 +#define GET_DIRFMTTEST (long) 570 +#define SET_DIRFMTTEST (long) 571 +#define GET_SCANCONTENTS (long) 572 +#define SET_SCANCONTENTS (long) 573 +#define GET_MHALLOWINBOX (long) 574 +#define SET_MHALLOWINBOX (long) 575 + +/* Driver flags */ + +#define DR_DISABLE (long) 0x1 /* driver is disabled */ +#define DR_LOCAL (long) 0x2 /* local file driver */ +#define DR_MAIL (long) 0x4 /* supports mail */ +#define DR_NEWS (long) 0x8 /* supports news */ +#define DR_READONLY (long) 0x10 /* driver only allows readonly access */ +#define DR_NOFAST (long) 0x20 /* "fast" data is slow (whole msg fetch) */ +#define DR_NAMESPACE (long) 0x40/* driver has a special namespace */ +#define DR_LOWMEM (long) 0x80 /* low amounts of memory available */ +#define DR_LOCKING (long) 0x100 /* driver does locking */ +#define DR_CRLF (long) 0x200 /* driver internal form uses CRLF newlines */ +#define DR_NOSTICKY (long) 0x400/* driver does not support sticky UIDs */ +#define DR_RECYCLE (long) 0x800 /* driver does stream recycling */ +#define DR_XPOINT (long) 0x1000 /* needs to be checkpointed */ + /* driver has no real internal date */ +#define DR_NOINTDATE (long) 0x2000 + /* driver does not announce new mail */ +#define DR_NONEWMAIL (long) 0x4000 + /* driver does not announce new mail when RO */ +#define DR_NONEWMAILRONLY (long) 0x8000 + /* driver can be halfopen */ +#define DR_HALFOPEN (long) 0x10000 +#define DR_DIRFMT (long) 0x20000/* driver is a directory-format */ +#define DR_MODSEQ (long) 0x40000/* driver supports modseqs */ + + +/* Cache management function codes */ + +#define CH_INIT (long) 10 /* initialize cache */ +#define CH_SIZE (long) 11 /* (re-)size the cache */ +#define CH_MAKEELT (long) 30 /* return elt, make if needed */ +#define CH_ELT (long) 31 /* return elt if exists */ +#define CH_SORTCACHE (long) 35 /* return sortcache entry, make if needed */ +#define CH_FREE (long) 40 /* free space used by elt */ + /* free space used by sortcache */ +#define CH_FREESORTCACHE (long) 43 +#define CH_EXPUNGE (long) 45 /* delete elt pointer from list */ + + +/* Mailbox open options + * For compatibility with the past, OP_DEBUG must always be 1. + */ + +#define OP_DEBUG (long) 0x1 /* debug protocol negotiations */ +#define OP_READONLY (long) 0x2 /* read-only open */ +#define OP_ANONYMOUS (long) 0x4 /* anonymous open of newsgroup */ +#define OP_SHORTCACHE (long) 0x8/* short (elt-only) caching */ +#define OP_SILENT (long) 0x10 /* don't pass up events (internal use) */ +#define OP_PROTOTYPE (long) 0x20/* return driver prototype */ +#define OP_HALFOPEN (long) 0x40 /* half-open (IMAP connect but no select) */ +#define OP_EXPUNGE (long) 0x80 /* silently expunge recycle stream */ +#define OP_SECURE (long) 0x100 /* don't do non-secure authentication */ +#define OP_TRYSSL (long) 0x200 /* try SSL first */ + /* use multiple newsrc files */ +#define OP_MULNEWSRC (long) 0x400 +#define OP_NOKOD (long) 0x800 /* suppress kiss-of-death */ +#define OP_SNIFF (long) 0x1000 /* metadata only open */ + /* reserved for application use */ +#define OP_RESERVED (unsigned long) 0xff000000 + + +/* Net open options */ + + /* no error messages */ +#define NET_SILENT ((unsigned long) 0x80000000) + /* no validation of SSL certificates */ +#define NET_NOVALIDATECERT ((unsigned long) 0x40000000) + /* no open timeout */ +#define NET_NOOPENTIMEOUT ((unsigned long) 0x20000000) + /* TLS not SSL */ +#define NET_TLSCLIENT ((unsigned long) 0x10000000) + /* try SSL mode */ +#define NET_TRYSSL ((unsigned long) 0x8000000) + +/* Close options */ + +#define CL_EXPUNGE (long) 1 /* expunge silently */ + + +/* Fetch options */ + +#define FT_UID (long) 0x1 /* argument is a UID */ +#define FT_PEEK (long) 0x2 /* peek at data */ +#define FT_NOT (long) 0x4 /* NOT flag for header lines fetch */ +#define FT_INTERNAL (long) 0x8 /* text can be internal strings */ + /* IMAP prefetch text when fetching header */ +#define FT_PREFETCHTEXT (long) 0x20 +#define FT_NOHDRS (long) 0x40 /* suppress fetching extra headers (note that + this breaks news handling) */ +#define FT_NEEDENV (long) 0x80 /* (internal use) include envelope */ +#define FT_NEEDBODY (long) 0x100/* (internal use) include body structure */ + /* no fetch lookahead */ +#define FT_NOLOOKAHEAD (long) 0x200 + /* (internal use) lookahead in hdr searching */ +#define FT_SEARCHLOOKAHEAD (long) 0x400 + /* stringstruct return hack */ +#define FT_RETURNSTRINGSTRUCT (long) 0x800 + + +/* Flagging options */ + +#define ST_UID (long) 0x1 /* argument is a UID sequence */ +#define ST_SILENT (long) 0x2 /* don't return results */ +#define ST_SET (long) 0x4 /* set vs. clear */ + + +/* Expunge options */ + +#define EX_UID (long) 0x1 /* argument is a UID sequence */ + + +/* Copy options */ + +#define CP_UID (long) 0x1 /* argument is a UID sequence */ +#define CP_MOVE (long) 0x2 /* delete from source after copying */ + /* set debug in any created stream */ +#define CP_DEBUG (long) 0x20000000 + +/* Search/sort/thread options */ + +#define SE_UID (long) 0x1 /* return UID */ +#define SE_FREE (long) 0x2 /* free search program after finished */ +#define SE_NOPREFETCH (long) 0x4/* no search prefetching */ +#define SO_FREE (long) 0x8 /* free sort program after finished */ +#define SE_NOSERVER (long) 0x10 /* don't do server-based search/sort/thread */ +#define SE_RETAIN (long) 0x20 /* retain previous search results */ +#define SO_OVERVIEW (long) 0x40 /* use overviews in searching (NNTP only) */ +#define SE_NEEDBODY (long) 0x80 /* include body structure in prefetch */ +#define SE_NOHDRS (long) 0x100 /* suppress prefetching extra headers (note + that this breaks news handling) */ +#define SE_NOLOCAL (long) 0x200 /* no local retry (IMAP only) */ + +#define SO_NOSERVER SE_NOSERVER /* compatibility name */ +#define SE_SILLYOK (long) 0x400 /* allow silly searches */ + + +/* Status options */ + +#define SA_MESSAGES (long) 0x1 /* number of messages */ +#define SA_RECENT (long) 0x2 /* number of recent messages */ +#define SA_UNSEEN (long) 0x4 /* number of unseen messages */ +#define SA_UIDNEXT (long) 0x8 /* next UID to be assigned */ + /* UID validity value */ +#define SA_UIDVALIDITY (long) 0x10 + /* set OP_DEBUG on any created stream */ +#define SA_DEBUG (long) 0x10000000 + /* use multiple newsrcs */ +#define SA_MULNEWSRC (long) 0x20000000 + +/* Mailgets flags */ + +#define MG_UID (long) 0x1 /* message number is a UID */ +#define MG_COPY (long) 0x2 /* must return copy of argument */ + +/* SASL authenticator categories */ + +#define AU_SECURE (long) 0x1 /* /secure allowed */ +#define AU_AUTHUSER (long) 0x2 /* /authuser=xxx allowed */ + /* authenticator hidden */ +#define AU_HIDE (long) 0x10000000 + /* authenticator disabled */ +#define AU_DISABLE (long) 0x20000000 + + +/* Garbage collection flags */ + +#define GC_ELT (long) 0x1 /* message cache elements */ +#define GC_ENV (long) 0x2 /* envelopes and bodies */ +#define GC_TEXTS (long) 0x4 /* cached texts */ + + +/* mm_log()/mm_notify() condition codes */ + +#define WARN (long) 1 /* mm_log warning type */ +#define ERROR (long) 2 /* mm_log error type */ +#define PARSE (long) 3 /* mm_log parse error type */ +#define BYE (long) 4 /* mm_notify stream dying */ +#define TCPDEBUG (long) 5 /* mm_log TCP debug babble */ + + +/* Bits from mail_parse_flags(). Don't change these, since the header format + * used by tenex, mtx, and mbx corresponds to these bits. + */ + +#define fSEEN 0x1 +#define fDELETED 0x2 +#define fFLAGGED 0x4 +#define fANSWERED 0x8 +#define fOLD 0x10 +#define fDRAFT 0x20 + +#define fEXPUNGED 0x8000 /* internal flag */ + +/* Bits for mm_list() and mm_lsub() */ + +/* Note that (LATT_NOINFERIORS LATT_HASCHILDREN LATT_HASNOCHILDREN) and + * (LATT_NOSELECT LATT_MARKED LATT_UNMARKED) each have eight possible states, + * but only four of these are valid. The other four are silly states which + * while invalid can unfortunately be expressed in the IMAP protocol. + */ + + /* terminal node in hierarchy */ +#define LATT_NOINFERIORS (long) 0x1 + /* name can not be selected */ +#define LATT_NOSELECT (long) 0x2 + /* changed since last accessed */ +#define LATT_MARKED (long) 0x4 + /* accessed since last changed */ +#define LATT_UNMARKED (long) 0x8 + /* name has referral to remote mailbox */ +#define LATT_REFERRAL (long) 0x10 + /* has selectable inferiors */ +#define LATT_HASCHILDREN (long) 0x20 + /* has no selectable inferiors */ +#define LATT_HASNOCHILDREN (long) 0x40 + + +/* Sort functions */ + +#define SORTDATE 0 /* date */ +#define SORTARRIVAL 1 /* arrival date */ +#define SORTFROM 2 /* from */ +#define SORTSUBJECT 3 /* subject */ +#define SORTTO 4 /* to */ +#define SORTCC 5 /* cc */ +#define SORTSIZE 6 /* size */ + + +/* imapreferral_t codes */ + +#define REFAUTHFAILED (long) 0 /* authentication referral -- not logged in */ +#define REFAUTH (long) 1 /* authentication referral -- logged in */ +#define REFSELECT (long) 2 /* select referral */ +#define REFCREATE (long) 3 +#define REFDELETE (long) 4 +#define REFRENAME (long) 5 +#define REFSUBSCRIBE (long) 6 +#define REFUNSUBSCRIBE (long) 7 +#define REFSTATUS (long) 8 +#define REFCOPY (long) 9 +#define REFAPPEND (long) 10 + + +/* sendcommand_t codes */ + + /* expunge response deferred */ +#define SC_EXPUNGEDEFERRED (long) 1 + +/* Block notification codes */ + +#define BLOCK_NONE 0 /* not blocked */ +#define BLOCK_SENSITIVE 1 /* sensitive code, disallow alarms */ +#define BLOCK_NONSENSITIVE 2 /* non-sensitive code, allow alarms */ +#define BLOCK_DNSLOOKUP 10 /* blocked on DNS lookup */ +#define BLOCK_TCPOPEN 11 /* blocked on TCP open */ +#define BLOCK_TCPREAD 12 /* blocked on TCP read */ +#define BLOCK_TCPWRITE 13 /* blocked on TCP write */ +#define BLOCK_TCPCLOSE 14 /* blocked on TCP close */ +#define BLOCK_FILELOCK 20 /* blocked on file locking */ + + +/* In-memory sized-text */ + +#define SIZEDTEXT struct mail_sizedtext + +SIZEDTEXT { + unsigned char *data; /* text */ + unsigned long size; /* size of text in octets */ +}; + + +/* String list */ + +#define STRINGLIST struct string_list + +STRINGLIST { + SIZEDTEXT text; /* string text */ + STRINGLIST *next; +}; + + +/* Parse results from mail_valid_net_parse */ + +#define NETMAXHOST 256 +#define NETMAXUSER 65 +#define NETMAXMBX (MAILTMPLEN/4) +#define NETMAXSRV 21 +typedef struct net_mailbox { + char host[NETMAXHOST]; /* host name (may be canonicalized) */ + char orighost[NETMAXHOST]; /* host name before canonicalization */ + char user[NETMAXUSER]; /* user name */ + char authuser[NETMAXUSER]; /* authentication user name */ + char mailbox[NETMAXMBX]; /* mailbox name */ + char service[NETMAXSRV]; /* service name */ + unsigned long port; /* TCP port number */ + unsigned int anoflag : 1; /* anonymous */ + unsigned int dbgflag : 1; /* debug flag */ + unsigned int secflag : 1; /* secure flag */ + unsigned int sslflag : 1; /* SSL driver flag */ + unsigned int trysslflag : 1; /* try SSL driver first flag */ + unsigned int novalidate : 1; /* don't validate certificates */ + unsigned int tlsflag : 1; /* TLS flag */ + unsigned int notlsflag : 1; /* do not do TLS flag */ + unsigned int readonlyflag : 1;/* want readonly */ + unsigned int norsh : 1; /* don't use rsh/ssh */ + unsigned int loser : 1; /* server is a loser */ + unsigned int tlssslv23 : 1; /* force SSLv23 client method over TLS */ +} NETMBX; + +/* Item in an address list */ + +#define ADDRESS struct mail_address + +ADDRESS { + char *personal; /* personal name phrase */ + char *adl; /* at-domain-list source route */ + char *mailbox; /* mailbox name */ + char *host; /* domain name of mailbox's host */ + char *error; /* error in address from SMTP module */ + struct { + char *type; /* address type (default "rfc822") */ + char *addr; /* address as xtext */ + } orcpt; + ADDRESS *next; /* pointer to next address in list */ +}; + + +/* Message envelope */ + +typedef struct mail_envelope { + unsigned int ngpathexists : 1; /* newsgroups may be bogus */ + unsigned int incomplete : 1; /* envelope may be incomplete */ + unsigned int imapenvonly : 1; /* envelope only has IMAP envelope */ + char *remail; /* remail header if any */ + ADDRESS *return_path; /* error return address */ + unsigned char *date; /* message composition date string */ + ADDRESS *from; /* originator address list */ + ADDRESS *sender; /* sender address list */ + ADDRESS *reply_to; /* reply address list */ + char *subject; /* message subject string */ + ADDRESS *to; /* primary recipient list */ + ADDRESS *cc; /* secondary recipient list */ + ADDRESS *bcc; /* blind secondary recipient list */ + char *in_reply_to; /* replied message ID */ + char *message_id; /* message ID */ + char *newsgroups; /* USENET newsgroups */ + char *followup_to; /* USENET reply newsgroups */ + char *references; /* USENET references */ + void *sparep; /* spare pointer reserved for main program */ +} ENVELOPE; + +/* Primary body types */ +/* If you change any of these you must also change body_types in rfc822.c */ + +#define TYPETEXT 0 /* unformatted text */ +#define TYPEMULTIPART 1 /* multiple part */ +#define TYPEMESSAGE 2 /* encapsulated message */ +#define TYPEAPPLICATION 3 /* application data */ +#define TYPEAUDIO 4 /* audio */ +#define TYPEIMAGE 5 /* static image */ +#define TYPEVIDEO 6 /* video */ +#define TYPEMODEL 7 /* model */ +#define TYPEOTHER 8 /* unknown */ +#define TYPEMAX 15 /* maximum type code */ + + +/* Body encodings */ +/* If you change any of these you must also change body_encodings in rfc822.c + */ + +#define ENC7BIT 0 /* 7 bit SMTP semantic data */ +#define ENC8BIT 1 /* 8 bit SMTP semantic data */ +#define ENCBINARY 2 /* 8 bit binary data */ +#define ENCBASE64 3 /* base-64 encoded data */ +#define ENCQUOTEDPRINTABLE 4 /* human-readable 8-as-7 bit data */ +#define ENCOTHER 5 /* unknown */ +#define ENCMAX 10 /* maximum encoding code */ + + +/* Body contents */ + +#define BODY struct mail_bodystruct +#define MESSAGE struct mail_body_message +#define PARAMETER struct mail_body_parameter +#define PART struct mail_body_part +#define PARTTEXT struct mail_body_text + +/* Message body text */ + +PARTTEXT { + unsigned long offset; /* offset from body origin */ + SIZEDTEXT text; /* text */ +}; + + +/* Message body structure */ + +BODY { + unsigned short type; /* body primary type */ + unsigned short encoding; /* body transfer encoding */ + char *subtype; /* subtype string */ + PARAMETER *parameter; /* parameter list */ + char *id; /* body identifier */ + char *description; /* body description */ + struct { /* body disposition */ + char *type; /* disposition type */ + PARAMETER *parameter; /* disposition parameters */ + } disposition; + STRINGLIST *language; /* body language */ + char *location; /* body content URI */ + PARTTEXT mime; /* MIME header */ + PARTTEXT contents; /* body part contents */ + union { /* different ways of accessing contents */ + PART *part; /* body part list */ + MESSAGE *msg; /* body encapsulated message */ + } nested; + struct { + unsigned long lines; /* size of text in lines */ + unsigned long bytes; /* size of text in octets */ + } size; + char *md5; /* MD5 checksum */ + void *sparep; /* spare pointer reserved for main program */ +}; + + +/* Parameter list */ + +PARAMETER { + char *attribute; /* parameter attribute name */ + char *value; /* parameter value */ + PARAMETER *next; /* next parameter in list */ +}; + + +/* Multipart content list */ + +PART { + BODY body; /* body information for this part */ + PART *next; /* next body part */ +}; + + +/* RFC-822 Message */ + +MESSAGE { + ENVELOPE *env; /* message envelope */ + BODY *body; /* message body */ + PARTTEXT full; /* full message */ + STRINGLIST *lines; /* lines used to filter header */ + PARTTEXT header; /* header text */ + PARTTEXT text; /* body text */ +}; + +/* Entry in the message cache array */ + +typedef struct message_cache { + unsigned long msgno; /* message number */ + unsigned int lockcount : 8; /* non-zero if multiple references */ + unsigned long rfc822_size; /* # of bytes of message as raw RFC822 */ + struct { /* c-client internal use only */ + unsigned long uid; /* message unique ID */ + unsigned long mod; /* modseq */ + PARTTEXT special; /* special text pointers */ + MESSAGE msg; /* internal message pointers */ + union { /* driver internal use */ + unsigned long data; + void *ptr; + } spare; + unsigned int sequence : 1; /* saved sequence bit */ + unsigned int dirty : 1; /* driver internal use */ + unsigned int filter : 1; /* driver internal use */ + unsigned int ghost : 1; /* driver internal use */ + } private; + /* internal date */ + unsigned int day : 5; /* day of month (1-31) */ + unsigned int month : 4; /* month of year (1-12) */ + unsigned int year : 7; /* year since BASEYEAR (expires in 127 yrs) */ + unsigned int hours: 5; /* hours (0-23) */ + unsigned int minutes: 6; /* minutes (0-59) */ + unsigned int seconds: 6; /* seconds (0-59) */ + unsigned int zoccident : 1; /* non-zero if west of UTC */ + unsigned int zhours : 4; /* hours from UTC (0-12) */ + unsigned int zminutes: 6; /* minutes (0-59) */ + /* system flags */ + unsigned int seen : 1; /* system Seen flag */ + unsigned int deleted : 1; /* system Deleted flag */ + unsigned int flagged : 1; /* system Flagged flag */ + unsigned int answered : 1; /* system Answered flag */ + unsigned int draft : 1; /* system Draft flag */ + unsigned int recent : 1; /* system Recent flag */ + /* message status */ + unsigned int valid : 1; /* elt has valid flags */ + unsigned int searched : 1; /* message was searched */ + unsigned int sequence : 1; /* message is in sequence */ + /* reserved for use by main program */ + unsigned int spare : 1; /* first spare bit */ + unsigned int spare2 : 1; /* second spare bit */ + unsigned int spare3 : 1; /* third spare bit */ + unsigned int spare4 : 1; /* fourth spare bit */ + unsigned int spare5 : 1; /* fifth spare bit */ + unsigned int spare6 : 1; /* sixth spare bit */ + unsigned int spare7 : 1; /* seventh spare bit */ + unsigned int spare8 : 1; /* eighth spare bit */ + void *sparep; /* spare pointer */ + unsigned long user_flags; /* user-assignable flags */ +} MESSAGECACHE; + +/* String structure */ + +#define STRINGDRIVER struct string_driver + +typedef struct mailstring { + void *data; /* driver-dependent data */ + unsigned long data1; /* driver-dependent data */ + unsigned long size; /* total length of string */ + char *chunk; /* base address of chunk */ + unsigned long chunksize; /* size of chunk */ + unsigned long offset; /* offset of this chunk in base */ + char *curpos; /* current position in chunk */ + unsigned long cursize; /* number of bytes remaining in chunk */ + STRINGDRIVER *dtb; /* driver that handles this type of string */ +} STRING; + + +/* Dispatch table for string driver */ + +STRINGDRIVER { + /* initialize string driver */ + void (*init) (STRING *s,void *data,unsigned long size); + /* get next character in string */ + char (*next) (STRING *s); + /* set position in string */ + void (*setpos) (STRING *s,unsigned long i); +}; + + +/* Stringstruct access routines */ + +#define INIT(s,d,data,size) ((*((s)->dtb = &d)->init) (s,data,size)) +#define SIZE(s) ((s)->size - GETPOS (s)) +#define CHR(s) (*(s)->curpos) +#define SNX(s) (--(s)->cursize ? *(s)->curpos++ : (*(s)->dtb->next) (s)) +#define GETPOS(s) ((s)->offset + ((s)->curpos - (s)->chunk)) +#define SETPOS(s,i) (*(s)->dtb->setpos) (s,i) + +/* Search program */ + +#define SEARCHPGM struct search_program +#define SEARCHHEADER struct search_header +#define SEARCHSET struct search_set +#define SEARCHOR struct search_or +#define SEARCHPGMLIST struct search_pgm_list + + +SEARCHHEADER { /* header search */ + SIZEDTEXT line; /* header line */ + SIZEDTEXT text; /* text in header */ + SEARCHHEADER *next; /* next in list */ +}; + + +SEARCHSET { /* message set */ + unsigned long first; /* sequence number */ + unsigned long last; /* last value, if a range */ + SEARCHSET *next; /* next in list */ +}; + + +SEARCHOR { + SEARCHPGM *first; /* first program */ + SEARCHPGM *second; /* second program */ + SEARCHOR *next; /* next in list */ +}; + + +SEARCHPGMLIST { + SEARCHPGM *pgm; /* search program */ + SEARCHPGMLIST *next; /* next in list */ +}; + +SEARCHPGM { /* search program */ + SEARCHSET *msgno; /* message numbers */ + SEARCHSET *uid; /* unique identifiers */ + SEARCHOR *or; /* or'ed in programs */ + SEARCHPGMLIST *not; /* and'ed not program */ + SEARCHHEADER *header; /* list of headers */ + STRINGLIST *bcc; /* bcc recipients */ + STRINGLIST *body; /* text in message body */ + STRINGLIST *cc; /* cc recipients */ + STRINGLIST *from; /* originator */ + STRINGLIST *keyword; /* keywords */ + STRINGLIST *unkeyword; /* unkeywords */ + STRINGLIST *subject; /* text in subject */ + STRINGLIST *text; /* text in headers and body */ + STRINGLIST *to; /* to recipients */ + unsigned long larger; /* larger than this size */ + unsigned long smaller; /* smaller than this size */ + unsigned long older; /* older than this interval */ + unsigned long younger; /* younger than this interval */ + unsigned short sentbefore; /* sent before this date */ + unsigned short senton; /* sent on this date */ + unsigned short sentsince; /* sent since this date */ + unsigned short before; /* before this date */ + unsigned short on; /* on this date */ + unsigned short since; /* since this date */ + unsigned int answered : 1; /* answered messages */ + unsigned int unanswered : 1; /* unanswered messages */ + unsigned int deleted : 1; /* deleted messages */ + unsigned int undeleted : 1; /* undeleted messages */ + unsigned int draft : 1; /* message draft */ + unsigned int undraft : 1; /* message undraft */ + unsigned int flagged : 1; /* flagged messages */ + unsigned int unflagged : 1; /* unflagged messages */ + unsigned int recent : 1; /* recent messages */ + unsigned int old : 1; /* old messages */ + unsigned int seen : 1; /* seen messages */ + unsigned int unseen : 1; /* unseen messages */ + /* These must be simulated in IMAP */ + STRINGLIST *return_path; /* error return address */ + STRINGLIST *sender; /* sender address list */ + STRINGLIST *reply_to; /* reply address list */ + STRINGLIST *in_reply_to; /* replied message ID */ + STRINGLIST *message_id; /* message ID */ + STRINGLIST *newsgroups; /* USENET newsgroups */ + STRINGLIST *followup_to; /* USENET reply newsgroups */ + STRINGLIST *references; /* USENET references */ +}; + + +/* Mailbox status */ + +typedef struct mbx_status { + long flags; /* validity flags */ + unsigned long messages; /* number of messages */ + unsigned long recent; /* number of recent messages */ + unsigned long unseen; /* number of unseen messages */ + unsigned long uidnext; /* next UID to be assigned */ + unsigned long uidvalidity; /* UID validity value */ +} MAILSTATUS; + +/* Sort program */ + +typedef void (*postsort_t) (void *sc); + +#define SORTPGM struct sort_program + +SORTPGM { + unsigned int reverse : 1; /* sort function is to be reversed */ + unsigned int abort : 1; /* abort sorting */ + short function; /* sort function */ + unsigned long nmsgs; /* number of messages being sorted */ + struct { + unsigned long cached; /* number of messages cached so far */ + unsigned long sorted; /* number of messages sorted so far */ + unsigned long postsorted; /* number of postsorted messages so far */ + } progress; + postsort_t postsort; /* post sorter */ + SORTPGM *next; /* next function */ +}; + + +/* Sort cache */ + +#define SORTCACHE struct sort_cache + +SORTCACHE { + unsigned int sorted : 1; /* message has been sorted */ + unsigned int postsorted : 1; /* message has been postsorted */ + unsigned int refwd : 1; /* subject is a re or fwd */ + unsigned int dirty : 1; /* has data not written to backup */ + SORTPGM *pgm; /* sort program */ + unsigned long num; /* message number (sequence or UID) */ + unsigned long date; /* sent date */ + unsigned long arrival; /* arrival date */ + unsigned long size; /* message size */ + char *from; /* from string */ + char *to; /* to string */ + char *cc; /* cc string */ + char *subject; /* extracted subject string */ + char *message_id; /* message-id string */ + char *unique; /* unique string, normally message-id */ + STRINGLIST *references; /* references string */ +}; + +/* ACL list */ + +#define ACLLIST struct acl_list + +ACLLIST { + char *identifier; /* authentication identifier */ + char *rights; /* access rights */ + ACLLIST *next; +}; + +/* Quota resource list */ + +#define QUOTALIST struct quota_list + +QUOTALIST { + char *name; /* resource name */ + unsigned long usage; /* resource usage */ + unsigned long limit; /* resource limit */ + QUOTALIST *next; /* next resource */ +}; + +/* Mail Access I/O stream */ + + +/* Structure for mail driver dispatch */ + +#define DRIVER struct driver + + +/* Mail I/O stream */ + +typedef struct mail_stream { + DRIVER *dtb; /* dispatch table for this driver */ + void *local; /* pointer to driver local data */ + char *mailbox; /* mailbox name (canonicalized) */ + char *original_mailbox; /* mailbox name (non-canonicalized) */ + unsigned short use; /* stream use count */ + unsigned short sequence; /* stream sequence */ + unsigned int inbox : 1; /* stream open on an INBOX */ + unsigned int lock : 1; /* stream lock flag */ + unsigned int debug : 1; /* stream debug flag */ + unsigned int silent : 1; /* don't pass events to main program */ + unsigned int rdonly : 1; /* stream read-only flag */ + unsigned int anonymous : 1; /* stream anonymous access flag */ + unsigned int scache : 1; /* stream short cache flag */ + unsigned int halfopen : 1; /* stream half-open flag */ + unsigned int secure : 1; /* stream secure flag */ + unsigned int tryssl : 1; /* stream tryssl flag */ + unsigned int mulnewsrc : 1; /* stream use multiple newsrc files */ + unsigned int perm_seen : 1; /* permanent Seen flag */ + unsigned int perm_deleted : 1;/* permanent Deleted flag */ + unsigned int perm_flagged : 1;/* permanent Flagged flag */ + unsigned int perm_answered :1;/* permanent Answered flag */ + unsigned int perm_draft : 1; /* permanent Draft flag */ + unsigned int kwd_create : 1; /* can create new keywords */ + unsigned int uid_nosticky : 1;/* UIDs are not preserved */ + unsigned int unhealthy : 1; /* unhealthy protocol negotiations */ + unsigned int nokod : 1; /* suppress kiss-of-death */ + unsigned int sniff : 1; /* metadata only */ + unsigned long perm_user_flags;/* mask of permanent user flags */ + unsigned long gensym; /* generated tag */ + unsigned long nmsgs; /* # of associated msgs */ + unsigned long recent; /* # of recent msgs */ + unsigned long uid_validity; /* UID validity sequence */ + unsigned long uid_last; /* last assigned UID */ + char *user_flags[NUSERFLAGS]; /* pointers to user flags in bit order */ + unsigned long cachesize; /* size of message cache */ + MESSAGECACHE **cache; /* message cache array */ + SORTCACHE **sc; /* sort cache array */ + unsigned long msgno; /* message number of `current' message */ + ENVELOPE *env; /* scratch buffer for envelope */ + BODY *body; /* scratch buffer for body */ + SIZEDTEXT text; /* scratch buffer for text */ + struct { + char *name; /* mailbox name to snarf from */ + unsigned long time; /* last snarf time */ + long options; /* snarf open options */ + } snarf; + struct { /* internal use only */ + struct { /* search temporaries */ + STRINGLIST *string; /* string(s) to search */ + long result; /* search result */ + char *text; /* cache of fetched text */ + } search; + STRING string; /* stringstruct return hack */ + } private; + /* reserved for use by main program */ + void *sparep; /* spare pointer */ + unsigned int spare : 1; /* first spare bit */ + unsigned int spare2 : 1; /* second spare bit */ + unsigned int spare3 : 1; /* third spare bit */ + unsigned int spare4 : 1; /* fourth spare bit */ + unsigned int spare5 : 1; /* fifth spare bit */ + unsigned int spare6 : 1; /* sixth spare bit */ + unsigned int spare7 : 1; /* seventh spare bit */ + unsigned int spare8 : 1; /* eighth spare bit */ +} MAILSTREAM; + +/* Mail I/O stream handle */ + +typedef struct mail_stream_handle { + MAILSTREAM *stream; /* pointer to mail stream */ + unsigned short sequence; /* sequence of what we expect stream to be */ +} MAILHANDLE; + + +/* Message overview */ + +typedef struct mail_overview { + char *subject; /* message subject string */ + ADDRESS *from; /* originator address list */ + char *date; /* message composition date string */ + char *message_id; /* message ID */ + char *references; /* USENET references */ + struct { /* may be 0 or NUL if unknown/undefined */ + unsigned long octets; /* message octets (probably LF-newline form) */ + unsigned long lines; /* message lines */ + char *xref; /* cross references */ + } optional; +} OVERVIEW; + +/* Network access I/O stream */ + + +/* Structure for network driver dispatch */ + +#define NETDRIVER struct net_driver + + +/* Network transport I/O stream */ + +typedef struct net_stream { + void *stream; /* driver's I/O stream */ + NETDRIVER *dtb; /* network driver */ +} NETSTREAM; + + +/* Network transport driver dispatch */ + +NETDRIVER { + void *(*open) (char *host,char *service,unsigned long port); + void *(*aopen) (NETMBX *mb,char *service,char *usrbuf); + char *(*getline) (void *stream); + long (*getbuffer) (void *stream,unsigned long size,char *buffer); + long (*soutr) (void *stream,char *string); + long (*sout) (void *stream,char *string,unsigned long size); + void (*close) (void *stream); + char *(*host) (void *stream); + char *(*remotehost) (void *stream); + unsigned long (*port) (void *stream); + char *(*localhost) (void *stream); +}; + + +/* Mailgets data identifier */ + +typedef struct getsdata { + MAILSTREAM *stream; + unsigned long msgno; + char *what; + STRINGLIST *stl; + unsigned long first; + unsigned long last; + long flags; +} GETS_DATA; + + +#define INIT_GETS(md,s,m,w,f,l) \ + md.stream = s, md.msgno = m, md.what = w, md.first = f, md.last = l, \ + md.stl = NIL, md.flags = NIL; + +/* Mail delivery I/O stream */ + +typedef struct send_stream { + NETSTREAM *netstream; /* network I/O stream */ + char *host; /* SMTP service host */ + char *reply; /* last reply string */ + long replycode; /* last reply code */ + unsigned int debug : 1; /* stream debug flag */ + unsigned int sensitive : 1; /* sensitive data in progress */ + unsigned int loser : 1; /* server is a loser */ + unsigned int saslcancel : 1; /* SASL cancelled by protocol */ + union { /* protocol specific */ + struct { /* SMTP specific */ + unsigned int ok : 1; /* supports ESMTP */ + struct { /* service extensions */ + unsigned int send : 1; /* supports SEND */ + unsigned int soml : 1; /* supports SOML */ + unsigned int saml : 1; /* supports SAML */ + unsigned int expn : 1; /* supports EXPN */ + unsigned int help : 1; /* supports HELP */ + unsigned int turn : 1; /* supports TURN */ + unsigned int etrn : 1; /* supports ETRN */ + unsigned int starttls:1;/* supports STARTTLS */ + unsigned int relay : 1; /* supports relaying */ + unsigned int pipe : 1; /* supports pipelining */ + unsigned int ensc : 1; /* supports enhanced status codes */ + unsigned int bmime : 1; /* supports BINARYMIME */ + unsigned int chunk : 1; /* supports CHUNKING */ + } service; + struct { /* 8-bit MIME transport */ + unsigned int ok : 1; /* supports 8-bit MIME */ + unsigned int want : 1; /* want 8-bit MIME */ + } eightbit; + struct { /* delivery status notification */ + unsigned int ok : 1; /* supports DSN */ + unsigned int want : 1; /* want DSN */ + struct { /* notification options */ + /* notify on failure */ + unsigned int failure : 1; + /* notify on delay */ + unsigned int delay : 1; + /* notify on success */ + unsigned int success : 1; + } notify; + unsigned int full : 1; /* return full headers */ + char *envid; /* envelope identifier as xtext */ + } dsn; + struct { /* size declaration */ + unsigned int ok : 1; /* supports SIZE */ + unsigned long limit; /* maximum size supported */ + } size; + struct { /* deliverby declaration */ + unsigned int ok : 1; /* supports DELIVERBY */ + unsigned long minby; /* minimum by-time */ + } deliverby; + struct { /* authenticated turn */ + unsigned int ok : 1; /* supports ATRN */ + char *domains; /* domains */ + } atrn; + /* supported SASL authenticators */ + unsigned int auth : MAXAUTHENTICATORS; + } esmtp; + struct { /* NNTP specific */ + unsigned int post : 1; /* supports POST */ + struct { /* NNTP extensions */ + unsigned int ok : 1; /* supports extensions */ + /* supports LISTGROUP */ + unsigned int listgroup : 1; + unsigned int over : 1; /* supports OVER */ + unsigned int hdr : 1; /* supports HDR */ + unsigned int pat : 1; /* supports PAT */ + /* supports STARTTLS */ + unsigned int starttls : 1; + /* server has MULTIDOMAIN */ + unsigned int multidomain : 1; + /* supports AUTHINFO USER */ + unsigned int authuser : 1; + /* supported authenticators */ + unsigned int sasl : MAXAUTHENTICATORS; + } ext; + } nntp; + } protocol; +} SENDSTREAM; + +/* Jacket into external interfaces */ + +typedef long (*readfn_t) (void *stream,unsigned long size,char *buffer); +typedef char *(*mailgets_t) (readfn_t f,void *stream,unsigned long size, + GETS_DATA *md); +typedef char *(*readprogress_t) (GETS_DATA *md,unsigned long octets); +typedef void *(*mailcache_t) (MAILSTREAM *stream,unsigned long msgno,long op); +typedef long (*mailproxycopy_t) (MAILSTREAM *stream,char *sequence, + char *mailbox,long options); +typedef long (*tcptimeout_t) (long overall,long last, char *host); +typedef void *(*authchallenge_t) (void *stream,unsigned long *len); +typedef long (*authrespond_t) (void *stream,char *s,unsigned long size); +typedef long (*authcheck_t) (void); +typedef long (*authclient_t) (authchallenge_t challenger, + authrespond_t responder,char *service,NETMBX *mb, + void *s,unsigned long *trial,char *user); +typedef char *(*authresponse_t) (void *challenge,unsigned long clen, + unsigned long *rlen); +typedef char *(*authserver_t) (authresponse_t responder,int argc,char *argv[]); +typedef void (*smtpverbose_t) (char *buffer); +typedef void (*imapenvelope_t) (MAILSTREAM *stream,unsigned long msgno, + ENVELOPE *env); +typedef char *(*imapreferral_t) (MAILSTREAM *stream,char *url,long code); +typedef void (*overview_t) (MAILSTREAM *stream,unsigned long uid,OVERVIEW *ov, + unsigned long msgno); +typedef unsigned long *(*sorter_t) (MAILSTREAM *stream,char *charset, + SEARCHPGM *spg,SORTPGM *pgm,long flags); +typedef void (*parseline_t) (ENVELOPE *env,char *hdr,char *data,char *host); +typedef ADDRESS *(*parsephrase_t) (char *phrase,char *end,char *host); +typedef void *(*blocknotify_t) (int reason,void *data); +typedef long (*kinit_t) (char *host,char *reason); +typedef void (*sendcommand_t) (MAILSTREAM *stream,char *cmd,long flags); +typedef char *(*newsrcquery_t) (MAILSTREAM *stream,char *mulname,char *name); +typedef void (*getacl_t) (MAILSTREAM *stream,char *mailbox,ACLLIST *acl); +typedef void (*listrights_t) (MAILSTREAM *stream,char *mailbox,char *id, + char *alwaysrights,STRINGLIST *possiblerights); +typedef void (*myrights_t) (MAILSTREAM *stream,char *mailbox,char *rights); +typedef void (*quota_t) (MAILSTREAM *stream,char *qroot,QUOTALIST *qlist); +typedef void (*quotaroot_t) (MAILSTREAM *stream,char *mbx,STRINGLIST *qroot); +typedef void (*sortresults_t) (MAILSTREAM *stream,unsigned long *list, + unsigned long size); +typedef char *(*userprompt_t) (void); +typedef long (*append_t) (MAILSTREAM *stream,void *data,char **flags, + char **date,STRING **message); +typedef void (*copyuid_t) (MAILSTREAM *stream,char *mailbox, + unsigned long uidvalidity,SEARCHSET *sourceset, + SEARCHSET *destset); +typedef void (*appenduid_t) (char *mailbox,unsigned long uidvalidity, + SEARCHSET *set); +typedef long (*dirfmttest_t) (char *name); +typedef long (*scancontents_t) (char *name,char *contents,unsigned long csiz, + unsigned long fsiz); + +typedef void (*freeeltsparep_t) (void **sparep); +typedef void (*freeenvelopesparep_t) (void **sparep); +typedef void (*freebodysparep_t) (void **sparep); +typedef void (*freestreamsparep_t) (void **sparep); +typedef void *(*sslstart_t) (void *stream,char *host,unsigned long flags); +typedef long (*sslcertificatequery_t) (char *reason,char *host,char *cert); +typedef void (*sslfailure_t) (char *host,char *reason,unsigned long flags); +typedef void (*logouthook_t) (void *data); +typedef char *(*sslclientcert_t) (void); +typedef char *(*sslclientkey_t) (void); + +/* Globals */ + +extern char *body_types[]; /* defined body type strings */ +extern char *body_encodings[]; /* defined body encoding strings */ +extern const char *days[]; /* day name strings */ +extern const char *months[]; /* month name strings */ + +/* Threading */ + +/* Thread node */ + +#define THREADNODE struct thread_node + +THREADNODE { + unsigned long num; /* message number */ + SORTCACHE *sc; /* (internal use) sortcache entry */ + THREADNODE *branch; /* branch at this point in tree */ + THREADNODE *next; /* next node */ +}; + +typedef void (*threadresults_t) (MAILSTREAM *stream,THREADNODE *tree); + + +/* Thread dispatch */ + +#define THREADER struct threader_list + +THREADER { + char *name; /* name of threader */ + THREADNODE *(*dispatch) (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, + long flags,sorter_t sorter); + THREADER *next; +}; + + +/* Container for references threading */ + +typedef void ** container_t; + +/* Namespaces */ + +#define NAMESPACE struct mail_namespace + +NAMESPACE { + char *name; /* name of this namespace */ + int delimiter; /* hierarchy delimiter */ + PARAMETER *param; /* namespace parameters */ + NAMESPACE *next; /* next namespace */ +}; + + +/* Authentication */ + +#define AUTHENTICATOR struct mail_authenticator + +AUTHENTICATOR { + long flags; /* authenticator flags */ + char *name; /* name of this authenticator */ + authcheck_t valid; /* authenticator valid on this system */ + authclient_t client; /* client function that supports it */ + authserver_t server; /* server function that supports it */ + AUTHENTICATOR *next; /* next authenticator */ +}; + +/* Mail driver dispatch */ + +DRIVER { + char *name; /* driver name */ + unsigned long flags; /* driver flags */ + DRIVER *next; /* next driver */ + /* mailbox is valid for us */ + DRIVER *(*valid) (char *mailbox); + /* manipulate driver parameters */ + void *(*parameters) (long function,void *value); + /* scan mailboxes */ + void (*scan) (MAILSTREAM *stream,char *ref,char *pat,char *contents); + /* list mailboxes */ + void (*list) (MAILSTREAM *stream,char *ref,char *pat); + /* list subscribed mailboxes */ + void (*lsub) (MAILSTREAM *stream,char *ref,char *pat); + /* subscribe to mailbox */ + long (*subscribe) (MAILSTREAM *stream,char *mailbox); + /* unsubscribe from mailbox */ + long (*unsubscribe) (MAILSTREAM *stream,char *mailbox); + /* create mailbox */ + long (*create) (MAILSTREAM *stream,char *mailbox); + /* delete mailbox */ + long (*mbxdel) (MAILSTREAM *stream,char *mailbox); + /* rename mailbox */ + long (*mbxren) (MAILSTREAM *stream,char *old,char *newname); + /* status of mailbox */ + long (*status) (MAILSTREAM *stream,char *mbx,long flags); + + /* open mailbox */ + MAILSTREAM *(*open) (MAILSTREAM *stream); + /* close mailbox */ + void (*close) (MAILSTREAM *stream,long options); + /* fetch message "fast" attributes */ + void (*fast) (MAILSTREAM *stream,char *sequence,long flags); + /* fetch message flags */ + void (*msgflags) (MAILSTREAM *stream,char *sequence,long flags); + /* fetch message overview */ + long (*overview) (MAILSTREAM *stream,overview_t ofn); + /* fetch message envelopes */ + ENVELOPE *(*structure) (MAILSTREAM *stream,unsigned long msgno,BODY **body, + long flags); + /* return RFC-822 header */ + char *(*header) (MAILSTREAM *stream,unsigned long msgno, + unsigned long *length,long flags); + /* return RFC-822 text */ + long (*text) (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); + /* load cache */ + long (*msgdata) (MAILSTREAM *stream,unsigned long msgno,char *section, + unsigned long first,unsigned long last,STRINGLIST *lines, + long flags); + /* return UID for message */ + unsigned long (*uid) (MAILSTREAM *stream,unsigned long msgno); + /* return message number from UID */ + unsigned long (*msgno) (MAILSTREAM *stream,unsigned long uid); + /* modify flags */ + void (*flag) (MAILSTREAM *stream,char *sequence,char *flag,long flags); + /* per-message modify flags */ + void (*flagmsg) (MAILSTREAM *stream,MESSAGECACHE *elt); + /* search for message based on criteria */ + long (*search) (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags); + /* sort messages */ + unsigned long *(*sort) (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, + SORTPGM *pgm,long flags); + /* thread messages */ + THREADNODE *(*thread) (MAILSTREAM *stream,char *type,char *charset, + SEARCHPGM *spg,long flag); + /* ping mailbox to see if still alive */ + long (*ping) (MAILSTREAM *stream); + /* check for new messages */ + void (*check) (MAILSTREAM *stream); + /* expunge deleted messages */ + long (*expunge) (MAILSTREAM *stream,char *sequence,long options); + /* copy messages to another mailbox */ + long (*copy) (MAILSTREAM *stream,char *sequence,char *mailbox,long options); + /* append string message to mailbox */ + long (*append) (MAILSTREAM *stream,char *mailbox,append_t af,void *data); + /* garbage collect stream */ + void (*gc) (MAILSTREAM *stream,long gcflags); +}; + + +#include "linkage.h" + +/* Compatibility support names for old interfaces */ + +#define GET_TRYALTFIRST GET_TRYSSLFIRST +#define SET_TRYALTFIRST SET_TRYSSLFIRST +#define GET_IMAPTRYALT GET_IMAPTRYSSL +#define SET_IMAPTRYALT SET_IMAPTRYSSL +#define OP_TRYALT OP_TRYSSL +#define altflag sslflag + +#define mail_close(stream) \ + mail_close_full (stream,NIL) +#define mail_fetchfast(stream,sequence) \ + mail_fetch_fast (stream,sequence,NIL) +#define mail_fetchfast_full mail_fetch_fast +#define mail_fetchflags(stream,sequence) \ + mail_fetch_flags (stream,sequence,NIL) +#define mail_fetchflags_full mail_fetch_flags +#define mail_fetchenvelope(stream,msgno) \ + mail_fetch_structure (stream,msgno,NIL,NIL) +#define mail_fetchstructure(stream,msgno,body) \ + mail_fetch_structure (stream,msgno,body,NIL) +#define mail_fetchstructure_full mail_fetch_structure +#define mail_fetchheader(stream,msgno) \ + mail_fetch_header (stream,msgno,NIL,NIL,NIL,FT_PEEK) +#define mail_fetchheader_full(stream,msgno,lines,len,flags) \ + mail_fetch_header (stream,msgno,NIL,lines,len,FT_PEEK | (flags)) +#define mail_fetchtext(stream,msgno) \ + mail_fetch_text (stream,msgno,NIL,NIL,NIL) +#define mail_fetchtext_full(stream,msgno,length,flags) \ + mail_fetch_text (stream,msgno,NIL,length,flags) +#define mail_fetchbody(stream,msgno,section,length) \ + mail_fetch_body (stream,msgno,section,length,NIL) +#define mail_fetchbody_full mail_fetch_body +#define mail_setflag(stream,sequence,flag) \ + mail_flag (stream,sequence,flag,ST_SET) +#define mail_setflag_full(stream,sequence,flag,flags) \ + mail_flag (stream,sequence,flag,ST_SET | (flags)) +#define mail_clearflag(stream,sequence,flag) \ + mail_flag (stream,sequence,flag,NIL) +#define mail_clearflag_full mail_flag +#define mail_search(stream,criteria) \ + mail_search_full (stream,NIL,mail_criteria (criteria),SE_FREE); +#define mail_expunge(stream) \ + mail_expunge_full (stream,NIL,NIL) +#define mail_copy(stream,sequence,mailbox) \ + mail_copy_full (stream,sequence,mailbox,NIL) +#define mail_move(stream,sequence,mailbox) \ + mail_copy_full (stream,sequence,mailbox,CP_MOVE) +#define mail_append(stream,mailbox,message) \ + mail_append_full (stream,mailbox,NIL,NIL,message) + +/* Interfaces for SVR4 locking brain-damage workaround */ + +/* Driver dispatching */ + +#define SAFE_DELETE(dtb,stream,mailbox) (*dtb->mbxdel) (stream,mailbox) +#define SAFE_RENAME(dtb,stream,old,newname) (*dtb->mbxren) (stream,old,newname) +#define SAFE_STATUS(dtb,stream,mbx,flags) (*dtb->status) (stream,mbx,flags) +#define SAFE_COPY(dtb,stream,sequence,mailbox,options) \ + (*dtb->copy) (stream,sequence,mailbox,options) +#define SAFE_APPEND(dtb,stream,mailbox,af,data) \ + (*dtb->append) (stream,mailbox,af,data) +#define SAFE_SCAN_CONTENTS(dtb,name,contents,csiz,fsiz) \ + scan_contents (dtb,name,contents,csiz,fsiz) + + +/* Driver callbacks */ + +#define MM_EXISTS mm_exists +#define MM_EXPUNGED mm_expunged +#define MM_FLAGS mm_flags +#define MM_NOTIFY mm_notify +#define MM_STATUS mm_status +#define MM_LOG mm_log +#define MM_CRITICAL mm_critical +#define MM_NOCRITICAL mm_nocritical +#define MM_DISKERROR mm_diskerror +#define MM_FATAL mm_fatal +#define MM_APPEND(af) (*af) + +/* Function prototypes */ + +void mm_searched (MAILSTREAM *stream,unsigned long number); +void mm_exists (MAILSTREAM *stream,unsigned long number); +void mm_expunged (MAILSTREAM *stream,unsigned long number); +void mm_flags (MAILSTREAM *stream,unsigned long number); +void mm_notify (MAILSTREAM *stream,char *string,long errflg); +void mm_list (MAILSTREAM *stream,int delimiter,char *name,long attributes); +void mm_lsub (MAILSTREAM *stream,int delimiter,char *name,long attributes); +void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status); +void mm_log (char *string,long errflg); +void mm_dlog (char *string); +void mm_login (NETMBX *mb,char *user,char *pwd,long trial); +void mm_critical (MAILSTREAM *stream); +void mm_nocritical (MAILSTREAM *stream); +long mm_diskerror (MAILSTREAM *stream,long errcode,long serious); +void mm_fatal (char *string); +void *mm_cache (MAILSTREAM *stream,unsigned long msgno,long op); + +extern STRINGDRIVER mail_string; +void mail_versioncheck (char *version); +void mail_link (DRIVER *driver); +void *mail_parameters (MAILSTREAM *stream,long function,void *value); +DRIVER *mail_valid (MAILSTREAM *stream,char *mailbox,char *purpose); +DRIVER *mail_valid_net (char *name,DRIVER *drv,char *host,char *mailbox); +long mail_valid_net_parse (char *name,NETMBX *mb); +long mail_valid_net_parse_work (char *name,NETMBX *mb,char *service); +void mail_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); +void mail_list (MAILSTREAM *stream,char *ref,char *pat); +void mail_lsub (MAILSTREAM *stream,char *ref,char *pat); +long mail_subscribe (MAILSTREAM *stream,char *mailbox); +long mail_unsubscribe (MAILSTREAM *stream,char *mailbox); +long mail_create (MAILSTREAM *stream,char *mailbox); +long mail_delete (MAILSTREAM *stream,char *mailbox); +long mail_rename (MAILSTREAM *stream,char *old,char *newname); +char *mail_utf7_valid (char *mailbox); +long mail_status (MAILSTREAM *stream,char *mbx,long flags); +long mail_status_default (MAILSTREAM *stream,char *mbx,long flags); +MAILSTREAM *mail_open (MAILSTREAM *stream,char *name,long options); +MAILSTREAM *mail_open_work (DRIVER *d,MAILSTREAM *stream,char *name, + long options); +MAILSTREAM *mail_close_full (MAILSTREAM *stream,long options); +MAILHANDLE *mail_makehandle (MAILSTREAM *stream); +void mail_free_handle (MAILHANDLE **handle); +MAILSTREAM *mail_stream (MAILHANDLE *handle); + +void mail_fetch_fast (MAILSTREAM *stream,char *sequence,long flags); +void mail_fetch_flags (MAILSTREAM *stream,char *sequence,long flags); +void mail_fetch_overview (MAILSTREAM *stream,char *sequence,overview_t ofn); +void mail_fetch_overview_sequence (MAILSTREAM *stream,char *sequence, + overview_t ofn); +void mail_fetch_overview_default (MAILSTREAM *stream,overview_t ofn); +ENVELOPE *mail_fetch_structure (MAILSTREAM *stream,unsigned long msgno, + BODY **body,long flags); +char *mail_fetch_message (MAILSTREAM *stream,unsigned long msgno, + unsigned long *len,long flags); +char *mail_fetch_header (MAILSTREAM *stream,unsigned long msgno,char *section, + STRINGLIST *lines,unsigned long *len,long flags); +char *mail_fetch_text (MAILSTREAM *stream,unsigned long msgno,char *section, + unsigned long *len,long flags); +char *mail_fetch_mime (MAILSTREAM *stream,unsigned long msgno,char *section, + unsigned long *len,long flags); +char *mail_fetch_body (MAILSTREAM *stream,unsigned long msgno,char *section, + unsigned long *len,long flags); +long mail_partial_text (MAILSTREAM *stream,unsigned long msgno,char *section, + unsigned long first,unsigned long last,long flags); +long mail_partial_body (MAILSTREAM *stream,unsigned long msgno,char *section, + unsigned long first,unsigned long last,long flags); +char *mail_fetch_text_return (GETS_DATA *md,SIZEDTEXT *t,unsigned long *len); +char *mail_fetch_string_return (GETS_DATA *md,STRING *bs,unsigned long i, + unsigned long *len,long flags); +long mail_read (void *stream,unsigned long size,char *buffer); +unsigned long mail_uid (MAILSTREAM *stream,unsigned long msgno); +unsigned long mail_msgno (MAILSTREAM *stream,unsigned long uid); +void mail_fetchfrom (char *s,MAILSTREAM *stream,unsigned long msgno, + long length); +void mail_fetchsubject (char *s,MAILSTREAM *stream,unsigned long msgno, + long length); +MESSAGECACHE *mail_elt (MAILSTREAM *stream,unsigned long msgno); +void mail_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags); +long mail_search_full (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm, + long flags); +long mail_search_default (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm, + long flags); +long mail_ping (MAILSTREAM *stream); +void mail_check (MAILSTREAM *stream); +long mail_expunge_full (MAILSTREAM *stream,char *sequence,long options); +long mail_copy_full (MAILSTREAM *stream,char *sequence,char *mailbox, + long options); +long mail_append_full (MAILSTREAM *stream,char *mailbox,char *flags,char *date, + STRING *message); +long mail_append_multiple (MAILSTREAM *stream,char *mailbox,append_t af, + void *data); +void mail_gc (MAILSTREAM *stream,long gcflags); +void mail_gc_msg (MESSAGE *msg,long gcflags); +void mail_gc_body (BODY *body); + +BODY *mail_body (MAILSTREAM *stream,unsigned long msgno, + unsigned char *section); +char *mail_date (char *string,MESSAGECACHE *elt); +char *mail_cdate (char *string,MESSAGECACHE *elt); +long mail_parse_date (MESSAGECACHE *elt,unsigned char *string); +void mail_exists (MAILSTREAM *stream,unsigned long nmsgs); +void mail_recent (MAILSTREAM *stream,unsigned long recent); +void mail_expunged (MAILSTREAM *stream,unsigned long msgno); +void mail_lock (MAILSTREAM *stream); +void mail_unlock (MAILSTREAM *stream); +void mail_debug (MAILSTREAM *stream); +void mail_nodebug (MAILSTREAM *stream); +void mail_dlog (char *string,long flag); +long mail_match_lines (STRINGLIST *lines,STRINGLIST *msglines,long flags); +unsigned long mail_filter (char *text,unsigned long len,STRINGLIST *lines, + long flags); +long mail_search_msg (MAILSTREAM *stream,unsigned long msgno,char *section, + SEARCHPGM *pgm); +long mail_search_header_text (char *s,STRINGLIST *st); +long mail_search_header (SIZEDTEXT *hdr,STRINGLIST *st); +long mail_search_text (MAILSTREAM *stream,unsigned long msgno,char *section, + STRINGLIST *st,long flags); +long mail_search_body (MAILSTREAM *stream,unsigned long msgno,BODY *body, + char *prefix,unsigned long section,long flags); +long mail_search_string (SIZEDTEXT *s,char *charset,STRINGLIST **st); +long mail_search_string_work (SIZEDTEXT *s,STRINGLIST **st); +long mail_search_keyword (MAILSTREAM *stream,MESSAGECACHE *elt,STRINGLIST *st, + long flag); +long mail_search_addr (ADDRESS *adr,STRINGLIST *st); +char *mail_search_gets (readfn_t f,void *stream,unsigned long size, + GETS_DATA *md); +SEARCHPGM *mail_criteria (char *criteria); +int mail_criteria_date (unsigned short *date,char **r); +int mail_criteria_string (STRINGLIST **s,char **r); +unsigned short mail_shortdate (unsigned int year,unsigned int month, + unsigned int day); +SEARCHSET *mail_parse_set (char *s,char **ret); +SEARCHSET *mail_append_set (SEARCHSET *set,unsigned long msgno); +unsigned long *mail_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, + SORTPGM *pgm,long flags); +unsigned long *mail_sort_cache (MAILSTREAM *stream,SORTPGM *pgm,SORTCACHE **sc, + long flags); +unsigned long *mail_sort_msgs (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, + SORTPGM *pgm,long flags); +SORTCACHE **mail_sort_loadcache (MAILSTREAM *stream,SORTPGM *pgm); +unsigned int mail_strip_subject (char *t,char **ret); +char *mail_strip_subject_wsp (char *s); +char *mail_strip_subject_blob (char *s); +int mail_sort_compare (const void *a1,const void *a2); +unsigned long mail_longdate (MESSAGECACHE *elt); +THREADNODE *mail_thread (MAILSTREAM *stream,char *type,char *charset, + SEARCHPGM *spg,long flags); +THREADNODE *mail_thread_msgs (MAILSTREAM *stream,char *type,char *charset, + SEARCHPGM *spg,long flags,sorter_t sorter); +THREADNODE *mail_thread_orderedsubject (MAILSTREAM *stream,char *charset, + SEARCHPGM *spg,long flags, + sorter_t sorter); +THREADNODE *mail_thread_references (MAILSTREAM *stream,char *charset, + SEARCHPGM *spg,long flags, + sorter_t sorter); +void mail_thread_loadcache (MAILSTREAM *stream,unsigned long uid,OVERVIEW *ov, + unsigned long msgno); +char *mail_thread_parse_msgid (char *s,char **ss); +STRINGLIST *mail_thread_parse_references (char *s,long flag); +long mail_thread_check_child (container_t mother,container_t daughter); +container_t mail_thread_prune_dummy (container_t msg,container_t ane); +container_t mail_thread_prune_dummy_work (container_t msg,container_t ane); +THREADNODE *mail_thread_c2node (MAILSTREAM *stream,container_t con,long flags); +THREADNODE *mail_thread_sort (THREADNODE *thr,THREADNODE **tc); +int mail_thread_compare_date (const void *a1,const void *a2); +long mail_sequence (MAILSTREAM *stream,unsigned char *sequence); +long mail_uid_sequence (MAILSTREAM *stream,unsigned char *sequence); +long mail_parse_flags (MAILSTREAM *stream,char *flag,unsigned long *uf); +long mail_usable_network_stream (MAILSTREAM *stream,char *name); + +MESSAGECACHE *mail_new_cache_elt (unsigned long msgno); +ENVELOPE *mail_newenvelope (void); +ADDRESS *mail_newaddr (void); +BODY *mail_newbody (void); +BODY *mail_initbody (BODY *body); +PARAMETER *mail_newbody_parameter (void); +PART *mail_newbody_part (void); +MESSAGE *mail_newmsg (void); +STRINGLIST *mail_newstringlist (void); +SEARCHPGM *mail_newsearchpgm (void); +SEARCHHEADER *mail_newsearchheader (char *line,char *text); +SEARCHSET *mail_newsearchset (void); +SEARCHOR *mail_newsearchor (void); +SEARCHPGMLIST *mail_newsearchpgmlist (void); +SORTPGM *mail_newsortpgm (void); +THREADNODE *mail_newthreadnode (SORTCACHE *sc); +ACLLIST *mail_newacllist (void); +QUOTALIST *mail_newquotalist (void); +void mail_free_body (BODY **body); +void mail_free_body_data (BODY *body); +void mail_free_body_parameter (PARAMETER **parameter); +void mail_free_body_part (PART **part); +void mail_free_cache (MAILSTREAM *stream); +void mail_free_elt (MESSAGECACHE **elt); +void mail_free_envelope (ENVELOPE **env); +void mail_free_address (ADDRESS **address); +void mail_free_stringlist (STRINGLIST **string); +void mail_free_searchpgm (SEARCHPGM **pgm); +void mail_free_searchheader (SEARCHHEADER **hdr); +void mail_free_searchset (SEARCHSET **set); +void mail_free_searchor (SEARCHOR **orl); +void mail_free_searchpgmlist (SEARCHPGMLIST **pgl); +void mail_free_namespace (NAMESPACE **n); +void mail_free_sortpgm (SORTPGM **pgm); +void mail_free_threadnode (THREADNODE **thr); +void mail_free_acllist (ACLLIST **al); +void mail_free_quotalist (QUOTALIST **ql); +void auth_link (AUTHENTICATOR *auth); +char *mail_auth (char *mechanism,authresponse_t resp,int argc,char *argv[]); +AUTHENTICATOR *mail_lookup_auth (unsigned long i); +unsigned int mail_lookup_auth_name (char *mechanism,long flags); + +NETSTREAM *net_open (NETMBX *mb,NETDRIVER *dv,unsigned long port, + NETDRIVER *ssld,char *ssls,unsigned long sslp); +NETSTREAM *net_open_work (NETDRIVER *dv,char *host,char *service, + unsigned long port,unsigned long portoverride, + unsigned long flags); +NETSTREAM *net_aopen (NETDRIVER *dv,NETMBX *mb,char *service,char *usrbuf); +char *net_getline (NETSTREAM *stream); + /* stream must be void* for use as readfn_t */ +long net_getbuffer (void *stream,unsigned long size,char *buffer); +long net_soutr (NETSTREAM *stream,char *string); +long net_sout (NETSTREAM *stream,char *string,unsigned long size); +void net_close (NETSTREAM *stream); +char *net_host (NETSTREAM *stream); +char *net_remotehost (NETSTREAM *stream); +unsigned long net_port (NETSTREAM *stream); +char *net_localhost (NETSTREAM *stream); + +long sm_subscribe (char *mailbox); +long sm_unsubscribe (char *mailbox); +char *sm_read (void **sdb); + +void ssl_onceonlyinit (void); +char *ssl_start_tls (char *s); +void ssl_server_init (char *server); + + +/* Server I/O functions */ + +int PBIN (void); +char *PSIN (char *s,int n); +long PSINR (char *s,unsigned long n); +int PBOUT (int c); +long INWAIT (long seconds); +int PSOUT (char *s); +int PSOUTR (SIZEDTEXT *s); +int PFLUSH (void); diff --git a/imap/src/c-client/misc.c b/imap/src/c-client/misc.c new file mode 100644 index 00000000..4b789af8 --- /dev/null +++ b/imap/src/c-client/misc.c @@ -0,0 +1,475 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Miscellaneous utility routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 5 July 1988 + * Last Edited: 6 December 2006 + * + * This original version of this file is + * Copyright 1988 Stanford University + * and was developed in the Symbolic Systems Resources Group of the Knowledge + * Systems Laboratory at Stanford University in 1987-88, and was funded by the + * Biomedical Research Technology Program of the NationalInstitutes of Health + * under grant number RR-00785. + */ + + +#include <ctype.h> +#include "c-client.h" + +/* Convert ASCII string to all uppercase + * Accepts: string pointer + * Returns: string pointer + * + * Don't use islower/toupper since this function must be ASCII only. + */ + +unsigned char *ucase (unsigned char *s) +{ + unsigned char *t; + /* if lowercase covert to upper */ + for (t = s; *t; t++) if ((*t >= 'a') && (*t <= 'z')) *t -= ('a' - 'A'); + return s; /* return string */ +} + + +/* Convert string to all lowercase + * Accepts: string pointer + * Returns: string pointer + * + * Don't use isupper/tolower since this function must be ASCII only. + */ + +unsigned char *lcase (unsigned char *s) +{ + unsigned char *t; + /* if uppercase covert to lower */ + for (t = s; *t; t++) if ((*t >= 'A') && (*t <= 'Z')) *t += ('a' - 'A'); + return s; /* return string */ +} + +/* Copy string to free storage + * Accepts: source string + * Returns: free storage copy of string + */ + +char *cpystr (const char *string) +{ + return string ? strcpy ((char *) fs_get (1 + strlen (string)),string) : NIL; +} + + +/* Copy text/size to free storage as sized text + * Accepts: destination sized text + * pointer to source text + * size of source text + * Returns: text as a char * + */ + +char *cpytxt (SIZEDTEXT *dst,char *text,unsigned long size) +{ + /* flush old space */ + if (dst->data) fs_give ((void **) &dst->data); + /* copy data in sized text */ + memcpy (dst->data = (unsigned char *) + fs_get ((size_t) (dst->size = size) + 1),text,(size_t) size); + dst->data[size] = '\0'; /* tie off text */ + return (char *) dst->data; /* convenience return */ +} + +/* Copy sized text to free storage as sized text + * Accepts: destination sized text + * source sized text + * Returns: text as a char * + */ + +char *textcpy (SIZEDTEXT *dst,SIZEDTEXT *src) +{ + /* flush old space */ + if (dst->data) fs_give ((void **) &dst->data); + /* copy data in sized text */ + memcpy (dst->data = (unsigned char *) + fs_get ((size_t) (dst->size = src->size) + 1), + src->data,(size_t) src->size); + dst->data[dst->size] = '\0'; /* tie off text */ + return (char *) dst->data; /* convenience return */ +} + + +/* Copy stringstruct to free storage as sized text + * Accepts: destination sized text + * source stringstruct + * Returns: text as a char * + */ + +char *textcpystring (SIZEDTEXT *text,STRING *bs) +{ + unsigned long i = 0; + /* clear old space */ + if (text->data) fs_give ((void **) &text->data); + /* make free storage space in sized text */ + text->data = (unsigned char *) fs_get ((size_t) (text->size = SIZE (bs)) +1); + while (i < text->size) text->data[i++] = SNX (bs); + text->data[i] = '\0'; /* tie off text */ + return (char *) text->data; /* convenience return */ +} + + +/* Copy stringstruct from offset to free storage as sized text + * Accepts: destination sized text + * source stringstruct + * offset into stringstruct + * size of source text + * Returns: text as a char * + */ + +char *textcpyoffstring (SIZEDTEXT *text,STRING *bs,unsigned long offset, + unsigned long size) +{ + unsigned long i = 0; + /* clear old space */ + if (text->data) fs_give ((void **) &text->data); + SETPOS (bs,offset); /* offset the string */ + /* make free storage space in sized text */ + text->data = (unsigned char *) fs_get ((size_t) (text->size = size) + 1); + while (i < size) text->data[i++] = SNX (bs); + text->data[i] = '\0'; /* tie off text */ + return (char *) text->data; /* convenience return */ +} + +/* Returns index of rightmost bit in word + * Accepts: pointer to a 32 bit value + * Returns: -1 if word is 0, else index of rightmost bit + * + * Bit is cleared in the word + */ + +unsigned long find_rightmost_bit (unsigned long *valptr) +{ + unsigned long value = *valptr; + unsigned long bit = 0; + if (!(value & 0xffffffff)) return 0xffffffff; + /* binary search for rightmost bit */ + if (!(value & 0xffff)) value >>= 16, bit += 16; + if (!(value & 0xff)) value >>= 8, bit += 8; + if (!(value & 0xf)) value >>= 4, bit += 4; + if (!(value & 0x3)) value >>= 2, bit += 2; + if (!(value & 0x1)) value >>= 1, bit += 1; + *valptr ^= (1 << bit); /* clear specified bit */ + return bit; +} + + +/* Return minimum of two integers + * Accepts: integer 1 + * integer 2 + * Returns: minimum + */ + +long min (long i,long j) +{ + return ((i < j) ? i : j); +} + + +/* Return maximum of two integers + * Accepts: integer 1 + * integer 2 + * Returns: maximum + */ + +long max (long i,long j) +{ + return ((i > j) ? i : j); +} + +/* Search, case-insensitive for ASCII characters + * Accepts: base string + * length of base string + * pattern string + * length of pattern string + * Returns: T if pattern exists inside base, else NIL + */ + +long search (unsigned char *base,long basec,unsigned char *pat,long patc) +{ + long i,j,k; + int c; + unsigned char mask[256]; + static unsigned char alphatab[256] = { + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223, + 223,223,223,223,223,223,223,223,223,223,223,255,255,255,255,255, + 255,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223, + 223,223,223,223,223,223,223,223,223,223,223,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255 + }; + /* validate arguments */ + if (base && (basec > 0) && pat && (basec >= patc)) { + if (patc <= 0) return T; /* empty pattern always succeeds */ + memset (mask,0,256); /* initialize search validity mask */ + for (i = 0; i < patc; i++) if (!mask[c = pat[i]]) { + /* mark single character if non-alphabetic */ + if (alphatab[c] & 0x20) mask[c] = T; + /* else mark both cases */ + else mask[c & 0xdf] = mask[c | 0x20] = T; + } + /* Boyer-Moore type search */ + for (i = --patc; i < basec; i += (mask[c] ? 1 : (j + 1))) + for (j = patc,c = base[k = i]; !((c ^ pat[j]) & alphatab[c]); + j--,c = base[--k]) + if (!j) return T; /* found a match! */ + } + return NIL; /* pattern not found */ +} + +/* Boyer-Moore string search + * Accepts: base string + * length of base string + * pattern string + * length of pattern string + * Returns: T if pattern exists inside base, else NIL + */ + +long ssearch (unsigned char *base,long basec,unsigned char *pat,long patc) +{ + long i,j,k; + int c; + unsigned char mask[256]; + /* validate arguments */ + if (base && (basec > 0) && pat && (basec >= patc)) { + if (patc <= 0) return T; /* empty pattern always succeeds */ + memset (mask,0,256); /* initialize search validity mask */ + for (i = 0; i < patc; i++) mask[pat[i]] = T; + /* Boyer-Moore type search */ + for (i = --patc, c = pat[i]; i < basec; i += (mask[c] ? 1 : (j + 1))) + for (j = patc,c = base[k = i]; (c == pat[j]); j--,c = base[--k]) + if (!j) return T; /* found a match! */ + } + return NIL; /* pattern not found */ +} + +/* Create a hash table + * Accepts: size of new table (note: should be a prime) + * Returns: hash table + */ + +HASHTAB *hash_create (size_t size) +{ + size_t i = sizeof (size_t) + size * sizeof (HASHENT *); + HASHTAB *ret = (HASHTAB *) memset (fs_get (i),0,i); + ret->size = size; + return ret; +} + + +/* Destroy hash table + * Accepts: hash table + */ + +void hash_destroy (HASHTAB **hashtab) +{ + if (*hashtab) { + hash_reset (*hashtab); /* reset hash table */ + fs_give ((void **) hashtab); + } +} + + +/* Reset hash table + * Accepts: hash table + */ + +void hash_reset (HASHTAB *hashtab) +{ + size_t i; + HASHENT *ent,*nxt; + /* free each hash entry */ + for (i = 0; i < hashtab->size; i++) if (ent = hashtab->table[i]) + for (hashtab->table[i] = NIL; ent; ent = nxt) { + nxt = ent->next; /* get successor */ + fs_give ((void **) &ent); /* flush this entry */ + } +} + +/* Calculate index into hash table + * Accepts: hash table + * entry name + * Returns: index + */ + +unsigned long hash_index (HASHTAB *hashtab,char *key) +{ + unsigned long i,ret; + /* polynomial of letters of the word */ + for (ret = 0; i = (unsigned int) *key++; ret += i) ret *= HASHMULT; + return ret % (unsigned long) hashtab->size; +} + + +/* Look up name in hash table + * Accepts: hash table + * key + * Returns: associated data + */ + +void **hash_lookup (HASHTAB *hashtab,char *key) +{ + HASHENT *ret; + for (ret = hashtab->table[hash_index (hashtab,key)]; ret; ret = ret->next) + if (!strcmp (key,ret->name)) return ret->data; + return NIL; +} + +/* Add entry to hash table + * Accepts: hash table + * key + * associated data + * number of extra data slots + * Returns: hash entry + * Caller is responsible for ensuring that entry isn't already in table + */ + +HASHENT *hash_add (HASHTAB *hashtab,char *key,void *data,long extra) +{ + unsigned long i = hash_index (hashtab,key); + size_t j = sizeof (HASHENT) + (extra * sizeof (void *)); + HASHENT *ret = (HASHENT *) memset (fs_get (j),0,j); + ret->next = hashtab->table[i];/* insert as new head in this index */ + ret->name = key; /* set up hash key */ + ret->data[0] = data; /* and first data */ + return hashtab->table[i] = ret; +} + + +/* Look up name in hash table + * Accepts: hash table + * key + * associated data + * number of extra data slots + * Returns: associated data + */ + +void **hash_lookup_and_add (HASHTAB *hashtab,char *key,void *data,long extra) +{ + HASHENT *ret; + unsigned long i = hash_index (hashtab,key); + size_t j = sizeof (HASHENT) + (extra * sizeof (void *)); + for (ret = hashtab->table[i]; ret; ret = ret->next) + if (!strcmp (key,ret->name)) return ret->data; + ret = (HASHENT *) memset (fs_get (j),0,j); + ret->next = hashtab->table[i];/* insert as new head in this index */ + ret->name = key; /* set up hash key */ + ret->data[0] = data; /* and first data */ + return (hashtab->table[i] = ret)->data; +} + +/* Convert two hex characters into byte + * Accepts: char for high nybble + * char for low nybble + * Returns: byte + * + * Arguments must be isxdigit validated + */ + +unsigned char hex2byte (unsigned char c1,unsigned char c2) +{ + /* merge the two nybbles */ + return ((c1 -= (isdigit (c1) ? '0' : ((c1 <= 'Z') ? 'A' : 'a') - 10)) << 4) + + (c2 - (isdigit (c2) ? '0' : ((c2 <= 'Z') ? 'A' : 'a') - 10)); +} + + +/* Compare two unsigned longs + * Accepts: first value + * second value + * Returns: -1 if l1 < l2, 0 if l1 == l2, 1 if l1 > l2 + */ + +int compare_ulong (unsigned long l1,unsigned long l2) +{ + if (l1 < l2) return -1; + if (l1 > l2) return 1; + return 0; +} + + +/* Compare two unsigned chars, case-independent + * Accepts: first value + * second value + * Returns: -1 if c1 < c2, 0 if c1 == c2, 1 if c1 > c2 + * + * Don't use isupper/tolower since this function must be ASCII only. + */ + +int compare_uchar (unsigned char c1,unsigned char c2) +{ + return compare_ulong (((c1 >= 'A') && (c1 <= 'Z')) ? c1 + ('a' - 'A') : c1, + ((c2 >= 'A') && (c2 <= 'Z')) ? c2 + ('a' - 'A') : c2); +} + +/* Compare two case-independent ASCII strings + * Accepts: first string + * second string + * Returns: -1 if s1 < s2, 0 if s1 == s2, 1 if s1 > s2 + */ + +int compare_cstring (unsigned char *s1,unsigned char *s2) +{ + int i; + if (!s1) return s2 ? -1 : 0; /* empty string cases */ + else if (!s2) return 1; + for (; *s1 && *s2; s1++,s2++) if (i = (compare_uchar (*s1,*s2))) return i; + if (*s1) return 1; /* first string is longer */ + return *s2 ? -1 : 0; /* second string longer : strings identical */ +} + + +/* Compare case-independent string with sized text + * Accepts: first string + * sized text + * Returns: -1 if s1 < s2, 0 if s1 == s2, 1 if s1 > s2 + */ + +int compare_csizedtext (unsigned char *s1,SIZEDTEXT *s2) +{ + int i; + unsigned char *s; + unsigned long j; + if (!s1) return s2 ? -1 : 0; /* null string cases */ + else if (!s2) return 1; + for (s = (char *) s2->data,j = s2->size; *s1 && j; ++s1,++s,--j) + if (i = (compare_uchar (*s1,*s))) return i; + if (*s1) return 1; /* first string is longer */ + return j ? -1 : 0; /* second string longer : strings identical */ +} diff --git a/imap/src/c-client/misc.h b/imap/src/c-client/misc.h new file mode 100644 index 00000000..1ac79485 --- /dev/null +++ b/imap/src/c-client/misc.h @@ -0,0 +1,110 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Miscellaneous utility routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 5 July 1988 + * Last Edited: 30 August 2006 + * + * This original version of this file is + * Copyright 1988 Stanford University + * and was developed in the Symbolic Systems Resources Group of the Knowledge + * Systems Laboratory at Stanford University in 1987-88, and was funded by the + * Biomedical Research Technology Program of the NationalInstitutes of Health + * under grant number RR-00785. + */ + +/* Hash table operations */ + +#define HASHMULT 29 /* hash polynomial multiplier */ + +#define HASHENT struct hash_entry + +HASHENT { + HASHENT *next; /* next entry with same hash code */ + char *name; /* name of this hash entry */ + void *data[1]; /* data of this hash entry */ +}; + + +#define HASHTAB struct hash_table + +HASHTAB { + size_t size; /* size of this table */ + HASHENT *table[1]; /* table */ +}; + + +/* KLUDGE ALERT!!! + * + * Yes, write() is overridden here instead of in osdep. This + * is because misc.h is one of the last files that most things #include, so + * this should avoid problems with some system #include file. + */ + +#define write safe_write + + +/* Some C compilers have these as macros */ + +#undef min +#undef max + + +/* And some C libraries have these as int functions */ + +#define min Min +#define max Max + + +/* Compatibility definitions */ + +#define pmatch(s,pat) \ + pmatch_full (s,pat,NIL) + +/* Function prototypes */ + +unsigned char *ucase (unsigned char *string); +unsigned char *lcase (unsigned char *string); +char *cpystr (const char *string); +char *cpytxt (SIZEDTEXT *dst,char *text,unsigned long size); +char *textcpy (SIZEDTEXT *dst,SIZEDTEXT *src); +char *textcpystring (SIZEDTEXT *text,STRING *bs); +char *textcpyoffstring (SIZEDTEXT *text,STRING *bs,unsigned long offset, + unsigned long size); +unsigned long find_rightmost_bit (unsigned long *valptr); +long min (long i,long j); +long max (long i,long j); +long search (unsigned char *base,long basec,unsigned char *pat,long patc); +long ssearch (unsigned char *base,long basec,unsigned char *pat,long patc); +HASHTAB *hash_create (size_t size); +void hash_destroy (HASHTAB **hashtab); +void hash_reset (HASHTAB *hashtab); +unsigned long hash_index (HASHTAB *hashtab,char *key); +void **hash_lookup (HASHTAB *hashtab,char *key); +HASHENT *hash_add (HASHTAB *hashtab,char *key,void *data,long extra); +void **hash_lookup_and_add (HASHTAB *hashtab,char *key,void *data,long extra); +unsigned char hex2byte (unsigned char c1,unsigned char c2); +int compare_ulong (unsigned long l1,unsigned long l2); +int compare_uchar (unsigned char c1,unsigned char c2); +int compare_cstring (unsigned char *s1,unsigned char *s2); +int compare_csizedtext (unsigned char *s1,SIZEDTEXT *s2); diff --git a/imap/src/c-client/netmsg.c b/imap/src/c-client/netmsg.c new file mode 100644 index 00000000..187e4ebb --- /dev/null +++ b/imap/src/c-client/netmsg.c @@ -0,0 +1,104 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Network message (SMTP/NNTP/POP2/POP3) routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 8 June 1995 + * Last Edited: 6 December 2006 + */ + + +#include <stdio.h> +#include <errno.h> +extern int errno; /* just in case */ +#include "c-client.h" +#include "netmsg.h" +#include "flstring.h" + +/* Network message read + * Accepts: file + * number of bytes to read + * buffer address + * Returns: T if success, NIL otherwise + */ + +long netmsg_read (void *stream,unsigned long count,char *buffer) +{ + return (fread (buffer,(size_t) 1,(size_t) count,(FILE *) stream) == count) ? + T : NIL; +} + +/* Slurp dot-terminated text from NET + * Accepts: NET stream + * place to return size + * place to return header size + * Returns: file descriptor + */ + +FILE *netmsg_slurp (NETSTREAM *stream,unsigned long *size,unsigned long *hsiz) +{ + unsigned long i; + char *s,*t,tmp[MAILTMPLEN]; + FILE *f = tmpfile (); + if (!f) { + sprintf (tmp,".%lx.%lx",(unsigned long) time (0),(unsigned long)getpid ()); + if (f = fopen (tmp,"wb+")) unlink (tmp); + else { + sprintf (tmp,"Unable to create scratch file: %.80s",strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + } + *size = 0; /* initially emtpy */ + if (hsiz) *hsiz = 0; + while (s = net_getline (stream)) { + if (*s == '.') { /* possible end of text? */ + if (s[1]) t = s + 1; /* pointer to true start of line */ + else { + fs_give ((void **) &s); /* free the line */ + break; /* end of data */ + } + } + else t = s; /* want the entire line */ + if (f) { /* copy it to the file */ + i = strlen (t); /* size of line */ + if ((fwrite (t,(size_t) 1,(size_t) i,f) == i) && + (fwrite ("\015\012",(size_t) 1,(size_t) 2,f) == 2)) { + *size += i + 2; /* tally up size of data */ + /* note header position */ + if (!i && hsiz && !*hsiz) *hsiz = *size; + } + else { + sprintf (tmp,"Error writing scratch file at byte %lu",*size); + MM_LOG (tmp,ERROR); + fclose (f); /* forget it */ + f = NIL; /* failure now */ + } + } + fs_give ((void **) &s); /* free the line */ + } + /* if making a file, rewind to start of file */ + if (f) fseek (f,(unsigned long) 0,SEEK_SET); + /* header consumes entire message */ + if (hsiz && !*hsiz) *hsiz = *size; + return f; /* return the file descriptor */ +} diff --git a/imap/src/c-client/netmsg.h b/imap/src/c-client/netmsg.h new file mode 100644 index 00000000..bacff576 --- /dev/null +++ b/imap/src/c-client/netmsg.h @@ -0,0 +1,32 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Network message (SMTP/NNTP/POP2/POP3) routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 8 June 1995 + * Last Edited: 30 August 2006 + */ + + + /* stream must be void* for use as readfn_t */ +long netmsg_read (void *stream,unsigned long count,char *buffer); +FILE *netmsg_slurp (NETSTREAM *stream,unsigned long *size,unsigned long *hsiz); diff --git a/imap/src/c-client/newsrc.c b/imap/src/c-client/newsrc.c new file mode 100644 index 00000000..41f1fd24 --- /dev/null +++ b/imap/src/c-client/newsrc.c @@ -0,0 +1,510 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Newsrc manipulation routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 12 September 1994 + * Last Edited: 30 August 2006 + */ + + +#include <ctype.h> +#include <stdio.h> +#include "c-client.h" +#include "newsrc.h" + +#ifndef OLDFILESUFFIX +#define OLDFILESUFFIX ".old" +#endif + +/* Error message + * Accepts: message format + * additional message string + * message level + * Returns: NIL, always + */ + +long newsrc_error (char *fmt,char *text,long errflg) +{ + char tmp[MAILTMPLEN]; + sprintf (tmp,fmt,text); + MM_LOG (tmp,errflg); + return NIL; +} + + +/* Write error message + * Accepts: newsrc name + * file designator + * file designator + * Returns: NIL, always + */ + +long newsrc_write_error (char *name,FILE *f1,FILE *f2) +{ + if (f1) fclose (f1); /* close file designators */ + if (f2) fclose (f2); + return newsrc_error ("Error writing to %.80s",name,ERROR); +} + + +/* Create newsrc file in local form + * Accepts: MAIL stream + * notification flag + * Returns: file designator of newsrc + */ + +FILE *newsrc_create (MAILSTREAM *stream,int notify) +{ + char *newsrc = (char *) mail_parameters (stream,GET_NEWSRC,stream); + FILE *f = fopen (newsrc,"wb"); + if (!f) newsrc_error ("Unable to create news state %.80s",newsrc,ERROR); + else if (notify) newsrc_error ("Creating news state %.80s",newsrc,WARN); + return f; +} + +/* Write new state in newsrc + * Accepts: file designator of newsrc + * group + * new subscription status character + * newline convention + * Returns: T if successful, NIL otherwise + */ + +long newsrc_newstate (FILE *f,char *group,char state,char *nl) +{ + long ret = (f && (fputs (group,f) != EOF) && ((putc (state,f)) != EOF) && + ((putc (' ',f)) != EOF) && (fputs (nl,f) != EOF)) ? LONGT : NIL; + if (fclose (f) == EOF) ret = NIL; + return ret; +} + + +/* Write messages in newsrc + * Accepts: file designator of newsrc + * MAIL stream + * message number/newsgroup message map + * newline convention + * Returns: T if successful, NIL otherwise + */ + +long newsrc_newmessages (FILE *f,MAILSTREAM *stream,char *nl) +{ + unsigned long i,j,k; + char tmp[MAILTMPLEN]; + MESSAGECACHE *elt; + int c = ' '; + if (stream->nmsgs) { /* have any messages? */ + for (i = 1,j = k = (mail_elt (stream,i)->private.uid > 1) ? 1 : 0; + i <= stream->nmsgs; ++i) { + /* deleted message? */ + if ((elt = mail_elt (stream,i))->deleted) { + k = elt->private.uid; /* this is the top of the current range */ + if (!j) j = k; /* if no range in progress, start one */ + } + else if (j) { /* unread message, ending a range */ + /* calculate end of range */ + if (k = elt->private.uid - 1) { + /* dump range */ + sprintf (tmp,(j == k) ? "%c%ld" : "%c%ld-%ld",c,j,k); + if (fputs (tmp,f) == EOF) return NIL; + c = ','; /* need a comma after the first time */ + } + j = 0; /* no more range in progress */ + } + } + if (j) { /* dump trailing range */ + sprintf (tmp,(j == k) ? "%c%ld" : "%c%ld-%ld",c,j,k); + if (fputs (tmp,f) == EOF) return NIL; + } + } + /* write trailing newline, return */ + return (fputs (nl,f) == EOF) ? NIL : LONGT; +} + +/* List subscribed newsgroups + * Accepts: MAIL stream + * prefix to append name + * pattern to search + */ + +void newsrc_lsub (MAILSTREAM *stream,char *pattern) +{ + char *s,*t,*lcl,name[MAILTMPLEN]; + int c = ' '; + int showuppers = pattern[strlen (pattern) - 1] == '%'; + FILE *f = fopen ((char *) mail_parameters (stream,GET_NEWSRC,stream),"rb"); + if (f) { /* got file? */ + /* remote name? */ + if (*(lcl = strcpy (name,pattern)) == '{') lcl = strchr (lcl,'}') + 1; + if (*lcl == '#') lcl += 6; /* namespace format name? */ + while (c != EOF) { /* yes, read newsrc */ + for (s = lcl; (s < (name + MAILTMPLEN - 1)) && ((c = getc (f)) != EOF) && + (c != ':') && (c != '!') && (c != '\015') && (c != '\012'); + *s++ = c); + if (c == ':') { /* found a subscribed newsgroup? */ + *s = '\0'; /* yes, tie off name */ + /* report if match */ + if (pmatch_full (name,pattern,'.')) mm_lsub (stream,'.',name,NIL); + else while (showuppers && (t = strrchr (lcl,'.'))) { + *t = '\0'; /* tie off the name */ + if (pmatch_full (name,pattern,'.')) + mm_lsub (stream,'.',name,LATT_NOSELECT); + } + } + while ((c != '\015') && (c != '\012') && (c != EOF)) c = getc (f); + } + fclose (f); + } +} + +/* Update subscription status of newsrc + * Accepts: MAIL stream + * group + * new subscription status character + * Returns: T if successful, NIL otherwise + */ + +long newsrc_update (MAILSTREAM *stream,char *group,char state) +{ + char tmp[MAILTMPLEN]; + char *newsrc = (char *) mail_parameters (stream,GET_NEWSRC,stream); + long ret = NIL; + FILE *f = fopen (newsrc,"r+b"); + if (f) { /* update existing file */ + int c = 0; + char *s,nl[3]; + long pos = 0; + nl[0] = nl[1] = nl[2]='\0'; /* no newline known yet */ + do { /* read newsrc */ + for (s = tmp; (s < (tmp + MAILTMPLEN - 1)) && ((c = getc (f)) != EOF) && + (c != ':') && (c != '!') && (c != '\015') && (c != '\012'); + *s++ = c) pos = ftell (f); + *s = '\0'; /* tie off name */ + /* found the newsgroup? */ + if (((c == ':') || (c == '!')) && !strcmp (tmp,group)) { + if (c == state) { /* already at that state? */ + if (c == ':') newsrc_error("Already subscribed to %.80s",group,WARN); + ret = LONGT; /* noop the update */ + } + /* write the character */ + else if (!fseek (f,pos,0)) ret = ((putc (state,f)) == EOF) ? NIL:LONGT; + if (fclose (f) == EOF) ret = NIL; + f = NIL; /* done with file */ + break; + } + /* gobble remainder of this line */ + while ((c != '\015') && (c != '\012') && (c != EOF)) c = getc (f); + /* need to know about newlines and found it? */ + if (!nl[0] && ((c == '\015') || (c == '\012')) && ((nl[0]=c) == '\015')){ + /* sniff and see if an LF */ + if ((c = getc (f)) == '\012') nl[1] = c; + else ungetc (c,f); /* nope, push it back */ + } + } while (c != EOF); + + if (f) { /* still haven't written it yet? */ + if (nl[0]) { /* know its newline convention? */ + fseek (f,0L,2); /* yes, seek to end of file */ + ret = newsrc_newstate (f,group,state,nl); + } + else { /* can't find a newline convention */ + fclose (f); /* punt the file */ + /* can't win if read something */ + if (pos) newsrc_error ("Unknown newline convention in %.80s", + newsrc,ERROR); + /* file must have been empty, rewrite it */ + else ret = newsrc_newstate(newsrc_create(stream,NIL),group,state,"\n"); + } + } + } + /* create new file */ + else ret = newsrc_newstate (newsrc_create (stream,T),group,state,"\n"); + return ret; /* return with update status */ +} + +/* Update newsgroup status in stream + * Accepts: newsgroup name + * MAIL stream + * Returns: number of recent messages + */ + +long newsrc_read (char *group,MAILSTREAM *stream) +{ + int c = 0; + char *s,tmp[MAILTMPLEN]; + unsigned long i,j; + MESSAGECACHE *elt; + unsigned long m = 1,recent = 0,unseen = 0; + FILE *f = fopen ((char *) mail_parameters (stream,GET_NEWSRC,stream),"rb"); + if (f) do { /* read newsrc */ + for (s = tmp; (s < (tmp + MAILTMPLEN - 1)) && ((c = getc (f)) != EOF) && + (c != ':') && (c != '!') && (c != '\015') && (c != '\012'); *s++ = c); + *s = '\0'; /* tie off name */ + if ((c==':') || (c=='!')) { /* found newsgroup? */ + if (strcmp (tmp,group)) /* group name match? */ + while ((c != '\015') && (c != '\012') && (c != EOF)) c = getc (f); + else { /* yes, skip leading whitespace */ + while ((c = getc (f)) == ' '); + /* only if unprocessed messages */ + if (stream->nmsgs) while (f && (m <= stream->nmsgs)) { + /* collect a number */ + if (isdigit (c)) { /* better have a number */ + for (i = 0,j = 0; isdigit (c); c = getc (f)) i = i*10 + (c-'0'); + if (c == '-') for (c = getc (f); isdigit (c); c = getc (f)) + j = j*10 +(c-'0');/* collect second value if range */ + if (!unseen && (mail_elt (stream,m)->private.uid < i)) unseen = m; + /* skip messages before first value */ + while ((m <= stream->nmsgs) && + ((elt = mail_elt (stream,m))->private.uid < i) && m++) + elt->valid = T; + /* do all messages in range */ + while ((m <= stream->nmsgs) && (elt = mail_elt (stream,m)) && + (j ? ((elt->private.uid >= i) && (elt->private.uid <= j)) : + (elt->private.uid == i)) && m++) + elt->valid = elt->deleted = T; + } + + switch (c) { /* what is the delimiter? */ + case ',': /* more to come */ + c = getc (f); /* get first character of number */ + break; + default: /* bogus character */ + sprintf (tmp,"Bogus character 0x%x in news state",(unsigned int)c); + MM_LOG (tmp,ERROR); + case EOF: case '\015': case '\012': + fclose (f); /* all done - close the file */ + f = NIL; + break; + } + } + else { /* empty newsgroup */ + while ((c != '\015') && (c != '\012') && (c != EOF)) c = getc (f); + fclose (f); /* all done - close the file */ + f = NIL; + } + } + } + } while (f && (c != EOF)); /* until file closed or EOF */ + if (f) { /* still have file open? */ + sprintf (tmp,"No state for newsgroup %.80s found, reading as new",group); + MM_LOG (tmp,WARN); + fclose (f); /* close the file */ + } + if (m <= stream->nmsgs) { /* any messages beyond newsrc range? */ + if (!unseen) unseen = m; /* then this must be the first unseen one */ + do { + elt = mail_elt (stream,m++); + elt->valid = elt->recent = T; + ++recent; /* count another recent message */ + } + while (m <= stream->nmsgs); + } + if (unseen) { /* report first unseen message */ + sprintf (tmp,"[UNSEEN] %lu is first unseen message in %.80s",unseen,group); + MM_NOTIFY (stream,tmp,(long) NIL); + } + return recent; +} + +/* Update newsgroup entry in newsrc + * Accepts: newsgroup name + * MAIL stream + * Returns: T if successful, NIL otherwise + */ + +long newsrc_write (char *group,MAILSTREAM *stream) +{ + long ret = NIL; + int c = 0,d = EOF; + char *newsrc = (char *) mail_parameters (stream,GET_NEWSRC,stream); + char *s,tmp[MAILTMPLEN],backup[MAILTMPLEN],nl[3]; + FILE *f,*bf; + nl[0] = nl[1] = nl[2] = '\0'; /* no newline known yet */ + if (f = fopen (newsrc,"rb")) {/* have existing newsrc file? */ + if (!(bf = fopen ((strcat (strcpy (backup,newsrc),OLDFILESUFFIX)),"wb"))) { + fclose (f); /* punt input file */ + return newsrc_error("Can't create backup news state %.80s",backup,ERROR); + } + /* copy to backup file */ + while ((c = getc (f)) != EOF) { + /* need to know about newlines and found it? */ + if (!nl[0] && ((c == '\015') || (c == '\012')) && ((nl[0]=c) == '\015')){ + /* sniff and see if an LF */ + if ((c = getc (f)) == '\012') nl[1] = c; + ungetc (c,f); /* push it back */ + } + /* write to backup file */ + if ((d = putc (c,bf)) == EOF) { + fclose (f); /* punt input file */ + return newsrc_error ("Error writing backup news state %.80s", + newsrc,ERROR); + } + } + fclose (f); /* close existing file */ + if (fclose (bf) == EOF) /* and backup file */ + return newsrc_error ("Error closing backup news state %.80s", + newsrc,ERROR); + if (d == EOF) { /* open for write if empty file */ + if (f = newsrc_create (stream,NIL)) bf = NIL; + else return NIL; + } + else if (!nl[0]) /* make sure newlines valid */ + return newsrc_error ("Unknown newline convention in %.80s",newsrc,ERROR); + /* now read backup file */ + else if (!(bf = fopen (backup,"rb"))) + return newsrc_error ("Error reading backup news state %.80s", + backup,ERROR); + /* open newsrc for writing */ + else if (!(f = fopen (newsrc,"wb"))) { + fclose (bf); /* punt backup */ + return newsrc_error ("Can't rewrite news state %.80s",newsrc,ERROR); + } + } + else { /* create new newsrc file */ + if (f = newsrc_create (stream,T)) bf = NIL; + else return NIL; /* can't create newsrc */ + } + + while (bf) { /* read newsrc */ + for (s = tmp; (s < (tmp + MAILTMPLEN - 1)) && ((c = getc (bf)) != EOF) && + (c != ':') && (c != '!') && (c != '\015') && (c != '\012'); *s++ = c); + *s = '\0'; /* tie off name */ + /* saw correct end of group delimiter? */ + if (tmp[0] && ((c == ':') || (c == '!'))) { + /* yes, write newsgroup name and delimiter */ + if ((tmp[0] && (fputs (tmp,f) == EOF)) || ((putc (c,f)) == EOF)) + return newsrc_write_error (newsrc,bf,f); + if (!strcmp (tmp,group)) {/* found correct group? */ + /* yes, write new status */ + if (!newsrc_newmessages (f,stream,nl[0] ? nl : "\n")) + return newsrc_write_error (newsrc,bf,f); + /* skip past old data */ + while (((c = getc (bf)) != EOF) && (c != '\015') && (c != '\012')); + /* skip past newline */ + while ((c == '\015') || (c == '\012')) c = getc (bf); + while (c != EOF) { /* copy remainder of file */ + if (putc (c,f) == EOF) return newsrc_write_error (newsrc,bf,f); + c = getc (bf); /* get next character */ + } + /* done with file */ + if (fclose (f) == EOF) return newsrc_write_error (newsrc,bf,NIL); + f = NIL; + } + /* copy remainder of line */ + else while (((c = getc (bf)) != EOF) && (c != '\015') && (c != '\012')) + if (putc (c,f) == EOF) return newsrc_write_error (newsrc,bf,f); + if (c == '\015') { /* write CR if seen */ + if (putc (c,f) == EOF) return newsrc_write_error (newsrc,bf,f); + /* sniff to see if LF */ + if (((c = getc (bf)) != EOF) && (c != '\012')) ungetc (c,bf); + } + /* write LF if seen */ + if ((c == '\012') && (putc (c,f) == EOF)) + return newsrc_write_error (newsrc,bf,f); + } + if (c == EOF) { /* hit end of file? */ + fclose (bf); /* yup, close the file */ + bf = NIL; + } + } + if (f) { /* still have newsrc file open? */ + ret = ((fputs (group,f) != EOF) && ((putc (':',f)) != EOF) && + newsrc_newmessages (f,stream,nl[0] ? nl : "\n")) ? LONGT : NIL; + if (fclose (f) == EOF) ret = newsrc_write_error (newsrc,NIL,NIL); + } + else ret = LONGT; + return ret; +} + +/* Get newsgroup state as text stream + * Accepts: MAIL stream + * newsgroup name + * Returns: string containing newsgroup state, or NIL if not found + */ + +char *newsrc_state (MAILSTREAM *stream,char *group) +{ + int c = 0; + char *s,tmp[MAILTMPLEN]; + long pos; + size_t size; + FILE *f = fopen ((char *) mail_parameters (stream,GET_NEWSRC,stream),"rb"); + if (f) do { /* read newsrc */ + for (s = tmp; (s < (tmp + MAILTMPLEN - 1)) && ((c = getc (f)) != EOF) && + (c != ':') && (c != '!') && (c != '\015') && (c != '\012'); *s++ = c); + *s = '\0'; /* tie off name */ + if ((c==':') || (c=='!')) { /* found newsgroup? */ + if (strcmp (tmp,group)) /* group name match? */ + while ((c != '\015') && (c != '\012') && (c != EOF)) c = getc (f); + else { /* yes, skip leading whitespace */ + do pos = ftell (f); + while ((c = getc (f)) == ' '); + /* count characters in state */ + for (size = 0; (c != '\015') && (c != '\012') && (c != EOF); size++) + c = getc (f); + /* now copy it */ + s = (char *) fs_get (size + 1); + fseek (f,pos,SEEK_SET); + fread (s,(size_t) 1,size,f); + s[size] = '\0'; /* tie off string */ + fclose (f); /* all done - close the file */ + return s; + } + } + } while (f && (c != EOF)); /* until file closed or EOF */ + sprintf (tmp,"No state for newsgroup %.80s found",group); + MM_LOG (tmp,WARN); + if (f) fclose (f); /* close the file */ + return NIL; /* not found return */ +} + +/* Check UID in newsgroup state + * Accepts: newsgroup state string + * uid + * returned recent count + * returned unseen count + */ + +void newsrc_check_uid (unsigned char *state,unsigned long uid, + unsigned long *recent,unsigned long *unseen) +{ + unsigned long i,j; + while (*state) { /* until run out of state string */ + /* collect a number */ + for (i = 0; isdigit (*state); i = i*10 + (*state++ - '0')); + if (*state != '-') j = i; /* coerce single mesage into range */ + else { /* have a range */ + for (j = 0; isdigit (*++state); j = j*10 + (*state - '0')); + if (!j) j = i; /* guard against -0 */ + if (j < i) return; /* bogon if end less than start */ + } + if (*state == ',') state++; /* skip past comma */ + else if (*state) return; /* otherwise it's a bogon */ + if (uid <= j) { /* covered by upper bound? */ + if (uid < i) ++*unseen; /* unseen if not covered by lower bound */ + return; /* don't need to look further */ + } + } + ++*unseen; /* not found in any range, message is unseen */ + ++*recent; /* and recent */ +} diff --git a/imap/src/c-client/newsrc.h b/imap/src/c-client/newsrc.h new file mode 100644 index 00000000..578f656d --- /dev/null +++ b/imap/src/c-client/newsrc.h @@ -0,0 +1,43 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Newsrc manipulation routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 12 September 1994 + * Last Edited: 30 August 2006 + */ + + +/* Function prototypes */ + +long newsrc_error (char *fmt,char *text,long errflg); +long newsrc_write_error (char *name,FILE *f1,FILE *f2); +FILE *newsrc_create (MAILSTREAM *stream,int notify); +long newsrc_newstate (FILE *f,char *group,char state,char *nl); +long newsrc_newmessages (FILE *f,MAILSTREAM *stream,char *nl); +void newsrc_lsub (MAILSTREAM *stream,char *pattern); +long newsrc_update (MAILSTREAM *stream,char *group,char state); +long newsrc_read (char *group,MAILSTREAM *stream); +long newsrc_write (char *group,MAILSTREAM *stream); +char *newsrc_state (MAILSTREAM *stream,char *group); +void newsrc_check_uid (unsigned char *state,unsigned long uid, + unsigned long *recent,unsigned long *unseen); diff --git a/imap/src/c-client/nl.h b/imap/src/c-client/nl.h new file mode 100644 index 00000000..520e9f7e --- /dev/null +++ b/imap/src/c-client/nl.h @@ -0,0 +1,34 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Newline routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 1 August 1988 + * Last Edited: 30 August 2006 + */ + + +/* Function prototypes */ + +unsigned long strcrlfcpy (unsigned char **dst,unsigned long *dstl, + unsigned char *src,unsigned long srcl); +unsigned long strcrlflen (STRING *s); diff --git a/imap/src/c-client/nntp.c b/imap/src/c-client/nntp.c new file mode 100644 index 00000000..fe90edba --- /dev/null +++ b/imap/src/c-client/nntp.c @@ -0,0 +1,2224 @@ +/* ======================================================================== + * Copyright 1988-2007 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Network News Transfer Protocol (NNTP) routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 10 February 1992 + * Last Edited: 6 September 2007 + */ + + +#include <ctype.h> +#include <stdio.h> +#include "c-client.h" +#include "newsrc.h" +#include "netmsg.h" +#include "flstring.h" + +/* Constants */ + +#define NNTPSSLPORT (long) 563 /* assigned SSL TCP contact port */ +#define NNTPGREET (long) 200 /* NNTP successful greeting */ + /* NNTP successful greeting w/o posting priv */ +#define NNTPGREETNOPOST (long) 201 +#define NNTPEXTOK (long) 202 /* NNTP extensions OK */ +#define NNTPGOK (long) 211 /* NNTP group selection OK */ +#define NNTPGLIST (long) 215 /* NNTP group list being returned */ +#define NNTPARTICLE (long) 220 /* NNTP article file */ +#define NNTPHEAD (long) 221 /* NNTP header text */ +#define NNTPBODY (long) 222 /* NNTP body text */ +#define NNTPOVER (long) 224 /* NNTP overview text */ +#define NNTPOK (long) 240 /* NNTP OK code */ +#define NNTPAUTHED (long) 281 /* NNTP successful authentication */ + /* NNTP successful authentication with data */ +#define NNTPAUTHEDDATA (long) 282 +#define NNTPREADY (long) 340 /* NNTP ready for data */ +#define NNTPWANTAUTH2 (long) 380/* NNTP authentication needed (old) */ +#define NNTPWANTPASS (long) 381 /* NNTP password needed */ +#define NNTPTLSSTART (long) 382 /* NNTP continue with TLS negotiation */ +#define NNTPCHALLENGE (long) 383/* NNTP challenge, want response */ +#define NNTPSOFTFATAL (long) 400/* NNTP soft fatal code */ +#define NNTPWANTAUTH (long) 480 /* NNTP authentication needed */ +#define NNTPBADCMD (long) 500 /* NNTP unrecognized command */ +#define IDLETIMEOUT (long) 3 /* defined in NNTPEXT WG base draft */ + + +/* NNTP I/O stream local data */ + +typedef struct nntp_local { + SENDSTREAM *nntpstream; /* NNTP stream for I/O */ + unsigned int dirty : 1; /* disk copy of .newsrc needs updating */ + unsigned int tlsflag : 1; /* TLS session */ + unsigned int tlssslv23 : 1; /* TLS using SSLv23 client method */ + unsigned int notlsflag : 1; /* TLS not used in session */ + unsigned int sslflag : 1; /* SSL session */ + unsigned int novalidate : 1; /* certificate not validated */ + unsigned int xover : 1; /* supports XOVER */ + unsigned int xhdr : 1; /* supports XHDR */ + char *name; /* remote newsgroup name */ + char *user; /* mailbox user */ + char *newsrc; /* newsrc file */ + char *over_fmt; /* overview format */ + unsigned long msgno; /* current text message number */ + FILE *txt; /* current text */ + unsigned long txtsize; /* current text size */ +} NNTPLOCAL; + + +/* Convenient access to local data */ + +#define LOCAL ((NNTPLOCAL *) stream->local) + + +/* Convenient access to protocol-specific data */ + +#define NNTP stream->protocol.nntp + + +/* Convenient access to extensions */ + +#define EXTENSION LOCAL->nntpstream->protocol.nntp.ext + +/* Function prototypes */ + +DRIVER *nntp_valid (char *name); +DRIVER *nntp_isvalid (char *name,char *mbx); +void *nntp_parameters (long function,void *value); +void nntp_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); +void nntp_list (MAILSTREAM *stream,char *ref,char *pat); +void nntp_lsub (MAILSTREAM *stream,char *ref,char *pat); +long nntp_canonicalize (char *ref,char *pat,char *pattern,char *wildmat); +long nntp_subscribe (MAILSTREAM *stream,char *mailbox); +long nntp_unsubscribe (MAILSTREAM *stream,char *mailbox); +long nntp_create (MAILSTREAM *stream,char *mailbox); +long nntp_delete (MAILSTREAM *stream,char *mailbox); +long nntp_rename (MAILSTREAM *stream,char *old,char *newname); +long nntp_status (MAILSTREAM *stream,char *mbx,long flags); +long nntp_getmap (MAILSTREAM *stream,char *name, + unsigned long first,unsigned long last, + unsigned long rnmsgs,unsigned long nmsgs,char *tmp); +MAILSTREAM *nntp_mopen (MAILSTREAM *stream); +void nntp_mclose (MAILSTREAM *stream,long options); +void nntp_fetchfast (MAILSTREAM *stream,char *sequence,long flags); +void nntp_flags (MAILSTREAM *stream,char *sequence,long flags); +long nntp_overview (MAILSTREAM *stream,overview_t ofn); +long nntp_parse_overview (OVERVIEW *ov,char *text,MESSAGECACHE *elt); +long nntp_over (MAILSTREAM *stream,char *sequence); +char *nntp_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *size, + long flags); +long nntp_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); +FILE *nntp_article (MAILSTREAM *stream,char *msgid,unsigned long *size, + unsigned long *hsiz); +void nntp_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt); +long nntp_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags); +long nntp_search_msg (MAILSTREAM *stream,unsigned long msgno,SEARCHPGM *pgm, + OVERVIEW *ov); +unsigned long *nntp_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, + SORTPGM *pgm,long flags); +SORTCACHE **nntp_sort_loadcache (MAILSTREAM *stream,SORTPGM *pgm, + unsigned long start,unsigned long last, + long flags); +THREADNODE *nntp_thread (MAILSTREAM *stream,char *type,char *charset, + SEARCHPGM *spg,long flags); +long nntp_ping (MAILSTREAM *stream); +void nntp_check (MAILSTREAM *stream); +long nntp_expunge (MAILSTREAM *stream,char *sequence,long options); +long nntp_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); +long nntp_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); + +long nntp_extensions (SENDSTREAM *stream,long flags); +long nntp_send (SENDSTREAM *stream,char *command,char *args); +long nntp_send_work (SENDSTREAM *stream,char *command,char *args); +long nntp_send_auth (SENDSTREAM *stream,long flags); +long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags); +void *nntp_challenge (void *s,unsigned long *len); +long nntp_response (void *s,char *response,unsigned long size); +long nntp_reply (SENDSTREAM *stream); +long nntp_fake (SENDSTREAM *stream,char *text); +long nntp_soutr (void *stream,char *s); + +/* Driver dispatch used by MAIL */ + +DRIVER nntpdriver = { + "nntp", /* driver name */ + /* driver flags */ +#ifdef INADEQUATE_MEMORY + DR_LOWMEM | +#endif + DR_NEWS|DR_READONLY|DR_NOFAST|DR_NAMESPACE|DR_CRLF|DR_RECYCLE|DR_XPOINT | + DR_NOINTDATE|DR_NONEWMAIL|DR_HALFOPEN, + (DRIVER *) NIL, /* next driver */ + nntp_valid, /* mailbox is valid for us */ + nntp_parameters, /* manipulate parameters */ + nntp_scan, /* scan mailboxes */ + nntp_list, /* find mailboxes */ + nntp_lsub, /* find subscribed mailboxes */ + nntp_subscribe, /* subscribe to mailbox */ + nntp_unsubscribe, /* unsubscribe from mailbox */ + nntp_create, /* create mailbox */ + nntp_delete, /* delete mailbox */ + nntp_rename, /* rename mailbox */ + nntp_status, /* status of mailbox */ + nntp_mopen, /* open mailbox */ + nntp_mclose, /* close mailbox */ + nntp_fetchfast, /* fetch message "fast" attributes */ + nntp_flags, /* fetch message flags */ + nntp_overview, /* fetch overview */ + NIL, /* fetch message structure */ + nntp_header, /* fetch message header */ + nntp_text, /* fetch message text */ + NIL, /* fetch message */ + NIL, /* unique identifier */ + NIL, /* message number from UID */ + NIL, /* modify flags */ + nntp_flagmsg, /* per-message modify flags */ + nntp_search, /* search for message based on criteria */ + nntp_sort, /* sort messages */ + nntp_thread, /* thread messages */ + nntp_ping, /* ping mailbox to see if still alive */ + nntp_check, /* check for new messages */ + nntp_expunge, /* expunge deleted messages */ + nntp_copy, /* copy messages to another mailbox */ + nntp_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + + /* prototype stream */ +MAILSTREAM nntpproto = {&nntpdriver}; + + + /* driver parameters */ +static unsigned long nntp_maxlogintrials = MAXLOGINTRIALS; +static long nntp_port = 0; +static long nntp_sslport = 0; +static unsigned long nntp_range = 0; +static long nntp_hidepath = 0; + +/* NNTP validate mailbox + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *nntp_valid (char *name) +{ + char tmp[MAILTMPLEN]; + return nntp_isvalid (name,tmp); +} + + +/* NNTP validate mailbox work routine + * Accepts: mailbox name + * buffer for returned mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *nntp_isvalid (char *name,char *mbx) +{ + NETMBX mb; + if (!mail_valid_net_parse (name,&mb) || strcmp (mb.service,nntpdriver.name)|| + mb.anoflag) return NIL; + if (mb.mailbox[0] != '#') strcpy (mbx,mb.mailbox); + /* namespace format name */ + else if ((mb.mailbox[1] == 'n') && (mb.mailbox[2] == 'e') && + (mb.mailbox[3] == 'w') && (mb.mailbox[4] == 's') && + (mb.mailbox[5] == '.')) strcpy (mbx,mb.mailbox+6); + else return NIL; /* bogus name */ + return &nntpdriver; +} + +/* News manipulate driver parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *nntp_parameters (long function,void *value) +{ + switch ((int) function) { + case SET_MAXLOGINTRIALS: + nntp_maxlogintrials = (unsigned long) value; + break; + case GET_MAXLOGINTRIALS: + value = (void *) nntp_maxlogintrials; + break; + case SET_NNTPPORT: + nntp_port = (long) value; + break; + case GET_NNTPPORT: + value = (void *) nntp_port; + break; + case SET_SSLNNTPPORT: + nntp_sslport = (long) value; + break; + case GET_SSLNNTPPORT: + value = (void *) nntp_sslport; + break; + case SET_NNTPRANGE: + nntp_range = (unsigned long) value; + break; + case GET_NNTPRANGE: + value = (void *) nntp_range; + break; + case SET_NNTPHIDEPATH: + nntp_hidepath = (long) value; + break; + case GET_NNTPHIDEPATH: + value = (void *) nntp_hidepath; + break; + case GET_NEWSRC: + if (value) + value = (void *) ((NNTPLOCAL *) ((MAILSTREAM *) value)->local)->newsrc; + break; + case GET_IDLETIMEOUT: + value = (void *) IDLETIMEOUT; + break; + case ENABLE_DEBUG: + if (value) + ((NNTPLOCAL *) ((MAILSTREAM *) value)->local)->nntpstream->debug = T; + break; + case DISABLE_DEBUG: + if (value) + ((NNTPLOCAL *) ((MAILSTREAM *) value)->local)->nntpstream->debug = NIL; + break; + default: + value = NIL; /* error case */ + break; + } + return value; +} + +/* NNTP mail scan mailboxes for string + * Accepts: mail stream + * reference + * pattern to search + * string to scan + */ + +void nntp_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) +{ + char tmp[MAILTMPLEN]; + if (nntp_canonicalize (ref,pat,tmp,NIL)) + mm_log ("Scan not valid for NNTP mailboxes",ERROR); +} + + +/* NNTP list newsgroups + * Accepts: mail stream + * reference + * pattern to search + */ + +void nntp_list (MAILSTREAM *stream,char *ref,char *pat) +{ + MAILSTREAM *st = stream; + char *s,*t,*lcl,pattern[MAILTMPLEN],name[MAILTMPLEN],wildmat[MAILTMPLEN]; + int showuppers = pat[strlen (pat) - 1] == '%'; + if (!*pat) { + if (nntp_canonicalize (ref,"*",pattern,NIL)) { + /* tie off name at root */ + if ((s = strchr (pattern,'}')) && (s = strchr (s+1,'.'))) *++s = '\0'; + else pattern[0] = '\0'; + mm_list (stream,'.',pattern,NIL); + } + } + /* ask server for open newsgroups */ + else if (nntp_canonicalize (ref,pat,pattern,wildmat) && + ((stream && LOCAL && LOCAL->nntpstream) || + (stream = mail_open (NIL,pattern,OP_HALFOPEN|OP_SILENT))) && + ((nntp_send (LOCAL->nntpstream,"LIST ACTIVE", + wildmat[0] ? wildmat : NIL) == NNTPGLIST) || + (nntp_send (LOCAL->nntpstream,"LIST",NIL) == NNTPGLIST))) { + /* namespace format name? */ + if (*(lcl = strchr (strcpy (name,pattern),'}') + 1) == '#') lcl += 6; + /* process data until we see final dot */ + while (s = net_getline (LOCAL->nntpstream->netstream)) { + if ((*s == '.') && !s[1]){/* end of text */ + fs_give ((void **) &s); + break; + } + if (t = strchr (s,' ')) { /* tie off after newsgroup name */ + *t = '\0'; + strcpy (lcl,s); /* make full form of name */ + /* report if match */ + if (pmatch_full (name,pattern,'.')) mm_list (stream,'.',name,NIL); + else while (showuppers && (t = strrchr (lcl,'.'))) { + *t = '\0'; /* tie off the name */ + if (pmatch_full (name,pattern,'.')) + mm_list (stream,'.',name,LATT_NOSELECT); + } + } + fs_give ((void **) &s); /* clean up */ + } + if (stream != st) mail_close (stream); + } +} + +/* NNTP list subscribed newsgroups + * Accepts: mail stream + * reference + * pattern to search + */ + +void nntp_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + void *sdb = NIL; + char *s,mbx[MAILTMPLEN]; + /* return data from newsrc */ + if (nntp_canonicalize (ref,pat,mbx,NIL)) newsrc_lsub (stream,mbx); + if (*pat == '{') { /* if remote pattern, must be NNTP */ + if (!nntp_valid (pat)) return; + ref = NIL; /* good NNTP pattern, punt reference */ + } + /* if remote reference, must be valid NNTP */ + if (ref && (*ref == '{') && !nntp_valid (ref)) return; + /* kludgy application of reference */ + if (ref && *ref) sprintf (mbx,"%s%s",ref,pat); + else strcpy (mbx,pat); + + if (s = sm_read (&sdb)) do if (nntp_valid (s) && pmatch (s,mbx)) + mm_lsub (stream,NIL,s,NIL); + while (s = sm_read (&sdb)); /* until no more subscriptions */ +} + +/* NNTP canonicalize newsgroup name + * Accepts: reference + * pattern + * returned single pattern + * returned wildmat pattern + * Returns: T on success, NIL on failure + */ + +long nntp_canonicalize (char *ref,char *pat,char *pattern,char *wildmat) +{ + char *s; + DRIVER *ret; + if (ref && *ref) { /* have a reference */ + if (!nntp_valid (ref)) return NIL; + strcpy (pattern,ref); /* copy reference to pattern */ + /* # overrides mailbox field in reference */ + if (*pat == '#') strcpy (strchr (pattern,'}') + 1,pat); + /* pattern starts, reference ends, with . */ + else if ((*pat == '.') && (pattern[strlen (pattern) - 1] == '.')) + strcat (pattern,pat + 1); /* append, omitting one of the period */ + else strcat (pattern,pat); /* anything else is just appended */ + } + else strcpy (pattern,pat); /* just have basic name */ + if ((ret = wildmat ? /* if valid and wildmat */ + nntp_isvalid (pattern,wildmat) : nntp_valid (pattern)) && wildmat) { + /* don't return wildmat if specials present */ + if (strpbrk (wildmat,",?![\\]")) wildmat[0] = '\0'; + /* replace all % with * */ + for (s = wildmat; s = strchr (s,'%'); *s = '*'); + } + return ret ? LONGT : NIL; +} + +/* NNTP subscribe to mailbox + * Accepts: mail stream + * mailbox to add to subscription list + * Returns: T on success, NIL on failure + */ + +long nntp_subscribe (MAILSTREAM *stream,char *mailbox) +{ + char mbx[MAILTMPLEN]; + return nntp_isvalid (mailbox,mbx) ? newsrc_update (stream,mbx,':') : NIL; +} + + +/* NNTP unsubscribe to mailbox + * Accepts: mail stream + * mailbox to delete from subscription list + * Returns: T on success, NIL on failure + */ + +long nntp_unsubscribe (MAILSTREAM *stream,char *mailbox) +{ + char mbx[MAILTMPLEN]; + return nntp_isvalid (mailbox,mbx) ? newsrc_update (stream,mbx,'!') : NIL; +} + +/* NNTP create mailbox + * Accepts: mail stream + * mailbox name to create + * Returns: T on success, NIL on failure + */ + +long nntp_create (MAILSTREAM *stream,char *mailbox) +{ + return NIL; /* never valid for NNTP */ +} + + +/* NNTP delete mailbox + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long nntp_delete (MAILSTREAM *stream,char *mailbox) +{ + return NIL; /* never valid for NNTP */ +} + + +/* NNTP rename mailbox + * Accepts: mail stream + * old mailbox name + * new mailbox name + * Returns: T on success, NIL on failure + */ + +long nntp_rename (MAILSTREAM *stream,char *old,char *newname) +{ + return NIL; /* never valid for NNTP */ +} + +/* NNTP status + * Accepts: mail stream + * mailbox name + * status flags + * Returns: T on success, NIL on failure + */ + +long nntp_status (MAILSTREAM *stream,char *mbx,long flags) +{ + MAILSTATUS status; + NETMBX mb; + unsigned long i,j,k,rnmsgs; + long ret = NIL; + char *s,*name,*state,tmp[MAILTMPLEN]; + char *old = (stream && !stream->halfopen) ? LOCAL->name : NIL; + MAILSTREAM *tstream = NIL; + if (!(mail_valid_net_parse (mbx,&mb) && !strcmp (mb.service,"nntp") && + *mb.mailbox && + ((mb.mailbox[0] != '#') || + ((mb.mailbox[1] == 'n') && (mb.mailbox[2] == 'e') && + (mb.mailbox[3] == 'w') && (mb.mailbox[4] == 's') && + (mb.mailbox[5] == '.'))))) { + sprintf (tmp,"Invalid NNTP name %s",mbx); + mm_log (tmp,ERROR); + return NIL; + } + /* note mailbox name */ + name = (*mb.mailbox == '#') ? mb.mailbox+6 : mb.mailbox; + /* stream to reuse? */ + if (!(stream && LOCAL->nntpstream && + mail_usable_network_stream (stream,mbx)) && + !(tstream = stream = + mail_open (NIL,mbx,OP_HALFOPEN|OP_SILENT| + ((flags & SA_MULNEWSRC) ? OP_MULNEWSRC : NIL)))) + return NIL; /* can't reuse or make a new one */ + + if (nntp_send (LOCAL->nntpstream,"GROUP",name) == NNTPGOK) { + status.flags = flags; /* status validity flags */ + k = strtoul (LOCAL->nntpstream->reply + 4,&s,10); + i = strtoul (s,&s,10); /* first assigned UID */ + /* next UID to be assigned */ + status.uidnext = (j = strtoul (s,NIL,10)) + 1; + /* maximum number of messages */ + rnmsgs = status.messages = (i | j) ? status.uidnext - i : 0; + if (k > status.messages) { /* check for absurdity */ + sprintf (tmp,"NNTP SERVER BUG (impossible message count): %lu > %lu", + k,status.messages); + mm_log (tmp,WARN); + } + /* restrict article range if needed */ + if (nntp_range && (status.messages > nntp_range)) { + i = status.uidnext - (status.messages = nntp_range); + if (k > nntp_range) k = nntp_range; + } + /* initially zero */ + status.recent = status.unseen = 0; + if (!status.messages); /* empty case */ + /* use server guesstimate in simple case */ + else if (!(flags & (SA_RECENT | SA_UNSEEN))) status.messages = k; + + /* have newsrc state? */ + else if (state = newsrc_state (stream,name)) { + /* yes, get the UID/sequence map */ + if (nntp_getmap (stream,name,i,status.uidnext - 1,rnmsgs, + status.messages,tmp)) { + /* calculate true count */ + for (status.messages = 0; + (s = net_getline (LOCAL->nntpstream->netstream)) && + strcmp (s,"."); ) { + /* only count if in range */ + if (((k = atol (s)) >= i) && (k < status.uidnext)) { + newsrc_check_uid (state,k,&status.recent,&status.unseen); + status.messages++; + } + fs_give ((void **) &s); + } + if (s) fs_give ((void **) &s); + } + /* assume c-client/NNTP map is entire range */ + else while (i < status.uidnext) + newsrc_check_uid (state,i++,&status.recent,&status.unseen); + fs_give ((void **) &state); + } + /* no .newsrc state, all messages new */ + else status.recent = status.unseen = status.messages; + /* UID validity is a constant */ + status.uidvalidity = stream->uid_validity; + /* pass status to main program */ + mm_status (stream,mbx,&status); + ret = T; /* succes */ + } + /* flush temporary stream */ + if (tstream) mail_close (tstream); + /* else reopen old newsgroup */ + else if (old && nntp_send (LOCAL->nntpstream,"GROUP",old) != NNTPGOK) { + mm_log (LOCAL->nntpstream->reply,ERROR); + stream->halfopen = T; /* go halfopen */ + } + return ret; /* success */ +} + +/* NNTP get map + * Accepts: stream + * newsgroup name + * first UID in map range + * last UID in map range + * reported total number of messages in newsgroup + * calculated number of messages in range + * temporary buffer + * Returns: T on success, NIL on failure + */ + +long nntp_getmap (MAILSTREAM *stream,char *name, + unsigned long first,unsigned long last, + unsigned long rnmsgs,unsigned long nmsgs,char *tmp) +{ + short trylistgroup = NIL; + if (rnmsgs > (nmsgs * 8)) /* small subrange? */ + trylistgroup = T; /* yes, can try LISTGROUP if [X]HDR fails */ + else switch ((int) nntp_send (LOCAL->nntpstream,"LISTGROUP",name)) { + case NNTPGOK: /* got data */ + return LONGT; + default: /* else give up if server claims LISTGROUP */ + if (EXTENSION.listgroup) return NIL; + } + /* build range */ + sprintf (tmp,"%lu-%lu",first,last); + if (EXTENSION.hdr) /* have HDR extension? */ + return (nntp_send (LOCAL->nntpstream,"HDR Date",tmp) == NNTPHEAD) ? + LONGT : NIL; + if (LOCAL->xhdr) /* try the experimental extension then */ + switch ((int) nntp_send (LOCAL->nntpstream,"XHDR Date",tmp)) { + case NNTPHEAD: /* got an overview? */ + return LONGT; + case NNTPBADCMD: /* unknown command? */ + LOCAL->xhdr = NIL; /* disable future XHDR attempts */ + } + if (trylistgroup && /* no [X]HDR, maybe do LISTGROUP after all */ + (nntp_send (LOCAL->nntpstream,"LISTGROUP",name) == NNTPGOK)) + return LONGT; + return NIL; +} + +/* NNTP open + * Accepts: stream to open + * Returns: stream on success, NIL on failure + */ + +MAILSTREAM *nntp_mopen (MAILSTREAM *stream) +{ + unsigned long i,j,k,nmsgs,rnmsgs; + char *s,*mbx,tmp[MAILTMPLEN]; + FILE *f; + NETMBX mb; + char *newsrc = (char *) mail_parameters (NIL,GET_NEWSRC,NIL); + newsrcquery_t nq = (newsrcquery_t) mail_parameters (NIL,GET_NEWSRCQUERY,NIL); + SENDSTREAM *nstream = NIL; + /* return prototype for OP_PROTOTYPE call */ + if (!stream) return &nntpproto; + mail_valid_net_parse (stream->mailbox,&mb); + /* note mailbox anme */ + mbx = (*mb.mailbox == '#') ? mb.mailbox+6 : mb.mailbox; + if (LOCAL) { /* recycle stream */ + nstream = LOCAL->nntpstream;/* remember NNTP protocol stream */ + sprintf (tmp,"Reusing connection to %s",net_host (nstream->netstream)); + if (!stream->silent) mm_log (tmp,(long) NIL); + if (stream->rdonly) mb.readonlyflag = T; + if (LOCAL->tlsflag) mb.tlsflag = T; + if (LOCAL->tlssslv23) mb.tlssslv23 = T; + if (LOCAL->notlsflag) mb.notlsflag = T; + if (LOCAL->sslflag) mb.sslflag = T; + if (LOCAL->novalidate) mb.novalidate = T; + if (LOCAL->nntpstream->loser) mb.loser = T; + if (stream->secure) mb.secflag = T; + LOCAL->nntpstream = NIL; /* keep nntp_mclose() from punting it */ + nntp_mclose (stream,NIL); /* do close action */ + stream->dtb = &nntpdriver; /* reattach this driver */ + } + /* copy flags */ + if (mb.dbgflag) stream->debug = T; + if (mb.readonlyflag) stream->rdonly = T; + if (mb.secflag) stream->secure = T; + mb.trysslflag = stream->tryssl = (mb.trysslflag || stream->tryssl) ? T : NIL; + if (!nstream) { /* open NNTP now if not already open */ + char *hostlist[2]; + hostlist[0] = strcpy (tmp,mb.host); + if (mb.port || nntp_port) + sprintf (tmp + strlen (tmp),":%lu",mb.port ? mb.port : nntp_port); + if (mb.tlsflag) strcat (tmp,"/tls"); + if (mb.tlssslv23) strcat (tmp,"/tls-sslv23"); + if (mb.notlsflag) strcat (tmp,"/notls"); + if (mb.sslflag) strcat (tmp,"/ssl"); + if (mb.novalidate) strcat (tmp,"/novalidate-cert"); + if (mb.loser) strcat (tmp,"/loser"); + if (mb.secflag) strcat (tmp,"/secure"); + if (mb.user[0]) sprintf (tmp + strlen (tmp),"/user=\"%s\"",mb.user); + hostlist[1] = NIL; + if (!(nstream = nntp_open (hostlist,NOP_READONLY | + (stream->debug ? NOP_DEBUG : NIL)))) return NIL; + } + + /* always zero messages if halfopen */ + if (stream->halfopen) i = j = k = rnmsgs = nmsgs = 0; + /* otherwise open the newsgroup */ + else if (nntp_send (nstream,"GROUP",mbx) == NNTPGOK) { + k = strtoul (nstream->reply + 4,&s,10); + i = strtoul (s,&s,10); + stream->uid_last = j = strtoul (s,&s,10); + rnmsgs = nmsgs = (i | j) ? 1 + j - i : 0; + if (k > nmsgs) { /* check for absurdity */ + sprintf (tmp,"NNTP SERVER BUG (impossible message count): %lu > %lu", + k,nmsgs); + mm_log (tmp,WARN); + } + /* restrict article range if needed */ + if (nntp_range && (nmsgs > nntp_range)) i = 1 + j - (nmsgs = nntp_range); + } + else { /* no such newsgroup */ + mm_log (nstream->reply,ERROR); + nntp_close (nstream); /* punt stream */ + return NIL; + } + /* instantiate local data */ + stream->local = memset (fs_get (sizeof (NNTPLOCAL)),0,sizeof (NNTPLOCAL)); + LOCAL->nntpstream = nstream; + /* save state for future recycling */ + if (mb.tlsflag) LOCAL->tlsflag = T; + if (mb.tlssslv23) LOCAL->tlssslv23 = T; + if (mb.notlsflag) LOCAL->notlsflag = T; + if (mb.sslflag) LOCAL->sslflag = T; + if (mb.novalidate) LOCAL->novalidate = T; + if (mb.loser) LOCAL->nntpstream->loser = T; + /* assume present until proven otherwise */ + LOCAL->xhdr = LOCAL->xover = T; + LOCAL->name = cpystr (mbx); /* copy newsgroup name */ + if (stream->mulnewsrc) { /* want to use multiple .newsrc files? */ + strcpy (tmp,newsrc); + s = tmp + strlen (tmp); /* end of string */ + *s++ = '-'; /* hyphen delimiter and host */ + lcase (strcpy (s,(long) mail_parameters (NIL,GET_NEWSRCCANONHOST,NIL) ? + net_host (nstream->netstream) : mb.host)); + LOCAL->newsrc = cpystr (nq ? (*nq) (stream,tmp,newsrc) : tmp); + } + else LOCAL->newsrc = cpystr (newsrc); + if (mb.user[0]) LOCAL->user = cpystr (mb.user); + stream->sequence++; /* bump sequence number */ + stream->rdonly = stream->perm_deleted = T; + /* UIDs are always valid */ + stream->uid_validity = 0xbeefface; + sprintf (tmp,"{%s:%lu/nntp",(long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ? + net_host (nstream->netstream) : mb.host, + net_port (nstream->netstream)); + if (LOCAL->tlsflag) strcat (tmp,"/tls"); + if (LOCAL->tlssslv23) strcat (tmp,"/tls-sslv23"); + if (LOCAL->notlsflag) strcat (tmp,"/notls"); + if (LOCAL->sslflag) strcat (tmp,"/ssl"); + if (LOCAL->novalidate) strcat (tmp,"/novalidate-cert"); + if (LOCAL->nntpstream->loser) strcat (tmp,"/loser"); + if (stream->secure) strcat (tmp,"/secure"); + if (stream->rdonly) strcat (tmp,"/readonly"); + if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user=\"%s\"",LOCAL->user); + if (stream->halfopen) strcat (tmp,"}<no_mailbox>"); + else sprintf (tmp + strlen (tmp),"}#news.%s",mbx); + fs_give ((void **) &stream->mailbox); + stream->mailbox = cpystr (tmp); + + if (EXTENSION.over && /* get overview format if have OVER */ + (nntp_send (LOCAL->nntpstream,"LIST","OVERVIEW.FMT") == NNTPGLIST) && + (f = netmsg_slurp (LOCAL->nntpstream->netstream,&k,NIL))) { + fread (LOCAL->over_fmt = (char *) fs_get ((size_t) k + 3), + (size_t) 1,(size_t) k,f); + LOCAL->over_fmt[k] = '\0'; + fclose (f); /* flush temp file */ + } + if (nmsgs) { /* if any messages exist */ + short silent = stream->silent; + stream->silent = T; /* don't notify main program yet */ + mail_exists (stream,nmsgs); /* silently set the cache to the guesstimate */ + /* get UID/sequence map, nuke holes */ + if (nntp_getmap (stream,mbx,i,j,rnmsgs,nmsgs,tmp)) { + for (nmsgs = 0; /* calculate true count */ + (s = net_getline (nstream->netstream)) && strcmp (s,"."); ) { + if ((k = atol (s)) > j){/* discard too high article numbers */ + sprintf (tmp,"NNTP SERVER BUG (out of range article ID): %lu > %lu", + k,j); + mm_notify (stream,tmp,NIL); + stream->unhealthy = T; + } + else if (k >= i) { /* silently ignore too-low article numbers */ + /* guard against server returning extra msgs */ + if (nmsgs == stream->nmsgs) mail_exists (stream,nmsgs+1); + /* create elt for this message, set UID */ + mail_elt (stream,++nmsgs)->private.uid = k; + } + fs_give ((void **) &s); + } + if (s) fs_give ((void **) &s); + } + /* assume c-client/NNTP map is entire range */ + else for (k = 1; k <= nmsgs; k++) mail_elt (stream,k)->private.uid = i++; + stream->unhealthy = NIL; /* set healthy */ + stream->nmsgs = 0; /* whack it back down */ + stream->silent = silent; /* restore old silent setting */ + mail_exists (stream,nmsgs); /* notify upper level that messages exist */ + /* read .newsrc entries */ + mail_recent (stream,newsrc_read (mbx,stream)); + } + else { /* empty newsgroup or halfopen */ + if (!(stream->silent || stream->halfopen)) { + sprintf (tmp,"Newsgroup %s is empty",mbx); + mm_log (tmp,WARN); + } + mail_exists (stream,(long) 0); + mail_recent (stream,(long) 0); + } + return stream; /* return stream to caller */ +} + +/* NNTP close + * Accepts: MAIL stream + * option flags + */ + +void nntp_mclose (MAILSTREAM *stream,long options) +{ + unsigned long i; + MESSAGECACHE *elt; + if (LOCAL) { /* only if a file is open */ + nntp_check (stream); /* dump final checkpoint */ + if (LOCAL->over_fmt) fs_give ((void **) &LOCAL->over_fmt); + if (LOCAL->name) fs_give ((void **) &LOCAL->name); + if (LOCAL->user) fs_give ((void **) &LOCAL->user); + if (LOCAL->newsrc) fs_give ((void **) &LOCAL->newsrc); + if (LOCAL->txt) fclose (LOCAL->txt); + /* close NNTP connection */ + if (LOCAL->nntpstream) nntp_close (LOCAL->nntpstream); + for (i = 1; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->private.spare.ptr) + fs_give ((void **) &elt->private.spare.ptr); + /* nuke the local data */ + fs_give ((void **) &stream->local); + stream->dtb = NIL; /* log out the DTB */ + } +} + +/* NNTP fetch fast information + * Accepts: MAIL stream + * sequence + * option flags + * This is ugly and slow + */ + +void nntp_fetchfast (MAILSTREAM *stream,char *sequence,long flags) +{ + unsigned long i; + MESSAGECACHE *elt; + /* get sequence */ + if (stream && LOCAL && ((flags & FT_UID) ? + mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) + for (i = 1; i <= stream->nmsgs; i++) { + if ((elt = mail_elt (stream,i))->sequence && (elt->valid = T) && + !(elt->day && elt->rfc822_size)) { + ENVELOPE **env = NIL; + ENVELOPE *e = NIL; + if (!stream->scache) env = &elt->private.msg.env; + else if (stream->msgno == i) env = &stream->env; + else env = &e; + if (!*env || !elt->rfc822_size) { + STRING bs; + unsigned long hs; + char *ht = (*stream->dtb->header) (stream,i,&hs,NIL); + /* need to make an envelope? */ + if (!*env) rfc822_parse_msg (env,NIL,ht,hs,NIL,BADHOST, + stream->dtb->flags); + /* need message size too, ugh */ + if (!elt->rfc822_size) { + (*stream->dtb->text) (stream,i,&bs,FT_PEEK); + elt->rfc822_size = hs + SIZE (&bs) - GETPOS (&bs); + } + } + /* if need date, have date in envelope? */ + if (!elt->day && *env && (*env)->date) + mail_parse_date (elt,(*env)->date); + /* sigh, fill in bogus default */ + if (!elt->day) elt->day = elt->month = 1; + mail_free_envelope (&e); + } + } +} + +/* NNTP fetch flags + * Accepts: MAIL stream + * sequence + * option flags + */ + +void nntp_flags (MAILSTREAM *stream,char *sequence,long flags) +{ + unsigned long i; + if ((flags & FT_UID) ? /* validate all elts */ + mail_uid_sequence (stream,sequence) : mail_sequence (stream,sequence)) + for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->valid = T; +} + +/* NNTP fetch overview + * Accepts: MAIL stream, sequence bits set + * overview return function + * Returns: T if successful, NIL otherwise + */ + +long nntp_overview (MAILSTREAM *stream,overview_t ofn) +{ + unsigned long i,j,k,uid; + char c,*s,*t,*v,tmp[MAILTMPLEN]; + MESSAGECACHE *elt; + OVERVIEW ov; + if (!LOCAL->nntpstream->netstream) return NIL; + /* scan sequence to load cache */ + for (i = 1; i <= stream->nmsgs; i++) + /* have cached overview yet? */ + if ((elt = mail_elt (stream,i))->sequence && !elt->private.spare.ptr) { + for (j = i + 1; /* no, find end of cache gap range */ + (j <= stream->nmsgs) && (elt = mail_elt (stream,j))->sequence && + !elt->private.spare.ptr; j++); + /* make NNTP range */ + sprintf (tmp,(i == (j - 1)) ? "%lu" : "%lu-%lu",mail_uid (stream,i), + mail_uid (stream,j - 1)); + i = j; /* advance beyond gap */ + /* ask server for overview data to cache */ + if (nntp_over (stream,tmp)) { + while ((s = net_getline (LOCAL->nntpstream->netstream)) && + strcmp (s,".")) { + /* death to embedded newlines */ + for (t = v = s; c = *v++;) + if ((c != '\012') && (c != '\015')) *t++ = c; + *t++ = '\0'; /* tie off string in case it was shortened */ + /* cache the overview if found its sequence */ + if ((uid = atol (s)) && (k = mail_msgno (stream,uid)) && + (t = strchr (s,'\t'))) { + if ((elt = mail_elt (stream,k))->private.spare.ptr) + fs_give ((void **) &elt->private.spare.ptr); + elt->private.spare.ptr = cpystr (t + 1); + } + else { /* shouldn't happen, snarl if it does */ + sprintf (tmp,"Server returned data for unknown UID %lu",uid); + mm_notify (stream,tmp,WARN); + stream->unhealthy = T; + } + /* flush the overview */ + fs_give ((void **) &s); + } + stream->unhealthy = NIL;/* set healthy */ + /* flush the terminating dot */ + if (s) fs_give ((void **) &s); + } + else i = stream->nmsgs; /* OVER failed, punt cache load */ + } + + /* now scan sequence to return overviews */ + if (ofn) for (i = 1; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->sequence) { + uid = mail_uid (stream,i);/* UID for this message */ + /* parse cached overview */ + if (nntp_parse_overview (&ov,s = (char *) elt->private.spare.ptr,elt)) + (*ofn) (stream,uid,&ov,i); + else { /* parse failed */ + (*ofn) (stream,uid,NIL,i); + if (s && *s) { /* unusable cached entry? */ + sprintf (tmp,"Unable to parse overview for UID %lu: %.500s",uid,s); + mm_notify (stream,tmp,WARN); + stream->unhealthy = T; + /* erase it from the cache */ + fs_give ((void **) &s); + } + stream->unhealthy = NIL;/* set healthy */ + /* insert empty cached text as necessary */ + if (!s) elt->private.spare.ptr = cpystr (""); + } + /* clean up overview data */ + if (ov.from) mail_free_address (&ov.from); + if (ov.subject) fs_give ((void **) &ov.subject); + } + return T; +} + +/* Send OVER to NNTP server + * Accepts: mail stream + * sequence to send + * Returns: T if success and overviews will follow, else NIL + */ + +long nntp_over (MAILSTREAM *stream,char *sequence) +{ + unsigned char *s; + /* test for Netscape Collabra server */ + if (EXTENSION.over && LOCAL->xover && + nntp_send (LOCAL->nntpstream,"OVER","0") == NNTPOVER) { + /* "Netscape-Collabra/3.52 03615 NNTP" responds to the OVER command with + * a bogus "Subject:From:Date:Bytes:Lines" response followed by overviews + * which lack the Message-ID and References:. This violates the draft + * NNTP specification (draft-ietf-nntpext-base-18.txt as of this writing). + * XOVER works fine. + */ + while ((s = net_getline (LOCAL->nntpstream->netstream)) && strcmp (s,".")){ + if (!isdigit (*s)) { /* is it that fetid piece of reptile dung? */ + EXTENSION.over = NIL; /* sure smells like it */ + mm_log ("Working around Netscape Collabra bug",WARN); + } + fs_give ((void **) &s); /* flush the overview */ + } + if (s) fs_give ((void **) &s); + /* don't do this test again */ + if (EXTENSION.over) LOCAL->xover = NIL; + } + if (EXTENSION.over) /* have OVER extension? */ + return (nntp_send (LOCAL->nntpstream,"OVER",sequence) == NNTPOVER) ? + LONGT : NIL; + if (LOCAL->xover) /* try the experiment extension then */ + switch ((int) nntp_send (LOCAL->nntpstream,"XOVER",sequence)) { + case NNTPOVER: /* got an overview? */ + return LONGT; + case NNTPBADCMD: /* unknown command? */ + LOCAL->xover = NIL; /* disable future XOVER attempts */ + } + return NIL; +} + +/* Parse OVERVIEW struct from cached NNTP OVER response + * Accepts: struct to load + * cached OVER response + * internaldate + * Returns: T if success, NIL if fail + */ + +long nntp_parse_overview (OVERVIEW *ov,char *text,MESSAGECACHE *elt) +{ + char *t; + /* nothing in overview yet */ + memset ((void *) ov,0,sizeof (OVERVIEW)); + /* no cached data */ + if (!(text && *text)) return NIL; + ov->subject = cpystr (text); /* make hackable copy of overview */ + /* find end of Subject */ + if (t = strchr (ov->subject,'\t')) { + *t++ = '\0'; /* tie off Subject, point to From */ + /* find end of From */ + if (ov->date = strchr (t,'\t')) { + *ov->date++ = '\0'; /* tie off From, point to Date */ + /* load internaldate too */ + if (!elt->day) mail_parse_date (elt,ov->date); + /* parse From */ + rfc822_parse_adrlist (&ov->from,t,BADHOST); + /* find end of Date */ + if (ov->message_id = strchr (ov->date,'\t')) { + /* tie off Date, point to Message-ID */ + *ov->message_id++ = '\0'; + /* find end of Message-ID */ + if (ov->references = strchr (ov->message_id,'\t')) { + /* tie off Message-ID, point to References */ + *ov->references++ = '\0'; + /* fine end of References */ + if (t = strchr (ov->references,'\t')) { + *t++ = '\0'; /* tie off References, point to octet size */ + /* parse size of message in octets */ + ov->optional.octets = atol (t); + /* find end of size */ + if (t = strchr (t,'\t')) { + /* parse size of message in lines */ + ov->optional.lines = atol (++t); + /* find Xref */ + if (ov->optional.xref = strchr (t,'\t')) + *ov->optional.xref++ = '\0'; + } + } + } + } + } + } + return ov->references ? T : NIL; +} + +/* NNTP fetch header as text + * Accepts: mail stream + * message number + * pointer to return size + * flags + * Returns: header text + */ + +char *nntp_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *size, + long flags) +{ + char tmp[MAILTMPLEN]; + MESSAGECACHE *elt; + FILE *f; + *size = 0; + if ((flags & FT_UID) && !(msgno = mail_msgno (stream,msgno))) return ""; + /* have header text? */ + if (!(elt = mail_elt (stream,msgno))->private.msg.header.text.data) { + sprintf (tmp,"%lu",mail_uid (stream,msgno)); + /* get header text */ + switch (nntp_send (LOCAL->nntpstream,"HEAD",tmp)) { + case NNTPHEAD: + if (f = netmsg_slurp (LOCAL->nntpstream->netstream,size,NIL)) { + fread (elt->private.msg.header.text.data = + (unsigned char *) fs_get ((size_t) *size + 3), + (size_t) 1,(size_t) *size,f); + fclose (f); /* flush temp file */ + /* tie off header with extra CRLF and NUL */ + elt->private.msg.header.text.data[*size] = '\015'; + elt->private.msg.header.text.data[++*size] = '\012'; + elt->private.msg.header.text.data[++*size] = '\0'; + elt->private.msg.header.text.size = *size; + elt->valid = T; /* make elt valid now */ + break; + } + /* fall into default case */ + default: /* failed, mark as deleted and empty */ + elt->valid = elt->deleted = T; + case NNTPSOFTFATAL: /* don't mark deleted if stream dead */ + *size = elt->private.msg.header.text.size = 0; + break; + } + } + /* just return size of text */ + else *size = elt->private.msg.header.text.size; + return elt->private.msg.header.text.data ? + (char *) elt->private.msg.header.text.data : ""; +} + +/* NNTP fetch body + * Accepts: mail stream + * message number + * pointer to stringstruct to initialize + * flags + * Returns: T if successful, else NIL + */ + +long nntp_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags) +{ + char tmp[MAILTMPLEN]; + MESSAGECACHE *elt; + INIT (bs,mail_string,(void *) "",0); + if ((flags & FT_UID) && !(msgno = mail_msgno (stream,msgno))) return NIL; + elt = mail_elt (stream,msgno); + /* different message, flush cache */ + if (LOCAL->txt && (LOCAL->msgno != msgno)) { + fclose (LOCAL->txt); + LOCAL->txt = NIL; + } + LOCAL->msgno = msgno; /* note cached message */ + if (!LOCAL->txt) { /* have file for this message? */ + sprintf (tmp,"%lu",elt->private.uid); + switch (nntp_send (LOCAL->nntpstream,"BODY",tmp)) { + case NNTPBODY: + if (LOCAL->txt = netmsg_slurp (LOCAL->nntpstream->netstream, + &LOCAL->txtsize,NIL)) break; + /* fall into default case */ + default: /* failed, mark as deleted */ + elt->deleted = T; + case NNTPSOFTFATAL: /* don't mark deleted if stream dead */ + return NIL; + } + } + if (!(flags & FT_PEEK)) { /* mark seen if needed */ + elt->seen = T; + mm_flags (stream,elt->msgno); + } + INIT (bs,file_string,(void *) LOCAL->txt,LOCAL->txtsize); + return T; +} + +/* NNTP fetch article from message ID (for news: URL support) + * Accepts: mail stream + * message ID + * pointer to return total message size + * pointer to return file size + * Returns: FILE * to message if successful, else NIL + */ + +FILE *nntp_article (MAILSTREAM *stream,char *msgid,unsigned long *size, + unsigned long *hsiz) +{ + return (nntp_send (LOCAL->nntpstream,"ARTICLE",msgid) == NNTPARTICLE) ? + netmsg_slurp (LOCAL->nntpstream->netstream,size,hsiz) : NIL; +} + + +/* NNTP per-message modify flag + * Accepts: MAIL stream + * message cache element + */ + +void nntp_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt) +{ + if (!LOCAL->dirty) { /* only bother checking if not dirty yet */ + if (elt->valid) { /* if done, see if deleted changed */ + if (elt->sequence != elt->deleted) LOCAL->dirty = T; + elt->sequence = T; /* leave the sequence set */ + } + /* note current setting of deleted flag */ + else elt->sequence = elt->deleted; + } +} + +/* NNTP search messages + * Accepts: mail stream + * character set + * search program + * option flags + * Returns: T on success, NIL on failure + */ + +long nntp_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags) +{ + unsigned long i; + MESSAGECACHE *elt; + OVERVIEW ov; + char *msg; + /* make sure that charset is good */ + if (msg = utf8_badcharset (charset)) { + MM_LOG (msg,ERROR); /* output error */ + fs_give ((void **) &msg); + return NIL; + } + utf8_searchpgm (pgm,charset); + if (flags & SO_OVERVIEW) { /* only if specified to use overview */ + /* identify messages that will be searched */ + for (i = 1; i <= stream->nmsgs; ++i) + mail_elt (stream,i)->sequence = nntp_search_msg (stream,i,pgm,NIL); + nntp_overview (stream,NIL); /* load the overview cache */ + } + /* init in case no overview at cleanup */ + memset ((void *) &ov,0,sizeof (OVERVIEW)); + /* otherwise do default search */ + for (i = 1; i <= stream->nmsgs; ++i) { + if (((flags & SO_OVERVIEW) && ((elt = mail_elt (stream,i))->sequence) && + nntp_parse_overview (&ov,(char *) elt->private.spare.ptr,elt)) ? + nntp_search_msg (stream,i,pgm,&ov) : + mail_search_msg (stream,i,NIL,pgm)) { + if (flags & SE_UID) mm_searched (stream,mail_uid (stream,i)); + else { /* mark as searched, notify mail program */ + mail_elt (stream,i)->searched = T; + if (!stream->silent) mm_searched (stream,i); + } + } + /* clean up overview data */ + if (ov.from) mail_free_address (&ov.from); + if (ov.subject) fs_give ((void **) &ov.subject); + } + return LONGT; +} + +/* NNTP search message + * Accepts: MAIL stream + * message number + * search program + * overview to search (NIL means preliminary pass) + * Returns: T if found, NIL otherwise + */ + +long nntp_search_msg (MAILSTREAM *stream,unsigned long msgno,SEARCHPGM *pgm, + OVERVIEW *ov) +{ + unsigned short d; + unsigned long now = (unsigned long) time (0); + MESSAGECACHE *elt = mail_elt (stream,msgno); + SEARCHHEADER *hdr; + SEARCHOR *or; + SEARCHPGMLIST *not; + if (pgm->msgno || pgm->uid) { /* message set searches */ + SEARCHSET *set; + /* message sequences */ + if (set = pgm->msgno) { /* must be inside this sequence */ + while (set) { /* run down until find matching range */ + if (set->last ? ((msgno < set->first) || (msgno > set->last)) : + msgno != set->first) set = set->next; + else break; + } + if (!set) return NIL; /* not found within sequence */ + } + if (set = pgm->uid) { /* must be inside this sequence */ + unsigned long uid = mail_uid (stream,msgno); + while (set) { /* run down until find matching range */ + if (set->last ? ((uid < set->first) || (uid > set->last)) : + uid != set->first) set = set->next; + else break; + } + if (!set) return NIL; /* not found within sequence */ + } + } + + /* Fast data searches */ + /* message flags */ + if ((pgm->answered && !elt->answered) || + (pgm->unanswered && elt->answered) || + (pgm->deleted && !elt->deleted) || + (pgm->undeleted && elt->deleted) || + (pgm->draft && !elt->draft) || + (pgm->undraft && elt->draft) || + (pgm->flagged && !elt->flagged) || + (pgm->unflagged && elt->flagged) || + (pgm->recent && !elt->recent) || + (pgm->old && elt->recent) || + (pgm->seen && !elt->seen) || + (pgm->unseen && elt->seen)) return NIL; + /* keywords */ + if ((pgm->keyword && !mail_search_keyword (stream,elt,pgm->keyword,LONGT)) || + (pgm->unkeyword && mail_search_keyword (stream,elt,pgm->unkeyword,NIL))) + return NIL; + if (ov) { /* only do this if real searching */ + MESSAGECACHE delt; + /* size ranges */ + if ((pgm->larger && (ov->optional.octets <= pgm->larger)) || + (pgm->smaller && (ov->optional.octets >= pgm->smaller))) return NIL; + /* date ranges */ + if ((pgm->sentbefore || pgm->senton || pgm->sentsince || + pgm->before || pgm->on || pgm->since) && + (!mail_parse_date (&delt,ov->date) || + !(d = mail_shortdate (delt.year,delt.month,delt.day)) || + (pgm->sentbefore && (d >= pgm->sentbefore)) || + (pgm->senton && (d != pgm->senton)) || + (pgm->sentsince && (d < pgm->sentsince)) || + (pgm->before && (d >= pgm->before)) || + (pgm->on && (d != pgm->on)) || + (pgm->since && (d < pgm->since)))) return NIL; + if (pgm->older || pgm->younger) { + unsigned long msgd = mail_longdate (elt); + if (pgm->older && msgd > (now - pgm->older)) return NIL; + if (pgm->younger && msgd < (now - pgm->younger)) return NIL; + } + if ((pgm->from && !mail_search_addr (ov->from,pgm->from)) || + (pgm->subject && !mail_search_header_text (ov->subject,pgm->subject))|| + (pgm->message_id && + !mail_search_header_text (ov->message_id,pgm->message_id)) || + (pgm->references && + !mail_search_header_text (ov->references,pgm->references))) + return NIL; + + + /* envelope searches */ + if (pgm->bcc || pgm->cc || pgm->to || pgm->return_path || pgm->sender || + pgm->reply_to || pgm->in_reply_to || pgm->newsgroups || + pgm->followup_to) { + ENVELOPE *env = mail_fetchenvelope (stream,msgno); + if (!env) return NIL; /* no envelope obtained */ + /* search headers */ + if ((pgm->bcc && !mail_search_addr (env->bcc,pgm->bcc)) || + (pgm->cc && !mail_search_addr (env->cc,pgm->cc)) || + (pgm->to && !mail_search_addr (env->to,pgm->to))) + return NIL; + /* These criteria are not supported by IMAP and have to be emulated */ + if ((pgm->return_path && + !mail_search_addr (env->return_path,pgm->return_path)) || + (pgm->sender && !mail_search_addr (env->sender,pgm->sender)) || + (pgm->reply_to && !mail_search_addr (env->reply_to,pgm->reply_to)) || + (pgm->in_reply_to && + !mail_search_header_text (env->in_reply_to,pgm->in_reply_to)) || + (pgm->newsgroups && + !mail_search_header_text (env->newsgroups,pgm->newsgroups)) || + (pgm->followup_to && + !mail_search_header_text (env->followup_to,pgm->followup_to))) + return NIL; + } + + /* search header lines */ + for (hdr = pgm->header; hdr; hdr = hdr->next) { + char *t,*e,*v; + SIZEDTEXT s; + STRINGLIST sth,stc; + sth.next = stc.next = NIL;/* only one at a time */ + sth.text.data = hdr->line.data; + sth.text.size = hdr->line.size; + /* get the header text */ + if ((t = mail_fetch_header (stream,msgno,NIL,&sth,&s.size, + FT_INTERNAL | FT_PEEK)) && strchr (t,':')) { + if (hdr->text.size) { /* anything matches empty search string */ + /* non-empty, copy field data */ + s.data = (unsigned char *) fs_get (s.size + 1); + /* for each line */ + for (v = (char *) s.data, e = t + s.size; t < e;) switch (*t) { + default: /* non-continuation, skip leading field name */ + while ((t < e) && (*t++ != ':')); + if ((t < e) && (*t == ':')) t++; + case '\t': case ' ': /* copy field data */ + while ((t < e) && (*t != '\015') && (*t != '\012')) *v++ = *t++; + *v++ = '\n'; /* tie off line */ + while (((*t == '\015') || (*t == '\012')) && (t < e)) t++; + } + /* calculate true size */ + s.size = v - (char *) s.data; + *v = '\0'; /* tie off results */ + stc.text.data = hdr->text.data; + stc.text.size = hdr->text.size; + /* search header */ + if (mail_search_header (&s,&stc)) fs_give ((void **) &s.data); + else { /* search failed */ + fs_give ((void **) &s.data); + return NIL; + } + } + } + else return NIL; /* no matching header text */ + } + /* search strings */ + if ((pgm->text && + !mail_search_text (stream,msgno,NIL,pgm->text,LONGT))|| + (pgm->body && !mail_search_text (stream,msgno,NIL,pgm->body,NIL))) + return NIL; + } + /* logical conditions */ + for (or = pgm->or; or; or = or->next) + if (!(nntp_search_msg (stream,msgno,or->first,ov) || + nntp_search_msg (stream,msgno,or->second,ov))) return NIL; + for (not = pgm->not; not; not = not->next) + if (nntp_search_msg (stream,msgno,not->pgm,ov)) return NIL; + return T; +} + +/* NNTP sort messages + * Accepts: mail stream + * character set + * search program + * sort program + * option flags + * Returns: vector of sorted message sequences or NIL if error + */ + +unsigned long *nntp_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, + SORTPGM *pgm,long flags) +{ + unsigned long i,start,last; + SORTCACHE **sc; + mailcache_t mailcache = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); + unsigned long *ret = NIL; + sortresults_t sr = (sortresults_t) mail_parameters (NIL,GET_SORTRESULTS,NIL); + if (spg) { /* only if a search needs to be done */ + int silent = stream->silent; + stream->silent = T; /* don't pass up mm_searched() events */ + /* search for messages */ + mail_search_full (stream,charset,spg,NIL); + stream->silent = silent; /* restore silence state */ + } + /* initialize progress counters */ + pgm->nmsgs = pgm->progress.cached = 0; + /* pass 1: count messages to sort */ + for (i = 1,start = last = 0; i <= stream->nmsgs; ++i) + if (mail_elt (stream,i)->searched) { + pgm->nmsgs++; + /* have this in the sortcache already? */ + if (!((SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE))->date) { + /* no, record as last message */ + last = mail_uid (stream,i); + /* and as first too if needed */ + if (!start) start = last; + } + } + if (pgm->nmsgs) { /* pass 2: load sort cache */ + sc = nntp_sort_loadcache (stream,pgm,start,last,flags); + /* pass 3: sort messages */ + if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags); + fs_give ((void **) &sc); /* don't need sort vector any more */ + } + /* empty sort results */ + else ret = (unsigned long *) memset (fs_get (sizeof (unsigned long)),0, + sizeof (unsigned long)); + /* also return via callback if requested */ + if (sr) (*sr) (stream,ret,pgm->nmsgs); + return ret; +} + +/* Mail load sortcache + * Accepts: mail stream, already searched + * sort program + * first UID to OVER + * last UID to OVER + * option flags + * Returns: vector of sortcache pointers matching search + */ + +SORTCACHE **nntp_sort_loadcache (MAILSTREAM *stream,SORTPGM *pgm, + unsigned long start,unsigned long last, + long flags) +{ + unsigned long i; + char c,*s,*t,*v,tmp[MAILTMPLEN]; + SORTPGM *pg; + SORTCACHE **sc,*r; + MESSAGECACHE telt; + ADDRESS *adr = NIL; + mailcache_t mailcache = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); + /* verify that the sortpgm is OK */ + for (pg = pgm; pg; pg = pg->next) switch (pg->function) { + case SORTARRIVAL: /* sort by arrival date */ + case SORTSIZE: /* sort by message size */ + case SORTDATE: /* sort by date */ + case SORTFROM: /* sort by first from */ + case SORTSUBJECT: /* sort by subject */ + break; + case SORTTO: /* sort by first to */ + mm_notify (stream,"[NNTPSORT] Can't do To-field sorting in NNTP",WARN); + break; + case SORTCC: /* sort by first cc */ + mm_notify (stream,"[NNTPSORT] Can't do cc-field sorting in NNTP",WARN); + break; + default: + fatal ("Unknown sort function"); + } + + if (start) { /* messages need to be loaded in sortcache? */ + /* yes, build range */ + if (start != last) sprintf (tmp,"%lu-%lu",start,last); + else sprintf (tmp,"%lu",start); + /* get it from the NNTP server */ + if (!nntp_over (stream,tmp)) return mail_sort_loadcache (stream,pgm); + while ((s = net_getline (LOCAL->nntpstream->netstream)) && strcmp (s,".")){ + /* death to embedded newlines */ + for (t = v = s; c = *v++;) if ((c != '\012') && (c != '\015')) *t++ = c; + *t++ = '\0'; /* tie off resulting string */ + /* parse OVER response */ + if ((i = mail_msgno (stream,atol (s))) && + (t = strchr (s,'\t')) && (v = strchr (++t,'\t'))) { + *v++ = '\0'; /* tie off subject */ + /* put stripped subject in sortcache */ + r = (SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE); + r->refwd = mail_strip_subject (t,&r->subject); + if (t = strchr (v,'\t')) { + *t++ = '\0'; /* tie off from */ + if (adr = rfc822_parse_address (&adr,adr,&v,BADHOST,0)) { + r->from = adr->mailbox; + adr->mailbox = NIL; + mail_free_address (&adr); + } + if (v = strchr (t,'\t')) { + *v++ = '\0'; /* tie off date */ + if (mail_parse_date (&telt,t)) r->date = mail_longdate (&telt); + if ((v = strchr (v,'\t')) && (v = strchr (++v,'\t'))) + r->size = atol (++v); + } + } + } + fs_give ((void **) &s); + } + if (s) fs_give ((void **) &s); + } + + /* calculate size of sortcache index */ + i = pgm->nmsgs * sizeof (SORTCACHE *); + /* instantiate the index */ + sc = (SORTCACHE **) memset (fs_get ((size_t) i),0,(size_t) i); + /* see what needs to be loaded */ + for (i = 1; !pgm->abort && (i <= stream->nmsgs); i++) + if ((mail_elt (stream,i))->searched) { + sc[pgm->progress.cached++] = + r = (SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE); + r->pgm = pgm; /* note sort program */ + r->num = (flags & SE_UID) ? mail_uid (stream,i) : i; + if (!r->date) r->date = r->num; + if (!r->arrival) r->arrival = mail_uid (stream,i); + if (!r->size) r->size = 1; + if (!r->from) r->from = cpystr (""); + if (!r->to) r->to = cpystr (""); + if (!r->cc) r->cc = cpystr (""); + if (!r->subject) r->subject = cpystr (""); + } + return sc; +} + + +/* NNTP thread messages + * Accepts: mail stream + * thread type + * character set + * search program + * option flags + * Returns: thread node tree + */ + +THREADNODE *nntp_thread (MAILSTREAM *stream,char *type,char *charset, + SEARCHPGM *spg,long flags) +{ + return mail_thread_msgs (stream,type,charset,spg,flags,nntp_sort); +} + +/* NNTP ping mailbox + * Accepts: MAIL stream + * Returns: T if stream alive, else NIL + */ + +long nntp_ping (MAILSTREAM *stream) +{ + return (nntp_send (LOCAL->nntpstream,"STAT",NIL) != NNTPSOFTFATAL); +} + + +/* NNTP check mailbox + * Accepts: MAIL stream + */ + +void nntp_check (MAILSTREAM *stream) +{ + /* never do if no updates */ + if (LOCAL->dirty) newsrc_write (LOCAL->name,stream); + LOCAL->dirty = NIL; +} + + +/* NNTP expunge mailbox + * Accepts: MAIL stream + * sequence to expunge if non-NIL + * expunge options + * Returns: T if success, NIL if failure + */ + +long nntp_expunge (MAILSTREAM *stream,char *sequence,long options) +{ + if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL); + return LONGT; +} + +/* NNTP copy message(s) + * Accepts: MAIL stream + * sequence + * destination mailbox + * option flags + * Returns: T if copy successful, else NIL + */ + +long nntp_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) +{ + mailproxycopy_t pc = + (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL); + if (pc) return (*pc) (stream,sequence,mailbox,options); + mm_log ("Copy not valid for NNTP",ERROR); + return NIL; +} + + +/* NNTP append message from stringstruct + * Accepts: MAIL stream + * destination mailbox + * append callback + * data for callback + * Returns: T if append successful, else NIL + */ + +long nntp_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + mm_log ("Append not valid for NNTP",ERROR); + return NIL; +} + +/* NNTP open connection + * Accepts: network driver + * service host list + * port number + * service name + * NNTP open options + * Returns: SEND stream on success, NIL on failure + */ + +SENDSTREAM *nntp_open_full (NETDRIVER *dv,char **hostlist,char *service, + unsigned long port,long options) +{ + SENDSTREAM *stream = NIL; + NETSTREAM *netstream = NIL; + NETMBX mb; + char tmp[MAILTMPLEN]; + long extok = LONGT; + NETDRIVER *ssld = (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL); + sslstart_t stls = (sslstart_t) mail_parameters (NIL,GET_SSLSTART,NIL); + if (!(hostlist && *hostlist)) mm_log ("Missing NNTP service host",ERROR); + else do { /* try to open connection */ + sprintf (tmp,"{%.200s/%.20s}",*hostlist,service ? service : "nntp"); + if (!mail_valid_net_parse (tmp,&mb) || mb.anoflag) { + sprintf (tmp,"Invalid host specifier: %.80s",*hostlist); + mm_log (tmp,ERROR); + } + else { /* light tryssl flag if requested */ + mb.trysslflag = (options & NOP_TRYSSL) ? T : NIL; + /* default port */ + if (mb.port) port = mb.port; + else if (!port) port = nntp_port ? nntp_port : NNTPTCPPORT; + if (netstream = /* try to open ordinary connection */ + net_open (&mb,dv,port, + (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL), + "*nntps",nntp_sslport ? nntp_sslport : NNTPSSLPORT)) { + stream = (SENDSTREAM *) fs_get (sizeof (SENDSTREAM)); + /* initialize stream */ + memset ((void *) stream,0,sizeof (SENDSTREAM)); + stream->netstream = netstream; + stream->host = cpystr ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ? + net_host (netstream) : mb.host); + stream->debug = (mb.dbgflag || (options & NOP_DEBUG)) ? T : NIL; + if (mb.loser) stream->loser = T; + /* process greeting */ + switch ((int) nntp_reply (stream)) { + case NNTPGREET: /* allow posting */ + NNTP.post = T; + mm_notify (NIL,stream->reply + 4,(long) NIL); + break; + case NNTPGREETNOPOST: /* posting not allowed, must be readonly */ + NNTP.post = NIL; + break; + default: + mm_log (stream->reply,ERROR); + stream = nntp_close (stream); + break; + } + } + } + } while (!stream && *++hostlist); + + /* get extensions */ + if (stream && extok) + extok = nntp_extensions (stream,(mb.secflag ? AU_SECURE : NIL) | + (mb.authuser[0] ? AU_AUTHUSER : NIL)); + if (stream && !dv && stls && NNTP.ext.starttls && + !mb.sslflag && !mb.notlsflag && + (nntp_send_work (stream,"STARTTLS",NNTP.ext.multidomain ? mb.host : NIL) + == NNTPTLSSTART)) { + mb.tlsflag = T; /* TLS OK, get into TLS at this end */ + stream->netstream->dtb = ssld; + /* negotiate TLS */ + if (stream->netstream->stream = + (*stls) (stream->netstream->stream,mb.host, + (mb.tlssslv23 ? NIL : NET_TLSCLIENT) | + (mb.novalidate ? NET_NOVALIDATECERT:NIL))) + extok = nntp_extensions (stream,(mb.secflag ? AU_SECURE : NIL) | + (mb.authuser[0] ? AU_AUTHUSER : NIL)); + else { + sprintf (tmp,"Unable to negotiate TLS with this server: %.80s",mb.host); + mm_log (tmp,ERROR); + /* close without doing QUIT */ + if (stream->netstream) net_close (stream->netstream); + stream->netstream = NIL; + stream = nntp_close (stream); + } + } + else if (mb.tlsflag) { /* user specified /tls but can't do it */ + mm_log ("Unable to negotiate TLS with this server",ERROR); + return NIL; + } + if (stream) { /* have a session? */ + if (mb.user[0]) { /* yes, have user name? */ + if ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL)) { + /* remote name for authentication */ + strncpy (mb.host,(long) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ? + net_remotehost (netstream) : net_host (netstream), + NETMAXHOST-1); + mb.host[NETMAXHOST-1] = '\0'; + } + if (!nntp_send_auth_work (stream,&mb,tmp,NIL)) + stream = nntp_close (stream); + } + /* authenticate if no-post and not readonly */ + else if (!(NNTP.post || (options & NOP_READONLY) || + nntp_send_auth (stream,NIL))) stream = nntp_close (stream); + } + + /* in case server demands MODE READER */ + if (stream) switch ((int) nntp_send_work (stream,"MODE","READER")) { + case NNTPGREET: + NNTP.post = T; + break; + case NNTPGREETNOPOST: + NNTP.post = NIL; + break; + case NNTPWANTAUTH: /* server wants auth first, do so and retry */ + case NNTPWANTAUTH2: /* remote name for authentication */ + if ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL)) { + strncpy (mb.host,(long) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ? + net_remotehost (netstream) : net_host (netstream),NETMAXHOST-1); + mb.host[NETMAXHOST-1] = '\0'; + } + if (nntp_send_auth_work (stream,&mb,tmp,NIL)) + switch ((int) nntp_send (stream,"MODE","READER")) { + case NNTPGREET: + NNTP.post = T; + break; + case NNTPGREETNOPOST: + NNTP.post = NIL; + break; + } + else stream = nntp_close (stream); + break; + } + if (stream) { /* looks like we have a stream? */ + /* yes, make sure can post if not readonly */ + if (!(NNTP.post || (options & NOP_READONLY))) stream = nntp_close (stream); + else if (extok) nntp_extensions (stream,(mb.secflag ? AU_SECURE : NIL) | + (mb.authuser[0] ? AU_AUTHUSER : NIL)); + } + return stream; +} + +/* NNTP extensions + * Accepts: stream + * authenticator flags + * Returns: T on success, NIL on failure + */ + +long nntp_extensions (SENDSTREAM *stream,long flags) +{ + unsigned long i; + char *t,*r,*args; + /* zap all old extensions */ + memset (&NNTP.ext,0,sizeof (NNTP.ext)); + if (stream->loser) return NIL;/* nothing at all for losers */ + /* get server extensions */ + switch ((int) nntp_send_work (stream,"LIST","EXTENSIONS")) { + case NNTPEXTOK: /* what NNTP base spec says */ + case NNTPGLIST: /* some servers do this instead */ + break; + default: /* no LIST EXTENSIONS on this server */ + return NIL; + } + NNTP.ext.ok = T; /* server offers extensions */ + while ((t = net_getline (stream->netstream)) && (t[1] || (*t != '.'))) { + if (stream->debug) mm_dlog (t); + /* get optional capability arguments */ + if (args = strchr (t,' ')) *args++ = '\0'; + if (!compare_cstring (t,"LISTGROUP")) NNTP.ext.listgroup = T; + else if (!compare_cstring (t,"OVER")) NNTP.ext.over = T; + else if (!compare_cstring (t,"HDR")) NNTP.ext.hdr = T; + else if (!compare_cstring (t,"PAT")) NNTP.ext.pat = T; + else if (!compare_cstring (t,"STARTTLS")) NNTP.ext.starttls = T; + else if (!compare_cstring (t,"MULTIDOMAIN")) NNTP.ext.multidomain = T; + + else if (!compare_cstring (t,"AUTHINFO") && args) { + char *sasl = NIL; + for (args = strtok_r (args," ",&r); args; args = strtok_r (NIL," ",&r)) { + if (!compare_cstring (args,"USER")) NNTP.ext.authuser = T; + else if (((args[0] == 'S') || (args[0] == 's')) && + ((args[1] == 'A') || (args[1] == 'a')) && + ((args[2] == 'S') || (args[2] == 's')) && + ((args[3] == 'L') || (args[3] == 'l')) && (args[4] == ':')) + sasl = args + 5; + } + if (sasl) { /* if SASL, look up authenticators */ + for (sasl = strtok_r (sasl,",",&r); sasl; sasl = strtok_r (NIL,",",&r)) + if ((i = mail_lookup_auth_name (sasl,flags)) && + (--i < MAXAUTHENTICATORS)) + NNTP.ext.sasl |= (1 << i); + /* disable LOGIN if PLAIN also advertised */ + if ((i = mail_lookup_auth_name ("PLAIN",NIL)) && + (--i < MAXAUTHENTICATORS) && (NNTP.ext.sasl & (1 << i)) && + (i = mail_lookup_auth_name ("LOGIN",NIL)) && + (--i < MAXAUTHENTICATORS)) NNTP.ext.sasl &= ~(1 << i); + } + } + fs_give ((void **) &t); + } + if (t) { /* flush end of text indicator */ + if (stream->debug) mm_dlog (t); + fs_give ((void **) &t); + } + return LONGT; +} + +/* NNTP close connection + * Accepts: SEND stream + * Returns: NIL always + */ + +SENDSTREAM *nntp_close (SENDSTREAM *stream) +{ + if (stream) { /* send "QUIT" */ + if (stream->netstream) nntp_send (stream,"QUIT",NIL); + /* do close actions */ + if (stream->netstream) net_close (stream->netstream); + if (stream->host) fs_give ((void **) &stream->host); + if (stream->reply) fs_give ((void **) &stream->reply); + fs_give ((void **) &stream);/* flush the stream */ + } + return NIL; +} + +/* NNTP deliver news + * Accepts: SEND stream + * message envelope + * message body + * Returns: T on success, NIL on failure + */ + +long nntp_mail (SENDSTREAM *stream,ENVELOPE *env,BODY *body) +{ + long ret; + RFC822BUFFER buf; + char *s,path[MAILTMPLEN],tmp[SENDBUFLEN+1]; + long error = NIL; + long retry = NIL; + buf.f = nntp_soutr; /* initialize buffer */ + buf.s = stream->netstream; + buf.end = (buf.beg = buf.cur = tmp) + SENDBUFLEN; + tmp[SENDBUFLEN] = '\0'; /* must have additional null guard byte */ + /* Gabba gabba hey, we need some brain damage to send netnews!!! + * + * First, we give ourselves a frontal lobotomy, and put in some UUCP + * syntax. It doesn't matter that it's completely bogus UUCP, and + * that UUCP has nothing to do with anything we're doing. It's been + * alleged that "Path: not-for-mail" is also acceptable, but we won't + * make assumptions unless the user says so. + * + * Second, we bop ourselves on the head with a ball-peen hammer. How + * dare we be so presumptious as to insert a *comment* in a Date: + * header line. Why, we were actually trying to be nice to a human + * by giving a symbolic timezone (such as PST) in addition to a + * numeric timezone (such as -0800). But the gods of news transport + * will have none of this. Unix weenies, tried and true, rule!!! + * + * Third, Netscape Collabra server doesn't give the NNTPWANTAUTH error + * until after requesting and receiving the entire message. So we can't + * call rely upon nntp_send() to do the auth retry. + */ + /* RFC-1036 requires this cretinism */ + sprintf (path,"Path: %s!%s\015\012",net_localhost (stream->netstream), + env->sender ? env->sender->mailbox : + (env->from ? env->from->mailbox : "not-for-mail")); + /* here's another cretinism */ + if (s = strstr (env->date," (")) *s = NIL; + do if ((ret = nntp_send_work (stream,"POST",NIL)) == NNTPREADY) + /* output data, return success status */ + ret = (net_soutr (stream->netstream, + nntp_hidepath ? "Path: not-for-mail\015\012" : path) && + rfc822_output_full (&buf,env,body,T)) ? + nntp_send_work (stream,".",NIL) : + nntp_fake (stream,"NNTP connection broken (message text)"); + while (((ret == NNTPWANTAUTH) || (ret == NNTPWANTAUTH2)) && + nntp_send_auth (stream,LONGT)); + if (s) *s = ' '; /* put the comment in the date back */ + if (ret == NNTPOK) return LONGT; + else if (ret < 400) { /* if not an error reply */ + sprintf (tmp,"Unexpected NNTP posting reply code %ld",ret); + mm_log (tmp,WARN); /* so someone looks at this eventually */ + if ((ret >= 200) && (ret < 300)) return LONGT; + } + return NIL; +} + +/* NNTP send command + * Accepts: SEND stream + * text + * Returns: reply code + */ + +long nntp_send (SENDSTREAM *stream,char *command,char *args) +{ + long ret; + switch ((int) (ret = nntp_send_work (stream,command,args))) { + case NNTPWANTAUTH: /* authenticate and retry */ + case NNTPWANTAUTH2: + if (nntp_send_auth (stream,LONGT)) + ret = nntp_send_work (stream,command,args); + else { /* we're probably hosed, nuke the session */ + nntp_send (stream,"QUIT",NIL); + /* close net connection */ + if (stream->netstream) net_close (stream->netstream); + stream->netstream = NIL; + } + default: /* all others just return */ + break; + } + return ret; +} + + +/* NNTP send command worker routine + * Accepts: SEND stream + * text + * Returns: reply code + */ + +long nntp_send_work (SENDSTREAM *stream,char *command,char *args) +{ + long ret; + char *s = (char *) fs_get (strlen (command) + (args ? strlen (args) + 1 : 0) + + 3); + if (!stream->netstream) ret = nntp_fake (stream,"NNTP connection lost"); + else { /* build the complete command */ + if (args) sprintf (s,"%s %s",command,args); + else strcpy (s,command); + if (stream->debug) mail_dlog (s,stream->sensitive); + strcat (s,"\015\012"); + /* send the command */ + ret = net_soutr (stream->netstream,s) ? nntp_reply (stream) : + nntp_fake (stream,"NNTP connection broken (command)"); + } + fs_give ((void **) &s); + return ret; +} + +/* NNTP send authentication if needed + * Accepts: SEND stream + * flags (non-NIL to get new extensions) + * Returns: T if need to redo command, NIL otherwise + */ + +long nntp_send_auth (SENDSTREAM *stream,long flags) +{ + NETMBX mb; + char tmp[MAILTMPLEN]; + /* remote name for authentication */ + sprintf (tmp,"{%.200s/nntp",(long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ? + ((long) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ? + net_remotehost (stream->netstream) : net_host (stream->netstream)): + stream->host); + if (stream->netstream->dtb == + (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL)) + strcat (tmp,"/ssl"); + strcat (tmp,"}<none>"); + mail_valid_net_parse (tmp,&mb); + return nntp_send_auth_work (stream,&mb,tmp,flags); +} + +/* NNTP send authentication worker routine + * Accepts: SEND stream + * NETMBX structure + * scratch buffer of length MAILTMPLEN + * flags (non-NIL to get new extensions) + * Returns: T if authenticated, NIL otherwise + */ + +long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags) +{ + unsigned long trial,auths; + char tmp[MAILTMPLEN],usr[MAILTMPLEN]; + AUTHENTICATOR *at; + char *lsterr = NIL; + long ret = NIL; + /* try SASL first */ + for (auths = NNTP.ext.sasl, stream->saslcancel = NIL; + !ret && stream->netstream && auths && + (at = mail_lookup_auth (find_rightmost_bit (&auths) + 1)); ) { + if (lsterr) { /* previous authenticator failed? */ + sprintf (tmp,"Retrying using %s authentication after %.80s", + at->name,lsterr); + mm_log (tmp,NIL); + fs_give ((void **) &lsterr); + } + trial = 0; /* initial trial count */ + tmp[0] = '\0'; /* empty buffer */ + if (stream->netstream) do { + if (lsterr) { + sprintf (tmp,"Retrying %s authentication after %.80s",at->name,lsterr); + mm_log (tmp,WARN); + fs_give ((void **) &lsterr); + } + stream->saslcancel = NIL; + if (nntp_send (stream,"AUTHINFO SASL",at->name) == NNTPCHALLENGE) { + /* hide client authentication responses */ + if (!(at->flags & AU_SECURE)) stream->sensitive = T; + if ((*at->client) (nntp_challenge,nntp_response,"nntp",mb,stream, + &trial,usr)) { + if (stream->replycode == NNTPAUTHED) ret = LONGT; + /* if main program requested cancellation */ + else if (!trial) mm_log ("NNTP Authentication cancelled",ERROR); + } + stream->sensitive = NIL;/* unhide */ + } + /* remember response if error and no cancel */ + if (!ret && trial) lsterr = cpystr (stream->reply); + } while (!ret && stream->netstream && trial && + (trial < nntp_maxlogintrials)); + } + + if (lsterr) { /* SAIL failed? */ + if (!stream->saslcancel) { /* don't do this if a cancel */ + sprintf (tmp,"Can not authenticate to NNTP server: %.80s",lsterr); + mm_log (tmp,ERROR); + } + fs_give ((void **) &lsterr); + } + else if (mb->secflag) /* no SASL, can't do /secure */ + mm_log ("Can't do secure authentication with this server",ERROR); + else if (mb->authuser[0]) /* or /authuser */ + mm_log ("Can't do /authuser with this server",ERROR); + /* Always try AUTHINFO USER, even if NNTP.ext.authuser isn't set. There + * are servers that require it but don't return it as an extension. + */ + else for (trial = 0, pwd[0] = 'x'; + !ret && pwd[0] && (trial < nntp_maxlogintrials) && + stream->netstream; ) { + pwd[0] = NIL; /* get user name and password */ + mm_login (mb,usr,pwd,trial++); + /* do the authentication */ + if (pwd[0]) switch ((int) nntp_send_work (stream,"AUTHINFO USER",usr)) { + case NNTPBADCMD: /* give up if unrecognized command */ + mm_log (NNTP.ext.authuser ? stream->reply : + "Can't do AUTHINFO USER to this server",ERROR); + trial = nntp_maxlogintrials; + break; + case NNTPAUTHED: /* successful authentication */ + ret = LONGT; /* guess no password was needed */ + break; + case NNTPWANTPASS: /* wants password */ + stream->sensitive = T; /* hide this command */ + if (nntp_send_work (stream,"AUTHINFO PASS",pwd) == NNTPAUTHED) + ret = LONGT; /* password OK */ + stream->sensitive = NIL; /* unhide */ + if (ret) break; /* OK if successful */ + default: /* authentication failed */ + mm_log (stream->reply,WARN); + if (trial == nntp_maxlogintrials) + mm_log ("Too many NNTP authentication failures",ERROR); + } + /* user refused to give a password */ + else mm_log ("Login aborted",ERROR); + } + memset (pwd,0,MAILTMPLEN); /* erase password */ + /* get new extensions if needed */ + if (ret && flags) nntp_extensions (stream,(mb->secflag ? AU_SECURE : NIL) | + (mb->authuser[0] ? AU_AUTHUSER : NIL)); + return ret; +} + +/* Get challenge to authenticator in binary + * Accepts: stream + * pointer to returned size + * Returns: challenge or NIL if not challenge + */ + +void *nntp_challenge (void *s,unsigned long *len) +{ + char tmp[MAILTMPLEN]; + void *ret = NIL; + SENDSTREAM *stream = (SENDSTREAM *) s; + if ((stream->replycode == NNTPCHALLENGE) && + !(ret = rfc822_base64 ((unsigned char *) stream->reply + 4, + strlen (stream->reply + 4),len))) { + sprintf (tmp,"NNTP SERVER BUG (invalid challenge): %.80s",stream->reply+4); + mm_log (tmp,ERROR); + } + return ret; +} + + +/* Send authenticator response in BASE64 + * Accepts: MAIL stream + * string to send + * length of string + * Returns: T, always + */ + +long nntp_response (void *s,char *response,unsigned long size) +{ + SENDSTREAM *stream = (SENDSTREAM *) s; + unsigned long i,j; + char *t,*u; + if (response) { /* make CRLFless BASE64 string */ + if (size) { + for (t = (char *) rfc822_binary ((void *) response,size,&i),u = t,j = 0; + j < i; j++) if (t[j] > ' ') *u++ = t[j]; + *u = '\0'; /* tie off string */ + i = nntp_send_work (stream,t,NIL); + fs_give ((void **) &t); + } + else i = nntp_send_work (stream,"",NIL); + } + else { /* abort requested */ + i = nntp_send_work (stream,"*",NIL); + stream->saslcancel = T; /* mark protocol-requested SASL cancel */ + } + return LONGT; +} + +/* NNTP get reply + * Accepts: SEND stream + * Returns: reply code + */ + +long nntp_reply (SENDSTREAM *stream) +{ + /* flush old reply */ + if (stream->reply) fs_give ((void **) &stream->reply); + /* get reply */ + if (!(stream->reply = net_getline (stream->netstream))) + return nntp_fake (stream,"NNTP connection broken (response)"); + if (stream->debug) mm_dlog (stream->reply); + /* handle continuation by recursion */ + if (stream->reply[3] == '-') return nntp_reply (stream); + /* return response code */ + return stream->replycode = atol (stream->reply); +} + + +/* NNTP set fake error + * Accepts: SEND stream + * error text + * Returns: error code + */ + +long nntp_fake (SENDSTREAM *stream,char *text) +{ + if (stream->netstream) { /* close net connection if still open */ + net_close (stream->netstream); + stream->netstream = NIL; + } + /* flush any old reply */ + if (stream->reply) fs_give ((void **) &stream->reply); + /* set up pseudo-reply string */ + stream->reply = (char *) fs_get (20+strlen (text)); + sprintf (stream->reply,"%ld %s",NNTPSOFTFATAL,text); + return NNTPSOFTFATAL; /* return error code */ +} + +/* NNTP filter mail + * Accepts: stream + * string + * Returns: T on success, NIL on failure + */ + +long nntp_soutr (void *stream,char *s) +{ + char c,*t; + /* "." on first line */ + if (s[0] == '.') net_soutr (stream,"."); + /* find lines beginning with a "." */ + while (t = strstr (s,"\015\012.")) { + c = *(t += 3); /* remember next character after "." */ + *t = '\0'; /* tie off string */ + /* output prefix */ + if (!net_soutr (stream,s)) return NIL; + *t = c; /* restore delimiter */ + s = t - 1; /* push pointer up to the "." */ + } + /* output remainder of text */ + return *s ? net_soutr (stream,s) : T; +} diff --git a/imap/src/c-client/nntp.h b/imap/src/c-client/nntp.h new file mode 100644 index 00000000..445ae4d7 --- /dev/null +++ b/imap/src/c-client/nntp.h @@ -0,0 +1,56 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Network News Transfer Protocol (NNTP) routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 10 February 1992 + * Last Edited: 30 August 2006 + */ + +/* Constants (should be in nntp.c) */ + +#define NNTPTCPPORT (long) 119 /* assigned TCP contact port */ + + +/* NNTP open options + * For compatibility with the past, NOP_DEBUG must always be 1. + */ + +#define NOP_DEBUG (long) 0x1 /* debug protocol negotiations */ +#define NOP_READONLY (long) 0x2 /* read-only open */ +#define NOP_TRYSSL (long) 0x4 /* try SSL first */ + /* reserved for application use */ +#define NOP_RESERVED (unsigned long) 0xff000000 + + +/* Compatibility support names */ + +#define nntp_open(hostlist,options) \ + nntp_open_full (NIL,hostlist,"nntp",NIL,options) + + +/* Function prototypes */ + +SENDSTREAM *nntp_open_full (NETDRIVER *dv,char **hostlist,char *service, + unsigned long port,long options); +SENDSTREAM *nntp_close (SENDSTREAM *stream); +long nntp_mail (SENDSTREAM *stream,ENVELOPE *msg,BODY *body); diff --git a/imap/src/c-client/pop3.c b/imap/src/c-client/pop3.c new file mode 100644 index 00000000..58a9ceb6 --- /dev/null +++ b/imap/src/c-client/pop3.c @@ -0,0 +1,1091 @@ +/* ======================================================================== + * Copyright 1988-2007 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Post Office Protocol 3 (POP3) client routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 6 June 1994 + * Last Edited: 4 April 2007 + */ + + +#include <ctype.h> +#include <stdio.h> +#include <time.h> +#include "c-client.h" +#include "flstring.h" +#include "netmsg.h" + +/* Parameters */ + +#define POP3TCPPORT (long) 110 /* assigned TCP contact port */ +#define POP3SSLPORT (long) 995 /* assigned SSL TCP contact port */ +#define IDLETIMEOUT (long) 10 /* defined in RFC 1939 */ + + +/* POP3 I/O stream local data */ + +typedef struct pop3_local { + NETSTREAM *netstream; /* TCP I/O stream */ + char *response; /* last server reply */ + char *reply; /* text of last server reply */ + unsigned long cached; /* current cached message uid */ + unsigned long hdrsize; /* current cached header size */ + FILE *txt; /* current cached file descriptor */ + struct { + unsigned int capa : 1; /* server has CAPA, definitely new */ + unsigned int expire : 1; /* server has EXPIRE */ + unsigned int logindelay : 1;/* server has LOGIN-DELAY */ + unsigned int stls : 1; /* server has STLS */ + unsigned int pipelining : 1;/* server has PIPELINING */ + unsigned int respcodes : 1; /* server has RESP-CODES */ + unsigned int top : 1; /* server has TOP */ + unsigned int uidl : 1; /* server has UIDL */ + unsigned int user : 1; /* server has USER */ + char *implementation; /* server implementation string */ + long delaysecs; /* minimum time between login (neg variable) */ + long expiredays; /* server-guaranteed minimum retention days */ + /* supported authenticators */ + unsigned int sasl : MAXAUTHENTICATORS; + } cap; + unsigned int sensitive : 1; /* sensitive data in progress */ + unsigned int loser : 1; /* server is a loser */ + unsigned int saslcancel : 1; /* SASL cancelled by protocol */ +} POP3LOCAL; + + +/* Convenient access to local data */ + +#define LOCAL ((POP3LOCAL *) stream->local) + +/* Function prototypes */ + +DRIVER *pop3_valid (char *name); +void *pop3_parameters (long function,void *value); +void pop3_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); +void pop3_list (MAILSTREAM *stream,char *ref,char *pat); +void pop3_lsub (MAILSTREAM *stream,char *ref,char *pat); +long pop3_subscribe (MAILSTREAM *stream,char *mailbox); +long pop3_unsubscribe (MAILSTREAM *stream,char *mailbox); +long pop3_create (MAILSTREAM *stream,char *mailbox); +long pop3_delete (MAILSTREAM *stream,char *mailbox); +long pop3_rename (MAILSTREAM *stream,char *old,char *newname); +long pop3_status (MAILSTREAM *stream,char *mbx,long flags); +MAILSTREAM *pop3_open (MAILSTREAM *stream); +long pop3_capa (MAILSTREAM *stream,long flags); +long pop3_auth (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr); +void *pop3_challenge (void *stream,unsigned long *len); +long pop3_response (void *stream,char *s,unsigned long size); +void pop3_close (MAILSTREAM *stream,long options); +void pop3_fetchfast (MAILSTREAM *stream,char *sequence,long flags); +char *pop3_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *size, + long flags); +long pop3_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); +unsigned long pop3_cache (MAILSTREAM *stream,MESSAGECACHE *elt); +long pop3_ping (MAILSTREAM *stream); +void pop3_check (MAILSTREAM *stream); +long pop3_expunge (MAILSTREAM *stream,char *sequence,long options); +long pop3_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); +long pop3_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); + +long pop3_send_num (MAILSTREAM *stream,char *command,unsigned long n); +long pop3_send (MAILSTREAM *stream,char *command,char *args); +long pop3_reply (MAILSTREAM *stream); +long pop3_fake (MAILSTREAM *stream,char *text); + +/* POP3 mail routines */ + + +/* Driver dispatch used by MAIL */ + +DRIVER pop3driver = { + "pop3", /* driver name */ + /* driver flags */ +#ifdef INADEQUATE_MEMORY + DR_LOWMEM | +#endif + DR_MAIL|DR_NOFAST|DR_CRLF|DR_NOSTICKY|DR_NOINTDATE|DR_NONEWMAIL, + (DRIVER *) NIL, /* next driver */ + pop3_valid, /* mailbox is valid for us */ + pop3_parameters, /* manipulate parameters */ + pop3_scan, /* scan mailboxes */ + pop3_list, /* find mailboxes */ + pop3_lsub, /* find subscribed mailboxes */ + pop3_subscribe, /* subscribe to mailbox */ + pop3_unsubscribe, /* unsubscribe from mailbox */ + pop3_create, /* create mailbox */ + pop3_delete, /* delete mailbox */ + pop3_rename, /* rename mailbox */ + pop3_status, /* status of mailbox */ + pop3_open, /* open mailbox */ + pop3_close, /* close mailbox */ + pop3_fetchfast, /* fetch message "fast" attributes */ + NIL, /* fetch message flags */ + NIL, /* fetch overview */ + NIL, /* fetch message structure */ + pop3_header, /* fetch message header */ + pop3_text, /* fetch message text */ + NIL, /* fetch message */ + NIL, /* unique identifier */ + NIL, /* message number from UID */ + NIL, /* modify flags */ + NIL, /* per-message modify flags */ + NIL, /* search for message based on criteria */ + NIL, /* sort messages */ + NIL, /* thread messages */ + pop3_ping, /* ping mailbox to see if still alive */ + pop3_check, /* check for new messages */ + pop3_expunge, /* expunge deleted messages */ + pop3_copy, /* copy messages to another mailbox */ + pop3_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + + /* prototype stream */ +MAILSTREAM pop3proto = {&pop3driver}; + + /* driver parameters */ +static unsigned long pop3_maxlogintrials = MAXLOGINTRIALS; +static long pop3_port = 0; +static long pop3_sslport = 0; + +/* POP3 mail validate mailbox + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *pop3_valid (char *name) +{ + NETMBX mb; + return (mail_valid_net_parse (name,&mb) && + !strcmp (mb.service,pop3driver.name) && !mb.authuser[0] && + !compare_cstring (mb.mailbox,"INBOX")) ? &pop3driver : NIL; +} + + +/* News manipulate driver parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *pop3_parameters (long function,void *value) +{ + switch ((int) function) { + case SET_MAXLOGINTRIALS: + pop3_maxlogintrials = (unsigned long) value; + break; + case GET_MAXLOGINTRIALS: + value = (void *) pop3_maxlogintrials; + break; + case SET_POP3PORT: + pop3_port = (long) value; + break; + case GET_POP3PORT: + value = (void *) pop3_port; + break; + case SET_SSLPOPPORT: + pop3_sslport = (long) value; + break; + case GET_SSLPOPPORT: + value = (void *) pop3_sslport; + break; + case GET_IDLETIMEOUT: + value = (void *) IDLETIMEOUT; + break; + default: + value = NIL; /* error case */ + break; + } + return value; +} + +/* POP3 mail scan mailboxes for string + * Accepts: mail stream + * reference + * pattern to search + * string to scan + */ + +void pop3_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) +{ + char tmp[MAILTMPLEN]; + if ((ref && *ref) ? /* have a reference */ + (pop3_valid (ref) && pmatch ("INBOX",pat)) : + (mail_valid_net (pat,&pop3driver,NIL,tmp) && pmatch ("INBOX",tmp))) + mm_log ("Scan not valid for POP3 mailboxes",ERROR); +} + + +/* POP3 mail find list of all mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void pop3_list (MAILSTREAM *stream,char *ref,char *pat) +{ + char tmp[MAILTMPLEN]; + if (ref && *ref) { /* have a reference */ + if (pop3_valid (ref) && pmatch ("INBOX",pat)) { + strcpy (strchr (strcpy (tmp,ref),'}')+1,"INBOX"); + mm_list (stream,NIL,tmp,LATT_NOINFERIORS); + } + } + else if (mail_valid_net (pat,&pop3driver,NIL,tmp) && pmatch ("INBOX",tmp)) { + strcpy (strchr (strcpy (tmp,pat),'}')+1,"INBOX"); + mm_list (stream,NIL,tmp,LATT_NOINFERIORS); + } +} + +/* POP3 mail find list of subscribed mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void pop3_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + void *sdb = NIL; + char *s,mbx[MAILTMPLEN]; + if (*pat == '{') { /* if remote pattern, must be POP3 */ + if (!pop3_valid (pat)) return; + ref = NIL; /* good POP3 pattern, punt reference */ + } + /* if remote reference, must be valid POP3 */ + if (ref && (*ref == '{') && !pop3_valid (ref)) return; + /* kludgy application of reference */ + if (ref && *ref) sprintf (mbx,"%s%s",ref,pat); + else strcpy (mbx,pat); + + if (s = sm_read (&sdb)) do if (pop3_valid (s) && pmatch (s,mbx)) + mm_lsub (stream,NIL,s,NIL); + while (s = sm_read (&sdb)); /* until no more subscriptions */ +} + + +/* POP3 mail subscribe to mailbox + * Accepts: mail stream + * mailbox to add to subscription list + * Returns: T on success, NIL on failure + */ + +long pop3_subscribe (MAILSTREAM *stream,char *mailbox) +{ + return sm_subscribe (mailbox); +} + + +/* POP3 mail unsubscribe to mailbox + * Accepts: mail stream + * mailbox to delete from subscription list + * Returns: T on success, NIL on failure + */ + +long pop3_unsubscribe (MAILSTREAM *stream,char *mailbox) +{ + return sm_unsubscribe (mailbox); +} + +/* POP3 mail create mailbox + * Accepts: mail stream + * mailbox name to create + * Returns: T on success, NIL on failure + */ + +long pop3_create (MAILSTREAM *stream,char *mailbox) +{ + return NIL; /* never valid for POP3 */ +} + + +/* POP3 mail delete mailbox + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long pop3_delete (MAILSTREAM *stream,char *mailbox) +{ + return NIL; /* never valid for POP3 */ +} + + +/* POP3 mail rename mailbox + * Accepts: mail stream + * old mailbox name + * new mailbox name + * Returns: T on success, NIL on failure + */ + +long pop3_rename (MAILSTREAM *stream,char *old,char *newname) +{ + return NIL; /* never valid for POP3 */ +} + +/* POP3 status + * Accepts: mail stream + * mailbox name + * status flags + * Returns: T on success, NIL on failure + */ + +long pop3_status (MAILSTREAM *stream,char *mbx,long flags) +{ + MAILSTATUS status; + unsigned long i; + long ret = NIL; + MAILSTREAM *tstream = + (stream && LOCAL->netstream && mail_usable_network_stream (stream,mbx)) ? + stream : mail_open (NIL,mbx,OP_SILENT); + if (tstream) { /* have a usable stream? */ + status.flags = flags; /* return status values */ + status.messages = tstream->nmsgs; + status.recent = tstream->recent; + if (flags & SA_UNSEEN) /* must search to get unseen messages */ + for (i = 1,status.unseen = 0; i <= tstream->nmsgs; i++) + if (!mail_elt (tstream,i)->seen) status.unseen++; + status.uidnext = tstream->uid_last + 1; + status.uidvalidity = tstream->uid_validity; + /* pass status to main program */ + mm_status (tstream,mbx,&status); + if (stream != tstream) mail_close (tstream); + ret = LONGT; + } + return ret; /* success */ +} + +/* POP3 mail open + * Accepts: stream to open + * Returns: stream on success, NIL on failure + */ + +MAILSTREAM *pop3_open (MAILSTREAM *stream) +{ + unsigned long i,j; + char *s,*t,tmp[MAILTMPLEN],usr[MAILTMPLEN]; + NETMBX mb; + MESSAGECACHE *elt; + /* return prototype for OP_PROTOTYPE call */ + if (!stream) return &pop3proto; + mail_valid_net_parse (stream->mailbox,&mb); + usr[0] = '\0'; /* initially no user name */ + if (stream->local) fatal ("pop3 recycle stream"); + /* /anonymous not supported */ + if (mb.anoflag || stream->anonymous) { + mm_log ("Anonymous POP3 login not available",ERROR); + return NIL; + } + /* /readonly not supported either */ + if (mb.readonlyflag || stream->rdonly) { + mm_log ("Read-only POP3 access not available",ERROR); + return NIL; + } + /* copy other switches */ + if (mb.dbgflag) stream->debug = T; + if (mb.secflag) stream->secure = T; + mb.trysslflag = stream->tryssl = (mb.trysslflag || stream->tryssl) ? T : NIL; + stream->local = /* instantiate localdata */ + (void *) memset (fs_get (sizeof (POP3LOCAL)),0,sizeof (POP3LOCAL)); + stream->sequence++; /* bump sequence number */ + stream->perm_deleted = T; /* deleted is only valid flag */ + + if ((LOCAL->netstream = /* try to open connection */ + net_open (&mb,NIL,pop3_port ? pop3_port : POP3TCPPORT, + (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL), + "*pop3s",pop3_sslport ? pop3_sslport : POP3SSLPORT)) && + pop3_reply (stream)) { + mm_log (LOCAL->reply,NIL); /* give greeting */ + if (!pop3_auth (stream,&mb,tmp,usr)) pop3_close (stream,NIL); + else if (pop3_send (stream,"STAT",NIL)) { + int silent = stream->silent; + stream->silent = T; + sprintf (tmp,"{%.200s:%lu/pop3", + (long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ? + net_host (LOCAL->netstream) : mb.host, + net_port (LOCAL->netstream)); + if (mb.tlsflag) strcat (tmp,"/tls"); + if (mb.tlssslv23) strcat (tmp,"/tls-sslv23"); + if (mb.notlsflag) strcat (tmp,"/notls"); + if (mb.sslflag) strcat (tmp,"/ssl"); + if (mb.novalidate) strcat (tmp,"/novalidate-cert"); + if (LOCAL->loser = mb.loser) strcat (tmp,"/loser"); + if (stream->secure) strcat (tmp,"/secure"); + sprintf (tmp + strlen (tmp),"/user=\"%s\"}%s",usr,mb.mailbox); + stream->inbox = T; /* always INBOX */ + fs_give ((void **) &stream->mailbox); + stream->mailbox = cpystr (tmp); + /* notify upper level */ + mail_exists (stream,stream->uid_last = strtoul (LOCAL->reply,NIL,10)); + mail_recent (stream,stream->nmsgs); + /* instantiate elt */ + for (i = 0; i < stream->nmsgs;) { + elt = mail_elt (stream,++i); + elt->valid = elt->recent = T; + elt->private.uid = i; + } + + /* trust LIST output if new server */ + if (!LOCAL->loser && LOCAL->cap.capa && pop3_send (stream,"LIST",NIL)) { + while ((s = net_getline (LOCAL->netstream)) && (*s != '.')) { + if ((i = strtoul (s,&t,10)) && (i <= stream->nmsgs) && + (j = strtoul (t,NIL,10))) mail_elt (stream,i)->rfc822_size = j; + fs_give ((void **) &s); + } + /* flush final dot */ + if (s) fs_give ((void **) &s); + else { /* lost connection */ + mm_log ("POP3 connection broken while itemizing messages",ERROR); + pop3_close (stream,NIL); + return NIL; + } + } + stream->silent = silent; /* notify main program */ + mail_exists (stream,stream->nmsgs); + /* notify if empty */ + if (!(stream->nmsgs || stream->silent)) mm_log ("Mailbox is empty",WARN); + } + else { /* error in STAT */ + mm_log (LOCAL->reply,ERROR); + pop3_close (stream,NIL); /* too bad */ + } + } + else { /* connection failed */ + if (LOCAL->reply) mm_log (LOCAL->reply,ERROR); + pop3_close (stream,NIL); /* failed, clean up */ + } + return LOCAL ? stream : NIL; /* if stream is alive, return to caller */ +} + +/* POP3 capabilities + * Accepts: stream + * authenticator flags + * Returns: T on success, NIL on failure + */ + +long pop3_capa (MAILSTREAM *stream,long flags) +{ + unsigned long i; + char *s,*t,*r,*args; + if (LOCAL->cap.implementation)/* zap all old capabilities */ + fs_give ((void **) &LOCAL->cap.implementation); + memset (&LOCAL->cap,0,sizeof (LOCAL->cap)); + /* get server capabilities */ + if (pop3_send (stream,"CAPA",NIL)) LOCAL->cap.capa = T; + else { + LOCAL->cap.user = T; /* guess worst-case old server */ + return NIL; /* no CAPA on this server */ + } + while ((t = net_getline (LOCAL->netstream)) && (t[1] || (*t != '.'))) { + if (stream->debug) mm_dlog (t); + /* get optional capability arguments */ + if (args = strchr (t,' ')) *args++ = '\0'; + if (!compare_cstring (t,"STLS")) LOCAL->cap.stls = T; + else if (!compare_cstring (t,"PIPELINING")) LOCAL->cap.pipelining = T; + else if (!compare_cstring (t,"RESP-CODES")) LOCAL->cap.respcodes = T; + else if (!compare_cstring (t,"TOP")) LOCAL->cap.top = T; + else if (!compare_cstring (t,"UIDL")) LOCAL->cap.uidl = T; + else if (!compare_cstring (t,"USER")) LOCAL->cap.user = T; + else if (!compare_cstring (t,"IMPLEMENTATION") && args) + LOCAL->cap.implementation = cpystr (args); + else if (!compare_cstring (t,"EXPIRE") && args) { + LOCAL->cap.expire = T; /* note that it is present */ + if (s = strchr(args,' ')){/* separate time from possible USER */ + *s++ = '\0'; + /* in case they add something after USER */ + if ((strlen (s) > 4) && (s[4] == ' ')) s[4] = '\0'; + } + LOCAL->cap.expire = /* get expiration time */ + (!compare_cstring (args,"NEVER")) ? 65535 : + ((s && !compare_cstring (s,"USER")) ? -atoi (args) : atoi (args)); + } + else if (!compare_cstring (t,"LOGIN-DELAY") && args) { + LOCAL->cap.logindelay = T;/* note that it is present */ + if (s = strchr(args,' ')){/* separate time from possible USER */ + *s++ = '\0'; + /* in case they add something after USER */ + if ((strlen (s) > 4) && (s[4] == ' ')) s[4] = '\0'; + } + /* get delay time */ + LOCAL->cap.delaysecs = (s && !compare_cstring (s,"USER")) ? + -atoi (args) : atoi (args); + } + else if (!compare_cstring (t,"SASL") && args) + for (args = strtok_r (args," ",&r); args; args = strtok_r (NIL," ",&r)) + if ((i = mail_lookup_auth_name (args,flags)) && + (--i < MAXAUTHENTICATORS)) + LOCAL->cap.sasl |= (1 << i); + fs_give ((void **) &t); + } + if (t) { /* flush end of text indicator */ + if (stream->debug) mm_dlog (t); + fs_give ((void **) &t); + } + return LONGT; +} + +/* POP3 authenticate + * Accepts: stream to login + * parsed network mailbox structure + * scratch buffer of length MAILTMPLEN + * place to return user name + * Returns: T on success, NIL on failure + */ + +long pop3_auth (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr) +{ + unsigned long i,trial,auths = 0; + char *t; + AUTHENTICATOR *at; + long ret = NIL; + long flags = (stream->secure ? AU_SECURE : NIL) | + (mb->authuser[0] ? AU_AUTHUSER : NIL); + long capaok = pop3_capa (stream,flags); + NETDRIVER *ssld = (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL); + sslstart_t stls = (sslstart_t) mail_parameters (NIL,GET_SSLSTART,NIL); + /* server has TLS? */ + if (stls && LOCAL->cap.stls && !mb->sslflag && !mb->notlsflag && + pop3_send (stream,"STLS",NIL)) { + mb->tlsflag = T; /* TLS OK, get into TLS at this end */ + LOCAL->netstream->dtb = ssld; + if (!(LOCAL->netstream->stream = + (*stls) (LOCAL->netstream->stream,mb->host, + (mb->tlssslv23 ? NIL : NET_TLSCLIENT) | + (mb->novalidate ? NET_NOVALIDATECERT : NIL)))) { + /* drat, drop this connection */ + if (LOCAL->netstream) net_close (LOCAL->netstream); + LOCAL->netstream= NIL; + return NIL; /* TLS negotiation failed */ + } + pop3_capa (stream,flags); /* get capabilities now that TLS in effect */ + } + else if (mb->tlsflag) { /* user specified /tls but can't do it */ + mm_log ("Unable to negotiate TLS with this server",ERROR); + return NIL; + } + /* get authenticators from capabilities */ + if (capaok) auths = LOCAL->cap.sasl; + /* get list of authenticators */ + else if (pop3_send (stream,"AUTH",NIL)) { + while ((t = net_getline (LOCAL->netstream)) && (t[1] || (*t != '.'))) { + if (stream->debug) mm_dlog (t); + if ((i = mail_lookup_auth_name (t,flags)) && (--i < MAXAUTHENTICATORS)) + auths |= (1 << i); + fs_give ((void **) &t); + } + if (t) { /* flush end of text indicator */ + if (stream->debug) mm_dlog (t); + fs_give ((void **) &t); + } + } + /* disable LOGIN if PLAIN also advertised */ + if ((i = mail_lookup_auth_name ("PLAIN",NIL)) && (--i < MAXAUTHENTICATORS) && + (auths & (1 << i)) && + (i = mail_lookup_auth_name ("LOGIN",NIL)) && (--i < MAXAUTHENTICATORS)) + auths &= ~(1 << i); + + if (auths) { /* got any authenticators? */ + if ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL)) { + /* remote name for authentication */ + strncpy (mb->host,(long) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ? + net_remotehost (LOCAL->netstream) : net_host (LOCAL->netstream), + NETMAXHOST-1); + mb->host[NETMAXHOST-1] = '\0'; + } + for (t = NIL, LOCAL->saslcancel = NIL; !ret && LOCAL->netstream && auths && + (at = mail_lookup_auth (find_rightmost_bit (&auths)+1)); ) { + if (t) { /* previous authenticator failed? */ + sprintf (pwd,"Retrying using %.80s authentication after %.80s", + at->name,t); + mm_log (pwd,NIL); + fs_give ((void **) &t); + } + trial = 0; /* initial trial count */ + do { + if (t) { + sprintf (pwd,"Retrying %s authentication after %.80s",at->name,t); + mm_log (pwd,WARN); + fs_give ((void **) &t); + } + LOCAL->saslcancel = NIL; + if (pop3_send (stream,"AUTH",at->name)) { + /* hide client authentication responses */ + if (!(at->flags & AU_SECURE)) LOCAL->sensitive = T; + if ((*at->client) (pop3_challenge,pop3_response,"pop",mb,stream, + &trial,usr) && LOCAL->response) { + if (*LOCAL->response == '+') ret = LONGT; + /* if main program requested cancellation */ + else if (!trial) mm_log ("POP3 Authentication cancelled",ERROR); + } + LOCAL->sensitive=NIL; /* unhide */ + } + /* remember response if error and no cancel */ + if (!ret && trial) t = cpystr (LOCAL->reply); + } while (!ret && trial && (trial < pop3_maxlogintrials) && + LOCAL->netstream); + } + if (t) { /* previous authenticator failed? */ + if (!LOCAL->saslcancel) { /* don't do this if a cancel */ + sprintf (pwd,"Can not authenticate to POP3 server: %.80s",t); + mm_log (pwd,ERROR); + } + fs_give ((void **) &t); + } + } + + else if (stream->secure) + mm_log ("Can't do secure authentication with this server",ERROR); + else if (mb->authuser[0]) + mm_log ("Can't do /authuser with this server",ERROR); + else if (!LOCAL->cap.user) mm_log ("Can't login to this server",ERROR); + else { /* traditional login */ + trial = 0; /* initial trial count */ + do { + pwd[0] = 0; /* prompt user for password */ + mm_login (mb,usr,pwd,trial++); + if (pwd[0]) { /* send login sequence if have password */ + if (pop3_send (stream,"USER",usr)) { + LOCAL->sensitive = T; /* hide this command */ + if (pop3_send (stream,"PASS",pwd)) ret = LONGT; + LOCAL->sensitive=NIL; /* unhide */ + } + if (!ret) { /* failure */ + mm_log (LOCAL->reply,WARN); + if (trial == pop3_maxlogintrials) + mm_log ("Too many login failures",ERROR); + } + } + /* user refused to give password */ + else mm_log ("Login aborted",ERROR); + } while (!ret && pwd[0] && (trial < pop3_maxlogintrials) && + LOCAL->netstream); + } + memset (pwd,0,MAILTMPLEN); /* erase password */ + /* get capabilities if logged in */ + if (ret && capaok) pop3_capa (stream,flags); + return ret; +} + +/* Get challenge to authenticator in binary + * Accepts: stream + * pointer to returned size + * Returns: challenge or NIL if not challenge + */ + +void *pop3_challenge (void *s,unsigned long *len) +{ + char tmp[MAILTMPLEN]; + void *ret = NIL; + MAILSTREAM *stream = (MAILSTREAM *) s; + if (stream && LOCAL->response && + (*LOCAL->response == '+') && (LOCAL->response[1] == ' ') && + !(ret = rfc822_base64 ((unsigned char *) LOCAL->reply, + strlen (LOCAL->reply),len))) { + sprintf (tmp,"POP3 SERVER BUG (invalid challenge): %.80s",LOCAL->reply); + mm_log (tmp,ERROR); + } + return ret; +} + + +/* Send authenticator response in BASE64 + * Accepts: MAIL stream + * string to send + * length of string + * Returns: T if successful, else NIL + */ + +long pop3_response (void *s,char *response,unsigned long size) +{ + MAILSTREAM *stream = (MAILSTREAM *) s; + unsigned long i,j,ret; + char *t,*u; + if (response) { /* make CRLFless BASE64 string */ + if (size) { + for (t = (char *) rfc822_binary ((void *) response,size,&i),u = t,j = 0; + j < i; j++) if (t[j] > ' ') *u++ = t[j]; + *u = '\0'; /* tie off string for mm_dlog() */ + if (stream->debug) mail_dlog (t,LOCAL->sensitive); + /* append CRLF */ + *u++ = '\015'; *u++ = '\012'; *u = '\0'; + ret = net_sout (LOCAL->netstream,t,u - t); + fs_give ((void **) &t); + } + else ret = net_sout (LOCAL->netstream,"\015\012",2); + } + else { /* abort requested */ + ret = net_sout (LOCAL->netstream,"*\015\012",3); + LOCAL->saslcancel = T; /* mark protocol-requested SASL cancel */ + } + pop3_reply (stream); /* get response */ + return ret; +} + +/* POP3 mail close + * Accepts: MAIL stream + * option flags + */ + +void pop3_close (MAILSTREAM *stream,long options) +{ + int silent = stream->silent; + if (LOCAL) { /* only if a file is open */ + if (LOCAL->netstream) { /* close POP3 connection */ + stream->silent = T; + if (options & CL_EXPUNGE) pop3_expunge (stream,NIL,NIL); + stream->silent = silent; + pop3_send (stream,"QUIT",NIL); + mm_notify (stream,LOCAL->reply,BYE); + } + /* close POP3 connection */ + if (LOCAL->netstream) net_close (LOCAL->netstream); + /* clean up */ + if (LOCAL->cap.implementation) + fs_give ((void **) &LOCAL->cap.implementation); + if (LOCAL->txt) fclose (LOCAL->txt); + LOCAL->txt = NIL; + if (LOCAL->response) fs_give ((void **) &LOCAL->response); + /* nuke the local data */ + fs_give ((void **) &stream->local); + stream->dtb = NIL; /* log out the DTB */ + } +} + +/* POP3 mail fetch fast information + * Accepts: MAIL stream + * sequence + * option flags + * This is ugly and slow + */ + +void pop3_fetchfast (MAILSTREAM *stream,char *sequence,long flags) +{ + unsigned long i; + MESSAGECACHE *elt; + /* get sequence */ + if (stream && LOCAL && ((flags & FT_UID) ? + mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) + for (i = 1; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->sequence && + !(elt->day && elt->rfc822_size)) { + ENVELOPE **env = NIL; + ENVELOPE *e = NIL; + if (!stream->scache) env = &elt->private.msg.env; + else if (stream->msgno == i) env = &stream->env; + else env = &e; + if (!*env || !elt->rfc822_size) { + STRING bs; + unsigned long hs; + char *ht = (*stream->dtb->header) (stream,i,&hs,NIL); + /* need to make an envelope? */ + if (!*env) rfc822_parse_msg (env,NIL,ht,hs,NIL,BADHOST, + stream->dtb->flags); + /* need message size too, ugh */ + if (!elt->rfc822_size) { + (*stream->dtb->text) (stream,i,&bs,FT_PEEK); + elt->rfc822_size = hs + SIZE (&bs) - GETPOS (&bs); + } + } + /* if need date, have date in envelope? */ + if (!elt->day && *env && (*env)->date) + mail_parse_date (elt,(*env)->date); + /* sigh, fill in bogus default */ + if (!elt->day) elt->day = elt->month = 1; + mail_free_envelope (&e); + } +} + +/* POP3 fetch header as text + * Accepts: mail stream + * message number + * pointer to return size + * flags + * Returns: header text + */ + +char *pop3_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *size, + long flags) +{ + unsigned long i; + char tmp[MAILTMPLEN]; + MESSAGECACHE *elt; + FILE *f = NIL; + *size = 0; /* initially no header size */ + if ((flags & FT_UID) && !(msgno = mail_msgno (stream,msgno))) return ""; + /* have header text already? */ + if (!(elt = mail_elt (stream,msgno))->private.msg.header.text.data) { + /* if have CAPA and TOP, assume good TOP */ + if (!LOCAL->loser && LOCAL->cap.top) { + sprintf (tmp,"TOP %lu 0",mail_uid (stream,msgno)); + if (pop3_send (stream,tmp,NIL)) + f = netmsg_slurp (LOCAL->netstream,&i, + &elt->private.msg.header.text.size); + } + /* otherwise load the cache with the message */ + else if (elt->private.msg.header.text.size = pop3_cache (stream,elt)) + f = LOCAL->txt; + if (f) { /* got it, make sure at start of file */ + fseek (f,(unsigned long) 0,SEEK_SET); + /* read header from the cache */ + fread (elt->private.msg.header.text.data = (unsigned char *) + fs_get ((size_t) elt->private.msg.header.text.size + 1), + (size_t) 1,(size_t) elt->private.msg.header.text.size,f); + /* tie off header text */ + elt->private.msg.header.text.data[elt->private.msg.header.text.size] = + '\0'; + /* close if not the cache */ + if (f != LOCAL->txt) fclose (f); + } + } + /* return size of text */ + if (size) *size = elt->private.msg.header.text.size; + return elt->private.msg.header.text.data ? + (char *) elt->private.msg.header.text.data : ""; +} + +/* POP3 fetch body + * Accepts: mail stream + * message number + * pointer to stringstruct to initialize + * flags + * Returns: T if successful, else NIL + */ + +long pop3_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags) +{ + MESSAGECACHE *elt; + INIT (bs,mail_string,(void *) "",0); + if ((flags & FT_UID) && !(msgno = mail_msgno (stream,msgno))) return NIL; + elt = mail_elt (stream,msgno); + pop3_cache (stream,elt); /* make sure cache loaded */ + if (!LOCAL->txt) return NIL; /* error if don't have a file */ + if (!(flags & FT_PEEK)) { /* mark seen if needed */ + elt->seen = T; + mm_flags (stream,elt->msgno); + } + INIT (bs,file_string,(void *) LOCAL->txt,elt->rfc822_size); + SETPOS (bs,LOCAL->hdrsize); /* skip past header */ + return T; +} + +/* POP3 cache message + * Accepts: mail stream + * message number + * Returns: header size + */ + +unsigned long pop3_cache (MAILSTREAM *stream,MESSAGECACHE *elt) +{ + /* already cached? */ + if (LOCAL->cached != mail_uid (stream,elt->msgno)) { + /* no, close current file */ + if (LOCAL->txt) fclose (LOCAL->txt); + LOCAL->txt = NIL; + LOCAL->cached = LOCAL->hdrsize = 0; + if (pop3_send_num (stream,"RETR",elt->msgno) && + (LOCAL->txt = netmsg_slurp (LOCAL->netstream,&elt->rfc822_size, + &LOCAL->hdrsize))) + /* set as current message number */ + LOCAL->cached = mail_uid (stream,elt->msgno); + else elt->deleted = T; + } + return LOCAL->hdrsize; +} + +/* POP3 mail ping mailbox + * Accepts: MAIL stream + * Returns: T if stream alive, else NIL + */ + +long pop3_ping (MAILSTREAM *stream) +{ + return pop3_send (stream,"NOOP",NIL); +} + + +/* POP3 mail check mailbox + * Accepts: MAIL stream + */ + +void pop3_check (MAILSTREAM *stream) +{ + if (pop3_ping (stream)) mm_log ("Check completed",NIL); +} + + +/* POP3 mail expunge mailbox + * Accepts: MAIL stream + * sequence to expunge if non-NIL + * expunge options + * Returns: T if success, NIL if failure + */ + +long pop3_expunge (MAILSTREAM *stream,char *sequence,long options) +{ + char tmp[MAILTMPLEN]; + MESSAGECACHE *elt; + unsigned long i = 1,n = 0; + long ret; + if (ret = sequence ? ((options & EX_UID) ? + mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence)) : + LONGT) { /* build selected sequence if needed */ + while (i <= stream->nmsgs) { + elt = mail_elt (stream,i); + if (elt->deleted && (sequence ? elt->sequence : T) && + pop3_send_num (stream,"DELE",i)) { + /* expunging currently cached message? */ + if (LOCAL->cached == mail_uid (stream,i)) { + /* yes, close current file */ + if (LOCAL->txt) fclose (LOCAL->txt); + LOCAL->txt = NIL; + LOCAL->cached = LOCAL->hdrsize = 0; + } + mail_expunged (stream,i); + n++; + } + else i++; /* try next message */ + } + if (!stream->silent) { /* only if not silent */ + if (n) { /* did we expunge anything? */ + sprintf (tmp,"Expunged %lu messages",n); + mm_log (tmp,(long) NIL); + } + else mm_log ("No messages deleted, so no update needed",(long) NIL); + } + } + return ret; +} + +/* POP3 mail copy message(s) + * Accepts: MAIL stream + * sequence + * destination mailbox + * option flags + * Returns: T if copy successful, else NIL + */ + +long pop3_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) +{ + mailproxycopy_t pc = + (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL); + if (pc) return (*pc) (stream,sequence,mailbox,options); + mm_log ("Copy not valid for POP3",ERROR); + return NIL; +} + + +/* POP3 mail append message from stringstruct + * Accepts: MAIL stream + * destination mailbox + * append callback + * data for callback + * Returns: T if append successful, else NIL + */ + +long pop3_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + mm_log ("Append not valid for POP3",ERROR); + return NIL; +} + +/* Internal routines */ + + +/* Post Office Protocol 3 send command with number argument + * Accepts: MAIL stream + * command + * number + * Returns: T if successful, NIL if failure + */ + +long pop3_send_num (MAILSTREAM *stream,char *command,unsigned long n) +{ + char tmp[MAILTMPLEN]; + sprintf (tmp,"%lu",mail_uid (stream,n)); + return pop3_send (stream,command,tmp); +} + + +/* Post Office Protocol 3 send command + * Accepts: MAIL stream + * command + * command argument + * Returns: T if successful, NIL if failure + */ + +long pop3_send (MAILSTREAM *stream,char *command,char *args) +{ + long ret; + char *s = (char *) fs_get (strlen (command) + (args ? strlen (args) + 1: 0) + + 3); + mail_lock (stream); /* lock up the stream */ + if (!LOCAL->netstream) ret = pop3_fake (stream,"POP3 connection lost"); + else { /* build the complete command */ + if (args) sprintf (s,"%s %s",command,args); + else strcpy (s,command); + if (stream->debug) mail_dlog (s,LOCAL->sensitive); + strcat (s,"\015\012"); + /* send the command */ + ret = net_soutr (LOCAL->netstream,s) ? pop3_reply (stream) : + pop3_fake (stream,"POP3 connection broken in command"); + } + fs_give ((void **) &s); + mail_unlock (stream); /* unlock stream */ + return ret; +} + +/* Post Office Protocol 3 get reply + * Accepts: MAIL stream + * Returns: T if success reply, NIL if error reply + */ + +long pop3_reply (MAILSTREAM *stream) +{ + char *s; + /* flush old reply */ + if (LOCAL->response) fs_give ((void **) &LOCAL->response); + /* get reply */ + if (!(LOCAL->response = net_getline (LOCAL->netstream))) + return pop3_fake (stream,"POP3 connection broken in response"); + if (stream->debug) mm_dlog (LOCAL->response); + LOCAL->reply = (s = strchr (LOCAL->response,' ')) ? s + 1 : LOCAL->response; + /* return success or failure */ + return (*LOCAL->response =='+') ? T : NIL; +} + + +/* Post Office Protocol 3 set fake error + * Accepts: MAIL stream + * error text + * Returns: NIL, always + */ + +long pop3_fake (MAILSTREAM *stream,char *text) +{ + mm_notify (stream,text,BYE); /* send bye alert */ + if (LOCAL->netstream) net_close (LOCAL->netstream); + LOCAL->netstream = NIL; /* farewell, dear TCP stream */ + /* flush any old reply */ + if (LOCAL->response) fs_give ((void **) &LOCAL->response); + LOCAL->reply = text; /* set up pseudo-reply string */ + return NIL; /* return error code */ +} diff --git a/imap/src/c-client/rfc822.c b/imap/src/c-client/rfc822.c new file mode 100644 index 00000000..d9239646 --- /dev/null +++ b/imap/src/c-client/rfc822.c @@ -0,0 +1,2373 @@ +/* ======================================================================== + * Copyright 1988-2008 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: RFC 2822 and MIME routines + * + * Author: Mark Crispin + * UW Technology + * University of Washington + * Seattle, WA 98195 + * Internet: MRC@Washington.EDU + * + * Date: 27 July 1988 + * Last Edited: 14 May 2008 + * + * This original version of this file is + * Copyright 1988 Stanford University + * and was developed in the Symbolic Systems Resources Group of the Knowledge + * Systems Laboratory at Stanford University in 1987-88, and was funded by the + * Biomedical Research Technology Program of the NationalInstitutes of Health + * under grant number RR-00785. + */ + + +#include <ctype.h> +#include <stdio.h> +#include <time.h> +#include "c-client.h" + + +/* Support for deprecated features in earlier specifications. Note that this + * module follows RFC 2822, and all use of "rfc822" in function names is + * for compatibility. Only the code identified by the conditionals below + * follows the earlier documents. + */ + +#define RFC733 1 /* parse "at" */ +#define RFC822 0 /* generate A-D-L (MUST be 0 for 2822) */ + +/* RFC-822 static data */ + +#define RFC822CONT " " /* RFC 2822 continuation */ + + /* should have been "Remailed-" */ +#define RESENTPREFIX "ReSent-" +static char *resentprefix = RESENTPREFIX; + /* syntax error host string */ +static const char *errhst = ERRHOST; + + +/* Body formats constant strings, must match definitions in mail.h */ + +char *body_types[TYPEMAX+1] = { + "TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO", + "MODEL", "X-UNKNOWN" +}; + + +char *body_encodings[ENCMAX+1] = { + "7BIT", "8BIT", "BINARY", "BASE64", "QUOTED-PRINTABLE", "X-UNKNOWN" +}; + + +/* Token delimiting special characters */ + + /* RFC 2822 specials */ +const char *specials = " ()<>@,;:\\\"[].\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177"; + /* RFC 2822 phrase specials (no space) */ +const char *rspecials = "()<>@,;:\\\"[].\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177"; + /* RFC 2822 dot-atom specials (no dot) */ +const char *wspecials = " ()<>@,;:\\\"[]\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177"; + /* RFC 2045 MIME body token specials */ +const char *tspecials = " ()<>@,;:\\\"[]/?=\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177"; + +/* Subtype defaulting (a no-no, but regretably necessary...) + * Accepts: type code + * Returns: default subtype name + */ + +char *rfc822_default_subtype (unsigned short type) +{ + switch (type) { + case TYPETEXT: /* default is TEXT/PLAIN */ + return "PLAIN"; + case TYPEMULTIPART: /* default is MULTIPART/MIXED */ + return "MIXED"; + case TYPEMESSAGE: /* default is MESSAGE/RFC822 */ + return "RFC822"; + case TYPEAPPLICATION: /* default is APPLICATION/OCTET-STREAM */ + return "OCTET-STREAM"; + case TYPEAUDIO: /* default is AUDIO/BASIC */ + return "BASIC"; + default: /* others have no default subtype */ + return "UNKNOWN"; + } +} + +/* RFC 2822 parsing routines */ + + +/* Parse an RFC 2822 message + * Accepts: pointer to return envelope + * pointer to return body + * pointer to header + * header byte count + * pointer to body stringstruct + * pointer to local host name + * recursion depth + * source driver flags + */ + +void rfc822_parse_msg_full (ENVELOPE **en,BODY **bdy,char *s,unsigned long i, + STRING *bs,char *host,unsigned long depth, + unsigned long flags) +{ + char c,*t,*d; + char *tmp = (char *) fs_get ((size_t) i + 100); + ENVELOPE *env = (*en = mail_newenvelope ()); + BODY *body = bdy ? (*bdy = mail_newbody ()) : NIL; + long MIMEp = -1; /* flag that MIME semantics are in effect */ + long PathP = NIL; /* flag that a Path: was seen */ + parseline_t pl = (parseline_t) mail_parameters (NIL,GET_PARSELINE,NIL); + if (!host) host = BADHOST; /* make sure that host is non-null */ + while (i && *s != '\n') { /* until end of header */ + t = tmp; /* initialize buffer pointer */ + c = ' '; /* and previous character */ + while (i && c) { /* collect text until logical end of line */ + switch (c = *s++) { /* slurp a character */ + case '\015': /* return, possible end of logical line */ + if (*s == '\n') break; /* ignore if LF follows */ + case '\012': /* LF, possible end of logical line */ + /* tie off unless next line starts with WS */ + if (*s != ' ' && *s != '\t') *t++ = c = '\0'; + break; + case '\t': /* tab */ + *t++ = ' '; /* coerce to space */ + break; + default: /* all other characters */ + *t++ = c; /* insert the character into the line */ + break; + } + if (!--i) *t++ = '\0'; /* see if end of header */ + } + + /* find header item type */ + if (t = d = strchr (tmp,':')) { + *d++ = '\0'; /* tie off header item, point at its data */ + while (*d == ' ') d++; /* flush whitespace */ + while ((tmp < t--) && (*t == ' ')) *t = '\0'; + ucase (tmp); /* coerce to uppercase */ + /* external callback */ + if (pl) (*pl) (env,tmp,d,host); + switch (*tmp) { /* dispatch based on first character */ + case '>': /* possible >From: */ + if (!strcmp (tmp+1,"FROM")) rfc822_parse_adrlist (&env->from,d,host); + break; + case 'B': /* possible bcc: */ + if (!strcmp (tmp+1,"CC")) rfc822_parse_adrlist (&env->bcc,d,host); + break; + case 'C': /* possible cc: or Content-<mumble>*/ + if (!strcmp (tmp+1,"C")) rfc822_parse_adrlist (&env->cc,d,host); + else if ((tmp[1] == 'O') && (tmp[2] == 'N') && (tmp[3] == 'T') && + (tmp[4] == 'E') && (tmp[5] == 'N') && (tmp[6] == 'T') && + (tmp[7] == '-') && body) + switch (MIMEp) { + case -1: /* unknown if MIME or not */ + if (!(MIMEp = /* see if MIME-Version header exists */ + search ((unsigned char *) s-1,i, + (unsigned char *)"\012MIME-Version",(long) 13))) { +#if 1 + /* This is a disgusting kludge, and most of the messages which + * benefit from it are spam. + */ + if (!strcmp (tmp+8,"TRANSFER-ENCODING") || + (!strcmp (tmp+8,"TYPE") && strchr (d,'/'))) { + MM_LOG ("Warning: MIME header encountered in non-MIME message", + PARSE); + MIMEp = 1; /* declare MIME now */ + } + else +#endif + break; /* non-MIME message */ + } + case T: /* definitely MIME */ + rfc822_parse_content_header (body,tmp+8,d); + } + break; + case 'D': /* possible Date: */ + if (!env->date && !strcmp (tmp+1,"ATE")) env->date = cpystr (d); + break; + case 'F': /* possible From: */ + if (!strcmp (tmp+1,"ROM")) rfc822_parse_adrlist (&env->from,d,host); + else if (!strcmp (tmp+1,"OLLOWUP-TO")) { + t = env->followup_to = (char *) fs_get (1 + strlen (d)); + while (c = *d++) if (c != ' ') *t++ = c; + *t++ = '\0'; + } + break; + case 'I': /* possible In-Reply-To: */ + if (!env->in_reply_to && !strcmp (tmp+1,"N-REPLY-TO")) + env->in_reply_to = cpystr (d); + break; + + case 'M': /* possible Message-ID: or MIME-Version: */ + if (!env->message_id && !strcmp (tmp+1,"ESSAGE-ID")) + env->message_id = cpystr (d); + else if (!strcmp (tmp+1,"IME-VERSION")) { + /* tie off at end of phrase */ + if (t = rfc822_parse_phrase (d)) *t = '\0'; + rfc822_skipws (&d); /* skip whitespace */ + /* known version? */ + if (strcmp (d,"1.0") && strcmp (d,"RFC-XXXX")) + MM_LOG ("Warning: message has unknown MIME version",PARSE); + MIMEp = T; /* note that we are MIME */ + } + break; + case 'N': /* possible Newsgroups: */ + if (!env->newsgroups && !strcmp (tmp+1,"EWSGROUPS")) { + t = env->newsgroups = (char *) fs_get (1 + strlen (d)); + while (c = *d++) if (c != ' ') *t++ = c; + *t++ = '\0'; + } + break; + case 'P': /* possible Path: */ + if (!strcmp (tmp+1,"ATH")) env->ngpathexists = T; + break; + case 'R': /* possible Reply-To: */ + if (!strcmp (tmp+1,"EPLY-TO")) + rfc822_parse_adrlist (&env->reply_to,d,host); + else if (!env->references && !strcmp (tmp+1,"EFERENCES")) + env->references = cpystr (d); + break; + case 'S': /* possible Subject: or Sender: */ + if (!env->subject && !strcmp (tmp+1,"UBJECT")) + env->subject = cpystr (d); + else if (!strcmp (tmp+1,"ENDER")) + rfc822_parse_adrlist (&env->sender,d,host); + break; + case 'T': /* possible To: */ + if (!strcmp (tmp+1,"O")) rfc822_parse_adrlist (&env->to,d,host); + break; + default: + break; + } + } + } + fs_give ((void **) &tmp); /* done with scratch buffer */ + /* default Sender: and Reply-To: to From: */ + if (!env->sender) env->sender = rfc822_cpy_adr (env->from); + if (!env->reply_to) env->reply_to = rfc822_cpy_adr (env->from); + /* now parse the body */ + if (body) rfc822_parse_content (body,bs,host,depth,flags); +} + +/* Parse a message body content + * Accepts: pointer to body structure + * body string + * pointer to local host name + * recursion depth + * source driver flags + */ + +void rfc822_parse_content (BODY *body,STRING *bs,char *h,unsigned long depth, + unsigned long flags) +{ + char c,c1,*s,*s1; + int f; + unsigned long i,j,k,m; + PARAMETER *param; + PART *part = NIL; + if (depth > MAXMIMEDEPTH) { /* excessively deep recursion? */ + body->type = TYPETEXT; /* yes, probably a malicious MIMEgram */ + MM_LOG ("Ignoring excessively deep MIME recursion",PARSE); + } + if (!body->subtype) /* default subtype if still unknown */ + body->subtype = cpystr (rfc822_default_subtype (body->type)); + /* note offset and sizes */ + body->contents.offset = GETPOS (bs); + /* note internal body size in all cases */ + body->size.bytes = body->contents.text.size = i = SIZE (bs); + if (!(flags & DR_CRLF)) body->size.bytes = strcrlflen (bs); + switch (body->type) { /* see if anything else special to do */ + case TYPETEXT: /* text content */ + if (!body->parameter) { /* no parameters set */ + body->parameter = mail_newbody_parameter (); + body->parameter->attribute = cpystr ("CHARSET"); + while (i--) { /* count lines and guess charset */ + c = SNX (bs); /* get current character */ + /* charset still unknown? */ + if (!body->parameter->value) { + if ((c == I2C_ESC) && (i && i--) && ((c = SNX (bs)) == I2C_MULTI) && + (i && i--) && (((c = SNX (bs)) == I2CS_94x94_JIS_NEW) || + (c == I2CS_94x94_JIS_OLD))) + body->parameter->value = cpystr ("ISO-2022-JP"); + else if (c & 0x80) body->parameter->value = cpystr ("X-UNKNOWN"); + } + if (c == '\n') body->size.lines++; + } + /* 7-bit content */ + if (!body->parameter->value) switch (body->encoding) { + case ENC7BIT: /* unadorned 7-bit */ + case ENC8BIT: /* unadorned 8-bit (but 7-bit content) */ + case ENCBINARY: /* binary (but 7-bit content( */ + body->parameter->value = cpystr ("US-ASCII"); + break; + default: /* QUOTED-PRINTABLE, BASE64, etc. */ + body->parameter->value = cpystr ("X-UNKNOWN"); + break; + } + } + /* just count lines */ + else while (i--) if ((SNX (bs)) == '\n') body->size.lines++; + break; + + case TYPEMESSAGE: /* encapsulated message */ + /* encapsulated RFC-822 message? */ + if (!strcmp (body->subtype,"RFC822")) { + body->nested.msg = mail_newmsg (); + switch (body->encoding) { /* make sure valid encoding */ + case ENC7BIT: /* these are valid nested encodings */ + case ENC8BIT: + case ENCBINARY: + break; + default: + MM_LOG ("Ignoring nested encoding of message contents",PARSE); + } + /* hunt for blank line */ + for (c = '\012',j = 0; (i > j) && ((c != '\012') || (CHR(bs) != '\012')); + j++) if ((c1 = SNX (bs)) != '\015') c = c1; + if (i > j) { /* unless no more text */ + c1 = SNX (bs); /* body starts here */ + j++; /* advance count */ + } + /* note body text offset and header size */ + body->nested.msg->header.text.size = j; + body->nested.msg->text.text.size = body->contents.text.size - j; + body->nested.msg->text.offset = GETPOS (bs); + body->nested.msg->full.offset = body->nested.msg->header.offset = + body->contents.offset; + body->nested.msg->full.text.size = body->contents.text.size; + /* copy header string */ + SETPOS (bs,body->contents.offset); + s = (char *) fs_get ((size_t) j + 1); + for (s1 = s,k = j; k--; *s1++ = SNX (bs)); + s[j] = '\0'; /* tie off string (not really necessary) */ + /* now parse the body */ + rfc822_parse_msg_full (&body->nested.msg->env,&body->nested.msg->body,s, + j,bs,h,depth+1,flags); + fs_give ((void **) &s); /* free header string */ + /* restore position */ + SETPOS (bs,body->contents.offset); + } + /* count number of lines */ + while (i--) if (SNX (bs) == '\n') body->size.lines++; + break; + case TYPEMULTIPART: /* multiple parts */ + switch (body->encoding) { /* make sure valid encoding */ + case ENC7BIT: /* these are valid nested encodings */ + case ENC8BIT: + case ENCBINARY: + break; + default: + MM_LOG ("Ignoring nested encoding of multipart contents",PARSE); + } + /* remember if digest */ + f = !strcmp (body->subtype,"DIGEST"); + /* find cookie */ + for (s1 = NIL,param = body->parameter; param && !s1; param = param->next) + if (!strcmp (param->attribute,"BOUNDARY")) s1 = param->value; + if (!s1) s1 = "-"; /* yucky default */ + j = strlen (s1) + 2; /* length of cookie and header */ + c = '\012'; /* initially at beginning of line */ + + while (i > j) { /* examine data */ + if (m = GETPOS (bs)) m--; /* get position in front of character */ + switch (c) { /* examine each line */ + case '\015': /* handle CRLF form */ + if (CHR (bs) == '\012'){/* following LF? */ + c = SNX (bs); i--; /* yes, slurp it */ + } + case '\012': /* at start of a line, start with -- ? */ + if (!(i && i-- && ((c = SNX (bs)) == '-') && i-- && + ((c = SNX (bs)) == '-'))) break; + /* see if cookie matches */ + if (k = j - 2) for (s = s1; i-- && *s++ == (c = SNX (bs)) && --k;); + if (k) break; /* strings didn't match if non-zero */ + /* terminating delimiter? */ + if ((c = ((i && i--) ? (SNX (bs)) : '\012')) == '-') { + if ((i && i--) && ((c = SNX (bs)) == '-') && + ((i && i--) ? (((c = SNX (bs)) == '\015') || (c=='\012')):T)) { + /* if have a final part calculate its size */ + if (part) part->body.mime.text.size = + (m > part->body.mime.offset) ? (m - part->body.mime.offset) :0; + part = NIL; i = 1; /* terminate scan */ + } + break; + } + /* swallow trailing whitespace */ + while ((c == ' ') || (c == '\t')) + c = ((i && i--) ? (SNX (bs)) : '\012'); + switch (c) { /* need newline after all of it */ + case '\015': /* handle CRLF form */ + if (i && CHR (bs) == '\012') { + c = SNX (bs); i--;/* yes, slurp it */ + } + case '\012': /* new line */ + if (part) { /* calculate size of previous */ + part->body.mime.text.size = + (m > part->body.mime.offset) ? (m-part->body.mime.offset) : 0; + /* instantiate next */ + part = part->next = mail_newbody_part (); + } /* otherwise start new list */ + else part = body->nested.part = mail_newbody_part (); + /* digest has a different default */ + if (f) part->body.type = TYPEMESSAGE; + /* note offset from main body */ + part->body.mime.offset = GETPOS (bs); + break; + default: /* whatever it was it wasn't valid */ + break; + } + break; + default: /* not at a line */ + c = SNX (bs); i--; /* get next character */ + break; + } + } + + /* calculate size of any final part */ + if (part) part->body.mime.text.size = i + + ((GETPOS(bs) > part->body.mime.offset) ? + (GETPOS(bs) - part->body.mime.offset) : 0); + /* make a scratch buffer */ + s1 = (char *) fs_get ((size_t) (k = MAILTMPLEN)); + /* in case empty multipart */ + if (!body->nested.part) body->nested.part = mail_newbody_part (); + /* parse non-empty body parts */ + for (part = body->nested.part; part; part = part->next) { + /* part non-empty (header and/or content)? */ + if (i = part->body.mime.text.size) { + /* move to that part of the body */ + SETPOS (bs,part->body.mime.offset); + /* until end of header */ + while (i && ((c = CHR (bs)) != '\015') && (c != '\012')) { + /* collect text until logical end of line */ + for (j = 0,c = ' '; c; ) { + /* make sure buffer big enough */ + if (j > (k - 10)) fs_resize ((void **) &s1,k += MAILTMPLEN); + switch (c1 = SNX (bs)) { + case '\015': /* return */ + if (i && (CHR (bs) == '\012')) { + c1 = SNX (bs); /* eat any LF following */ + i--; + } + case '\012': /* newline, possible end of logical line */ + /* tie off unless continuation */ + if (!i || ((CHR (bs) != ' ') && (CHR (bs) != '\t'))) + s1[j] = c = '\0'; + break; + case '\t': /* tab */ + case ' ': /* insert whitespace if not already there */ + if (c != ' ') s1[j++] = c = ' '; + break; + default: /* all other characters */ + s1[j++] = c = c1; /* insert the character into the line */ + break; + } + /* end of data ties off the header */ + if (!i || !--i) s1[j++] = c = '\0'; + } + + /* find header item type */ + if (((s1[0] == 'C') || (s1[0] == 'c')) && + ((s1[1] == 'O') || (s1[1] == 'o')) && + ((s1[2] == 'N') || (s1[2] == 'n')) && + ((s1[3] == 'T') || (s1[3] == 't')) && + ((s1[4] == 'E') || (s1[4] == 'e')) && + ((s1[5] == 'N') || (s1[5] == 'n')) && + ((s1[6] == 'T') || (s1[6] == 't')) && + (s1[7] == '-') && (s = strchr (s1+8,':'))) { + /* tie off and flush whitespace */ + for (*s++ = '\0'; *s == ' '; s++); + /* parse the header */ + rfc822_parse_content_header (&part->body,ucase (s1+8),s); + } + } + /* skip header trailing (CR)LF */ + if (i && (CHR (bs) =='\015')) {i--; c1 = SNX (bs);} + if (i && (CHR (bs) =='\012')) {i--; c1 = SNX (bs);} + j = bs->size; /* save upper level size */ + /* set offset for next level, fake size to i */ + bs->size = GETPOS (bs) + i; + part->body.mime.text.size -= i; + /* now parse it */ + rfc822_parse_content (&part->body,bs,h,depth+1,flags); + bs->size = j; /* restore current level size */ + } + else { /* zero-length part, use default subtype */ + part->body.subtype = cpystr (rfc822_default_subtype (part->body.type)); + /* see if anything else special to do */ + switch (part->body.type) { + case TYPETEXT: /* text content */ + /* default parameters */ + if (!part->body.parameter) { + part->body.parameter = mail_newbody_parameter (); + part->body.parameter->attribute = cpystr ("CHARSET"); + /* only assume US-ASCII if 7BIT */ + part->body.parameter->value = + cpystr ((part->body.encoding == ENC7BIT) ? + "US-ASCII" : "X-UNKNOWN"); + } + break; + case TYPEMESSAGE: /* encapsulated message in digest */ + part->body.nested.msg = mail_newmsg (); + break; + default: + break; + } + } + } + fs_give ((void **) &s1); /* finished with scratch buffer */ + break; + default: /* nothing special to do in any other case */ + break; + } +} + +/* Parse RFC 2822 body content header + * Accepts: body to write to + * possible content name + * remainder of header + */ + +void rfc822_parse_content_header (BODY *body,char *name,char *s) +{ + char c,*t,tmp[MAILTMPLEN]; + long i; + STRINGLIST *stl; + rfc822_skipws (&s); /* skip leading comments */ + /* flush whitespace */ + if (t = strchr (name,' ')) *t = '\0'; + switch (*name) { /* see what kind of content */ + case 'I': /* possible Content-ID */ + if (!(strcmp (name+1,"D") || body->id)) body->id = cpystr (s); + break; + case 'D': /* possible Content-Description */ + if (!(strcmp (name+1,"ESCRIPTION") || body->description)) + body->description = cpystr (s); + if (!(strcmp (name+1,"ISPOSITION") || body->disposition.type)) { + /* get type word */ + if (!(name = rfc822_parse_word (s,tspecials))) break; + c = *name; /* remember delimiter */ + *name = '\0'; /* tie off type */ + body->disposition.type = ucase (cpystr (s)); + *name = c; /* restore delimiter */ + rfc822_skipws (&name); /* skip whitespace */ + rfc822_parse_parameter (&body->disposition.parameter,name); + } + break; + case 'L': /* possible Content-Language */ + if (!(strcmp (name+1,"ANGUAGE") || body->language)) { + stl = NIL; /* process languages */ + while (s && (name = rfc822_parse_word (s,tspecials))) { + c = *name; /* save delimiter */ + *name = '\0'; /* tie off subtype */ + if (stl) stl = stl->next = mail_newstringlist (); + else stl = body->language = mail_newstringlist (); + stl->text.data = (unsigned char *) ucase (cpystr (s)); + stl->text.size = strlen ((char *) stl->text.data); + *name = c; /* restore delimiter */ + rfc822_skipws (&name); /* skip whitespace */ + if (*name == ',') { /* any more languages? */ + s = ++name; /* advance to it them */ + rfc822_skipws (&s); + } + else s = NIL; /* bogus or end of list */ + } + } + else if (!(strcmp (name+1,"OCATION") || body->location)) + body->location = cpystr (s); + break; + case 'M': /* possible Content-MD5 */ + if (!(strcmp (name+1,"D5") || body->md5)) body->md5 = cpystr (s); + break; + + case 'T': /* possible Content-Type/Transfer-Encoding */ + if (!(strcmp (name+1,"YPE") || body->subtype || body->parameter)) { + /* get type word */ + if (!(name = rfc822_parse_word (s,tspecials))) break; + c = *name; /* remember delimiter */ + *name = '\0'; /* tie off type */ + /* search for body type */ + for (i = 0,s = rfc822_cpy (s); + (i <= TYPEMAX) && body_types[i] && + compare_cstring (s,body_types[i]); i++); + if (i > TYPEMAX) { /* fell off end of loop? */ + body->type = TYPEOTHER; /* coerce to X-UNKNOWN */ + sprintf (tmp,"MIME type table overflow: %.100s",s); + MM_LOG (tmp,PARSE); + } + else { /* record body type index */ + body->type = (unsigned short) i; + /* and name if new type */ + if (body_types[body->type]) fs_give ((void **) &s); + else { /* major MIME body type unknown to us */ + body_types[body->type] = ucase (s); + sprintf (tmp,"Unknown MIME type: %.100s",s); + MM_LOG (tmp,PARSE); + } + } + *name = c; /* restore delimiter */ + rfc822_skipws (&name); /* skip whitespace */ + if ((*name == '/') && /* subtype? */ + (name = rfc822_parse_word ((s = ++name),tspecials))) { + c = *name; /* save delimiter */ + *name = '\0'; /* tie off subtype */ + rfc822_skipws (&s); /* copy subtype */ + if (s) body->subtype = ucase (rfc822_cpy (s)); + *name = c; /* restore delimiter */ + rfc822_skipws (&name); /* skip whitespace */ + } + else if (!name) { /* no subtype, was a subtype delimiter? */ + name = s; /* barf, restore pointer */ + rfc822_skipws (&name); /* skip leading whitespace */ + } + rfc822_parse_parameter (&body->parameter,name); + } + + else if (!strcmp (name+1,"RANSFER-ENCODING")) { + if (!(name = rfc822_parse_word (s,tspecials))) break; + c = *name; /* remember delimiter */ + *name = '\0'; /* tie off encoding */ + /* search for body encoding */ + for (i = 0,s = rfc822_cpy (s); + (i <= ENCMAX) && body_encodings[i] && + compare_cstring (s,body_encodings[i]); i++); + if (i > ENCMAX) { /* fell off end of loop? */ + body->encoding = ENCOTHER; + sprintf (tmp,"MIME encoding table overflow: %.100s",s); + MM_LOG (tmp,PARSE); + } + else { /* record body encoding index */ + body->encoding = (unsigned short) i; + /* and name if new encoding */ + if (body_encodings[body->encoding]) fs_give ((void **) &s); + else { + body_encodings[body->encoding] = ucase (s); + sprintf (tmp,"Unknown MIME transfer encoding: %.100s",s); + MM_LOG (tmp,PARSE); + } + } + *name = c; /* restore delimiter */ + /* ??check for cruft here?? */ + } + break; + default: /* otherwise unknown */ + break; + } +} + +/* Parse RFC 2822 body parameter list + * Accepts: parameter list to write to + * text of list + */ + +void rfc822_parse_parameter (PARAMETER **par,char *text) +{ + char c,*s,tmp[MAILTMPLEN]; + PARAMETER *param = NIL; + /* parameter list? */ + while (text && (*text == ';') && + (text = rfc822_parse_word ((s = ++text),tspecials))) { + c = *text; /* remember delimiter */ + *text = '\0'; /* tie off attribute name */ + rfc822_skipws (&s); /* skip leading attribute whitespace */ + if (!*s) *text = c; /* must have an attribute name */ + else { /* instantiate a new parameter */ + if (*par) param = param->next = mail_newbody_parameter (); + else param = *par = mail_newbody_parameter (); + param->attribute = ucase (cpystr (s)); + *text = c; /* restore delimiter */ + rfc822_skipws (&text); /* skip whitespace before equal sign */ + if ((*text == '=') && /* make sure have value */ + (text = rfc822_parse_word ((s = ++text),tspecials))) { + c = *text; /* remember delimiter */ + *text = '\0'; /* tie off value */ + rfc822_skipws (&s); /* skip leading value whitespace */ + if (*s) param->value = rfc822_cpy (s); + *text = c; /* restore delimiter */ + rfc822_skipws (&text); + } + if (!param->value) { /* value not found? */ + param->value = cpystr ("MISSING_PARAMETER_VALUE"); + sprintf (tmp,"Missing parameter value: %.80s",param->attribute); + MM_LOG (tmp,PARSE); + } + } + } + /* string not present */ + if (!text) MM_LOG ("Missing parameter",PARSE); + else if (*text) { /* must be end of poop */ + sprintf (tmp,"Unexpected characters at end of parameters: %.80s",text); + MM_LOG (tmp,PARSE); + } +} + +/* Parse RFC 2822 address list + * Accepts: address list to write to + * input string + * default host name + */ + +void rfc822_parse_adrlist (ADDRESS **lst,char *string,char *host) +{ + int c; + char *s,tmp[MAILTMPLEN]; + ADDRESS *last = *lst; + ADDRESS *adr; + if (!string) return; /* no string */ + rfc822_skipws (&string); /* skip leading WS */ + if (!*string) return; /* empty string */ + /* run to tail of list */ + if (last) while (last->next) last = last->next; + while (string) { /* loop until string exhausted */ + while (*string == ',') { /* RFC 822 allowed null addresses!! */ + ++string; /* skip the comma */ + rfc822_skipws (&string); /* and any leading WS */ + } + if (!*string) string = NIL; /* punt if ran out of string */ + /* got an address? */ + else if (adr = rfc822_parse_address (lst,last,&string,host,0)) { + last = adr; /* new tail address */ + if (string) { /* analyze what follows */ + rfc822_skipws (&string); + switch (c = *(unsigned char *) string) { + case ',': /* comma? */ + ++string; /* then another address follows */ + break; + default: + s = isalnum (c) ? "Must use comma to separate addresses: %.80s" : + "Unexpected characters at end of address: %.80s"; + sprintf (tmp,s,string); + MM_LOG (tmp,PARSE); + last = last->next = mail_newaddr (); + last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS"); + last->host = cpystr (errhst); + /* falls through */ + case '\0': /* null-specified address? */ + string = NIL; /* punt remainder of parse */ + break; + } + } + } + else if (string) { /* bad mailbox */ + rfc822_skipws (&string); /* skip WS */ + if (!*string) strcpy (tmp,"Missing address after comma"); + else sprintf (tmp,"Invalid mailbox list: %.80s",string); + MM_LOG (tmp,PARSE); + string = NIL; + (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS"); + adr->host = cpystr (errhst); + if (last) last = last->next = adr; + else *lst = last = adr; + break; + } + } +} + +/* Parse RFC 2822 address + * Accepts: address list to write to + * tail of address list + * pointer to input string + * default host name + * group nesting depth + * Returns: new list tail + */ + +ADDRESS *rfc822_parse_address (ADDRESS **lst,ADDRESS *last,char **string, + char *defaulthost,unsigned long depth) +{ + ADDRESS *adr; + if (!*string) return NIL; /* no string */ + rfc822_skipws (string); /* skip leading WS */ + if (!**string) return NIL; /* empty string */ + if (adr = rfc822_parse_group (lst,last,string,defaulthost,depth)) last = adr; + /* got an address? */ + else if (adr = rfc822_parse_mailbox (string,defaulthost)) { + if (!*lst) *lst = adr; /* yes, first time through? */ + else last->next = adr; /* no, append to the list */ + /* set for subsequent linking */ + for (last = adr; last->next; last = last->next); + } + else if (*string) return NIL; + return last; +} + +/* Parse RFC 2822 group + * Accepts: address list to write to + * pointer to tail of address list + * pointer to input string + * default host name + * group nesting depth + */ + +ADDRESS *rfc822_parse_group (ADDRESS **lst,ADDRESS *last,char **string, + char *defaulthost,unsigned long depth) +{ + char tmp[MAILTMPLEN]; + char *p,*s; + ADDRESS *adr; + if (depth > MAXGROUPDEPTH) { /* excessively deep recursion? */ + MM_LOG ("Ignoring excessively deep group recursion",PARSE); + return NIL; /* probably abusive */ + } + if (!*string) return NIL; /* no string */ + rfc822_skipws (string); /* skip leading WS */ + if (!**string || /* trailing whitespace or not group */ + ((*(p = *string) != ':') && !(p = rfc822_parse_phrase (*string)))) + return NIL; + s = p; /* end of candidate phrase */ + rfc822_skipws (&s); /* find delimiter */ + if (*s != ':') return NIL; /* not really a group */ + *p = '\0'; /* tie off group name */ + p = ++s; /* continue after the delimiter */ + rfc822_skipws (&p); /* skip subsequent whitespace */ + /* write as address */ + (adr = mail_newaddr ())->mailbox = rfc822_cpy (*string); + if (!*lst) *lst = adr; /* first time through? */ + else last->next = adr; /* no, append to the list */ + last = adr; /* set for subsequent linking */ + *string = p; /* continue after this point */ + while (*string && **string && (**string != ';')) { + if (adr = rfc822_parse_address (lst,last,string,defaulthost,depth+1)) { + last = adr; /* new tail address */ + if (*string) { /* anything more? */ + rfc822_skipws (string); /* skip whitespace */ + switch (**string) { /* see what follows */ + case ',': /* another address? */ + ++*string; /* yes, skip past the comma */ + case ';': /* end of group? */ + case '\0': /* end of string */ + break; + default: + sprintf (tmp,"Unexpected characters after address in group: %.80s", + *string); + MM_LOG (tmp,PARSE); + *string = NIL; /* cancel remainder of parse */ + last = last->next = mail_newaddr (); + last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS_IN_GROUP"); + last->host = cpystr (errhst); + } + } + } + else { /* bogon */ + sprintf (tmp,"Invalid group mailbox list: %.80s",*string); + MM_LOG (tmp,PARSE); + *string = NIL; /* cancel remainder of parse */ + (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS_IN_GROUP"); + adr->host = cpystr (errhst); + last = last->next = adr; + } + } + if (*string) { /* skip close delimiter */ + if (**string == ';') ++*string; + rfc822_skipws (string); + } + /* append end of address mark to the list */ + last->next = (adr = mail_newaddr ()); + last = adr; /* set for subsequent linking */ + return last; /* return the tail */ +} + +/* Parse RFC 2822 mailbox + * Accepts: pointer to string pointer + * default host + * Returns: address list + * + * Updates string pointer + */ + +ADDRESS *rfc822_parse_mailbox (char **string,char *defaulthost) +{ + ADDRESS *adr = NIL; + char *s,*end; + parsephrase_t pp = (parsephrase_t) mail_parameters (NIL,GET_PARSEPHRASE,NIL); + if (!*string) return NIL; /* no string */ + rfc822_skipws (string); /* flush leading whitespace */ + if (!**string) return NIL; /* empty string */ + if (*(s = *string) == '<') /* note start, handle case of phraseless RA */ + adr = rfc822_parse_routeaddr (s,string,defaulthost); + /* otherwise, expect at least one word */ + else if (end = rfc822_parse_phrase (s)) { + if ((adr = rfc822_parse_routeaddr (end,string,defaulthost))) { + /* phrase is a personal name */ + if (adr->personal) fs_give ((void **) &adr->personal); + *end = '\0'; /* tie off phrase */ + adr->personal = rfc822_cpy (s); + } + /* call external phraseparser if phrase only */ + else if (pp && rfc822_phraseonly (end) && + (adr = (*pp) (s,end,defaulthost))) { + *string = end; /* update parse pointer */ + rfc822_skipws (string); /* skip WS in the normal way */ + } + else adr = rfc822_parse_addrspec (s,string,defaulthost); + } + return adr; /* return the address */ +} + + +/* Check if address is a phrase only + * Accepts: pointer to end of phrase + * Returns: T if phrase only, else NIL; + */ + +long rfc822_phraseonly (char *end) +{ + while (*end == ' ') ++end; /* call rfc822_skipws() instead?? */ + switch (*end) { + case '\0': case ',': case ';': + return LONGT; /* is a phrase only */ + } + return NIL; /* something other than phase is here */ +} + +/* Parse RFC 2822 route-address + * Accepts: string pointer + * pointer to string pointer to update + * Returns: address + * + * Updates string pointer + */ + +ADDRESS *rfc822_parse_routeaddr (char *string,char **ret,char *defaulthost) +{ + char tmp[MAILTMPLEN]; + ADDRESS *adr; + char *s,*t,*adl; + size_t adllen,i; + if (!string) return NIL; + rfc822_skipws (&string); /* flush leading whitespace */ + /* must start with open broket */ + if (*string != '<') return NIL; + t = ++string; /* see if A-D-L there */ + rfc822_skipws (&t); /* flush leading whitespace */ + for (adl = NIL,adllen = 0; /* parse possible A-D-L */ + (*t == '@') && (s = rfc822_parse_domain (t+1,&t));) { + i = strlen (s) + 2; /* @ plus domain plus delimiter or NUL */ + if (adl) { /* have existing A-D-L? */ + fs_resize ((void **) &adl,adllen + i); + sprintf (adl + adllen - 1,",@%s",s); + } + /* write initial A-D-L */ + else sprintf (adl = (char *) fs_get (i),"@%s",s); + adllen += i; /* new A-D-L length */ + fs_give ((void **) &s); /* don't need domain any more */ + rfc822_skipws (&t); /* skip WS */ + if (*t != ',') break; /* put if not comma */ + t++; /* skip the comma */ + rfc822_skipws (&t); /* skip WS */ + } + if (adl) { /* got an A-D-L? */ + if (*t != ':') { /* make sure syntax good */ + sprintf (tmp,"Unterminated at-domain-list: %.80s%.80s",adl,t); + MM_LOG (tmp,PARSE); + } + else string = ++t; /* continue parse from this point */ + } + + /* parse address spec */ + if (!(adr = rfc822_parse_addrspec (string,ret,defaulthost))) { + if (adl) fs_give ((void **) &adl); + return NIL; + } + if (adl) adr->adl = adl; /* have an A-D-L? */ + if (*ret) if (**ret == '>') { /* make sure terminated OK */ + ++*ret; /* skip past the broket */ + rfc822_skipws (ret); /* flush trailing WS */ + if (!**ret) *ret = NIL; /* wipe pointer if at end of string */ + return adr; /* return the address */ + } + sprintf (tmp,"Unterminated mailbox: %.80s@%.80s",adr->mailbox, + *adr->host == '@' ? "<null>" : adr->host); + MM_LOG (tmp,PARSE); + adr->next = mail_newaddr (); + adr->next->mailbox = cpystr ("MISSING_MAILBOX_TERMINATOR"); + adr->next->host = cpystr (errhst); + return adr; /* return the address */ +} + +/* Parse RFC 2822 address-spec + * Accepts: string pointer + * pointer to string pointer to update + * default host + * Returns: address + * + * Updates string pointer + */ + +ADDRESS *rfc822_parse_addrspec (char *string,char **ret,char *defaulthost) +{ + ADDRESS *adr; + char c,*s,*t,*v,*end; + if (!string) return NIL; /* no string */ + rfc822_skipws (&string); /* flush leading whitespace */ + if (!*string) return NIL; /* empty string */ + /* find end of mailbox */ + if (!(t = rfc822_parse_word (string,wspecials))) return NIL; + adr = mail_newaddr (); /* create address block */ + c = *t; /* remember delimiter */ + *t = '\0'; /* tie off mailbox */ + /* copy mailbox */ + adr->mailbox = rfc822_cpy (string); + *t = c; /* restore delimiter */ + end = t; /* remember end of mailbox */ + rfc822_skipws (&t); /* skip whitespace */ + while (*t == '.') { /* some cretin taking RFC 822 too seriously? */ + string = ++t; /* skip past the dot and any WS */ + rfc822_skipws (&string); + /* get next word of mailbox */ + if (t = rfc822_parse_word (string,wspecials)) { + end = t; /* remember new end of mailbox */ + c = *t; /* remember delimiter */ + *t = '\0'; /* tie off word */ + s = rfc822_cpy (string); /* copy successor part */ + *t = c; /* restore delimiter */ + /* build new mailbox */ + sprintf (v = (char *) fs_get (strlen (adr->mailbox) + strlen (s) + 2), + "%s.%s",adr->mailbox,s); + fs_give ((void **) &adr->mailbox); + adr->mailbox = v; /* new host name */ + rfc822_skipws (&t); /* skip WS after mailbox */ + } + else { /* barf */ + MM_LOG ("Invalid mailbox part after .",PARSE); + break; + } + } + t = end; /* remember delimiter in case no host */ + + rfc822_skipws (&end); /* sniff ahead at what follows */ +#if RFC733 /* RFC 733 used "at" instead of "@" */ + if (((*end == 'a') || (*end == 'A')) && + ((end[1] == 't') || (end[1] == 'T')) && + ((end[2] == ' ') || (end[2] == '\t') || (end[2] == '\015') || + (end[2] == '\012') || (end[2] == '('))) + *++end = '@'; +#endif + if (*end != '@') end = t; /* host name missing */ + /* otherwise parse host name */ + else if (!(adr->host = rfc822_parse_domain (++end,&end))) + adr->host = cpystr (errhst); + /* default host if missing */ + if (!adr->host) adr->host = cpystr (defaulthost); + /* try person name in comments if missing */ + if (end && !(adr->personal && *adr->personal)) { + while (*end == ' ') ++end; /* see if we can find a person name here */ + if ((*end == '(') && (s = rfc822_skip_comment (&end,LONGT)) && strlen (s)) + adr->personal = rfc822_cpy (s); + rfc822_skipws (&end); /* skip any other WS in the normal way */ + } + /* set return to end pointer */ + *ret = (end && *end) ? end : NIL; + return adr; /* return the address we got */ +} + +/* Parse RFC 2822 domain + * Accepts: string pointer + * pointer to return end of domain + * Returns: domain name or NIL if failure + */ + +char *rfc822_parse_domain (char *string,char **end) +{ + char *ret = NIL; + char c,*s,*t,*v; + rfc822_skipws (&string); /* skip whitespace */ + if (*string == '[') { /* domain literal? */ + if (!(*end = rfc822_parse_word (string + 1,"]\\"))) + MM_LOG ("Empty domain literal",PARSE); + else if (**end != ']') MM_LOG ("Unterminated domain literal",PARSE); + else { + size_t len = ++*end - string; + strncpy (ret = (char *) fs_get (len + 1),string,len); + ret[len] = '\0'; /* tie off literal */ + } + } + /* search for end of host */ + else if (t = rfc822_parse_word (string,wspecials)) { + c = *t; /* remember delimiter */ + *t = '\0'; /* tie off host */ + ret = rfc822_cpy (string); /* copy host */ + *t = c; /* restore delimiter */ + *end = t; /* remember end of domain */ + rfc822_skipws (&t); /* skip WS after host */ + while (*t == '.') { /* some cretin taking RFC 822 too seriously? */ + string = ++t; /* skip past the dot and any WS */ + rfc822_skipws (&string); + if (string = rfc822_parse_domain (string,&t)) { + *end = t; /* remember new end of domain */ + c = *t; /* remember delimiter */ + *t = '\0'; /* tie off host */ + s = rfc822_cpy (string);/* copy successor part */ + *t = c; /* restore delimiter */ + /* build new domain */ + sprintf (v = (char *) fs_get (strlen (ret) + strlen (s) + 2), + "%s.%s",ret,s); + fs_give ((void **) &ret); + ret = v; /* new host name */ + rfc822_skipws (&t); /* skip WS after domain */ + } + else { /* barf */ + MM_LOG ("Invalid domain part after .",PARSE); + break; + } + } + } + else MM_LOG ("Missing or invalid host name after @",PARSE); + return ret; +} + +/* Parse RFC 2822 phrase + * Accepts: string pointer + * Returns: pointer to end of phrase + */ + +char *rfc822_parse_phrase (char *s) +{ + char *curpos; + if (!s) return NIL; /* no-op if no string */ + /* find first word of phrase */ + curpos = rfc822_parse_word (s,NIL); + if (!curpos) return NIL; /* no words means no phrase */ + if (!*curpos) return curpos; /* check if string ends with word */ + s = curpos; /* sniff past the end of this word and WS */ + rfc822_skipws (&s); /* skip whitespace */ + /* recurse to see if any more */ + return (s = rfc822_parse_phrase (s)) ? s : curpos; +} + +/* Parse RFC 2822 word + * Accepts: string pointer + * delimiter (or NIL for phrase word parsing) + * Returns: pointer to end of word + */ + +char *rfc822_parse_word (char *s,const char *delimiters) +{ + char *st,*str; + if (!s) return NIL; /* no string */ + rfc822_skipws (&s); /* flush leading whitespace */ + if (!*s) return NIL; /* empty string */ + str = s; /* hunt pointer for strpbrk */ + while (T) { /* look for delimiter, return if none */ + if (!(st = strpbrk (str,delimiters ? delimiters : wspecials))) + return str + strlen (str); + /* ESC in phrase */ + if (!delimiters && (*st == I2C_ESC)) { + str = ++st; /* always skip past ESC */ + switch (*st) { /* special hack for RFC 1468 (ISO-2022-JP) */ + case I2C_MULTI: /* multi byte sequence */ + switch (*++st) { + case I2CS_94x94_JIS_OLD:/* old JIS (1978) */ + case I2CS_94x94_JIS_NEW:/* new JIS (1983) */ + str = ++st; /* skip past the shift to JIS */ + while (st = strchr (st,I2C_ESC)) + if ((*++st == I2C_G0_94) && ((st[1] == I2CS_94_ASCII) || + (st[1] == I2CS_94_JIS_ROMAN) || + (st[1] == I2CS_94_JIS_BUGROM))) { + str = st += 2; /* skip past the shift back to ASCII */ + break; + } + /* eats entire text if no shift back */ + if (!st || !*st) return str + strlen (str); + } + break; + case I2C_G0_94: /* single byte sequence */ + switch (st[1]) { + case I2CS_94_ASCII: /* shift to ASCII */ + case I2CS_94_JIS_ROMAN: /* shift to JIS-Roman */ + case I2CS_94_JIS_BUGROM:/* old buggy definition of JIS-Roman */ + str = st + 2; /* skip past the shift */ + break; + } + } + } + + else switch (*st) { /* dispatch based on delimiter */ + case '"': /* quoted string */ + /* look for close quote */ + while (*++st != '"') switch (*st) { + case '\0': /* unbalanced quoted string */ + return NIL; /* sick sick sick */ + case '\\': /* quoted character */ + if (!*++st) return NIL; /* skip the next character */ + default: /* ordinary character */ + break; /* no special action */ + } + str = ++st; /* continue parse */ + break; + case '\\': /* quoted character */ + /* This is wrong; a quoted-pair can not be part of a word. However, + * domain-literal is parsed as a word and quoted-pairs can be used + * *there*. Either way, it's pretty pathological. + */ + if (st[1]) { /* not on NUL though... */ + str = st + 2; /* skip quoted character and go on */ + break; + } + default: /* found a word delimiter */ + return (st == s) ? NIL : st; + } + } +} + +/* Copy an RFC 2822 format string + * Accepts: string + * Returns: copy of string + */ + +char *rfc822_cpy (char *src) +{ + /* copy and unquote */ + return rfc822_quote (cpystr (src)); +} + + +/* Unquote an RFC 2822 format string + * Accepts: string + * Returns: string + */ + +char *rfc822_quote (char *src) +{ + char *ret = src; + if (strpbrk (src,"\\\"")) { /* any quoting in string? */ + char *dst = ret; + while (*src) { /* copy string */ + if (*src == '\"') src++; /* skip double quote entirely */ + else { + if (*src == '\\') src++;/* skip over single quote, copy next always */ + *dst++ = *src++; /* copy character */ + } + } + *dst = '\0'; /* tie off string */ + } + return ret; /* return our string */ +} + + +/* Copy address list + * Accepts: address list + * Returns: address list + */ + +ADDRESS *rfc822_cpy_adr (ADDRESS *adr) +{ + ADDRESS *dadr; + ADDRESS *ret = NIL; + ADDRESS *prev = NIL; + while (adr) { /* loop while there's still an MAP adr */ + dadr = mail_newaddr (); /* instantiate a new address */ + if (!ret) ret = dadr; /* note return */ + if (prev) prev->next = dadr;/* tie on to the end of any previous */ + dadr->personal = cpystr (adr->personal); + dadr->adl = cpystr (adr->adl); + dadr->mailbox = cpystr (adr->mailbox); + dadr->host = cpystr (adr->host); + prev = dadr; /* this is now the previous */ + adr = adr->next; /* go to next address in list */ + } + return (ret); /* return the MTP address list */ +} + +/* Skips RFC 2822 whitespace + * Accepts: pointer to string pointer + */ + +void rfc822_skipws (char **s) +{ + while (T) switch (**s) { + case ' ': case '\t': case '\015': case '\012': + ++*s; /* skip all forms of LWSP */ + break; + case '(': /* start of comment */ + if (rfc822_skip_comment (s,(long) NIL)) break; + default: + return; /* end of whitespace */ + } +} + + +/* Skips RFC 2822 comment + * Accepts: pointer to string pointer + * trim flag + * Returns: pointer to first non-blank character of comment + */ + +char *rfc822_skip_comment (char **s,long trim) +{ + char *ret,tmp[MAILTMPLEN]; + char *s1 = *s; + char *t = NIL; + /* skip past whitespace */ + for (ret = ++s1; *ret == ' '; ret++); + do switch (*s1) { /* get character of comment */ + case '(': /* nested comment? */ + if (!rfc822_skip_comment (&s1,(long) NIL)) return NIL; + t = --s1; /* last significant char at end of comment */ + break; + case ')': /* end of comment? */ + *s = ++s1; /* skip past end of comment */ + if (trim) { /* if level 0, must trim */ + if (t) t[1] = '\0'; /* tie off comment string */ + else *ret = '\0'; /* empty comment */ + } + return ret; + case '\\': /* quote next character? */ + if (*++s1) { /* next character non-null? */ + t = s1; /* update last significant character pointer */ + break; /* all OK */ + } + case '\0': /* end of string */ + sprintf (tmp,"Unterminated comment: %.80s",*s); + MM_LOG (tmp,PARSE); + **s = '\0'; /* nuke duplicate messages in case reparse */ + return NIL; /* this is wierd if it happens */ + case ' ': /* whitespace isn't significant */ + break; + default: /* random character */ + t = s1; /* update last significant character pointer */ + break; + } while (s1++); + return NIL; /* impossible, but pacify lint et al */ +} + +/* Buffered output routines */ + + +/* Output character to buffer + * Accepts: buffer + * character to write + * Returns: T if success, NIL if error + */ + +static long rfc822_output_char (RFC822BUFFER *buf,int c) +{ + if ((buf->cur == buf->end) && !rfc822_output_flush (buf)) return NIL; + *buf->cur++ = c; /* add character, soutr buffer if full */ + return (buf->cur == buf->end) ? rfc822_output_flush (buf) : LONGT; +} + + +/* Output data to buffer + * Accepts: buffer + * data to write + * size of data + * Returns: T if success, NIL if error + */ + +static long rfc822_output_data (RFC822BUFFER *buf,char *string,long len) +{ + while (len) { /* until request satified */ + long i; + if (i = min (len,buf->end - buf->cur)) { + memcpy (buf->cur,string,i); + buf->cur += i; /* blat data */ + string += i; + len -= i; + } + /* soutr buffer now if full */ + if ((len || (buf->cur == buf->end)) && !rfc822_output_flush (buf)) + return NIL; + } + return LONGT; +} + +/* Output string to buffer + * Accepts: buffer + * string to write + * Returns: T if success, NIL if error + */ + +static long rfc822_output_string (RFC822BUFFER *buf,char *string) +{ + return rfc822_output_data (buf,string,strlen (string)); +} + + +/* Flush buffer + * Accepts: buffer + * I/O routine + * stream for I/O routine + * Returns: T if success, NIL if error + */ + +long rfc822_output_flush (RFC822BUFFER *buf) +{ + *buf->cur = '\0'; /* tie off buffer at this point */ + return (*buf->f) (buf->s,buf->cur = buf->beg); +} + +/* Message writing routines */ + + +/* Output RFC 822 message + * Accepts: temporary buffer as a SIZEDTEXT + * envelope + * body + * I/O routine + * stream for I/O routine + * non-zero if 8-bit output desired + * Returns: T if successful, NIL if failure + * + * This routine always uses standard specials for phrases and does not write + * bcc entries, since it is called from the SMTP and NNTP routines. If you + * need to do something different you need to arm an rfc822outfull_t and/or + * rfc822out_t function. + */ + +long rfc822_output_full (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,long ok8) +{ + rfc822outfull_t r822of = + (rfc822outfull_t) mail_parameters (NIL,GET_RFC822OUTPUTFULL,NIL); + rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL); + /* call external RFC 2822 output generator */ + if (r822of) return (*r822of) (buf,env,body,ok8); + else if (r822o) return (*r822o) (buf->cur,env,body,buf->f,buf->s,ok8); + /* encode body as necessary */ + if (ok8) rfc822_encode_body_8bit (env,body); + else rfc822_encode_body_7bit (env,body); + /* output header and body */ + return rfc822_output_header (buf,env,body,NIL,NIL) && + rfc822_output_text (buf,body) && rfc822_output_flush (buf); +} + +/* Output RFC 822 header + * Accepts: buffer + * envelope + * body + * non-standard specials to be used for phrases if non-NIL + * flags (non-zero to include bcc + * Returns: T if success, NIL if failure + */ + +long rfc822_output_header (RFC822BUFFER *buf,ENVELOPE *env,BODY *body, + const char *specials,long flags) +{ + long i = env->remail ? strlen (env->remail) : 0; + return /* write header */ + (!i || /* snip extra CRLF from remail header */ + rfc822_output_data (buf,env->remail, + ((i > 4) && (env->remail[i-4] == '\015')) ? + i - 2 : i)) && + rfc822_output_header_line (buf,"Newsgroups",i,env->newsgroups) && + rfc822_output_header_line (buf,"Date",i,env->date) && + rfc822_output_address_line (buf,"From",i,env->from,specials) && + rfc822_output_address_line (buf,"Sender",i,env->sender,specials) && + rfc822_output_address_line (buf,"Reply-To",i,env->reply_to,specials) && + rfc822_output_header_line (buf,"Subject",i,env->subject) && + ((env->bcc && !(env->to || env->cc)) ? + rfc822_output_string (buf,"To: undisclosed recipients: ;\015\012") : + LONGT) && + rfc822_output_address_line (buf,"To",i,env->to,specials) && + rfc822_output_address_line (buf,"cc",i,env->cc,specials) && + (flags ? rfc822_output_address_line (buf,"bcc",i,env->bcc,specials) : T) && + rfc822_output_header_line (buf,"In-Reply-To",i,env->in_reply_to) && + rfc822_output_header_line (buf,"Message-ID",i,env->message_id) && + rfc822_output_header_line (buf,"Followup-to",i,env->followup_to) && + rfc822_output_header_line (buf,"References",i,env->references) && + (env->remail || !body || + (rfc822_output_string (buf,"MIME-Version: 1.0\015\012") && + rfc822_output_body_header (buf,body))) && + /* write terminating blank line */ + rfc822_output_string (buf,"\015\012"); +} + +/* Output RFC 2822 header text line + * Accepts: buffer + * pointer to header type + * non-NIL if resending + * pointer to text + * Returns: T if success, NIL if failure + */ + +long rfc822_output_header_line (RFC822BUFFER *buf,char *type,long resent, + char *text) +{ + return !text || + ((resent ? rfc822_output_string (buf,resentprefix) : LONGT) && + rfc822_output_string (buf,type) && rfc822_output_string (buf,": ") && + rfc822_output_string (buf,text) && rfc822_output_string (buf,"\015\012")); +} + + +/* Output RFC 2822 header address line + * Accepts: buffer + * pointer to header type + * non-NIL if resending + * address(s) to interpret + * non-standard specials to be used for phrases if non-NIL + * Returns: T if success, NIL if failure + */ + +long rfc822_output_address_line (RFC822BUFFER *buf,char *type,long resent, + ADDRESS *adr,const char *specials) +{ + long pretty = strlen (type); + return !adr || + ((resent ? rfc822_output_string (buf,resentprefix) : LONGT) && + rfc822_output_data (buf,type,pretty) && rfc822_output_string (buf,": ") && + rfc822_output_address_list (buf,adr, + resent ? pretty + sizeof (RESENTPREFIX) - 1 : + pretty,specials) && + rfc822_output_string (buf,"\015\012")); +} + +/* Output RFC 2822 address list + * Accepts: buffer + * pointer to address list + * non-zero if pretty-printing + * non-standard specials to be used for phrases if non-NIL + * Returns: T if success, NIL if failure + */ + +long rfc822_output_address_list (RFC822BUFFER *buf,ADDRESS *adr,long pretty, + const char *specials) +{ + long n; + /* default to rspecials */ + if (!specials) specials = rspecials; + for (n = 0; adr; adr = adr->next) { + char *base = buf->cur; + if (adr->host) { /* ordinary address? */ + if (!(pretty && n)) { /* suppress if pretty and in group */ + if ( /* use phrase <route-addr> if phrase */ +#if RFC822 + adr->adl || /* or A-D-L */ +#endif + (adr->personal && *adr->personal)) { + if (!((adr->personal ? rfc822_output_cat (buf,adr->personal, + rspecials) : LONGT) && + rfc822_output_string (buf," <") && + rfc822_output_address (buf,adr) && + rfc822_output_string (buf,">"))) return NIL; + } + else if (!rfc822_output_address (buf,adr)) return NIL; + if (adr->next && adr->next->mailbox && + !rfc822_output_string (buf,", ")) return NIL; + } + } + else if (adr->mailbox) { /* start of group? */ + /* yes, write group */ + if (!(rfc822_output_cat (buf,adr->mailbox,rspecials) && + rfc822_output_string (buf,": "))) return NIL; + ++n; /* in a group now */ + } + else if (n) { /* must be end of group (but be paranoid) */ + if (!rfc822_output_char (buf,';') || + ((!--n && adr->next && adr->next->mailbox) && + !rfc822_output_string (buf,", "))) return NIL; + } + if (pretty && adr->next && /* pretty printing? */ + ((pretty += ((buf->cur > base) ? buf->cur - base : + (buf->end - base) + (buf->cur - buf->beg))) >= 78)) { + if (!(rfc822_output_string (buf,"\015\012") && + rfc822_output_string (buf,RFC822CONT))) return NIL; + base = buf->cur; /* update base for pretty printing */ + pretty = sizeof (RFC822CONT) - 1; + } + } + return LONGT; +} + +/* Write RFC 2822 route-address to string + * Accepts: buffer + * pointer to single address + * Returns: T if success, NIL if failure + */ + +long rfc822_output_address (RFC822BUFFER *buf,ADDRESS *adr) +{ + return !adr || !adr->host || + ( +#if RFC822 /* old code with A-D-L support */ + (!adr->adl || (rfc822_output_string (buf,adr->adl) && + rfc822_output_char (buf,':'))) && +#endif + rfc822_output_cat (buf,adr->mailbox,NIL) && + ((*adr->host == '@') || /* unless null host (HIGHLY discouraged!) */ + (rfc822_output_char (buf,'@') && + rfc822_output_cat (buf,adr->host,NIL)))); +} + + +/* Output RFC 2822 string with concatenation + * Accepts: buffer + * string to concatenate + * list of special characters or NIL for dot-atom format + * Returns: T if success, NIL if failure + */ + +long rfc822_output_cat (RFC822BUFFER *buf,char *src,const char *specials) +{ + char *s; + if (!*src || /* empty string or any specials present? */ + (specials ? (T && strpbrk (src,specials)) : + (strpbrk (src,wspecials) || (*src == '.') || strstr (src,"..") || + (src[strlen (src) - 1] == '.')))) { + /* yes, write as quoted string*/ + if (!rfc822_output_char (buf,'"')) return NIL; + /* embedded quote characters? */ + for (; s = strpbrk (src,"\\\""); src = s + 1) { + /* yes, insert quoting */ + if (!(rfc822_output_data (buf,src,s-src) && + rfc822_output_char (buf,'\\') && + rfc822_output_char (buf,*s))) return NIL; + } + /* return string and trailing quote*/ + return rfc822_output_string (buf,src) && rfc822_output_char (buf,'"'); + } + /* easy case */ + return rfc822_output_string (buf,src); +} + +/* Output MIME parameter list + * Accepts: buffer + * parameter list + * Returns: T if success, NIL if failure + */ + +long rfc822_output_parameter (RFC822BUFFER *buf,PARAMETER *param) +{ + while (param) { + if (rfc822_output_string (buf,"; ") && + rfc822_output_string (buf,param->attribute) && + rfc822_output_char (buf,'=') && + rfc822_output_cat (buf,param->value,tspecials)) param = param->next; + else return NIL; + } + return LONGT; +} + + +/* Output RFC 2822 stringlist + * Accepts: buffer + * stringlist + * Returns: T if success, NIL if failure + */ + +long rfc822_output_stringlist (RFC822BUFFER *buf,STRINGLIST *stl) +{ + while (stl) + if (!rfc822_output_cat (buf,(char *) stl->text.data,tspecials) || + ((stl = stl->next) && !rfc822_output_string (buf,", "))) + return NIL; + return LONGT; +} + +/* Output body content header + * Accepts: buffer + * body to interpret + * Returns: T if success, NIL if failure + */ + +long rfc822_output_body_header (RFC822BUFFER *buf,BODY *body) +{ + return /* type and subtype*/ + rfc822_output_string (buf,"Content-Type: ") && + rfc822_output_string (buf,body_types[body->type]) && + rfc822_output_char (buf,'/') && + rfc822_output_string (buf,body->subtype ? body->subtype : + rfc822_default_subtype (body->type)) && + /* parameters (w/ US-ASCII default */ + (body->parameter ? rfc822_output_parameter (buf,body->parameter) : + ((body->type != TYPETEXT) || + (rfc822_output_string (buf,"; CHARSET=") && + rfc822_output_string (buf,(body->encoding == ENC7BIT) ? + "US-ASCII" : "X-UNKNOWN")))) && + (!body->encoding || /* note: 7BIT never output as encoding! */ + (rfc822_output_string (buf,"\015\012Content-Transfer-Encoding: ") && + rfc822_output_string (buf,body_encodings[body->encoding]))) && + (!body->id || /* identification */ + (rfc822_output_string (buf,"\015\012Content-ID: ") && + rfc822_output_string (buf,body->id))) && + (!body->description || /* description */ + (rfc822_output_string (buf,"\015\012Content-Description: ") && + rfc822_output_string (buf,body->description))) && + (!body->md5 || /* MD5 checksum */ + (rfc822_output_string (buf,"\015\012Content-MD5: ") && + rfc822_output_string (buf,body->md5))) && + (!body->language || /* language */ + (rfc822_output_string (buf,"\015\012Content-Language: ") && + rfc822_output_stringlist (buf,body->language))) && + (!body->location || /* location */ + (rfc822_output_string (buf,"\015\012Content-Location: ") && + rfc822_output_string (buf,body->location))) && + (!body->disposition.type || /* disposition */ + (rfc822_output_string (buf,"\015\012Content-Disposition: ") && + rfc822_output_string (buf,body->disposition.type) && + rfc822_output_parameter (buf,body->disposition.parameter))) && + rfc822_output_string (buf,"\015\012"); +} + +/* Encode a body for 7BIT transmittal + * Accepts: envelope + * body + */ + +void rfc822_encode_body_7bit (ENVELOPE *env,BODY *body) +{ + void *f; + PART *part; + PARAMETER **param; + if (body) switch (body->type) { + case TYPEMULTIPART: /* multi-part */ + for (param = &body->parameter; + *param && strcmp ((*param)->attribute,"BOUNDARY"); + param = &(*param)->next); + if (!*param) { /* cookie not set up yet? */ + char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/ + sprintf (tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (), + (unsigned long) random (),(unsigned long) time (0), + (unsigned long) getpid ()); + (*param) = mail_newbody_parameter (); + (*param)->attribute = cpystr ("BOUNDARY"); + (*param)->value = cpystr (tmp); + } + part = body->nested.part; /* encode body parts */ + do rfc822_encode_body_7bit (env,&part->body); + while (part = part->next); /* until done */ + break; + case TYPEMESSAGE: /* encapsulated message */ + switch (body->encoding) { + case ENC7BIT: + break; + case ENC8BIT: + MM_LOG ("8-bit included message in 7-bit message body",PARSE); + break; + case ENCBINARY: + MM_LOG ("Binary included message in 7-bit message body",PARSE); + break; + default: + fatal ("Invalid rfc822_encode_body_7bit message encoding"); + } + break; /* can't change encoding */ + default: /* all else has some encoding */ + switch (body->encoding) { + case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */ + /* remember old 8-bit contents */ + f = (void *) body->contents.text.data; + body->contents.text.data = + rfc822_8bit (body->contents.text.data, + body->contents.text.size,&body->contents.text.size); + body->encoding = ENCQUOTEDPRINTABLE; + fs_give (&f); /* flush old binary contents */ + break; + case ENCBINARY: /* encode binary into BASE64 */ + /* remember old binary contents */ + f = (void *) body->contents.text.data; + body->contents.text.data = + rfc822_binary ((void *) body->contents.text.data, + body->contents.text.size,&body->contents.text.size); + body->encoding = ENCBASE64; + fs_give (&f); /* flush old binary contents */ + default: /* otherwise OK */ + break; + } + break; + } +} + +/* Encode a body for 8BIT transmittal + * Accepts: envelope + * body + */ + +void rfc822_encode_body_8bit (ENVELOPE *env,BODY *body) +{ + void *f; + PART *part; + PARAMETER **param; + if (body) switch (body->type) { + case TYPEMULTIPART: /* multi-part */ + for (param = &body->parameter; + *param && strcmp ((*param)->attribute,"BOUNDARY"); + param = &(*param)->next); + if (!*param) { /* cookie not set up yet? */ + char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/ + sprintf (tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (), + (unsigned long) random (),(unsigned long) time (0), + (unsigned long) getpid ()); + (*param) = mail_newbody_parameter (); + (*param)->attribute = cpystr ("BOUNDARY"); + (*param)->value = cpystr (tmp); + } + part = body->nested.part; /* encode body parts */ + do rfc822_encode_body_8bit (env,&part->body); + while (part = part->next); /* until done */ + break; + case TYPEMESSAGE: /* encapsulated message */ + switch (body->encoding) { + case ENC7BIT: + case ENC8BIT: + break; + case ENCBINARY: + MM_LOG ("Binary included message in 8-bit message body",PARSE); + break; + default: + fatal ("Invalid rfc822_encode_body_7bit message encoding"); + } + break; /* can't change encoding */ + default: /* other type, encode binary into BASE64 */ + if (body->encoding == ENCBINARY) { + /* remember old binary contents */ + f = (void *) body->contents.text.data; + body->contents.text.data = + rfc822_binary ((void *) body->contents.text.data, + body->contents.text.size,&body->contents.text.size); + body->encoding = ENCBASE64; + fs_give (&f); /* flush old binary contents */ + } + break; + } +} + +/* Output RFC 822 text + * Accepts: buffer + * body + * Returns: T if successful, NIL if failure + */ + +long rfc822_output_text (RFC822BUFFER *buf,BODY *body) +{ + /* MULTIPART gets special handling */ + if (body->type == TYPEMULTIPART) { + char *cookie,tmp[MAILTMPLEN]; + PARAMETER *param; + PART *part; + /* find cookie */ + for (param = body->parameter; param && strcmp (param->attribute,"BOUNDARY"); + param = param->next); + if (param) cookie = param->value; + else { /* make cookie not in BASE64 or QUOTEPRINT*/ + sprintf (cookie = tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (), + (unsigned long) random (),(unsigned long) time (0), + (unsigned long) getpid ()); + (param = mail_newbody_parameter ())->attribute = cpystr ("BOUNDARY"); + param->value = cpystr (tmp); + param->next = body->parameter; + body->parameter = param; + } + /* output each part */ + for (part = body->nested.part; part; part = part->next) + if (!(rfc822_output_string (buf,"--") && + rfc822_output_string (buf,cookie) && + rfc822_output_string (buf,"\015\012") && + rfc822_output_body_header (buf,&part->body) && + rfc822_output_string (buf,"\015\012") && + rfc822_output_text (buf,&part->body))) return NIL; + /* output trailing cookie */ + return rfc822_output_string (buf,"--") && + rfc822_output_string (buf,cookie) && + rfc822_output_string (buf,"--\015\012"); + } + /* output segment and trailing CRLF */ + return (!body->contents.text.data || + rfc822_output_string (buf,(char *) body->contents.text.data)) && + rfc822_output_string (buf,"\015\012"); +} + +/* Body contents encoding/decoding routines */ + + +/* Convert BASE64 contents to binary + * Accepts: source + * length of source + * pointer to return destination length + * Returns: destination as binary or NIL if error + */ + +#define WSP 0176 /* NUL, TAB, LF, FF, CR, SPC */ +#define JNK 0177 +#define PAD 0100 + +void *rfc822_base64 (unsigned char *src,unsigned long srcl,unsigned long *len) +{ + char c,*s,tmp[MAILTMPLEN]; + void *ret = fs_get ((size_t) ((*len = 4 + ((srcl * 3) / 4))) + 1); + char *d = (char *) ret; + int e; + static char decode[256] = { + WSP,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,WSP,WSP,JNK,WSP,WSP,JNK,JNK, + JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK, + WSP,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,076,JNK,JNK,JNK,077, + 064,065,066,067,070,071,072,073,074,075,JNK,JNK,JNK,PAD,JNK,JNK, + JNK,000,001,002,003,004,005,006,007,010,011,012,013,014,015,016, + 017,020,021,022,023,024,025,026,027,030,031,JNK,JNK,JNK,JNK,JNK, + JNK,032,033,034,035,036,037,040,041,042,043,044,045,046,047,050, + 051,052,053,054,055,056,057,060,061,062,063,JNK,JNK,JNK,JNK,JNK, + JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK, + JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK, + JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK, + JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK, + JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK, + JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK, + JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK, + JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK + }; + /* initialize block */ + memset (ret,0,((size_t) *len) + 1); + *len = 0; /* in case we return an error */ + + /* simple-minded decode */ + for (e = 0; srcl--; ) switch (c = decode[*src++]) { + default: /* valid BASE64 data character */ + switch (e++) { /* install based on quantum position */ + case 0: + *d = c << 2; /* byte 1: high 6 bits */ + break; + case 1: + *d++ |= c >> 4; /* byte 1: low 2 bits */ + *d = c << 4; /* byte 2: high 4 bits */ + break; + case 2: + *d++ |= c >> 2; /* byte 2: low 4 bits */ + *d = c << 6; /* byte 3: high 2 bits */ + break; + case 3: + *d++ |= c; /* byte 3: low 6 bits */ + e = 0; /* reinitialize mechanism */ + break; + } + break; + case WSP: /* whitespace */ + break; + case PAD: /* padding */ + switch (e++) { /* check quantum position */ + case 3: /* one = is good enough in quantum 3 */ + /* make sure no data characters in remainder */ + for (; srcl; --srcl) switch (decode[*src++]) { + /* ignore space, junk and extraneous padding */ + case WSP: case JNK: case PAD: + break; + default: /* valid BASE64 data character */ + /* This indicates bad MIME. One way that it can be caused is if + a single-section message was BASE64 encoded and then something + (e.g. a mailing list processor) appended text. The problem is + that in 1 out of 3 cases, there is no padding and hence no way + to detect the end of the data. Consequently, prudent software + will always encapsulate a BASE64 segment inside a MULTIPART. + */ + sprintf (tmp,"Possible data truncation in rfc822_base64(): %.80s", + (char *) src - 1); + if (s = strpbrk (tmp,"\015\012")) *s = NIL; + mm_log (tmp,PARSE); + srcl = 1; /* don't issue any more messages */ + break; + } + break; + case 2: /* expect a second = in quantum 2 */ + if (srcl && (*src == '=')) break; + default: /* impossible quantum position */ + fs_give (&ret); + return NIL; + } + break; + case JNK: /* junk character */ + fs_give (&ret); + return NIL; + } + *len = d - (char *) ret; /* calculate data length */ + *d = '\0'; /* NUL terminate just in case */ + return ret; /* return the string */ +} + +/* Convert binary contents to BASE64 + * Accepts: source + * length of source + * pointer to return destination length + * Returns: destination as BASE64 + */ + +unsigned char *rfc822_binary (void *src,unsigned long srcl,unsigned long *len) +{ + unsigned char *ret,*d; + unsigned char *s = (unsigned char *) src; + char *v = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + unsigned long i = ((srcl + 2) / 3) * 4; + *len = i += 2 * ((i / 60) + 1); + d = ret = (unsigned char *) fs_get ((size_t) ++i); + /* process tuplets */ + for (i = 0; srcl >= 3; s += 3, srcl -= 3) { + *d++ = v[s[0] >> 2]; /* byte 1: high 6 bits (1) */ + /* byte 2: low 2 bits (1), high 4 bits (2) */ + *d++ = v[((s[0] << 4) + (s[1] >> 4)) & 0x3f]; + /* byte 3: low 4 bits (2), high 2 bits (3) */ + *d++ = v[((s[1] << 2) + (s[2] >> 6)) & 0x3f]; + *d++ = v[s[2] & 0x3f]; /* byte 4: low 6 bits (3) */ + if ((++i) == 15) { /* output 60 characters? */ + i = 0; /* restart line break count, insert CRLF */ + *d++ = '\015'; *d++ = '\012'; + } + } + if (srcl) { + *d++ = v[s[0] >> 2]; /* byte 1: high 6 bits (1) */ + /* byte 2: low 2 bits (1), high 4 bits (2) */ + *d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f]; + /* byte 3: low 4 bits (2), high 2 bits (3) */ + *d++ = srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) : 0)) & 0x3f] : '='; + /* byte 4: low 6 bits (3) */ + *d++ = srcl ? v[s[2] & 0x3f] : '='; + if (srcl) srcl--; /* count third character if processed */ + if ((++i) == 15) { /* output 60 characters? */ + i = 0; /* restart line break count, insert CRLF */ + *d++ = '\015'; *d++ = '\012'; + } + } + *d++ = '\015'; *d++ = '\012'; /* insert final CRLF */ + *d = '\0'; /* tie off string */ + if (((unsigned long) (d - ret)) != *len) fatal ("rfc822_binary logic flaw"); + return ret; /* return the resulting string */ +} + +/* Convert QUOTED-PRINTABLE contents to 8BIT + * Accepts: source + * length of source + * pointer to return destination length + * Returns: destination as 8-bit text or NIL if error + */ + +unsigned char *rfc822_qprint (unsigned char *src,unsigned long srcl, + unsigned long *len) +{ + char tmp[MAILTMPLEN]; + unsigned int bogon = NIL; + unsigned char *ret = (unsigned char *) fs_get ((size_t) srcl + 1); + unsigned char *d = ret; + unsigned char *t = d; + unsigned char *s = src; + unsigned char c,e; + *len = 0; /* in case we return an error */ + /* until run out of characters */ + while (((unsigned long) (s - src)) < srcl) { + switch (c = *s++) { /* what type of character is it? */ + case '=': /* quoting character */ + if (((unsigned long) (s - src)) < srcl) switch (c = *s++) { + case '\0': /* end of data */ + s--; /* back up pointer */ + break; + case '\015': /* non-significant line break */ + if ((((unsigned long) (s - src)) < srcl) && (*s == '\012')) s++; + case '\012': /* bare LF */ + t = d; /* accept any leading spaces */ + break; + default: /* two hex digits then */ + if (!(isxdigit (c) && (((unsigned long) (s - src)) < srcl) && + (e = *s++) && isxdigit (e))) { + /* This indicates bad MIME. One way that it can be caused is if + a single-section message was QUOTED-PRINTABLE encoded and then + something (e.g. a mailing list processor) appended text. The + problem is that there is no way to determine where the encoded + data ended and the appended crud began. Consequently, prudent + software will always encapsulate a QUOTED-PRINTABLE segment + inside a MULTIPART. + */ + if (!bogon++) { /* only do this once */ + sprintf (tmp,"Invalid quoted-printable sequence: =%.80s", + (char *) s - 1); + mm_log (tmp,PARSE); + } + *d++ = '='; /* treat = as ordinary character */ + *d++ = c; /* and the character following */ + t = d; /* note point of non-space */ + break; + } + *d++ = hex2byte (c,e); /* merge the two hex digits */ + t = d; /* note point of non-space */ + break; + } + break; + case ' ': /* space, possibly bogus */ + *d++ = c; /* stash the space but don't update s */ + break; + case '\015': /* end of line */ + case '\012': /* bare LF */ + d = t; /* slide back to last non-space, drop in */ + default: + *d++ = c; /* stash the character */ + t = d; /* note point of non-space */ + } + } + *d = '\0'; /* tie off results */ + *len = d - ret; /* calculate length */ + return ret; /* return the string */ +} + +/* Convert 8BIT contents to QUOTED-PRINTABLE + * Accepts: source + * length of source + * pointer to return destination length + * Returns: destination as quoted-printable text + */ + +#define MAXL (size_t) 75 /* 76th position only used by continuation = */ + +unsigned char *rfc822_8bit (unsigned char *src,unsigned long srcl, + unsigned long *len) +{ + unsigned long lp = 0; + unsigned char *ret = (unsigned char *) + fs_get ((size_t) (3*srcl + 3*(((3*srcl)/MAXL) + 1))); + unsigned char *d = ret; + char *hex = "0123456789ABCDEF"; + unsigned char c; + while (srcl--) { /* for each character */ + /* true line break? */ + if (((c = *src++) == '\015') && (*src == '\012') && srcl) { + *d++ = '\015'; *d++ = *src++; srcl--; + lp = 0; /* reset line count */ + } + else { /* not a line break */ + /* quoting required? */ + if (iscntrl (c) || (c == 0x7f) || (c & 0x80) || (c == '=') || + ((c == ' ') && (*src == '\015'))) { + if ((lp += 3) > MAXL) { /* yes, would line overflow? */ + *d++ = '='; *d++ = '\015'; *d++ = '\012'; + lp = 3; /* set line count */ + } + *d++ = '='; /* quote character */ + *d++ = hex[c >> 4]; /* high order 4 bits */ + *d++ = hex[c & 0xf]; /* low order 4 bits */ + } + else { /* ordinary character */ + if ((++lp) > MAXL) { /* would line overflow? */ + *d++ = '='; *d++ = '\015'; *d++ = '\012'; + lp = 1; /* set line count */ + } + *d++ = c; /* ordinary character */ + } + } + } + *d = '\0'; /* tie off destination */ + *len = d - ret; /* calculate true size */ + /* try to give some space back */ + fs_resize ((void **) &ret,(size_t) *len + 1); + return ret; +} + +/* Legacy Routines */ + +/* + * WARNING: These routines are for compatibility with old software only. + * + * Their use in new software is to be avoided. + * + * These interfaces do not provide satisfactory buffer checking. In + * versions of c-client prior to imap-2005, they did not provide any + * buffer checking at all. + * + * As a half-hearted attempt, these new compatability functions for the + * legacy interfaces limit what they write to size SENDBUFLEN and will + * fatal() if more than that is written. However, that isn't good enough + * since several of these functions *append* to the buffer, and return an + * updated pointer. Consequently, there is no way of knowing what the + * actual available space is in the buffer, yet the function will still + * write up to SENDBUFLEN bytes even if there is much less space actually + * available. The result is a buffer overflow. + * + * You won't get a buffer overflow if you never attempt to append using + * these interfaces, but you can get the fatal() if it tries to write + * more than SENDBUFLEN bytes. + * + * To avoid this problem, use the corresponding rfc822_output_???() + * functions instead, e.g., rfc822_output_address() instead of + * rfc822_address(). + * + */ + +/* Flush routine, only called if overflow + * Accepts: stream + * string to output + * Returns: never + */ + +static long rfc822_legacy_soutr (void *stream,char *string) +{ + fatal ("rfc822.c legacy routine buffer overflow"); + return NIL; +} + +/* Legacy write RFC 2822 header from message structure + * Accepts: scratch buffer to write into + * message envelope + * message body + */ + +void rfc822_header (char *header,ENVELOPE *env,BODY *body) +{ + RFC822BUFFER buf; + /* write at start of buffer */ + buf.end = (buf.beg = buf.cur = header) + SENDBUFLEN - 1; + buf.f = rfc822_legacy_soutr; + buf.s = NIL; + rfc822_output_header (&buf,env,body,NIL,NIL); + *buf.cur = '\0'; /* tie off buffer */ +} + + +/* Legacy write RFC 2822 text from header line + * Accepts: pointer to destination string pointer + * pointer to header type + * message to interpret + * pointer to text + */ + +void rfc822_header_line (char **header,char *type,ENVELOPE *env,char *text) +{ + RFC822BUFFER buf; + /* append to buffer */ + buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1; + buf.f = rfc822_legacy_soutr; + buf.s = NIL; + rfc822_output_header_line (&buf,type,env->remail ? LONGT : NIL,text); + *(*header = buf.cur) = '\0'; /* tie off buffer */ +} + +/* Legacy write RFC 2822 address from header line + * Accepts: pointer to destination string pointer + * pointer to header type + * message to interpret + * address to interpret + */ + +void rfc822_address_line (char **header,char *type,ENVELOPE *env,ADDRESS *adr) +{ + RFC822BUFFER buf; + /* append to buffer */ + buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1; + buf.f = rfc822_legacy_soutr; + buf.s = NIL; + rfc822_output_address_line (&buf,type,env->remail ? LONGT : NIL,adr,NIL); + *(*header = buf.cur) = '\0'; /* tie off buffer */ +} + + +/* Legacy write RFC 2822 address list + * Accepts: pointer to destination string + * address to interpret + * header base if pretty-printing + * Returns: end of destination string + */ + +char *rfc822_write_address_full (char *dest,ADDRESS *adr,char *base) +{ + RFC822BUFFER buf; + /* append to buffer */ + buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1; + buf.f = rfc822_legacy_soutr; + buf.s = NIL; + rfc822_output_address_list (&buf,adr,base ? dest - base : 0,NIL); + *buf.cur = '\0'; /* tie off buffer */ + return buf.cur; +} + + +/* Legacy write RFC 2822 route-address to string + * Accepts: pointer to destination string + * address to interpret + */ + +void rfc822_address (char *dest,ADDRESS *adr) +{ + RFC822BUFFER buf; + /* append to buffer */ + buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1; + buf.f = rfc822_legacy_soutr; + buf.s = NIL; + rfc822_output_address (&buf,adr); + *buf.cur = '\0'; /* tie off buffer */ +} + +/* Concatenate RFC 2822 string + * Accepts: pointer to destination string + * pointer to string to concatenate + * list of special characters or NIL for dot-atom format + */ + +void rfc822_cat (char *dest,char *src,const char *specials) +{ + RFC822BUFFER buf; + /* append to buffer */ + buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1; + buf.f = rfc822_legacy_soutr; + buf.s = NIL; + rfc822_output_cat (&buf,src,specials); + *buf.cur = '\0'; /* tie off buffer */ +} + + +/* Legacy write body content header + * Accepts: pointer to destination string pointer + * pointer to body to interpret + */ + +void rfc822_write_body_header (char **dst,BODY *body) +{ + RFC822BUFFER buf; + /* append to buffer */ + buf.end = (buf.beg = buf.cur = *dst + strlen (*dst)) + SENDBUFLEN - 1; + buf.f = rfc822_legacy_soutr; + buf.s = NIL; + rfc822_output_body_header (&buf,body); + *(*dst = buf.cur) = '\0'; /* tie off buffer */ +} + +/* Legacy output RFC 822 message + * Accepts: temporary buffer + * envelope + * body + * I/O routine + * stream for I/O routine + * non-zero if 8-bit output desired + * Returns: T if successful, NIL if failure + */ + +long rfc822_output (char *t,ENVELOPE *env,BODY *body,soutr_t f,void *s, + long ok8bit) +{ + long ret; + rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL); + /* call external RFC 2822 output generator */ + if (r822o) ret = (*r822o) (t,env,body,f,s,ok8bit); + else { /* output generator not armed */ + RFC822BUFFER buf; /* use our own buffer rather than trust */ + char tmp[SENDBUFLEN+1]; /* client to give us a big enough one */ + buf.f = f; + buf.s = s; + buf.end = (buf.beg = buf.cur = t) + SENDBUFLEN - 1; + tmp[SENDBUFLEN] = '\0'; /* must have additional guard byte */ + ret = rfc822_output_full (&buf,env,body,ok8bit); + } + return ret; +} + + +/* Legacy output RFC 822 body + * Accepts: body + * I/O routine + * stream for I/O routine + * Returns: T if successful, NIL if failure + */ + +long rfc822_output_body (BODY *body,soutr_t f,void *s) +{ + RFC822BUFFER buf; + char tmp[SENDBUFLEN+1]; + buf.f = f; + buf.s = s; + buf.end = (buf.beg = buf.cur = tmp) + SENDBUFLEN; + tmp[SENDBUFLEN] = '\0'; /* must have additional guard byte */ + return rfc822_output_text (&buf,body) && rfc822_output_flush (&buf); +} diff --git a/imap/src/c-client/rfc822.h b/imap/src/c-client/rfc822.h new file mode 100644 index 00000000..0621732a --- /dev/null +++ b/imap/src/c-client/rfc822.h @@ -0,0 +1,127 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: RFC 2822 and MIME routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 27 July 1988 + * Last Edited: 30 August 2006 + * + * This original version of this file is + * Copyright 1988 Stanford University + * and was developed in the Symbolic Systems Resources Group of the Knowledge + * Systems Laboratory at Stanford University in 1987-88, and was funded by the + * Biomedical Research Technology Program of the NationalInstitutes of Health + * under grant number RR-00785. + */ + +#define MAXGROUPDEPTH 50 /* RFC [2]822 doesn't allow any group nesting */ +#define MAXMIMEDEPTH 50 /* more than any sane MIMEgram */ + +/* Output buffering for RFC [2]822 */ + +typedef long (*soutr_t) (void *stream,char *string); +typedef long (*rfc822out_t) (char *tmp,ENVELOPE *env,BODY *body,soutr_t f, + void *s,long ok8bit); + +typedef struct rfc822buffer { + soutr_t f; /* I/O flush routine */ + void *s; /* stream for I/O routine */ + char *beg; /* start of buffer */ + char *cur; /* current buffer pointer */ + char *end; /* end of buffer */ +} RFC822BUFFER; + +typedef long (*rfc822outfull_t) (RFC822BUFFER *buf,ENVELOPE *env,BODY *body, + long ok8bit); + +/* Function prototypes */ + +char *rfc822_default_subtype (unsigned short type); +void rfc822_parse_msg_full (ENVELOPE **en,BODY **bdy,char *s,unsigned long i, + STRING *bs,char *host,unsigned long depth, + unsigned long flags); +void rfc822_parse_content (BODY *body,STRING *bs,char *h,unsigned long depth, + unsigned long flags); +void rfc822_parse_content_header (BODY *body,char *name,char *s); +void rfc822_parse_parameter (PARAMETER **par,char *text); +void rfc822_parse_adrlist (ADDRESS **lst,char *string,char *host); +ADDRESS *rfc822_parse_address (ADDRESS **lst,ADDRESS *last,char **string, + char *defaulthost,unsigned long depth); +ADDRESS *rfc822_parse_group (ADDRESS **lst,ADDRESS *last,char **string, + char *defaulthost,unsigned long depth); +ADDRESS *rfc822_parse_mailbox (char **string,char *defaulthost); +long rfc822_phraseonly (char *end); +ADDRESS *rfc822_parse_routeaddr (char *string,char **ret,char *defaulthost); +ADDRESS *rfc822_parse_addrspec (char *string,char **ret,char *defaulthost); +char *rfc822_parse_domain (char *string,char **end); +char *rfc822_parse_phrase (char *string); +char *rfc822_parse_word (char *string,const char *delimiters); +char *rfc822_cpy (char *src); +char *rfc822_quote (char *src); +ADDRESS *rfc822_cpy_adr (ADDRESS *adr); +void rfc822_skipws (char **s); +char *rfc822_skip_comment (char **s,long trim); + +long rfc822_output_full (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,long ok8); +long rfc822_output_flush (RFC822BUFFER *buf); +long rfc822_output_header (RFC822BUFFER *buf,ENVELOPE *env,BODY *body, + const char *specials,long flags); +long rfc822_output_header_line (RFC822BUFFER *buf,char *type,long resent, + char *text); +long rfc822_output_address_line (RFC822BUFFER *buf,char *type,long resent, + ADDRESS *adr,const char *specials); +long rfc822_output_address_list (RFC822BUFFER *buf,ADDRESS *adr,long pretty, + const char *specials); +long rfc822_output_address (RFC822BUFFER *buf,ADDRESS *adr); +long rfc822_output_cat (RFC822BUFFER *buf,char *src,const char *specials); +long rfc822_output_parameter (RFC822BUFFER *buf,PARAMETER *param); +long rfc822_output_stringlist (RFC822BUFFER *buf,STRINGLIST *stl); +long rfc822_output_body_header (RFC822BUFFER *buf,BODY *body); +void rfc822_encode_body_7bit (ENVELOPE *env,BODY *body); +void rfc822_encode_body_8bit (ENVELOPE *env,BODY *body); +long rfc822_output_text (RFC822BUFFER *buf,BODY *body); +void *rfc822_base64 (unsigned char *src,unsigned long srcl,unsigned long *len); +unsigned char *rfc822_binary (void *src,unsigned long srcl,unsigned long *len); +unsigned char *rfc822_qprint (unsigned char *src,unsigned long srcl, + unsigned long *len); +unsigned char *rfc822_8bit (unsigned char *src,unsigned long srcl, + unsigned long *len); + +/* Legacy routines for compatibility with the past */ + +void rfc822_header (char *header,ENVELOPE *env,BODY *body); +void rfc822_header_line (char **header,char *type,ENVELOPE *env,char *text); +void rfc822_address_line (char **header,char *type,ENVELOPE *env,ADDRESS *adr); +char *rfc822_write_address_full (char *dest,ADDRESS *adr,char *base); +void rfc822_address (char *dest,ADDRESS *adr); +void rfc822_cat (char *dest,char *src,const char *specials); +void rfc822_write_body_header (char **header,BODY *body); +long rfc822_output (char *t,ENVELOPE *env,BODY *body,soutr_t f,void *s, + long ok8bit); +long rfc822_output_body (BODY *body,soutr_t f,void *s); + + +#define rfc822_write_address(dest,adr) \ + rfc822_write_address_full (dest,adr,NIL) + +#define rfc822_parse_msg(en,bdy,s,i,bs,host,flags) \ + rfc822_parse_msg_full (en,bdy,s,i,bs,host,0,flags) diff --git a/imap/src/c-client/smanager.c b/imap/src/c-client/smanager.c new file mode 100644 index 00000000..cee659ff --- /dev/null +++ b/imap/src/c-client/smanager.c @@ -0,0 +1,129 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Subscription Manager + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 3 December 1992 + * Last Edited: 6 December 2006 + */ + + +#include <stdio.h> +#include <ctype.h> +#include "c-client.h" + +/* Subscribe to mailbox + * Accepts: mailbox name + * Returns: T on success, NIL on failure + */ + +long sm_subscribe (char *mailbox) +{ + FILE *f; + char *s,db[MAILTMPLEN],tmp[MAILTMPLEN]; + /* canonicalize INBOX */ + if (!compare_cstring (mailbox,"INBOX")) mailbox = "INBOX"; + SUBSCRIPTIONFILE (db); /* open subscription database */ + if (f = fopen (db,"r")) { /* make sure not already there */ + while (fgets (tmp,MAILTMPLEN,f)) { + if (s = strchr (tmp,'\n')) *s = '\0'; + if (!strcmp (tmp,mailbox)) {/* already subscribed? */ + sprintf (tmp,"Already subscribed to mailbox %.80s",mailbox); + MM_LOG (tmp,ERROR); + fclose (f); + return NIL; + } + } + fclose (f); + } + if (!(f = fopen (db,"a"))) { /* append new entry */ + MM_LOG ("Can't append to subscription database",ERROR); + return NIL; + } + fprintf (f,"%s\n",mailbox); + return (fclose (f) == EOF) ? NIL : T; +} + +/* Unsubscribe from mailbox + * Accepts: mailbox name + * Returns: T on success, NIL on failure + */ + +long sm_unsubscribe (char *mailbox) +{ + FILE *f,*tf; + char *s,tmp[MAILTMPLEN],old[MAILTMPLEN],newname[MAILTMPLEN]; + int found = NIL; + /* canonicalize INBOX */ + if (!compare_cstring (mailbox,"INBOX")) mailbox = "INBOX"; + SUBSCRIPTIONFILE (old); /* make file names */ + SUBSCRIPTIONTEMP (newname); + if (!(f = fopen (old,"r"))) /* open subscription database */ + MM_LOG ("No subscriptions",ERROR); + else if (!(tf = fopen (newname,"w"))) { + MM_LOG ("Can't create subscription temporary file",ERROR); + fclose (f); + } + else { + while (fgets (tmp,MAILTMPLEN,f)) { + if (s = strchr (tmp,'\n')) *s = '\0'; + if (strcmp (tmp,mailbox)) fprintf (tf,"%s\n",tmp); + else found = T; /* found the name */ + } + fclose (f); + if (fclose (tf) == EOF) + MM_LOG ("Can't write subscription temporary file",ERROR); + else if (!found) { + sprintf (tmp,"Not subscribed to mailbox %.80s",mailbox); + MM_LOG (tmp,ERROR); /* error if at end */ + } + else if (!unlink (old) && !rename (newname,old)) return LONGT; + else MM_LOG ("Can't update subscription database",ERROR); + } + return NIL; +} + +/* Read subscription database + * Accepts: pointer to subscription database handle (handle NIL if first time) + * Returns: character string for subscription database or NIL if done + */ + +static char sbname[MAILTMPLEN]; + +char *sm_read (void **sdb) +{ + FILE *f = (FILE *) *sdb; + char *s; + if (!f) { /* first time through? */ + SUBSCRIPTIONFILE (sbname); /* open subscription database */ + /* make sure not already there */ + if (f = fopen (sbname,"r")) *sdb = (void *) f; + else return NIL; + } + if (fgets (sbname,MAILTMPLEN,f)) { + if (s = strchr (sbname,'\n')) *s = '\0'; + return sbname; + } + fclose (f); /* all done */ + *sdb = NIL; /* zap sdb */ + return NIL; +} diff --git a/imap/src/c-client/smtp.c b/imap/src/c-client/smtp.c new file mode 100644 index 00000000..0a3cf489 --- /dev/null +++ b/imap/src/c-client/smtp.c @@ -0,0 +1,793 @@ +/* ======================================================================== + * Copyright 1988-2008 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Simple Mail Transfer Protocol (SMTP) routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 27 July 1988 + * Last Edited: 28 January 2008 + * + * This original version of this file is + * Copyright 1988 Stanford University + * and was developed in the Symbolic Systems Resources Group of the Knowledge + * Systems Laboratory at Stanford University in 1987-88, and was funded by the + * Biomedical Research Technology Program of the National Institutes of Health + * under grant number RR-00785. + */ + + +#include <ctype.h> +#include <stdio.h> +#include "c-client.h" + +/* Constants */ + +#define SMTPSSLPORT (long) 465 /* former assigned SSL TCP contact port */ +#define SMTPGREET (long) 220 /* SMTP successful greeting */ +#define SMTPAUTHED (long) 235 /* SMTP successful authentication */ +#define SMTPOK (long) 250 /* SMTP OK code */ +#define SMTPAUTHREADY (long) 334/* SMTP ready for authentication */ +#define SMTPREADY (long) 354 /* SMTP ready for data */ +#define SMTPSOFTFATAL (long) 421/* SMTP soft fatal code */ +#define SMTPWANTAUTH (long) 505 /* SMTP authentication needed */ +#define SMTPWANTAUTH2 (long) 530/* SMTP authentication needed */ +#define SMTPUNAVAIL (long) 550 /* SMTP mailbox unavailable */ +#define SMTPHARDERROR (long) 554/* SMTP miscellaneous hard failure */ + + +/* Convenient access to protocol-specific data */ + +#define ESMTP stream->protocol.esmtp + + +/* Function prototypes */ + +void *smtp_challenge (void *s,unsigned long *len); +long smtp_response (void *s,char *response,unsigned long size); +long smtp_auth (SENDSTREAM *stream,NETMBX *mb,char *tmp); +long smtp_rcpt (SENDSTREAM *stream,ADDRESS *adr,long *error); +long smtp_send (SENDSTREAM *stream,char *command,char *args); +long smtp_reply (SENDSTREAM *stream); +long smtp_ehlo (SENDSTREAM *stream,char *host,NETMBX *mb); +long smtp_fake (SENDSTREAM *stream,char *text); +static long smtp_seterror (SENDSTREAM *stream,long code,char *text); +long smtp_soutr (void *stream,char *s); + +/* Mailer parameters */ + +static unsigned long smtp_maxlogintrials = MAXLOGINTRIALS; +static long smtp_port = 0; /* default port override */ +static long smtp_sslport = 0; + + +#ifndef RFC2821 +#define RFC2821 /* RFC 2821 compliance */ +#endif + +/* SMTP limits, current as of RFC 2821 */ + +#define SMTPMAXLOCALPART 64 +#define SMTPMAXDOMAIN 255 +#define SMTPMAXPATH 256 + + +/* I have seen local parts of more than 64 octets, in spite of the SMTP + * limits. So, we'll have a more generous limit that's still guaranteed + * not to pop the buffer, and let the server worry about it. As of this + * writing, it comes out to 240. Anyone with a mailbox name larger than + * that is in serious need of a life or at least a new ISP! 23 June 1998 + */ + +#define MAXLOCALPART ((MAILTMPLEN - (SMTPMAXDOMAIN + SMTPMAXPATH + 32)) / 2) + +/* Mail Transfer Protocol manipulate driver parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *smtp_parameters (long function,void *value) +{ + switch ((int) function) { + case SET_MAXLOGINTRIALS: + smtp_maxlogintrials = (unsigned long) value; + break; + case GET_MAXLOGINTRIALS: + value = (void *) smtp_maxlogintrials; + break; + case SET_SMTPPORT: + smtp_port = (long) value; + break; + case GET_SMTPPORT: + value = (void *) smtp_port; + break; + case SET_SSLSMTPPORT: + smtp_sslport = (long) value; + break; + case GET_SSLSMTPPORT: + value = (void *) smtp_sslport; + break; + default: + value = NIL; /* error case */ + break; + } + return value; +} + +/* Mail Transfer Protocol open connection + * Accepts: network driver + * service host list + * port number + * service name + * SMTP open options + * Returns: SEND stream on success, NIL on failure + */ + +SENDSTREAM *smtp_open_full (NETDRIVER *dv,char **hostlist,char *service, + unsigned long port,long options) +{ + SENDSTREAM *stream = NIL; + long reply; + char *s,tmp[MAILTMPLEN]; + NETSTREAM *netstream; + NETMBX mb; + if (!(hostlist && *hostlist)) mm_log ("Missing SMTP service host",ERROR); + /* maximum domain name is 64 characters */ + else do if (strlen (*hostlist) < SMTPMAXDOMAIN) { + sprintf (tmp,"{%.1000s}",*hostlist); + if (!mail_valid_net_parse_work (tmp,&mb,service ? service : "smtp") || + mb.anoflag || mb.readonlyflag) { + sprintf (tmp,"Invalid host specifier: %.80s",*hostlist); + mm_log (tmp,ERROR); + } + else { /* light tryssl flag if requested */ + mb.trysslflag = (options & SOP_TRYSSL) ? T : NIL; + /* explicit port overrides all */ + if (mb.port) port = mb.port; + /* else /submit overrides port argument */ + else if (!compare_cstring (mb.service,"submit")) { + port = SUBMITTCPPORT; /* override port, use IANA name */ + strcpy (mb.service,"submission"); + } + /* else port argument overrides SMTP port */ + else if (!port) port = smtp_port ? smtp_port : SMTPTCPPORT; + if (netstream = /* try to open ordinary connection */ + net_open (&mb,dv,port, + (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL), + "*smtps",smtp_sslport ? smtp_sslport : SMTPSSLPORT)) { + stream = (SENDSTREAM *) memset (fs_get (sizeof (SENDSTREAM)),0, + sizeof (SENDSTREAM)); + stream->netstream = netstream; + stream->host = cpystr ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ? + net_host (netstream) : mb.host); + stream->debug = (mb.dbgflag || (options & OP_DEBUG)) ? T : NIL; + if (options & SOP_SECURE) mb.secflag = T; + /* get name of local host to use */ + s = compare_cstring ("localhost",mb.host) ? + net_localhost (netstream) : "localhost"; + + do reply = smtp_reply (stream); + while ((reply < 100) || (stream->reply[3] == '-')); + if (reply != SMTPGREET){/* get SMTP greeting */ + sprintf (tmp,"SMTP greeting failure: %.80s",stream->reply); + mm_log (tmp,ERROR); + stream = smtp_close (stream); + } + /* try EHLO first, then HELO */ + else if (((reply = smtp_ehlo (stream,s,&mb)) != SMTPOK) && + ((reply = smtp_send (stream,"HELO",s)) != SMTPOK)) { + sprintf (tmp,"SMTP hello failure: %.80s",stream->reply); + mm_log (tmp,ERROR); + stream = smtp_close (stream); + } + else { + NETDRIVER *ssld =(NETDRIVER *)mail_parameters(NIL,GET_SSLDRIVER,NIL); + sslstart_t stls = (sslstart_t) mail_parameters(NIL,GET_SSLSTART,NIL); + ESMTP.ok = T; /* ESMTP server, start TLS if present */ + if (!dv && stls && ESMTP.service.starttls && + !mb.sslflag && !mb.notlsflag && + (smtp_send (stream,"STARTTLS",NIL) == SMTPGREET)) { + mb.tlsflag = T; /* TLS OK, get into TLS at this end */ + stream->netstream->dtb = ssld; + /* TLS started, negotiate it */ + if (!(stream->netstream->stream = (*stls) + (stream->netstream->stream,mb.host, + (mb.tlssslv23 ? NIL : NET_TLSCLIENT) | + (mb.novalidate ? NET_NOVALIDATECERT:NIL)))){ + /* TLS negotiation failed after STARTTLS */ + sprintf (tmp,"Unable to negotiate TLS with this server: %.80s", + mb.host); + mm_log (tmp,ERROR); + /* close without doing QUIT */ + if (stream->netstream) net_close (stream->netstream); + stream->netstream = NIL; + stream = smtp_close (stream); + } + /* TLS OK, re-negotiate EHLO */ + else if ((reply = smtp_ehlo (stream,s,&mb)) != SMTPOK) { + sprintf (tmp,"SMTP EHLO failure after STARTTLS: %.80s", + stream->reply); + mm_log (tmp,ERROR); + stream = smtp_close (stream); + } + else ESMTP.ok = T; /* TLS OK and EHLO successful */ + } + else if (mb.tlsflag) {/* user specified /tls but can't do it */ + sprintf (tmp,"TLS unavailable with this server: %.80s",mb.host); + mm_log (tmp,ERROR); + stream = smtp_close (stream); + } + + /* remote name for authentication */ + if (stream && ((mb.secflag || mb.user[0]))) { + if (ESMTP.auth) { /* use authenticator? */ + if ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL)) { + /* remote name for authentication */ + strncpy (mb.host, + (long) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ? + net_remotehost (netstream) : net_host (netstream), + NETMAXHOST-1); + mb.host[NETMAXHOST-1] = '\0'; + } + if (!smtp_auth (stream,&mb,tmp)) stream = smtp_close (stream); + } + else { /* no available authenticators? */ + sprintf (tmp,"%sSMTP authentication not available: %.80s", + mb.secflag ? "Secure " : "",mb.host); + mm_log (tmp,ERROR); + stream = smtp_close (stream); + } + } + } + } + } + } while (!stream && *++hostlist); + if (stream) { /* set stream options if have a stream */ + if (options &(SOP_DSN | SOP_DSN_NOTIFY_FAILURE | SOP_DSN_NOTIFY_DELAY | + SOP_DSN_NOTIFY_SUCCESS | SOP_DSN_RETURN_FULL)) { + ESMTP.dsn.want = T; + if (options & SOP_DSN_NOTIFY_FAILURE) ESMTP.dsn.notify.failure = T; + if (options & SOP_DSN_NOTIFY_DELAY) ESMTP.dsn.notify.delay = T; + if (options & SOP_DSN_NOTIFY_SUCCESS) ESMTP.dsn.notify.success = T; + if (options & SOP_DSN_RETURN_FULL) ESMTP.dsn.full = T; + } + if (options & SOP_8BITMIME) ESMTP.eightbit.want = T; + } + return stream; +} + +/* SMTP authenticate + * Accepts: stream to login + * parsed network mailbox structure + * scratch buffer + * place to return user name + * Returns: T on success, NIL on failure + */ + +long smtp_auth (SENDSTREAM *stream,NETMBX *mb,char *tmp) +{ + unsigned long trial,auths; + char *lsterr = NIL; + char usr[MAILTMPLEN]; + AUTHENTICATOR *at; + long ret = NIL; + for (auths = ESMTP.auth, stream->saslcancel = NIL; + !ret && stream->netstream && auths && + (at = mail_lookup_auth (find_rightmost_bit (&auths) + 1)); ) { + if (lsterr) { /* previous authenticator failed? */ + sprintf (tmp,"Retrying using %s authentication after %.80s", + at->name,lsterr); + mm_log (tmp,NIL); + fs_give ((void **) &lsterr); + } + trial = 0; /* initial trial count */ + tmp[0] = '\0'; /* empty buffer */ + if (stream->netstream) do { + if (lsterr) { + sprintf (tmp,"Retrying %s authentication after %.80s",at->name,lsterr); + mm_log (tmp,WARN); + fs_give ((void **) &lsterr); + } + stream->saslcancel = NIL; + if (smtp_send (stream,"AUTH",at->name) == SMTPAUTHREADY) { + /* hide client authentication responses */ + if (!(at->flags & AU_SECURE)) stream->sensitive = T; + if ((*at->client) (smtp_challenge,smtp_response,"smtp",mb,stream, + &trial,usr)) { + if (stream->replycode == SMTPAUTHED) { + ESMTP.auth = NIL; /* disable authenticators */ + ret = LONGT; + } + /* if main program requested cancellation */ + else if (!trial) mm_log ("SMTP Authentication cancelled",ERROR); + } + stream->sensitive = NIL;/* unhide */ + } + /* remember response if error and no cancel */ + if (!ret && trial) lsterr = cpystr (stream->reply); + } while (!ret && stream->netstream && trial && + (trial < smtp_maxlogintrials)); + } + if (lsterr) { /* previous authenticator failed? */ + if (!stream->saslcancel) { /* don't do this if a cancel */ + sprintf (tmp,"Can not authenticate to SMTP server: %.80s",lsterr); + mm_log (tmp,ERROR); + } + fs_give ((void **) &lsterr); + } + return ret; /* authentication failed */ +} + +/* Get challenge to authenticator in binary + * Accepts: stream + * pointer to returned size + * Returns: challenge or NIL if not challenge + */ + +void *smtp_challenge (void *s,unsigned long *len) +{ + char tmp[MAILTMPLEN]; + void *ret = NIL; + SENDSTREAM *stream = (SENDSTREAM *) s; + if ((stream->replycode == SMTPAUTHREADY) && + !(ret = rfc822_base64 ((unsigned char *) stream->reply + 4, + strlen (stream->reply + 4),len))) { + sprintf (tmp,"SMTP SERVER BUG (invalid challenge): %.80s",stream->reply+4); + mm_log (tmp,ERROR); + } + return ret; +} + + +/* Send authenticator response in BASE64 + * Accepts: MAIL stream + * string to send + * length of string + * Returns: T, always + */ + +long smtp_response (void *s,char *response,unsigned long size) +{ + SENDSTREAM *stream = (SENDSTREAM *) s; + unsigned long i,j; + char *t,*u; + if (response) { /* make CRLFless BASE64 string */ + if (size) { + for (t = (char *) rfc822_binary ((void *) response,size,&i),u = t,j = 0; + j < i; j++) if (t[j] > ' ') *u++ = t[j]; + *u = '\0'; /* tie off string */ + i = smtp_send (stream,t,NIL); + fs_give ((void **) &t); + } + else i = smtp_send (stream,"",NIL); + } + else { /* abort requested */ + i = smtp_send (stream,"*",NIL); + stream->saslcancel = T; /* mark protocol-requested SASL cancel */ + } + return LONGT; +} + +/* Mail Transfer Protocol close connection + * Accepts: SEND stream + * Returns: NIL always + */ + +SENDSTREAM *smtp_close (SENDSTREAM *stream) +{ + if (stream) { /* send "QUIT" */ + if (stream->netstream) { /* do close actions if have netstream */ + smtp_send (stream,"QUIT",NIL); + if (stream->netstream) /* could have been closed during "QUIT" */ + net_close (stream->netstream); + } + /* clean up */ + if (stream->host) fs_give ((void **) &stream->host); + if (stream->reply) fs_give ((void **) &stream->reply); + if (ESMTP.dsn.envid) fs_give ((void **) &ESMTP.dsn.envid); + if (ESMTP.atrn.domains) fs_give ((void **) &ESMTP.atrn.domains); + fs_give ((void **) &stream);/* flush the stream */ + } + return NIL; +} + +/* Mail Transfer Protocol deliver mail + * Accepts: SEND stream + * delivery option (MAIL, SEND, SAML, SOML) + * message envelope + * message body + * Returns: T on success, NIL on failure + */ + +long smtp_mail (SENDSTREAM *stream,char *type,ENVELOPE *env,BODY *body) +{ + RFC822BUFFER buf; + char tmp[SENDBUFLEN+1]; + long error = NIL; + long retry = NIL; + buf.f = smtp_soutr; /* initialize buffer */ + buf.s = stream->netstream; + buf.end = (buf.beg = buf.cur = tmp) + SENDBUFLEN; + tmp[SENDBUFLEN] = '\0'; /* must have additional null guard byte */ + if (!(env->to || env->cc || env->bcc)) { + /* no recipients in request */ + smtp_seterror (stream,SMTPHARDERROR,"No recipients specified"); + return NIL; + } + do { /* make sure stream is in good shape */ + smtp_send (stream,"RSET",NIL); + if (retry) { /* need to retry with authentication? */ + NETMBX mb; + /* yes, build remote name for authentication */ + sprintf (tmp,"{%.200s/smtp%s}<none>", + (long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ? + ((long) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ? + net_remotehost (stream->netstream) : + net_host (stream->netstream)) : + stream->host, + (stream->netstream->dtb == + (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL)) ? + "/ssl" : ""); + mail_valid_net_parse (tmp,&mb); + if (!smtp_auth (stream,&mb,tmp)) return NIL; + retry = NIL; /* no retry at this point */ + } + + strcpy (tmp,"FROM:<"); /* compose "MAIL FROM:<return-path>" */ +#ifdef RFC2821 + if (env->return_path && env->return_path->host && + !((strlen (env->return_path->mailbox) > SMTPMAXLOCALPART) || + (strlen (env->return_path->host) > SMTPMAXDOMAIN))) { + rfc822_cat (tmp,env->return_path->mailbox,NIL); + sprintf (tmp + strlen (tmp),"@%s",env->return_path->host); + } +#else /* old code with A-D-L support */ + if (env->return_path && env->return_path->host && + !((env->return_path->adl && + (strlen (env->return_path->adl) > SMTPMAXPATH)) || + (strlen (env->return_path->mailbox) > SMTPMAXLOCALPART) || + (strlen (env->return_path->host) > SMTPMAXDOMAIN))) + rfc822_address (tmp,env->return_path); +#endif + strcat (tmp,">"); + if (ESMTP.ok) { + if (ESMTP.eightbit.ok && ESMTP.eightbit.want) + strcat (tmp," BODY=8BITMIME"); + if (ESMTP.dsn.ok && ESMTP.dsn.want) { + strcat (tmp,ESMTP.dsn.full ? " RET=FULL" : " RET=HDRS"); + if (ESMTP.dsn.envid) + sprintf (tmp + strlen (tmp)," ENVID=%.100s",ESMTP.dsn.envid); + } + } + /* send "MAIL FROM" command */ + switch (smtp_send (stream,type,tmp)) { + case SMTPUNAVAIL: /* mailbox unavailable? */ + case SMTPWANTAUTH: /* wants authentication? */ + case SMTPWANTAUTH2: + if (ESMTP.auth) retry = T;/* yes, retry with authentication */ + case SMTPOK: /* looks good */ + break; + default: /* other failure */ + return NIL; + } + /* negotiate the recipients */ + if (!retry && env->to) retry = smtp_rcpt (stream,env->to,&error); + if (!retry && env->cc) retry = smtp_rcpt (stream,env->cc,&error); + if (!retry && env->bcc) retry = smtp_rcpt (stream,env->bcc,&error); + if (!retry && error) { /* any recipients failed? */ + smtp_send (stream,"RSET",NIL); + smtp_seterror (stream,SMTPHARDERROR,"One or more recipients failed"); + return NIL; + } + } while (retry); + /* negotiate data command */ + if (!(smtp_send (stream,"DATA",NIL) == SMTPREADY)) return NIL; + /* send message data */ + if (!rfc822_output_full (&buf,env,body, + ESMTP.eightbit.ok && ESMTP.eightbit.want)) { + smtp_fake (stream,"SMTP connection broken (message data)"); + return NIL; /* can't do much else here */ + } + /* send trailing dot */ + return (smtp_send (stream,".",NIL) == SMTPOK) ? LONGT : NIL; +} + +/* Simple Mail Transfer Protocol send VERBose + * Accepts: SMTP stream + * Returns: T if successful, else NIL + * + * Descriptive text formerly in [al]pine sources: + * At worst, this command may cause the SMTP connection to get nuked. Modern + * sendmail's recognize it, and possibly other SMTP implementations (the "ON" + * arg is for PMDF). What's more, if it works, the reply code and accompanying + * text may vary from server to server. + */ + +long smtp_verbose (SENDSTREAM *stream) +{ + /* accept any 2xx reply code */ + return ((smtp_send (stream,"VERB","ON") / (long) 100) == 2) ? LONGT : NIL; +} + +/* Internal routines */ + + +/* Simple Mail Transfer Protocol send recipient + * Accepts: SMTP stream + * address list + * pointer to error flag + * Returns: T if should retry, else NIL + */ + +long smtp_rcpt (SENDSTREAM *stream,ADDRESS *adr,long *error) +{ + char *s,tmp[2*MAILTMPLEN],orcpt[MAILTMPLEN]; + while (adr) { /* for each address on the list */ + /* clear any former error */ + if (adr->error) fs_give ((void **) &adr->error); + if (adr->host) { /* ignore group syntax */ + /* enforce SMTP limits to protect the buffer */ + if (strlen (adr->mailbox) > MAXLOCALPART) { + adr->error = cpystr ("501 Recipient name too long"); + *error = T; + } + else if ((strlen (adr->host) > SMTPMAXDOMAIN)) { + adr->error = cpystr ("501 Recipient domain too long"); + *error = T; + } +#ifndef RFC2821 /* old code with A-D-L support */ + else if (adr->adl && (strlen (adr->adl) > SMTPMAXPATH)) { + adr->error = cpystr ("501 Path too long"); + *error = T; + } +#endif + + else { + strcpy (tmp,"TO:<"); /* compose "RCPT TO:<return-path>" */ +#ifdef RFC2821 + rfc822_cat (tmp,adr->mailbox,NIL); + sprintf (tmp + strlen (tmp),"@%s>",adr->host); +#else /* old code with A-D-L support */ + rfc822_address (tmp,adr); + strcat (tmp,">"); +#endif + /* want notifications */ + if (ESMTP.ok && ESMTP.dsn.ok && ESMTP.dsn.want) { + /* yes, start with prefix */ + strcat (tmp," NOTIFY="); + s = tmp + strlen (tmp); + if (ESMTP.dsn.notify.failure) strcat (s,"FAILURE,"); + if (ESMTP.dsn.notify.delay) strcat (s,"DELAY,"); + if (ESMTP.dsn.notify.success) strcat (s,"SUCCESS,"); + /* tie off last comma */ + if (*s) s[strlen (s) - 1] = '\0'; + else strcat (tmp,"NEVER"); + if (adr->orcpt.addr) { + sprintf (orcpt,"%.498s;%.498s", + adr->orcpt.type ? adr->orcpt.type : "rfc822", + adr->orcpt.addr); + sprintf (tmp + strlen (tmp)," ORCPT=%.500s",orcpt); + } + } + switch (smtp_send (stream,"RCPT",tmp)) { + case SMTPOK: /* looks good */ + break; + case SMTPUNAVAIL: /* mailbox unavailable? */ + case SMTPWANTAUTH: /* wants authentication? */ + case SMTPWANTAUTH2: + if (ESMTP.auth) return T; + default: /* other failure */ + *error = T; /* note that an error occurred */ + adr->error = cpystr (stream->reply); + } + } + } + adr = adr->next; /* do any subsequent recipients */ + } + return NIL; /* no retry called for */ +} + +/* Simple Mail Transfer Protocol send command + * Accepts: SEND stream + * text + * Returns: reply code + */ + +long smtp_send (SENDSTREAM *stream,char *command,char *args) +{ + long ret; + char *s = (char *) fs_get (strlen (command) + (args ? strlen (args) + 1 : 0) + + 3); + /* build the complete command */ + if (args) sprintf (s,"%s %s",command,args); + else strcpy (s,command); + if (stream->debug) mail_dlog (s,stream->sensitive); + strcat (s,"\015\012"); + /* send the command */ + if (stream->netstream && net_soutr (stream->netstream,s)) { + do stream->replycode = smtp_reply (stream); + while ((stream->replycode < 100) || (stream->reply[3] == '-')); + ret = stream->replycode; + } + else ret = smtp_fake (stream,"SMTP connection broken (command)"); + fs_give ((void **) &s); + return ret; +} + + +/* Simple Mail Transfer Protocol get reply + * Accepts: SMTP stream + * Returns: reply code + */ + +long smtp_reply (SENDSTREAM *stream) +{ + smtpverbose_t pv = (smtpverbose_t) mail_parameters (NIL,GET_SMTPVERBOSE,NIL); + long reply; + /* flush old reply */ + if (stream->reply) fs_give ((void **) &stream->reply); + /* get reply */ + if (stream->netstream && (stream->reply = net_getline (stream->netstream))) { + if (stream->debug) mm_dlog (stream->reply); + /* return response code */ + reply = atol (stream->reply); + if (pv && (reply < 100)) (*pv) (stream->reply); + } + else reply = smtp_fake (stream,"SMTP connection broken (reply)"); + return reply; +} + +/* Simple Mail Transfer Protocol send EHLO + * Accepts: SMTP stream + * host name to use in EHLO + * NETMBX structure + * Returns: reply code + */ + +long smtp_ehlo (SENDSTREAM *stream,char *host,NETMBX *mb) +{ + unsigned long i,j; + long flags = (mb->secflag ? AU_SECURE : NIL) | + (mb->authuser[0] ? AU_AUTHUSER : NIL); + char *s,*t,*r,tmp[MAILTMPLEN]; + /* clear ESMTP data */ + memset (&ESMTP,0,sizeof (ESMTP)); + if (mb->loser) return 500; /* never do EHLO if a loser */ + sprintf (tmp,"EHLO %s",host); /* build the complete command */ + if (stream->debug) mm_dlog (tmp); + strcat (tmp,"\015\012"); + /* send the command */ + if (!net_soutr (stream->netstream,tmp)) + return smtp_fake (stream,"SMTP connection broken (EHLO)"); + /* got an OK reply? */ + do if ((i = smtp_reply (stream)) == SMTPOK) { + /* hack for AUTH= */ + if (stream->reply[4] && stream->reply[5] && stream->reply[6] && + stream->reply[7] && (stream->reply[8] == '=')) stream->reply[8] = ' '; + /* get option code */ + if (!(s = strtok_r (stream->reply+4," ",&r))); + /* have option, does it have a value */ + else if ((t = strtok_r (NIL," ",&r)) && *t) { + /* EHLO options which take arguments */ + if (!compare_cstring (s,"SIZE")) { + if (isdigit (*t)) ESMTP.size.limit = strtoul (t,&t,10); + ESMTP.size.ok = T; + } + else if (!compare_cstring (s,"DELIVERBY")) { + if (isdigit (*t)) ESMTP.deliverby.minby = strtoul (t,&t,10); + ESMTP.deliverby.ok = T; + } + else if (!compare_cstring (s,"ATRN")) { + ESMTP.atrn.domains = cpystr (t); + ESMTP.atrn.ok = T; + } + else if (!compare_cstring (s,"AUTH")) + do if ((j = mail_lookup_auth_name (t,flags)) && + (--j < MAXAUTHENTICATORS)) ESMTP.auth |= (1 << j); + while ((t = strtok_r (NIL," ",&r)) && *t); + } + /* EHLO options which do not take arguments */ + else if (!compare_cstring (s,"SIZE")) ESMTP.size.ok = T; + else if (!compare_cstring (s,"8BITMIME")) ESMTP.eightbit.ok = T; + else if (!compare_cstring (s,"DSN")) ESMTP.dsn.ok = T; + else if (!compare_cstring (s,"ATRN")) ESMTP.atrn.ok = T; + else if (!compare_cstring (s,"SEND")) ESMTP.service.send = T; + else if (!compare_cstring (s,"SOML")) ESMTP.service.soml = T; + else if (!compare_cstring (s,"SAML")) ESMTP.service.saml = T; + else if (!compare_cstring (s,"EXPN")) ESMTP.service.expn = T; + else if (!compare_cstring (s,"HELP")) ESMTP.service.help = T; + else if (!compare_cstring (s,"TURN")) ESMTP.service.turn = T; + else if (!compare_cstring (s,"ETRN")) ESMTP.service.etrn = T; + else if (!compare_cstring (s,"STARTTLS")) ESMTP.service.starttls = T; + else if (!compare_cstring (s,"RELAY")) ESMTP.service.relay = T; + else if (!compare_cstring (s,"PIPELINING")) ESMTP.service.pipe = T; + else if (!compare_cstring (s,"ENHANCEDSTATUSCODES")) + ESMTP.service.ensc = T; + else if (!compare_cstring (s,"BINARYMIME")) ESMTP.service.bmime = T; + else if (!compare_cstring (s,"CHUNKING")) ESMTP.service.chunk = T; + } + while ((i < 100) || (stream->reply[3] == '-')); + /* disable LOGIN if PLAIN also advertised */ + if ((j = mail_lookup_auth_name ("PLAIN",NIL)) && (--j < MAXAUTHENTICATORS) && + (ESMTP.auth & (1 << j)) && + (j = mail_lookup_auth_name ("LOGIN",NIL)) && (--j < MAXAUTHENTICATORS)) + ESMTP.auth &= ~(1 << j); + return i; /* return the response code */ +} + +/* Simple Mail Transfer Protocol set fake error and abort + * Accepts: SMTP stream + * error text + * Returns: SMTPSOFTFATAL, always + */ + +long smtp_fake (SENDSTREAM *stream,char *text) +{ + if (stream->netstream) { /* close net connection if still open */ + net_close (stream->netstream); + stream->netstream = NIL; + } + /* set last error */ + return smtp_seterror (stream,SMTPSOFTFATAL,text); +} + + +/* Simple Mail Transfer Protocol set error + * Accepts: SMTP stream + * SMTP error code + * error text + * Returns: error code + */ + +static long smtp_seterror (SENDSTREAM *stream,long code,char *text) +{ + /* flush any old reply */ + if (stream->reply ) fs_give ((void **) &stream->reply); + /* set up pseudo-reply string */ + stream->reply = (char *) fs_get (20+strlen (text)); + sprintf (stream->reply,"%ld %s",code,text); + return code; /* return error code */ +} + + +/* Simple Mail Transfer Protocol filter mail + * Accepts: stream + * string + * Returns: T on success, NIL on failure + */ + +long smtp_soutr (void *stream,char *s) +{ + char c,*t; + /* "." on first line */ + if (s[0] == '.') net_sout (stream,".",1); + /* find lines beginning with a "." */ + while (t = strstr (s,"\015\012.")) { + c = *(t += 3); /* remember next character after "." */ + *t = '\0'; /* tie off string */ + /* output prefix */ + if (!net_sout (stream,s,t-s)) return NIL; + *t = c; /* restore delimiter */ + s = t - 1; /* push pointer up to the "." */ + } + /* output remainder of text */ + return *s ? net_soutr (stream,s) : T; +} diff --git a/imap/src/c-client/smtp.h b/imap/src/c-client/smtp.h new file mode 100644 index 00000000..4be82db4 --- /dev/null +++ b/imap/src/c-client/smtp.h @@ -0,0 +1,76 @@ +/* ======================================================================== + * Copyright 1988-2007 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Simple Mail Transfer Protocol (SMTP) routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 27 July 1988 + * Last Edited: 15 August 2007 + * + * This original version of this file is + * Copyright 1988 Stanford University + * and was developed in the Symbolic Systems Resources Group of the Knowledge + * Systems Laboratory at Stanford University in 1987-88, and was funded by the + * Biomedical Research Technology Program of the NationalInstitutes of Health + * under grant number RR-00785. + */ + +/* Constants (should be in smtp.c) */ + +#define SMTPTCPPORT (long) 25 /* assigned TCP contact port */ +#define SUBMITTCPPORT (long) 587/* assigned TCP contact port */ + + +/* SMTP open options + * For compatibility with the past, SOP_DEBUG must always be 1. + */ + +#define SOP_DEBUG (long) 1 /* debug protocol negotiations */ +#define SOP_DSN (long) 2 /* DSN requested */ + /* DSN notification, none set mean NEVER */ +#define SOP_DSN_NOTIFY_FAILURE (long) 4 +#define SOP_DSN_NOTIFY_DELAY (long) 8 +#define SOP_DSN_NOTIFY_SUCCESS (long) 16 + /* DSN return full msg vs. header */ + +#define SOP_DSN_RETURN_FULL (long) 32 +#define SOP_8BITMIME (long) 64 /* 8-bit MIME requested */ +#define SOP_SECURE (long) 256 /* don't do non-secure authentication */ +#define SOP_TRYSSL (long) 512 /* try SSL first */ +#define SOP_TRYALT SOP_TRYSSL /* old name */ + /* reserved for application use */ +#define SOP_RESERVED (unsigned long) 0xff000000 + + +/* Compatibility support names */ + +#define smtp_open(hostlist,options) \ + smtp_open_full (NIL,hostlist,"smtp",NIL,options) + + +/* Function prototypes */ + +void *smtp_parameters (long function,void *value); +SENDSTREAM *smtp_open_full (NETDRIVER *dv,char **hostlist,char *service, + unsigned long port,long options); +SENDSTREAM *smtp_close (SENDSTREAM *stream); +long smtp_mail (SENDSTREAM *stream,char *type,ENVELOPE *msg,BODY *body); +long smtp_verbose (SENDSTREAM *stream); diff --git a/imap/src/c-client/sslio.h b/imap/src/c-client/sslio.h new file mode 100644 index 00000000..8063f3fc --- /dev/null +++ b/imap/src/c-client/sslio.h @@ -0,0 +1,70 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: SSL routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 7 February 2001 + * Last Edited: 30 August 2006 + */ + +/* SSL driver */ + +struct ssl_driver { /* must parallel NETDRIVER in mail.h */ + SSLSTREAM *(*open) (char *host,char *service,unsigned long port); + SSLSTREAM *(*aopen) (NETMBX *mb,char *service,char *usrbuf); + char *(*getline) (SSLSTREAM *stream); + long (*getbuffer) (SSLSTREAM *stream,unsigned long size,char *buffer); + long (*soutr) (SSLSTREAM *stream,char *string); + long (*sout) (SSLSTREAM *stream,char *string,unsigned long size); + void (*close) (SSLSTREAM *stream); + char *(*host) (SSLSTREAM *stream); + char *(*remotehost) (SSLSTREAM *stream); + unsigned long (*port) (SSLSTREAM *stream); + char *(*localhost) (SSLSTREAM *stream); +}; + + +/* SSL stdio stream */ + +typedef struct ssl_stdiostream { + SSLSTREAM *sslstream; /* SSL stream */ + int octr; /* output counter */ + char *optr; /* output pointer */ + char obuf[SSLBUFLEN]; /* output buffer */ +} SSLSTDIOSTREAM; + + +/* Function prototypes */ + +SSLSTREAM *ssl_open (char *host,char *service,unsigned long port); +SSLSTREAM *ssl_aopen (NETMBX *mb,char *service,char *usrbuf); +char *ssl_getline (SSLSTREAM *stream); +long ssl_getbuffer (SSLSTREAM *stream,unsigned long size,char *buffer); +long ssl_getdata (SSLSTREAM *stream); +long ssl_soutr (SSLSTREAM *stream,char *string); +long ssl_sout (SSLSTREAM *stream,char *string,unsigned long size); +void ssl_close (SSLSTREAM *stream); +char *ssl_host (SSLSTREAM *stream); +char *ssl_remotehost (SSLSTREAM *stream); +unsigned long ssl_port (SSLSTREAM *stream); +char *ssl_localhost (SSLSTREAM *stream); +long ssl_server_input_wait (long seconds); diff --git a/imap/src/c-client/tcp.h b/imap/src/c-client/tcp.h new file mode 100644 index 00000000..b9f095a8 --- /dev/null +++ b/imap/src/c-client/tcp.h @@ -0,0 +1,59 @@ +/* ======================================================================== + * Copyright 1988-2007 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: TCP/IP routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 1 August 1988 + * Last Edited: 31 January 2007 + */ + + +/* Dummy definition overridden by TCP routines */ + +#ifndef TCPSTREAM +#define TCPSTREAM void +#endif + + +/* Function prototypes */ + +void *tcp_parameters (long function,void *value); +TCPSTREAM *tcp_open (char *host,char *service,unsigned long port); +TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf); +char *tcp_getline (TCPSTREAM *stream); +long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *buffer); +long tcp_getdata (TCPSTREAM *stream); +long tcp_soutr (TCPSTREAM *stream,char *string); +long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size); +void tcp_close (TCPSTREAM *stream); +char *tcp_host (TCPSTREAM *stream); +char *tcp_remotehost (TCPSTREAM *stream); +unsigned long tcp_port (TCPSTREAM *stream); +char *tcp_localhost (TCPSTREAM *stream); +char *tcp_clientaddr (void); +char *tcp_clienthost (void); +long tcp_clientport (void); +char *tcp_serveraddr (void); +char *tcp_serverhost (void); +long tcp_serverport (void); +char *tcp_canonical (char *name); +long tcp_isclienthost (char *host); diff --git a/imap/src/c-client/utf8.c b/imap/src/c-client/utf8.c new file mode 100644 index 00000000..8fda7ffd --- /dev/null +++ b/imap/src/c-client/utf8.c @@ -0,0 +1,2554 @@ +/* ======================================================================== + * Copyright 1988-2008 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: UTF-8 routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 11 June 1997 + * Last Edited: 17 January 2008 + */ + + +#include <stdio.h> +#include <ctype.h> +#include "c-client.h" + +/* *** IMPORTANT *** + * + * There is a very important difference between "character set" and "charset", + * and the comments in this file reflect these differences. A "character set" + * (also known as "coded character set") is a mapping between codepoints and + * characters. A "charset" is as defined in MIME, and incorporates one or more + * coded character sets in a character encoding scheme. See RFC 2130 for more + * details. + */ + + +/* Character set conversion tables */ + +#include "iso_8859.c" /* 8-bit single-byte coded graphic */ +#include "koi8_r.c" /* Cyrillic - Russia */ +#include "koi8_u.c" /* Cyrillic - Ukraine */ +#include "tis_620.c" /* Thai */ +#include "viscii.c" /* Vietnamese */ +#include "windows.c" /* Windows */ +#include "ibm.c" /* IBM */ +#include "gb_2312.c" /* Chinese (PRC) - simplified */ +#include "gb_12345.c" /* Chinese (PRC) - traditional */ +#include "jis_0208.c" /* Japanese - basic */ +#include "jis_0212.c" /* Japanese - supplementary */ +#include "ksc_5601.c" /* Korean */ +#include "big5.c" /* Taiwanese (ROC) - industrial standard */ +#include "cns11643.c" /* Taiwanese (ROC) - national standard */ + + +#include "widths.c" /* Unicode character widths */ +#include "tmap.c" /* Unicode titlecase mapping */ +#include "decomtab.c" /* Unicode decomposions */ + +/* EUC parameters */ + +#ifdef GBTOUNICODE /* PRC simplified Chinese */ +static const struct utf8_eucparam gb_param = { + BASE_GB2312_KU,BASE_GB2312_TEN,MAX_GB2312_KU,MAX_GB2312_TEN, + (void *) gb2312tab}; +#endif + + +#ifdef GB12345TOUNICODE /* PRC traditional Chinese */ +static const struct utf8_eucparam gbt_param = { + BASE_GB12345_KU,BASE_GB12345_TEN,MAX_GB12345_KU,MAX_GB12345_TEN, + (void *) gb12345tab}; +#endif + + +#ifdef BIG5TOUNICODE /* ROC traditional Chinese */ +static const struct utf8_eucparam big5_param[] = { + {BASE_BIG5_KU,BASE_BIG5_TEN_0,MAX_BIG5_KU,MAX_BIG5_TEN_0,(void *) big5tab}, + {BASE_BIG5_KU,BASE_BIG5_TEN_1,MAX_BIG5_KU,MAX_BIG5_TEN_1,NIL} +}; +#endif + + +#ifdef JISTOUNICODE /* Japanese */ +static const struct utf8_eucparam jis_param[] = { + {BASE_JIS0208_KU,BASE_JIS0208_TEN,MAX_JIS0208_KU,MAX_JIS0208_TEN, + (void *) jis0208tab}, + {MIN_KANA_8,0,MAX_KANA_8,0,(void *) KANA_8}, +#ifdef JIS0212TOUNICODE /* Japanese extended */ + {BASE_JIS0212_KU,BASE_JIS0212_TEN,MAX_JIS0212_KU,MAX_JIS0212_TEN, + (void *) jis0212tab} +#else + {0,0,0,0,NIL} +#endif +}; +#endif + + +#ifdef KSCTOUNICODE /* Korean */ +static const struct utf8_eucparam ksc_param = { + BASE_KSC5601_KU,BASE_KSC5601_TEN,MAX_KSC5601_KU,MAX_KSC5601_TEN, + (void *) ksc5601tab}; +#endif + +/* List of supported charsets */ + +static const CHARSET utf8_csvalid[] = { + {"US-ASCII",CT_ASCII,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + NIL,NIL,NIL}, + {"UTF-8",CT_UTF8,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + NIL,SC_UNICODE,NIL}, + {"UTF-7",CT_UTF7,CF_PRIMARY | CF_POSTING | CF_UNSUPRT, + NIL,SC_UNICODE,"UTF-8"}, + {"ISO-8859-1",CT_1BYTE0,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + NIL,SC_LATIN_1,NIL}, + {"ISO-8859-2",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) iso8859_2tab,SC_LATIN_2,NIL}, + {"ISO-8859-3",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) iso8859_3tab,SC_LATIN_3,NIL}, + {"ISO-8859-4",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) iso8859_4tab,SC_LATIN_4,NIL}, + {"ISO-8859-5",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) iso8859_5tab,SC_CYRILLIC,"KOI8-R"}, + {"ISO-8859-6",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) iso8859_6tab,SC_ARABIC,NIL}, + {"ISO-8859-7",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) iso8859_7tab,SC_GREEK,NIL}, + {"ISO-8859-8",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) iso8859_8tab,SC_HEBREW,NIL}, + {"ISO-8859-9",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) iso8859_9tab,SC_LATIN_5,NIL}, + {"ISO-8859-10",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) iso8859_10tab,SC_LATIN_6,NIL}, + {"ISO-8859-11",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) iso8859_11tab,SC_THAI,NIL}, +#if 0 /* ISO 8859-12 reserved for ISCII(?) */ + {"ISO-8859-12",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) iso8859_12tab,NIL,NIL}, +#endif + {"ISO-8859-13",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) iso8859_13tab,SC_LATIN_7,NIL}, + {"ISO-8859-14",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) iso8859_14tab,SC_LATIN_8,NIL}, + {"ISO-8859-15",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) iso8859_15tab,SC_LATIN_9,NIL}, + {"ISO-8859-16",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) iso8859_16tab,SC_LATIN_10,NIL}, + {"KOI8-R",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) koi8rtab,SC_CYRILLIC,NIL}, + {"KOI8-U",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) koi8utab,SC_CYRILLIC | SC_UKRANIAN,NIL}, + {"KOI8-RU",CT_1BYTE,CF_DISPLAY, + (void *) koi8utab,SC_CYRILLIC | SC_UKRANIAN,"KOI8-U"}, + {"TIS-620",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) tis620tab,SC_THAI,"ISO-8859-11"}, + {"VISCII",CT_1BYTE8,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) visciitab,SC_VIETNAMESE,NIL}, + +#ifdef GBTOUNICODE + {"GBK",CT_DBYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) &gb_param,SC_CHINESE_SIMPLIFIED,NIL}, + {"GB2312",CT_DBYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) &gb_param,SC_CHINESE_SIMPLIFIED,"GBK"}, + {"CN-GB",CT_DBYTE,CF_DISPLAY, + (void *) &gb_param,SC_CHINESE_SIMPLIFIED,"GBK"}, +#ifdef CNS1TOUNICODE + {"ISO-2022-CN",CT_2022,CF_PRIMARY | CF_UNSUPRT, + NIL,SC_CHINESE_SIMPLIFIED | SC_CHINESE_TRADITIONAL, + NIL}, +#endif +#endif +#ifdef GB12345TOUNICODE + {"CN-GB-12345",CT_DBYTE,CF_PRIMARY | CF_DISPLAY, + (void *) &gbt_param,SC_CHINESE_TRADITIONAL,"BIG5"}, +#endif +#ifdef BIG5TOUNICODE + {"BIG5",CT_DBYTE2,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) big5_param,SC_CHINESE_TRADITIONAL,NIL}, + {"CN-BIG5",CT_DBYTE2,CF_DISPLAY, + (void *) big5_param,SC_CHINESE_TRADITIONAL,"BIG5"}, + {"BIG-5",CT_DBYTE2,CF_DISPLAY, + (void *) big5_param,SC_CHINESE_TRADITIONAL,"BIG5"}, +#endif +#ifdef JISTOUNICODE + {"ISO-2022-JP",CT_2022,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + NIL,SC_JAPANESE,NIL}, + {"EUC-JP",CT_EUC,CF_PRIMARY | CF_DISPLAY, + (void *) jis_param,SC_JAPANESE,"ISO-2022-JP"}, + {"SHIFT_JIS",CT_SJIS,CF_PRIMARY | CF_DISPLAY, + NIL,SC_JAPANESE,"ISO-2022-JP"}, + {"SHIFT-JIS",CT_SJIS,CF_PRIMARY | CF_DISPLAY, + NIL,SC_JAPANESE,"ISO-2022-JP"}, +#ifdef JIS0212TOUNICODE + {"ISO-2022-JP-1",CT_2022,CF_UNSUPRT, + NIL,SC_JAPANESE,"ISO-2022-JP"}, +#ifdef GBTOUNICODE +#ifdef KSCTOUNICODE + {"ISO-2022-JP-2",CT_2022,CF_UNSUPRT, + NIL, + SC_LATIN_1 | SC_LATIN_2 | SC_LATIN_3 | SC_LATIN_4 | SC_LATIN_5 | + SC_LATIN_6 | SC_LATIN_7 | SC_LATIN_8 | SC_LATIN_9 | SC_LATIN_10 | + SC_ARABIC | SC_CYRILLIC | SC_GREEK | SC_HEBREW | SC_THAI | + SC_VIETNAMESE | SC_CHINESE_SIMPLIFIED | SC_JAPANESE | SC_KOREAN +#ifdef CNS1TOUNICODE + | SC_CHINESE_TRADITIONAL +#endif + ,"UTF-8"}, +#endif +#endif +#endif +#endif + +#ifdef KSCTOUNICODE + {"ISO-2022-KR",CT_2022,CF_PRIMARY | CF_DISPLAY | CF_UNSUPRT, + NIL,SC_KOREAN,"EUC-KR"}, + {"EUC-KR",CT_DBYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) &ksc_param,SC_KOREAN,NIL}, + {"KSC5601",CT_DBYTE,CF_PRIMARY | CF_DISPLAY, + (void *) &ksc_param,SC_KOREAN,"EUC-KR"}, + {"KSC_5601",CT_DBYTE,CF_PRIMARY | CF_DISPLAY, + (void *) &ksc_param,SC_KOREAN,"EUC-KR"}, + {"KS_C_5601-1987",CT_DBYTE,CF_DISPLAY, + (void *) &ksc_param,SC_KOREAN,"EUC-KR"}, + {"KS_C_5601-1989",CT_DBYTE,CF_DISPLAY, + (void *) &ksc_param,SC_KOREAN,"EUC-KR"}, + {"KS_C_5601-1992",CT_DBYTE,CF_DISPLAY, + (void *) &ksc_param,SC_KOREAN,"EUC-KR"}, + {"KS_C_5601-1997",CT_DBYTE,CF_DISPLAY, + (void *) &ksc_param,SC_KOREAN,"EUC-KR"}, +#endif + + /* deep sigh */ + {"WINDOWS-874",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) windows_874tab,SC_THAI,"ISO-8859-11"}, + {"CP874",CT_1BYTE,CF_DISPLAY, + (void *) windows_874tab,SC_THAI,"ISO-8859-11"}, +#ifdef GBTOUNICODE + {"WINDOWS-936",CT_DBYTE,CF_PRIMARY | CF_DISPLAY, + (void *) &gb_param,SC_CHINESE_SIMPLIFIED,"GBK"}, + {"CP936",CT_DBYTE,CF_DISPLAY, + (void *) &gb_param,SC_CHINESE_SIMPLIFIED,"GBK"}, +#endif +#ifdef KSCTOUNICODE + {"WINDOWS-949",CT_DBYTE,CF_PRIMARY | CF_DISPLAY, + (void *) &ksc_param,SC_KOREAN,"EUC-KR"}, + {"CP949",CT_DBYTE,CF_DISPLAY, + (void *) &ksc_param,SC_KOREAN,"EUC-KR"}, + {"X-WINDOWS-949",CT_DBYTE,CF_PRIMARY | CF_DISPLAY, + (void *) &ksc_param,SC_KOREAN,"EUC-KR"}, +#endif + {"WINDOWS-1250",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) windows_1250tab,SC_LATIN_2,"ISO-8859-2"}, + {"CP1250",CT_1BYTE,CF_DISPLAY, + (void *) windows_1250tab,SC_LATIN_2,"ISO-8859-2"}, + {"WINDOWS-1251",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING, + (void *) windows_1251tab,SC_CYRILLIC,"KOI8-R"}, + {"CP1251",CT_1BYTE,CF_DISPLAY, + (void *) windows_1251tab,SC_CYRILLIC,"KOI8-R"}, + {"WINDOWS-1252",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) windows_1252tab,SC_LATIN_1,"ISO-8859-1"}, + {"CP1252",CT_1BYTE,CF_DISPLAY, + (void *) windows_1252tab,SC_LATIN_1,"ISO-8859-1"}, + {"WINDOWS-1253",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) windows_1253tab,SC_GREEK,"ISO-8859-7"}, + {"CP1253",CT_1BYTE,CF_DISPLAY, + (void *) windows_1253tab,SC_GREEK,"ISO-8859-7"}, + {"WINDOWS-1254",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) windows_1254tab,SC_LATIN_5,"ISO-8859-9"}, + {"CP1254",CT_1BYTE,CF_DISPLAY, + (void *) windows_1254tab,SC_LATIN_5,"ISO-8859-9"}, + {"WINDOWS-1255",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) windows_1255tab,SC_HEBREW,"ISO-8859-8"}, + {"CP1255",CT_1BYTE,CF_DISPLAY, + (void *) windows_1255tab,SC_HEBREW,"ISO-8859-8"}, + {"WINDOWS-1256",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) windows_1256tab,SC_ARABIC,"ISO-8859-6"}, + {"CP1256",CT_1BYTE,CF_DISPLAY, + (void *) windows_1256tab,SC_ARABIC,"ISO-8859-6"}, + {"WINDOWS-1257",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) windows_1257tab,SC_LATIN_7,"ISO-8859-13"}, + {"CP1257",CT_1BYTE,CF_DISPLAY, + (void *) windows_1257tab,SC_LATIN_7,"ISO-8859-13"}, + {"WINDOWS-1258",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) windows_1258tab,SC_VIETNAMESE,"VISCII"}, + {"CP1258",CT_1BYTE,CF_DISPLAY, + (void *) windows_1258tab,SC_VIETNAMESE,"VISCII"}, + + /* deeper sigh */ + {"IBM367",CT_ASCII,CF_PRIMARY | CF_DISPLAY, + NIL,NIL,"US-ASCII"}, + {"IBM437",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_437tab,SC_LATIN_1,"ISO-8859-1"}, + {"IBM737",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_737tab,SC_GREEK,"ISO-8859-7"}, + {"IBM775",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_775tab,SC_LATIN_7,"ISO-8859-13"}, + {"IBM850",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_850tab,SC_LATIN_1,"ISO-8859-1"}, + {"IBM852",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_852tab,SC_LATIN_2,"ISO-8859-2"}, + {"IBM855",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_855tab,SC_CYRILLIC,"ISO-8859-5"}, + {"IBM857",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_857tab,SC_LATIN_5,"ISO-8859-9"}, + {"IBM860",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_860tab,SC_LATIN_1,"ISO-8859-1"}, + {"IBM861",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_861tab,SC_LATIN_6,"ISO-8859-10"}, + {"IBM862",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_862tab,SC_HEBREW,"ISO-8859-8"}, + {"IBM863",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_863tab,SC_LATIN_1,"ISO-8859-1"}, + {"IBM864",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_864tab,SC_ARABIC,"ISO-8859-6"}, + {"IBM865",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_865tab,SC_LATIN_6,"ISO-8859-10"}, + {"IBM866",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_866tab,SC_CYRILLIC,"KOI8-R"}, + {"IBM869",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_869tab,SC_GREEK,"ISO-8859-7"}, + {"IBM874",CT_1BYTE,CF_PRIMARY | CF_DISPLAY, + (void *) ibm_874tab,SC_THAI,"ISO-8859-11"}, + /* deepest sigh */ + {"ANSI_X3.4-1968",CT_ASCII,CF_DISPLAY, + NIL,NIL,"US-ASCII"}, + {"UNICODE-1-1-UTF-7",CT_UTF7,CF_UNSUPRT, + NIL,SC_UNICODE,"UTF-8"}, + /* these should never appear in email */ + {"UCS-2",CT_UCS2,CF_PRIMARY | CF_DISPLAY | CF_NOEMAIL, + NIL,SC_UNICODE,"UTF-8"}, + {"UCS-4",CT_UCS4,CF_PRIMARY | CF_DISPLAY | CF_NOEMAIL, + NIL,SC_UNICODE,"UTF-8"}, + {"UTF-16",CT_UTF16,CF_PRIMARY | CF_DISPLAY | CF_NOEMAIL, + NIL,SC_UNICODE,"UTF-8"}, + NIL +}; + +/* Non-Unicode Script table */ + +static const SCRIPT utf8_scvalid[] = { + {"Arabic",NIL,SC_ARABIC}, + {"Chinese Simplified","China, Singapore",SC_CHINESE_SIMPLIFIED}, + {"Chinese Traditional","Taiwan, Hong Kong, Macao",SC_CHINESE_TRADITIONAL}, + {"Cyrillic",NIL,SC_CYRILLIC}, + {"Cyrillic Ukranian",NIL,SC_UKRANIAN}, + {"Greek",NIL,SC_GREEK}, + {"Hebrew",NIL,SC_HEBREW}, + {"Japanese",NIL,SC_JAPANESE}, + {"Korean",NIL,SC_KOREAN}, + {"Latin-1","Western Europe",SC_LATIN_1}, + {"Latin-2","Eastern Europe",SC_LATIN_2}, + {"Latin-3","Southern Europe",SC_LATIN_3}, + {"Latin-4","Northern Europe",SC_LATIN_4}, + {"Latin-5","Turkish",SC_LATIN_5}, + {"Latin-6","Nordic",SC_LATIN_6}, + {"Latin-7","Baltic",SC_LATIN_7}, + {"Latin-8","Celtic",SC_LATIN_8}, + {"Latin-9","Euro",SC_LATIN_9}, + {"Latin-10","Balkan",SC_LATIN_10}, + {"Thai",NIL,SC_THAI}, + {"Vietnamese",NIL,SC_VIETNAMESE}, + NIL +}; + +/* Look up script name or return entire table + * Accepts: script name or NIL + * Returns: pointer to script table entry or NIL if unknown + */ + +SCRIPT *utf8_script (char *script) +{ + unsigned long i; + if (!script) return (SCRIPT *) &utf8_scvalid[0]; + else if (*script && (strlen (script) < 128)) + for (i = 0; utf8_scvalid[i].name; i++) + if (!compare_cstring (script,utf8_scvalid[i].name)) + return (SCRIPT *) &utf8_scvalid[i]; + return NIL; /* failed */ +} + + +/* Look up charset name or return entire table + * Accepts: charset name or NIL + * Returns: charset table entry or NIL if unknown + */ + +const CHARSET *utf8_charset (char *charset) +{ + unsigned long i; + if (!charset) return (CHARSET *) &utf8_csvalid[0]; + else if (*charset && (strlen (charset) < 128)) + for (i = 0; utf8_csvalid[i].name; i++) + if (!compare_cstring (charset,utf8_csvalid[i].name)) + return (CHARSET *) &utf8_csvalid[i]; + return NIL; /* failed */ +} + +/* Validate charset and generate error message if invalid + * Accepts: bad character set + * Returns: NIL if good charset, else error message string + */ + +#define BADCSS "[BADCHARSET (" +#define BADCSE ")] Unknown charset: " + +char *utf8_badcharset (char *charset) +{ + char *msg = NIL; + if (!utf8_charset (charset)) { + char *s,*t; + unsigned long i,j; + /* calculate size of header, trailer, and bad + * charset plus charset names */ + for (i = 0, j = sizeof (BADCSS) + sizeof (BADCSE) + strlen (charset) - 2; + utf8_csvalid[i].name; i++) + j += strlen (utf8_csvalid[i].name) + 1; + /* not built right */ + if (!i) fatal ("No valid charsets!"); + /* header */ + for (s = msg = (char *) fs_get (j), t = BADCSS; *t; *s++ = *t++); + /* each charset */ + for (i = 0; utf8_csvalid[i].name; *s++ = ' ', i++) + for (t = utf8_csvalid[i].name; *t; *s++ = *t++); + /* back over last space, trailer */ + for (t = BADCSE, --s; *t; *s++ = *t++); + /* finally bogus charset */ + for (t = charset; *t; *s++ = *t++); + *s++ = '\0'; /* finally tie off string */ + if (s != (msg + j)) fatal ("charset msg botch"); + } + return msg; +} + +/* Convert charset labelled sized text to UTF-8 + * Accepts: source sized text + * charset + * pointer to returned sized text if non-NIL + * flags + * Returns: T if successful, NIL if failure + */ + +long utf8_text (SIZEDTEXT *text,char *charset,SIZEDTEXT *ret,long flags) +{ + ucs4cn_t cv = (flags & U8T_CASECANON) ? ucs4_titlecase : NIL; + ucs4de_t de = (flags & U8T_DECOMPOSE) ? ucs4_decompose_recursive : NIL; + const CHARSET *cs = (charset && *charset) ? + utf8_charset (charset) : utf8_infercharset (text); + if (cs) return (text && ret) ? utf8_text_cs (text,cs,ret,cv,de) : LONGT; + if (ret) { /* no conversion possible */ + ret->data = text->data; /* so return source */ + ret->size = text->size; + } + return NIL; /* failure */ +} + + +/* Operations used in converting data */ + +#define UTF8_COUNT_BMP(count,c,cv,de) { \ + void *more = NIL; \ + if (cv) c = (*cv) (c); \ + if (de) c = (*de) (c,&more); \ + do count += UTF8_SIZE_BMP(c); \ + while (more && (c = (*de) (U8G_ERROR,&more)));\ +} + +#define UTF8_WRITE_BMP(b,c,cv,de) { \ + void *more = NIL; \ + if (cv) c = (*cv) (c); \ + if (de) c = (*de) (c,&more); \ + do UTF8_PUT_BMP (b,c) \ + while (more && (c = (*de) (U8G_ERROR,&more)));\ +} + +#define UTF8_COUNT(count,c,cv,de) { \ + void *more = NIL; \ + if (cv) c = (*cv) (c); \ + if (de) c = (*de) (c,&more); \ + do count += utf8_size (c); \ + while (more && (c = (*de) (U8G_ERROR,&more)));\ +} + +#define UTF8_WRITE(b,c,cv,de) { \ + void *more = NIL; \ + if (cv) c = (*cv) (c); \ + if (de) c = (*de) (c,&more); \ + do b = utf8_put (b,c); \ + while (more && (c = (*de) (U8G_ERROR,&more)));\ +} + +/* Convert sized text to UTF-8 given CHARSET block + * Accepts: source sized text + * CHARSET block + * pointer to returned sized text + * canonicalization function + * decomposition function + * Returns: T if successful, NIL if failure + */ + +long utf8_text_cs (SIZEDTEXT *text,const CHARSET *cs,SIZEDTEXT *ret, + ucs4cn_t cv,ucs4de_t de) +{ + ret->data = text->data; /* default to source */ + ret->size = text->size; + switch (cs->type) { /* convert if type known */ + case CT_ASCII: /* 7-bit ASCII no table */ + case CT_UTF8: /* variable UTF-8 encoded Unicode no table */ + if (cv || de) utf8_text_utf8 (text,ret,cv,de); + break; + case CT_1BYTE0: /* 1 byte no table */ + utf8_text_1byte0 (text,ret,cv,de); + break; + case CT_1BYTE: /* 1 byte ASCII + table 0x80-0xff */ + utf8_text_1byte (text,ret,cs->tab,cv,de); + break; + case CT_1BYTE8: /* 1 byte table 0x00 - 0xff */ + utf8_text_1byte8 (text,ret,cs->tab,cv,de); + break; + case CT_EUC: /* 2 byte ASCII + utf8_eucparam base/CS2/CS3 */ + utf8_text_euc (text,ret,cs->tab,cv,de); + break; + case CT_DBYTE: /* 2 byte ASCII + utf8_eucparam */ + utf8_text_dbyte (text,ret,cs->tab,cv,de); + break; + case CT_DBYTE2: /* 2 byte ASCII + utf8_eucparam plane1/2 */ + utf8_text_dbyte2 (text,ret,cs->tab,cv,de); + break; + case CT_UTF7: /* variable UTF-7 encoded Unicode no table */ + utf8_text_utf7 (text,ret,cv,de); + break; + case CT_UCS2: /* 2 byte 16-bit Unicode no table */ + utf8_text_ucs2 (text,ret,cv,de); + break; + case CT_UCS4: /* 4 byte 32-bit Unicode no table */ + utf8_text_ucs4 (text,ret,cv,de); + break; + case CT_UTF16: /* variable UTF-16 encoded Unicode no table */ + utf8_text_utf16 (text,ret,cv,de); + break; + case CT_2022: /* variable ISO-2022 encoded no table*/ + utf8_text_2022 (text,ret,cv,de); + break; + case CT_SJIS: /* 2 byte Shift-JIS encoded JIS no table */ + utf8_text_sjis (text,ret,cv,de); + break; + default: /* unknown character set type */ + return NIL; + } + return LONGT; /* return success */ +} + +/* Reverse mapping routines + * + * These routines only support character sets, not all possible charsets. In + * particular, they do not support any Unicode encodings or ISO 2022. + * + * As a special dispensation, utf8_cstext() and utf8_cstocstext() support + * support ISO-2022-JP if EUC-JP can be reverse mapped; and utf8_rmaptext() + * will generated ISO-2022-JP using an EUC-JP rmap if flagged to do so. + * + * No attempt is made to map "equivalent" Unicode characters or Unicode + * characters that have the same glyph; nor is there any attempt to handle + * combining characters or otherwise do any stringprep. Maybe later. + */ + + +/* Convert UTF-8 sized text to charset + * Accepts: source sized text + * destination charset + * pointer to returned sized text + * substitute character if not in cs, else NIL to return failure + * Returns: T if successful, NIL if failure + */ + + +long utf8_cstext (SIZEDTEXT *text,char *charset,SIZEDTEXT *ret, + unsigned long errch) +{ + short iso2022jp = !compare_cstring (charset,"ISO-2022-JP"); + unsigned short *rmap = utf8_rmap (iso2022jp ? "EUC-JP" : charset); + return rmap ? utf8_rmaptext (text,rmap,ret,errch,iso2022jp) : NIL; +} + +/* Convert charset labelled sized text to another charset + * Accepts: source sized text + * source charset + * pointer to returned sized text + * destination charset + * substitute character if not in dest cs, else NIL to return failure + * Returns: T if successful, NIL if failure + * + * This routine has the same restricts as utf8_cstext(). + */ + +long utf8_cstocstext (SIZEDTEXT *src,char *sc,SIZEDTEXT *dst,char *dc, + unsigned long errch) +{ + SIZEDTEXT utf8; + const CHARSET *scs,*dcs; + unsigned short *rmap; + long ret = NIL; + long iso2022jp; + /* lookup charsets and reverse map */ + if ((dc && (dcs = utf8_charset (dc))) && + (rmap = (iso2022jp = ((dcs->type == CT_2022) && + !compare_cstring (dcs->name,"ISO-2022-JP"))) ? + utf8_rmap ("EUC-JP") : utf8_rmap_cs (dcs)) && + (scs = (sc && *sc) ? utf8_charset (sc) : utf8_infercharset (src))) { + /* init temporary buffer */ + memset (&utf8,NIL,sizeof (SIZEDTEXT)); + /* source cs equivalent to dest cs? */ + if ((scs->type == dcs->type) && (scs->tab == dcs->tab)) { + dst->data = src->data; /* yes, just copy pointers */ + dst->size = src->size; + ret = LONGT; + } + /* otherwise do the conversion */ + else ret = (utf8_text_cs (src,scs,&utf8,NIL,NIL) && + utf8_rmaptext (&utf8,rmap,dst,errch,iso2022jp)); + /* flush temporary buffer */ + if (utf8.data && (utf8.data != src->data) && (utf8.data != dst->data)) + fs_give ((void **) &utf8.data); + } + return ret; +} + +/* Cached rmap */ + +static const CHARSET *currmapcs = NIL; +static unsigned short *currmap = NIL; + + +/* Cache and return map for UTF-8 -> character set + * Accepts: character set name + * Returns: cached map if character set found, else NIL + */ + +unsigned short *utf8_rmap (char *charset) +{ + return (currmapcs && !compare_cstring (charset,currmapcs->name)) ? currmap : + utf8_rmap_cs (utf8_charset (charset)); +} + + +/* Cache and return map for UTF-8 -> character set given CHARSET block + * Accepts: CHARSET block + * Returns: cached map if character set found, else NIL + */ + +unsigned short *utf8_rmap_cs (const CHARSET *cs) +{ + unsigned short *ret = NIL; + if (!cs); /* have charset? */ + else if (cs == currmapcs) ret = currmap; + else if (ret = utf8_rmap_gen (cs,currmap)) { + currmapcs = cs; + currmap = ret; + } + return ret; +} + +/* Return map for UTF-8 -> character set given CHARSET block + * Accepts: CHARSET block + * old map to recycle + * Returns: map if character set found, else NIL + */ + +unsigned short *utf8_rmap_gen (const CHARSET *cs,unsigned short *oldmap) +{ + unsigned short u,*tab,*rmap; + unsigned int i,m,ku,ten; + struct utf8_eucparam *param,*p2; + switch (cs->type) { /* is a character set? */ + case CT_ASCII: /* 7-bit ASCII no table */ + case CT_1BYTE0: /* 1 byte no table */ + case CT_1BYTE: /* 1 byte ASCII + table 0x80-0xff */ + case CT_1BYTE8: /* 1 byte table 0x00 - 0xff */ + case CT_EUC: /* 2 byte ASCII + utf8_eucparam base/CS2/CS3 */ + case CT_DBYTE: /* 2 byte ASCII + utf8_eucparam */ + case CT_DBYTE2: /* 2 byte ASCII + utf8_eucparam plane1/2 */ + case CT_SJIS: /* 2 byte Shift-JIS */ + rmap = oldmap ? oldmap : /* recycle old map if supplied else make new */ + (unsigned short *) fs_get (65536 * sizeof (unsigned short)); + /* initialize table for ASCII */ + for (i = 0; i < 128; i++) rmap[i] = (unsigned short) i; + /* populate remainder of table with NOCHAR */ +#define NOCHARBYTE (NOCHAR & 0xff) +#if NOCHAR - ((NOCHARBYTE << 8) | NOCHARBYTE) + while (i < 65536) rmap[i++] = NOCHAR; +#else + memset (rmap + 128,NOCHARBYTE,(65536 - 128) * sizeof (unsigned short)); +#endif + break; + default: /* unsupported charset type */ + rmap = NIL; /* no map possible */ + } + if (rmap) { /* have a map? */ + switch (cs->type) { /* additional reverse map actions */ + case CT_1BYTE0: /* 1 byte no table */ + for (i = 128; i < 256; i++) rmap[i] = (unsigned short) i; + break; + case CT_1BYTE: /* 1 byte ASCII + table 0x80-0xff */ + for (tab = (unsigned short *) cs->tab,i = 128; i < 256; i++) + if (tab[i & BITS7] != UBOGON) rmap[tab[i & BITS7]] = (unsigned short)i; + break; + case CT_1BYTE8: /* 1 byte table 0x00 - 0xff */ + for (tab = (unsigned short *) cs->tab,i = 0; i < 256; i++) + if (tab[i] != UBOGON) rmap[tab[i]] = (unsigned short) i; + break; + case CT_EUC: /* 2 byte ASCII + utf8_eucparam base/CS2/CS3 */ + for (param = (struct utf8_eucparam *) cs->tab, + tab = (unsigned short *) param->tab, ku = 0; + ku < param->max_ku; ku++) + for (ten = 0; ten < param->max_ten; ten++) + if ((u = tab[(ku * param->max_ten) + ten]) != UBOGON) + rmap[u] = ((ku + param->base_ku) << 8) + + (ten + param->base_ten) + 0x8080; + break; + + case CT_DBYTE: /* 2 byte ASCII + utf8_eucparam */ + for (param = (struct utf8_eucparam *) cs->tab, + tab = (unsigned short *) param->tab, ku = 0; + ku < param->max_ku; ku++) + for (ten = 0; ten < param->max_ten; ten++) + if ((u = tab[(ku * param->max_ten) + ten]) != UBOGON) + rmap[u] = ((ku + param->base_ku) << 8) + (ten + param->base_ten); + break; + case CT_DBYTE2: /* 2 byte ASCII + utf8_eucparam plane1/2 */ + param = (struct utf8_eucparam *) cs->tab; + p2 = param + 1; /* plane 2 parameters */ + /* only ten parameters should differ */ + if ((param->base_ku != p2->base_ku) || (param->max_ku != p2->max_ku)) + fatal ("ku definition error for CT_DBYTE2 charset"); + /* total codepoints in each ku */ + m = param->max_ten + p2->max_ten; + tab = (unsigned short *) param->tab; + for (ku = 0; ku < param->max_ku; ku++) { + for (ten = 0; ten < param->max_ten; ten++) + if ((u = tab[(ku * m) + ten]) != UBOGON) + rmap[u] = ((ku + param->base_ku) << 8) + (ten + param->base_ten); + for (ten = 0; ten < p2->max_ten; ten++) + if ((u = tab[(ku * m) + param->max_ten + ten]) != UBOGON) + rmap[u] = ((ku + param->base_ku) << 8) + (ten + p2->base_ten); + } + break; + case CT_SJIS: /* 2 byte Shift-JIS */ + for (ku = 0; ku < MAX_JIS0208_KU; ku++) + for (ten = 0; ten < MAX_JIS0208_TEN; ten++) + if ((u = jis0208tab[ku][ten]) != UBOGON) { + int sku = ku + BASE_JIS0208_KU; + int sten = ten + BASE_JIS0208_TEN; + rmap[u] = ((((sku + 1) >> 1) + ((sku < 95) ? 112 : 176)) << 8) + + sten + ((sku % 2) ? ((sten > 95) ? 32 : 31) : 126); + } + /* JIS Roman */ + rmap[UCS2_YEN] = JISROMAN_YEN; + rmap[UCS2_OVERLINE] = JISROMAN_OVERLINE; + /* JIS hankaku katakana */ + for (u = 0; u < (MAX_KANA_8 - MIN_KANA_8); u++) + rmap[UCS2_KATAKANA + u] = MIN_KANA_8 + u; + break; + } + /* hack: map NBSP to SP if otherwise no map */ + if (rmap[0x00a0] == NOCHAR) rmap[0x00a0] = rmap[0x0020]; + } + return rmap; /* return map */ +} + +/* Convert UTF-8 sized text to charset using rmap + * Accepts: source sized text + * conversion rmap + * pointer to returned sized text + * substitute character if not in rmap, else NIL to return failure + * ISO-2022-JP conversion flag + * Returns T if successful, NIL if failure + * + * This routine doesn't try to convert to all possible charsets; in particular + * it doesn't support other Unicode encodings or any ISO 2022 other than + * ISO-2022-JP. + */ + +long utf8_rmaptext (SIZEDTEXT *text,unsigned short *rmap,SIZEDTEXT *ret, + unsigned long errch,long iso2022jp) +{ + unsigned long i,u,c; + /* get size of buffer */ + if (i = utf8_rmapsize (text,rmap,errch,iso2022jp)) { + unsigned char *s = text->data; + unsigned char *t = ret->data = (unsigned char *) fs_get (i); + ret->size = i - 1; /* number of octets in destination buffer */ + /* start non-zero ISO-2022-JP state at 1 */ + if (iso2022jp) iso2022jp = 1; + /* convert string, ignore BOM */ + for (i = text->size; i;) if ((u = utf8_get (&s,&i)) != UCS2_BOM) { + /* substitute error character for NOCHAR */ + if ((u & U8GM_NONBMP) || ((c = rmap[u]) == NOCHAR)) c = errch; + switch (iso2022jp) { /* depends upon ISO 2022 mode */ + case 0: /* ISO 2022 not in effect */ + /* two-byte character */ + if (c > 0xff) *t++ = (unsigned char) (c >> 8); + /* single-byte or low-byte of two-byte */ + *t++ = (unsigned char) (c & 0xff); + break; + case 1: /* ISO 2022 Roman */ + /* <ch> */ + if (c < 0x80) *t++ = (unsigned char) c; + else { /* JIS character */ + *t++ = I2C_ESC; /* ESC $ B <hi> <lo> */ + *t++ = I2C_MULTI; + *t++ = I2CS_94x94_JIS_NEW; + *t++ = (unsigned char) (c >> 8) & 0x7f; + *t++ = (unsigned char) c & 0x7f; + iso2022jp = 2; /* shift to ISO 2022 JIS */ + } + break; + case 2: /* ISO 2022 JIS */ + if (c > 0x7f) { /* <hi> <lo> */ + *t++ = (unsigned char) (c >> 8) & 0x7f; + *t++ = (unsigned char) c & 0x7f; + } + else { /* ASCII character */ + *t++ = I2C_ESC; /* ESC ( J <ch> */ + *t++ = I2C_G0_94; + *t++ = I2CS_94_JIS_ROMAN; + *t++ = (unsigned char) c; + iso2022jp = 1; /* shift to ISO 2022 Roman */ + } + break; + } + } + if (iso2022jp == 2) { /* ISO-2022-JP string must end in Roman */ + *t++ = I2C_ESC; /* ESC ( J */ + *t++ = I2C_G0_94; + *t++ = I2CS_94_JIS_ROMAN; + } + *t++ = NIL; /* tie off returned data */ + return LONGT; /* return success */ + } + ret->data = NIL; + ret->size = 0; + return NIL; /* failure */ +} + +/* Calculate size of convertsion of UTF-8 sized text to charset using rmap + * Accepts: source sized text + * conversion rmap + * pointer to returned sized text + * substitute character if not in rmap, else NIL to return failure + * ISO-2022-JP conversion flag + * Returns size+1 if successful, NIL if failure + * + * This routine doesn't try to handle to all possible charsets; in particular + * it doesn't support other Unicode encodings or any ISO 2022 other than + * ISO-2022-JP. + */ + +unsigned long utf8_rmapsize (SIZEDTEXT *text,unsigned short *rmap, + unsigned long errch,long iso2022jp) +{ + unsigned long i,u,c; + unsigned long ret = 1; /* terminating NUL */ + unsigned char *s = text->data; + if (iso2022jp) iso2022jp = 1; /* start non-zero ISO-2022-JP state at 1 */ + for (i = text->size; i;) if ((u = utf8_get (&s,&i)) != UCS2_BOM) { + if ((u & U8GM_NONBMP) || (((c = rmap[u]) == NOCHAR) && !(c = errch))) + return NIL; /* not in BMP, or NOCHAR and no err char */ + switch (iso2022jp) { /* depends upon ISO 2022 mode */ + case 0: /* ISO 2022 not in effect */ + ret += (c > 0xff) ? 2 : 1; + break; + case 1: /* ISO 2022 Roman */ + if (c < 0x80) ret += 1; /* <ch> */ + else { /* JIS character */ + ret += 5; /* ESC $ B <hi> <lo> */ + iso2022jp = 2; /* shift to ISO 2022 JIS */ + } + break; + case 2: /* ISO 2022 JIS */ + if (c > 0x7f) ret += 2; /* <hi> <lo> */ + else { /* ASCII character */ + ret += 4; /* ESC ( J <ch> */ + iso2022jp = 1; /* shift to ISO 2022 Roman */ + } + break; + } + } + if (iso2022jp == 2) { /* ISO-2022-JP string must end in Roman */ + ret += 3; /* ESC ( J */ + iso2022jp = 1; /* reset state to Roman */ + } + return ret; +} + +/* Convert UCS-4 to charset using rmap + * Accepts: source UCS-4 character(s) + * numver of UCS-4 characters + * conversion rmap + * pointer to returned sized text + * substitute character if not in rmap, else NIL to return failure + * Returns T if successful, NIL if failure + * + * Currently only supports BMP characters, and does not support ISO-2022 + */ + +long ucs4_rmaptext (unsigned long *ucs4,unsigned long len,unsigned short *rmap, + SIZEDTEXT *ret,unsigned long errch) +{ + long size = ucs4_rmaplen (ucs4,len,rmap,errch); + return (size >= 0) ? /* build in newly-created buffer */ + ucs4_rmapbuf (ret->data = (unsigned char *) fs_get ((ret->size = size) +1), + ucs4,len,rmap,errch) : NIL; +} + +/* Return size of UCS-4 string converted to other CS via rmap + * Accepts: source UCS-4 character(s) + * numver of UCS-4 characters + * conversion rmap + * substitute character if not in rmap, else NIL to return failure + * Returns: length if success, negative if failure (no-convert) + */ + +long ucs4_rmaplen (unsigned long *ucs4,unsigned long len,unsigned short *rmap, + unsigned long errch) +{ + long ret; + unsigned long i,u,c; + /* count non-BOM characters */ + for (ret = 0,i = 0; i < len; ++i) if ((u = ucs4[i]) != UCS2_BOM) { + if ((u & U8GM_NONBMP) || (((c = rmap[u]) == NOCHAR) && !(c = errch))) + return -1; /* not in BMP, or NOCHAR and no err char? */ + ret += (c > 0xff) ? 2 : 1; + } + return ret; +} + + +/* Stuff buffer with UCS-4 string converted to other CS via rmap + * Accepts: destination buffer + * source UCS-4 character(s) + * number of UCS-4 characters + * conversion rmap + * substitute character if not in rmap, else NIL to return failure + * Returns: T, always + */ + +long ucs4_rmapbuf (unsigned char *t,unsigned long *ucs4,unsigned long len, + unsigned short *rmap,unsigned long errch) +{ + unsigned long i,u,c; + /* convert non-BOM characters */ + for (i = 0; i < len; ++i) if ((u = ucs4[i]) != UCS2_BOM) { + /* substitute error character for NOCHAR */ + if ((u & U8GM_NONBMP) || ((c = rmap[u]) == NOCHAR)) c = errch; + /* two-byte character? */ + if (c > 0xff) *t++ = (unsigned char) (c >> 8); + /* single-byte or low-byte of two-byte */ + *t++ = (unsigned char) (c & 0xff); + } + *t++ = NIL; /* tie off returned data */ + return LONGT; +} + +/* Return UCS-4 Unicode character from UTF-8 string + * Accepts: pointer to string + * remaining octets in string + * Returns: UCS-4 character with pointer and count updated + * or error code with pointer and count unchanged + */ + +unsigned long utf8_get (unsigned char **s,unsigned long *i) +{ + unsigned char *t = *s; + unsigned long j = *i; + /* decode raw UTF-8 string */ + unsigned long ret = utf8_get_raw (&t,&j); + if (ret & U8G_ERROR); /* invalid raw UTF-8 decoding? */ + /* no, is it surrogate? */ + else if ((ret >= UTF16_SURR) && (ret <= UTF16_MAXSURR)) ret = U8G_SURROGA; + /* or in non-Unicode ISO 10646 space? */ + else if (ret > UCS4_MAXUNICODE) ret = U8G_NOTUNIC; + else { + *s = t; /* all is well, update pointer */ + *i = j; /* and counter */ + } + return ret; /* return value */ +} + +/* Return raw (including non-Unicode) UCS-4 character from UTF-8 string + * Accepts: pointer to string + * remaining octets in string + * Returns: UCS-4 character with pointer and count updated + * or error code with pointer and count unchanged + */ + +unsigned long utf8_get_raw (unsigned char **s,unsigned long *i) +{ + unsigned char c,c1; + unsigned char *t = *s; + unsigned long j = *i; + unsigned long ret = U8G_NOTUTF8; + int more = 0; + do { /* make sure have source octets available */ + if (!j--) return more ? U8G_ENDSTRI : U8G_ENDSTRG; + /* UTF-8 continuation? */ + else if (((c = *t++) > 0x7f) && (c < 0xc0)) { + /* continuation when not in progress */ + if (!more) return U8G_BADCONT; + --more; /* found a continuation octet */ + ret <<= 6; /* shift current value by 6 bits */ + ret |= c & 0x3f; /* merge continuation octet */ + } + /* incomplete UTF-8 character */ + else if (more) return U8G_INCMPLT; + else { /* start of sequence */ + c1 = j ? *t : 0xbf; /* assume valid continuation if incomplete */ + if (c < 0x80) ret = c; /* U+0000 - U+007f */ + else if (c < 0xc2); /* c0 and c1 never valid */ + else if (c < 0xe0) { /* U+0080 - U+07ff */ + if (c &= 0x1f) more = 1; + } + else if (c < 0xf0) { /* U+0800 - U+ffff */ + if ((c &= 0x0f) || (c1 >= 0xa0)) more = 2; + } + else if (c < 0xf8) { /* U+10000 - U+10ffff (and 110000 - 1fffff) */ + if ((c &= 0x07) || (c1 >= 0x90)) more = 3; + } + else if (c < 0xfc) { /* ISO 10646 200000 - 3ffffff */ + if ((c &= 0x03) || (c1 >= 0x88)) more = 4; + } + else if (c < 0xfe) { /* ISO 10646 4000000 - 7fffffff */ + if ((c &= 0x01) || (c1 >= 0x84)) more = 5; + } + /* fe and ff never valid */ + if (more) { /* multi-octet, make sure more to come */ + if (!j) return U8G_ENDSTRI; + ret = c; /* continuation needed, save start bits */ + } + } + } while (more); + if (!(ret & U8G_ERROR)) { /* success return? */ + *s = t; /* yes, update pointer */ + *i = j; /* and counter */ + } + return ret; /* return value */ +} + +/* Return UCS-4 character from named charset string + * Accepts: charset + * pointer to string + * remaining octets in string + * Returns: UCS-4 character with pointer and count updated, negative if error + * + * Error codes are the same as utf8_get(). + */ + +unsigned long ucs4_cs_get (CHARSET *cs,unsigned char **s,unsigned long *i) +{ + unsigned char c,c1,ku,ten; + unsigned long ret,d; + unsigned char *t = *s; + unsigned long j = *i; + struct utf8_eucparam *p1,*p2,*p3; + if (j--) c = *t++; /* get first octet */ + else return U8G_ENDSTRG; /* empty string */ + switch (cs->type) { /* convert if type known */ + case CT_UTF8: /* variable UTF-8 encoded Unicode no table */ + return utf8_get (s,i); + case CT_ASCII: /* 7-bit ASCII no table */ + if (c >= 0x80) return U8G_NOTUTF8; + case CT_1BYTE0: /* 1 byte no table */ + ret = c; /* identity */ + break; + case CT_1BYTE: /* 1 byte ASCII + table 0x80-0xff */ + ret = (c > 0x80) ? ((unsigned short *) cs->tab)[c & BITS7] : c; + break; + case CT_1BYTE8: /* 1 byte table 0x00 - 0xff */ + ret = ((unsigned short *) cs->tab)[c]; + break; + + case CT_EUC: /* 2 byte ASCII + utf8_eucparam base/CS2/CS3 */ + if (c & BIT8) { + p1 = (struct utf8_eucparam *) cs->tab; + p2 = p1 + 1; + p3 = p1 + 2; + if (j--) c1 = *t++; /* get second octet */ + else return U8G_ENDSTRI; + if (!(c1 & BIT8)) return U8G_NOTUTF8; + switch (c) { /* check 8bit code set */ + case EUC_CS2: /* CS2 */ + if (p2->base_ku) { /* CS2 set up? */ + if (p2->base_ten) { /* yes, multibyte? */ + if (j--) c = *t++; /* get second octet */ + else return U8G_ENDSTRI; + if ((c & BIT8) && + ((ku = (c1 & BITS7) - p2->base_ku) < p2->max_ku) && + ((ten = (c & BITS7) - p2->base_ten) < p2->max_ten)) { + ret = ((unsigned short *) p2->tab)[(ku*p2->max_ten) + ten]; + break; + } + } + else if ((c1 >= p2->base_ku) && (c1 < p2->max_ku)) { + ret = c1 + ((unsigned long) p2->tab); + break; + } + } + return U8G_NOTUTF8; /* CS2 not set up or bogus */ + case EUC_CS3: /* CS3 */ + if (p3->base_ku) { /* CS3 set up? */ + if (p3->base_ten) { /* yes, multibyte? */ + if (j--) c = *t++; /* get second octet */ + else return U8G_ENDSTRI; + if ((c & BIT8) && + ((ku = (c1 & BITS7) - p3->base_ku) < p3->max_ku) && + ((ten = (c & BITS7) - p3->base_ten) < p3->max_ten)) { + ret = ((unsigned short *) p3->tab)[(ku*p3->max_ten) + ten]; + break; + } + } + else if ((c1 >= p3->base_ku) && (c1 < p3->max_ku)) { + ret = c1 + ((unsigned long) p3->tab); + break; + } + } + return U8G_NOTUTF8; /* CS3 not set up or bogus */ + default: + if (((ku = (c & BITS7) - p1->base_ku) >= p1->max_ku) || + ((ten = (c1 & BITS7) - p1->base_ten) >= p1->max_ten)) + return U8G_NOTUTF8; + ret = ((unsigned short *) p1->tab)[(ku*p1->max_ten) + ten]; + /* special hack for JIS X 0212: merge rows less than 10 */ + if ((ret == UBOGON) && ku && (ku < 10) && p3->tab && p3->base_ten) + ret = ((unsigned short *) p3->tab) + [((ku - (p3->base_ku - p1->base_ku))*p3->max_ten) + ten]; + break; + } + } + else ret = c; /* ASCII character */ + break; + + case CT_DBYTE: /* 2 byte ASCII + utf8_eucparam */ + if (c & BIT8) { /* double-byte character? */ + p1 = (struct utf8_eucparam *) cs->tab; + if (j--) c1 = *t++; /* get second octet */ + else return U8G_ENDSTRI; + if (((ku = c - p1->base_ku) < p1->max_ku) && + ((ten = c1 - p1->base_ten) < p1->max_ten)) + ret = ((unsigned short *) p1->tab)[(ku*p1->max_ten) + ten]; + else return U8G_NOTUTF8; + } + else ret = c; /* ASCII character */ + break; + case CT_DBYTE2: /* 2 byte ASCII + utf8_eucparam plane1/2 */ + if (c & BIT8) { /* double-byte character? */ + p1 = (struct utf8_eucparam *) cs->tab; + p2 = p1 + 1; + if (j--) c1 = *t++; /* get second octet */ + else return U8G_ENDSTRI; + if (c1 & BIT8) { /* high vs. low plane */ + if ((ku = c - p2->base_ku) < p2->max_ku && + ((ten = c1 - p2->base_ten) < p2->max_ten)) + ret = ((unsigned short *) p1->tab) + [(ku*(p1->max_ten + p2->max_ten)) + p1->max_ten + ten]; + else return U8G_NOTUTF8; + } + else if ((ku = c - p1->base_ku) < p1->max_ku && + ((ten = c1 - p1->base_ten) < p1->max_ten)) + ret = ((unsigned short *) p1->tab) + [(ku*(p1->max_ten + p2->max_ten)) + ten]; + else return U8G_NOTUTF8; + } + else ret = c; /* ASCII character */ + break; + case CT_SJIS: /* 2 byte Shift-JIS encoded JIS no table */ + /* compromise - do yen sign but not overline */ + if (!(c & BIT8)) ret = (c == JISROMAN_YEN) ? UCS2_YEN : c; + /* half-width katakana? */ + else if ((c >= MIN_KANA_8) && (c < MAX_KANA_8)) ret = c + KANA_8; + else { /* Shift-JIS */ + if (j--) c1 = *t++; /* get second octet */ + else return U8G_ENDSTRI; + SJISTOJIS (c,c1); + c = JISTOUNICODE (c,c1,ku,ten); + } + break; + + case CT_UCS2: /* 2 byte 16-bit Unicode no table */ + ret = c << 8; + if (j--) c = *t++; /* get second octet */ + else return U8G_ENDSTRI; /* empty string */ + ret |= c; + break; + case CT_UCS4: /* 4 byte 32-bit Unicode no table */ + if (c & 0x80) return U8G_NOTUTF8; + if (j < 3) return U8G_ENDSTRI; + j -= 3; /* count three octets */ + ret = c << 24; + ret |= (*t++) << 16; + ret |= (*t++) << 8; + ret |= (*t++); + break; + case CT_UTF16: /* variable UTF-16 encoded Unicode no table */ + ret = c << 8; + if (j--) c = *t++; /* get second octet */ + else return U8G_ENDSTRI; /* empty string */ + ret |= c; + /* surrogate? */ + if ((ret >= UTF16_SURR) && (ret <= UTF16_MAXSURR)) { + /* invalid first surrogate */ + if ((ret > UTF16_SURRHEND) || (j < 2)) return U8G_NOTUTF8; + j -= 2; /* count two octets */ + d = (*t++) << 8; /* first octet of second surrogate */ + d |= *t++; /* second octet of second surrogate */ + if ((d < UTF16_SURRL) || (d > UTF16_SURRLEND)) return U8G_NOTUTF8; + ret = UTF16_BASE + ((ret & UTF16_MASK) << UTF16_SHIFT) + + (d & UTF16_MASK); + } + break; + default: /* unknown/unsupported character set type */ + return U8G_NOTUTF8; + } + *s = t; /* update pointer and counter */ + *i = j; + return ret; +} + +/* Produce charset validity map for BMP + * Accepts: list of charsets to map + * Returns: validity map, indexed by BMP codepoint + * + * Bit 0x1 is the "not-CJK" character bit + */ + +unsigned long *utf8_csvalidmap (char *charsets[]) +{ + unsigned short u,*tab; + unsigned int m,ku,ten; + unsigned long i,csi,csb; + struct utf8_eucparam *param,*p2; + char *s; + const CHARSET *cs; + unsigned long *ret = (unsigned long *) + fs_get (i = 0x10000 * sizeof (unsigned long)); + memset (ret,0,i); /* zero the entire vector */ + /* mark all the non-CJK codepoints */ + /* U+0000 - U+2E7F non-CJK */ + for (i = 0; i < 0x2E7F; ++i) ret[i] = 0x1; + /* U+2E80 - U+2EFF CJK Radicals Supplement + * U+2F00 - U+2FDF Kangxi Radicals + * U+2FE0 - U+2FEF unassigned + * U+2FF0 - U+2FFF Ideographic Description Characters + * U+3000 - U+303F CJK Symbols and Punctuation + * U+3040 - U+309F Hiragana + * U+30A0 - U+30FF Katakana + * U+3100 - U+312F BoPoMoFo + * U+3130 - U+318F Hangul Compatibility Jamo + * U+3190 - U+319F Kanbun + * U+31A0 - U+31BF BoPoMoFo Extended + * U+31C0 - U+31EF CJK Strokes + * U+31F0 - U+31FF Katakana Phonetic Extensions + * U+3200 - U+32FF Enclosed CJK Letters and Months + * U+3300 - U+33FF CJK Compatibility + * U+3400 - U+4DBF CJK Unified Ideographs Extension A + * U+4DC0 - U+4DFF Yijing Hexagram Symbols + * U+4E00 - U+9FFF CJK Unified Ideographs + * U+A000 - U+A48F Yi Syllables + * U+A490 - U+A4CF Yi Radicals + * U+A700 - U+A71F Modifier Tone Letters + */ + for (i = 0xa720; i < 0xabff; ++i) ret[i] = 0x1; + /* U+AC00 - U+D7FF Hangul Syllables */ + for (i = 0xd800; i < 0xf8ff; ++i) ret[i] = 0x1; + /* U+F900 - U+FAFF CJK Compatibility Ideographs */ + for (i = 0xfb00; i < 0xfe2f; ++i) ret[i] = 0x1; + /* U+FE30 - U+FE4F CJK Compatibility Forms + * U+FE50 - U+FE6F Small Form Variants (for CNS 11643) + */ + for (i = 0xfe70; i < 0xfeff; ++i) ret[i] = 0x1; + /* U+FF00 - U+FFEF CJK Compatibility Ideographs */ + for (i = 0xfff0; i < 0x10000; ++i) ret[i] = 0x1; + + /* for each supplied charset */ + for (csi = 1; ret && charsets && (s = charsets[csi - 1]); ++csi) { + /* substitute EUC-JP for ISO-2022-JP */ + if (!compare_cstring (s,"ISO-2022-JP")) s = "EUC-JP"; + /* look up charset */ + if (cs = utf8_charset (s)) { + csb = 1 << csi; /* charset bit */ + switch (cs->type) { + case CT_ASCII: /* 7-bit ASCII no table */ + case CT_1BYTE0: /* 1 byte no table */ + case CT_1BYTE: /* 1 byte ASCII + table 0x80-0xff */ + case CT_1BYTE8: /* 1 byte table 0x00 - 0xff */ + case CT_EUC: /* 2 byte ASCII + utf8_eucparam base/CS2/CS3 */ + case CT_DBYTE: /* 2 byte ASCII + utf8_eucparam */ + case CT_DBYTE2: /* 2 byte ASCII + utf8_eucparam plane1/2 */ + case CT_SJIS: /* 2 byte Shift-JIS */ + /* supported charset type, all ASCII is OK */ + for (i = 0; i < 128; ++i) ret[i] |= csb; + break; + default: /* unsupported charset type */ + fs_give ((void **) &ret); + break; + } + /* now do additional operations */ + if (ret) switch (cs->type) { + case CT_1BYTE0: /* 1 byte no table */ + for (i = 128; i < 256; i++) ret[i] |= csb; + break; + case CT_1BYTE: /* 1 byte ASCII + table 0x80-0xff */ + for (tab = (unsigned short *) cs->tab,i = 128; i < 256; i++) + if (tab[i & BITS7] != UBOGON) ret[tab[i & BITS7]] |= csb; + break; + case CT_1BYTE8: /* 1 byte table 0x00 - 0xff */ + for (tab = (unsigned short *) cs->tab,i = 0; i < 256; i++) + if (tab[i] != UBOGON) ret[tab[i]] |= csb; + break; + case CT_EUC: /* 2 byte ASCII + utf8_eucparam base/CS2/CS3 */ + for (param = (struct utf8_eucparam *) cs->tab, + tab = (unsigned short *) param->tab, ku = 0; + ku < param->max_ku; ku++) + for (ten = 0; ten < param->max_ten; ten++) + if ((u = tab[(ku * param->max_ten) + ten]) != UBOGON) + ret[u] |= csb; + break; + + case CT_DBYTE: /* 2 byte ASCII + utf8_eucparam */ + for (param = (struct utf8_eucparam *) cs->tab, + tab = (unsigned short *) param->tab, ku = 0; + ku < param->max_ku; ku++) + for (ten = 0; ten < param->max_ten; ten++) + if ((u = tab[(ku * param->max_ten) + ten]) != UBOGON) + ret[u] |= csb; + break; + case CT_DBYTE2: /* 2 byte ASCII + utf8_eucparam plane1/2 */ + param = (struct utf8_eucparam *) cs->tab; + p2 = param + 1; /* plane 2 parameters */ + /* only ten parameters should differ */ + if ((param->base_ku != p2->base_ku) || (param->max_ku != p2->max_ku)) + fatal ("ku definition error for CT_DBYTE2 charset"); + /* total codepoints in each ku */ + m = param->max_ten + p2->max_ten; + tab = (unsigned short *) param->tab; + for (ku = 0; ku < param->max_ku; ku++) { + for (ten = 0; ten < param->max_ten; ten++) + if ((u = tab[(ku * m) + ten]) != UBOGON) + ret[u] |= csb; + for (ten = 0; ten < p2->max_ten; ten++) + if ((u = tab[(ku * m) + param->max_ten + ten]) != UBOGON) + ret[u] |= csb; + } + break; + case CT_SJIS: /* 2 byte Shift-JIS */ + for (ku = 0; ku < MAX_JIS0208_KU; ku++) + for (ten = 0; ten < MAX_JIS0208_TEN; ten++) + if ((u = jis0208tab[ku][ten]) != UBOGON) ret[u] |= csb; + /* JIS hankaku katakana */ + for (u = 0; u < (MAX_KANA_8 - MIN_KANA_8); u++) + ret[UCS2_KATAKANA + u] |= csb; + break; + } + } + /* invalid charset, punt */ + else fs_give ((void **) &ret); + } + return ret; +} + +/* Infer charset from unlabelled sized text + * Accepts: sized text + * Returns: charset if one inferred, or NIL if unknown + */ + +const CHARSET *utf8_infercharset (SIZEDTEXT *src) +{ + long iso2022jp = NIL; + long eightbit = NIL; + unsigned long i; + /* look for ISO 2022 */ + if (src) for (i = 0; i < src->size; i++) { + /* ESC sequence? */ + if ((src->data[i] == I2C_ESC) && (++i < src->size)) switch (src->data[i]) { + case I2C_MULTI: /* yes, multibyte? */ + if (++i < src->size) switch (src->data[i]) { + case I2CS_94x94_JIS_OLD: /* JIS X 0208-1978 */ + case I2CS_94x94_JIS_NEW: /* JIS X 0208-1983 */ + case I2CS_94x94_JIS_EXT: /* JIS X 0212-1990 (kludge...) */ + iso2022jp = T; /* found an ISO-2022-JP sequence */ + break; + default: /* other multibyte */ + return NIL; /* definitely invalid */ + } + break; + case I2C_G0_94: /* single byte */ + if (++i < src->size) switch (src->data[i]) { + case I2CS_94_JIS_BUGROM: /* in case old buggy software */ + case I2CS_94_JIS_ROMAN: /* JIS X 0201-1976 left half */ + case I2CS_94_ASCII: /* ASCII */ + case I2CS_94_BRITISH: /* good enough for gov't work */ + break; + default: /* other 94 single byte */ + return NIL; /* definitely invalid */ + } + } + /* if possible UTF-8 and not ISO-2022-JP */ + else if (!iso2022jp && (eightbit >= 0) && (src->data[i] & BIT8) && + (eightbit = utf8_validate (src->data + i,src->size - i)) > 0) + i += eightbit - 1; /* skip past all but last of UTF-8 char */ + } + /* ISO-2022-JP overrides other guesses */ + if (iso2022jp) return utf8_charset ("ISO-2022-JP"); + if (eightbit > 0) return utf8_charset ("UTF-8"); + return eightbit ? NIL : utf8_charset ("US-ASCII"); +} + + +/* Validate that character at this position is UTF-8 + * Accepts: string pointer + * size of remaining string + * Returns: size of UTF-8 character in octets or -1 if not UTF-8 + */ + +long utf8_validate (unsigned char *s,unsigned long i) +{ + unsigned long j = i; + return (utf8_get (&s,&i) & U8G_ERROR) ? -1 : j - i; +} + +/* Convert ISO 8859-1 to UTF-8 + * Accepts: source sized text + * pointer to return sized text + * canonicalization function + */ + +void utf8_text_1byte0 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de) +{ + unsigned long i; + unsigned char *s; + unsigned int c; + for (ret->size = i = 0; i < text->size;) { + c = text->data[i++]; + UTF8_COUNT_BMP (ret->size,c,cv,de) + } + (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] =NIL; + for (i = 0; i < text->size;) { + c = text->data[i++]; + UTF8_WRITE_BMP (s,c,cv,de) /* convert UCS-2 to UTF-8 */ + } +} + + +/* Convert single byte ASCII+8bit character set sized text to UTF-8 + * Accepts: source sized text + * pointer to return sized text + * conversion table + * canonicalization function + */ + +void utf8_text_1byte (SIZEDTEXT *text,SIZEDTEXT *ret,void *tab,ucs4cn_t cv, + ucs4de_t de) +{ + unsigned long i; + unsigned char *s; + unsigned int c; + unsigned short *tbl = (unsigned short *) tab; + for (ret->size = i = 0; i < text->size;) { + if ((c = text->data[i++]) & BIT8) c = tbl[c & BITS7]; + UTF8_COUNT_BMP (ret->size,c,cv,de) + } + (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] =NIL; + for (i = 0; i < text->size;) { + if ((c = text->data[i++]) & BIT8) c = tbl[c & BITS7]; + UTF8_WRITE_BMP (s,c,cv,de) /* convert UCS-2 to UTF-8 */ + } +} + +/* Convert single byte 8bit character set sized text to UTF-8 + * Accepts: source sized text + * pointer to return sized text + * conversion table + * canonicalization function + */ + +void utf8_text_1byte8 (SIZEDTEXT *text,SIZEDTEXT *ret,void *tab,ucs4cn_t cv, + ucs4de_t de) +{ + unsigned long i; + unsigned char *s; + unsigned int c; + unsigned short *tbl = (unsigned short *) tab; + for (ret->size = i = 0; i < text->size;) { + c = tbl[text->data[i++]]; + UTF8_COUNT_BMP (ret->size,c,cv,de) + } + (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] =NIL; + for (i = 0; i < text->size;) { + c = tbl[text->data[i++]]; + UTF8_WRITE_BMP (s,c,cv,de) /* convert UCS-2 to UTF-8 */ + } +} + +/* Convert EUC sized text to UTF-8 + * Accepts: source sized text + * pointer to return sized text + * EUC parameter table + * canonicalization function + */ + +void utf8_text_euc (SIZEDTEXT *text,SIZEDTEXT *ret,void *tab,ucs4cn_t cv, + ucs4de_t de) +{ + unsigned long i; + unsigned char *s; + unsigned int pass,c,c1,ku,ten; + struct utf8_eucparam *p1 = (struct utf8_eucparam *) tab; + struct utf8_eucparam *p2 = p1 + 1; + struct utf8_eucparam *p3 = p1 + 2; + unsigned short *t1 = (unsigned short *) p1->tab; + unsigned short *t2 = (unsigned short *) p2->tab; + unsigned short *t3 = (unsigned short *) p3->tab; + for (pass = 0,s = NIL,ret->size = 0; pass <= 1; pass++) { + for (i = 0; i < text->size;) { + /* not CS0? */ + if ((c = text->data[i++]) & BIT8) { + /* yes, must have another high byte */ + if ((i >= text->size) || !((c1 = text->data[i++]) & BIT8)) + c = UBOGON; /* out of space or bogon */ + else switch (c) { /* check 8bit code set */ + case EUC_CS2: /* CS2 */ + if (p2->base_ku) { /* CS2 set up? */ + if (p2->base_ten) /* yes, multibyte? */ + c = ((i < text->size) && ((c = text->data[i++]) & BIT8) && + ((ku = (c1 & BITS7) - p2->base_ku) < p2->max_ku) && + ((ten = (c & BITS7) - p2->base_ten) < p2->max_ten)) ? + t2[(ku*p2->max_ten) + ten] : UBOGON; + else c = ((c1 >= p2->base_ku) && (c1 < p2->max_ku)) ? + c1 + ((unsigned long) p2->tab) : UBOGON; + } + else { /* CS2 not set up */ + c = UBOGON; /* swallow byte, say bogon */ + if (i < text->size) i++; + } + break; + case EUC_CS3: /* CS3 */ + if (p3->base_ku) { /* CS3 set up? */ + if (p3->base_ten) /* yes, multibyte? */ + c = ((i < text->size) && ((c = text->data[i++]) & BIT8) && + ((ku = (c1 & BITS7) - p3->base_ku) < p3->max_ku) && + ((ten = (c & BITS7) - p3->base_ten) < p3->max_ten)) ? + t3[(ku*p3->max_ten) + ten] : UBOGON; + else c = ((c1 >= p3->base_ku) && (c1 < p3->max_ku)) ? + c1 + ((unsigned long) p3->tab) : UBOGON; + } + else { /* CS3 not set up */ + c = UBOGON; /* swallow byte, say bogon */ + if (i < text->size) i++; + } + break; + + default: + if (((ku = (c & BITS7) - p1->base_ku) >= p1->max_ku) || + ((ten = (c1 & BITS7) - p1->base_ten) >= p1->max_ten)) c = UBOGON; + else if (((c = t1[(ku*p1->max_ten) + ten]) == UBOGON) && + /* special hack for JIS X 0212: merge rows less than 10 */ + ku && (ku < 10) && t3 && p3->base_ten) + c = t3[((ku - (p3->base_ku - p1->base_ku))*p3->max_ten) + ten]; + } + } + /* convert if second pass */ + if (pass) UTF8_WRITE_BMP (s,c,cv,de) + else UTF8_COUNT_BMP (ret->size,c,cv,de); + } + if (!pass) (s = ret->data = (unsigned char *) + fs_get (ret->size + 1))[ret->size] =NIL; + } +} + + +/* Convert ASCII + double-byte sized text to UTF-8 + * Accepts: source sized text + * pointer to return sized text + * conversion table + * canonicalization function + */ + +void utf8_text_dbyte (SIZEDTEXT *text,SIZEDTEXT *ret,void *tab,ucs4cn_t cv, + ucs4de_t de) +{ + unsigned long i; + unsigned char *s; + unsigned int c,c1,ku,ten; + struct utf8_eucparam *p1 = (struct utf8_eucparam *) tab; + unsigned short *t1 = (unsigned short *) p1->tab; + for (ret->size = i = 0; i < text->size;) { + if ((c = text->data[i++]) & BIT8) { + /* special hack for GBK: 0x80 is Euro */ + if ((c == 0x80) && (t1 == (unsigned short *) gb2312tab)) c = UCS2_EURO; + else c = ((i < text->size) && (c1 = text->data[i++]) && + ((ku = c - p1->base_ku) < p1->max_ku) && + ((ten = c1 - p1->base_ten) < p1->max_ten)) ? + t1[(ku*p1->max_ten) + ten] : UBOGON; + } + UTF8_COUNT_BMP (ret->size,c,cv,de) + } + (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] = NIL; + for (i = 0; i < text->size;) { + if ((c = text->data[i++]) & BIT8) { + /* special hack for GBK: 0x80 is Euro */ + if ((c == 0x80) && (t1 == (unsigned short *) gb2312tab)) c = UCS2_EURO; + else c = ((i < text->size) && (c1 = text->data[i++]) && + ((ku = c - p1->base_ku) < p1->max_ku) && + ((ten = c1 - p1->base_ten) < p1->max_ten)) ? + t1[(ku*p1->max_ten) + ten] : UBOGON; + } + UTF8_WRITE_BMP (s,c,cv,de) /* convert UCS-2 to UTF-8 */ + } +} + +/* Convert ASCII + double byte 2 plane sized text to UTF-8 + * Accepts: source sized text + * pointer to return sized text + * conversion table + * canonicalization function + */ + +void utf8_text_dbyte2 (SIZEDTEXT *text,SIZEDTEXT *ret,void *tab,ucs4cn_t cv, + ucs4de_t de) +{ + unsigned long i; + unsigned char *s; + unsigned int c,c1,ku,ten; + struct utf8_eucparam *p1 = (struct utf8_eucparam *) tab; + struct utf8_eucparam *p2 = p1 + 1; + unsigned short *t = (unsigned short *) p1->tab; + for (ret->size = i = 0; i < text->size;) { + if ((c = text->data[i++]) & BIT8) { + if ((i >= text->size) || !(c1 = text->data[i++])) + c = UBOGON; /* out of space or bogon */ + else if (c1 & BIT8) /* high vs. low plane */ + c = ((ku = c - p2->base_ku) < p2->max_ku && + ((ten = c1 - p2->base_ten) < p2->max_ten)) ? + t[(ku*(p1->max_ten + p2->max_ten)) + p1->max_ten + ten] :UBOGON; + else c = ((ku = c - p1->base_ku) < p1->max_ku && + ((ten = c1 - p1->base_ten) < p1->max_ten)) ? + t[(ku*(p1->max_ten + p2->max_ten)) + ten] : UBOGON; + } + UTF8_COUNT_BMP (ret->size,c,cv,de) + } + (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] = NIL; + for (i = 0; i < text->size;) { + if ((c = text->data[i++]) & BIT8) { + if ((i >= text->size) || !(c1 = text->data[i++])) + c = UBOGON; /* out of space or bogon */ + else if (c1 & BIT8) /* high vs. low plane */ + c = ((ku = c - p2->base_ku) < p2->max_ku && + ((ten = c1 - p2->base_ten) < p2->max_ten)) ? + t[(ku*(p1->max_ten + p2->max_ten)) + p1->max_ten + ten] :UBOGON; + else c = ((ku = c - p1->base_ku) < p1->max_ku && + ((ten = c1 - p1->base_ten) < p1->max_ten)) ? + t[(ku*(p1->max_ten + p2->max_ten)) + ten] : UBOGON; + } + UTF8_WRITE_BMP (s,c,cv,de) /* convert UCS-2 to UTF-8 */ + } +} + +#ifdef JISTOUNICODE /* Japanese */ +/* Convert Shift JIS sized text to UTF-8 + * Accepts: source sized text + * pointer to return sized text + * canonicalization function + */ + +void utf8_text_sjis (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv, + ucs4de_t de) +{ + unsigned long i; + unsigned char *s; + unsigned int c,c1,ku,ten; + for (ret->size = i = 0; i < text->size;) { + if ((c = text->data[i++]) & BIT8) { + /* half-width katakana */ + if ((c >= MIN_KANA_8) && (c < MAX_KANA_8)) c += KANA_8; + else if (i >= text->size) c = UBOGON; + else { /* Shift-JIS */ + c1 = text->data[i++]; + SJISTOJIS (c,c1); + c = JISTOUNICODE (c,c1,ku,ten); + } + } + /* compromise - do yen sign but not overline */ + else if (c == JISROMAN_YEN) c = UCS2_YEN; + UTF8_COUNT_BMP (ret->size,c,cv,de) + } + (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] = NIL; + for (i = 0; i < text->size;) { + if ((c = text->data[i++]) & BIT8) { + /* half-width katakana */ + if ((c >= MIN_KANA_8) && (c < MAX_KANA_8)) c += KANA_8; + else { /* Shift-JIS */ + c1 = text->data[i++]; + SJISTOJIS (c,c1); + c = JISTOUNICODE (c,c1,ku,ten); + } + } + /* compromise - do yen sign but not overline */ + else if (c == JISROMAN_YEN) c = UCS2_YEN; + UTF8_WRITE_BMP (s,c,cv,de) /* convert UCS-2 to UTF-8 */ + } +} +#endif + +/* Convert ISO-2022 sized text to UTF-8 + * Accepts: source sized text + * pointer to returned sized text + * canonicalization function + */ + +void utf8_text_2022 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de) +{ + unsigned long i; + unsigned char *s; + unsigned int pass,state,c,co,gi,gl,gr,g[4],ku,ten; + for (pass = 0,s = NIL,ret->size = 0; pass <= 1; pass++) { + gi = 0; /* quell compiler warnings */ + state = I2S_CHAR; /* initialize engine */ + g[0]= g[2] = I2CS_ASCII; /* G0 and G2 are ASCII */ + g[1]= g[3] = I2CS_ISO8859_1;/* G1 and G3 are ISO-8850-1 */ + gl = I2C_G0; gr = I2C_G1; /* left is G0, right is G1 */ + for (i = 0; i < text->size;) { + c = text->data[i++]; + switch (state) { /* dispatch based upon engine state */ + case I2S_ESC: /* ESC seen */ + switch (c) { /* process intermediate character */ + case I2C_MULTI: /* multibyte character? */ + state = I2S_MUL; /* mark multibyte flag seen */ + break; + case I2C_SS2: /* single shift GL to G2 */ + case I2C_SS2_ALT: /* Taiwan SeedNet */ + gl |= I2C_SG2; + break; + case I2C_SS3: /* single shift GL to G3 */ + case I2C_SS3_ALT: /* Taiwan SeedNet */ + gl |= I2C_SG3; + break; + case I2C_LS2: /* shift GL to G2 */ + gl = I2C_G2; + break; + case I2C_LS3: /* shift GL to G3 */ + gl = I2C_G3; + break; + case I2C_LS1R: /* shift GR to G1 */ + gr = I2C_G1; + break; + case I2C_LS2R: /* shift GR to G2 */ + gr = I2C_G2; + break; + case I2C_LS3R: /* shift GR to G3 */ + gr = I2C_G3; + break; + case I2C_G0_94: case I2C_G1_94: case I2C_G2_94: case I2C_G3_94: + g[gi = c - I2C_G0_94] = (state == I2S_MUL) ? I2CS_94x94 : I2CS_94; + state = I2S_INT; /* ready for character set */ + break; + case I2C_G0_96: case I2C_G1_96: case I2C_G2_96: case I2C_G3_96: + g[gi = c - I2C_G0_96] = (state == I2S_MUL) ? I2CS_96x96 : I2CS_96; + state = I2S_INT; /* ready for character set */ + break; + default: /* bogon */ + if (pass) *s++ = I2C_ESC,*s++ = c; + else ret->size += 2; + state = I2S_CHAR; /* return to previous state */ + } + break; + + case I2S_MUL: /* ESC $ */ + switch (c) { /* process multibyte intermediate character */ + case I2C_G0_94: case I2C_G1_94: case I2C_G2_94: case I2C_G3_94: + g[gi = c - I2C_G0_94] = I2CS_94x94; + state = I2S_INT; /* ready for character set */ + break; + case I2C_G0_96: case I2C_G1_96: case I2C_G2_96: case I2C_G3_96: + g[gi = c - I2C_G0_96] = I2CS_96x96; + state = I2S_INT; /* ready for character set */ + break; + default: /* probably omitted I2CS_94x94 */ + g[gi = I2C_G0] = I2CS_94x94 | c; + state = I2S_CHAR; /* return to character state */ + } + break; + case I2S_INT: + state = I2S_CHAR; /* return to character state */ + g[gi] |= c; /* set character set */ + break; + + case I2S_CHAR: /* character data */ + switch (c) { + case I2C_ESC: /* ESC character */ + state = I2S_ESC; /* see if ISO-2022 prefix */ + break; + case I2C_SI: /* shift GL to G0 */ + gl = I2C_G0; + break; + case I2C_SO: /* shift GL to G1 */ + gl = I2C_G1; + break; + case I2C_SS2_ALT: /* single shift GL to G2 */ + case I2C_SS2_ALT_7: + gl |= I2C_SG2; + break; + case I2C_SS3_ALT: /* single shift GL to G3 */ + case I2C_SS3_ALT_7: + gl |= I2C_SG3; + break; + + default: /* ordinary character */ + co = c; /* note original character */ + if (gl & (3 << 2)) { /* single shifted? */ + gi = g[gl >> 2]; /* get shifted character set */ + gl &= 0x3; /* cancel shift */ + } + /* select left or right half */ + else gi = (c & BIT8) ? g[gr] : g[gl]; + c &= BITS7; /* make 7-bit */ + switch (gi) { /* interpret in character set */ + case I2CS_ASCII: /* ASCII */ + break; /* easy! */ + case I2CS_BRITISH: /* British ASCII */ + /* Pound sterling sign */ + if (c == BRITISH_POUNDSTERLING) c = UCS2_POUNDSTERLING; + break; + case I2CS_JIS_ROMAN: /* JIS Roman */ + case I2CS_JIS_BUGROM: /* old bugs */ + switch (c) { /* two exceptions to ASCII */ + case JISROMAN_YEN: /* Yen sign */ + c = UCS2_YEN; + break; + /* overline */ + case JISROMAN_OVERLINE: + c = UCS2_OVERLINE; + break; + } + break; + case I2CS_JIS_KANA: /* JIS hankaku katakana */ + if ((c >= MIN_KANA_7) && (c < MAX_KANA_7)) c += KANA_7; + break; + + case I2CS_ISO8859_1: /* Latin-1 (West European) */ + c |= BIT8; /* just turn on high bit */ + break; + case I2CS_ISO8859_2: /* Latin-2 (Czech, Slovak) */ + c = iso8859_2tab[c]; + break; + case I2CS_ISO8859_3: /* Latin-3 (Dutch, Turkish) */ + c = iso8859_3tab[c]; + break; + case I2CS_ISO8859_4: /* Latin-4 (Scandinavian) */ + c = iso8859_4tab[c]; + break; + case I2CS_ISO8859_5: /* Cyrillic */ + c = iso8859_5tab[c]; + break; + case I2CS_ISO8859_6: /* Arabic */ + c = iso8859_6tab[c]; + break; + case I2CS_ISO8859_7: /* Greek */ + c = iso8859_7tab[c]; + break; + case I2CS_ISO8859_8: /* Hebrew */ + c = iso8859_8tab[c]; + break; + case I2CS_ISO8859_9: /* Latin-5 (Finnish, Portuguese) */ + c = iso8859_9tab[c]; + break; + case I2CS_TIS620: /* Thai */ + c = tis620tab[c]; + break; + case I2CS_ISO8859_10: /* Latin-6 (Northern Europe) */ + c = iso8859_10tab[c]; + break; + case I2CS_ISO8859_13: /* Latin-7 (Baltic) */ + c = iso8859_13tab[c]; + break; + case I2CS_VSCII: /* Vietnamese */ + c = visciitab[c]; + break; + case I2CS_ISO8859_14: /* Latin-8 (Celtic) */ + c = iso8859_14tab[c]; + break; + case I2CS_ISO8859_15: /* Latin-9 (Euro) */ + c = iso8859_15tab[c]; + break; + case I2CS_ISO8859_16: /* Latin-10 (Baltic) */ + c = iso8859_16tab[c]; + break; + + default: /* all other character sets */ + /* multibyte character set */ + if ((gi & I2CS_MUL) && !(c & BIT8) && isgraph (c)) { + c = (i < text->size) ? text->data[i++] : 0; + switch (gi) { +#ifdef GBTOUNICODE + case I2CS_GB: /* GB 2312 */ + co |= BIT8; /* make into EUC */ + c |= BIT8; + c = GBTOUNICODE (co,c,ku,ten); + break; +#endif +#ifdef JISTOUNICODE + case I2CS_JIS_OLD:/* JIS X 0208-1978 */ + case I2CS_JIS_NEW:/* JIS X 0208-1983 */ + c = JISTOUNICODE (co,c,ku,ten); + break; +#endif +#ifdef JIS0212TOUNICODE + case I2CS_JIS_EXT:/* JIS X 0212-1990 */ + c = JIS0212TOUNICODE (co,c,ku,ten); + break; +#endif +#ifdef KSCTOUNICODE + case I2CS_KSC: /* KSC 5601 */ + co |= BIT8; /* make into EUC */ + c |= BIT8; + c = KSCTOUNICODE (co,c,ku,ten); + break; +#endif +#ifdef CNS1TOUNICODE + case I2CS_CNS1: /* CNS 11643 plane 1 */ + c = CNS1TOUNICODE (co,c,ku,ten); + break; +#endif +#ifdef CNS2TOUNICODE + case I2CS_CNS2: /* CNS 11643 plane 2 */ + c = CNS2TOUNICODE (co,c,ku,ten); + break; +#endif +#ifdef CNS3TOUNICODE + case I2CS_CNS3: /* CNS 11643 plane 3 */ + c = CNS3TOUNICODE (co,c,ku,ten); + break; +#endif +#ifdef CNS4TOUNICODE + case I2CS_CNS4: /* CNS 11643 plane 4 */ + c = CNS4TOUNICODE (co,c,ku,ten); + break; +#endif +#ifdef CNS5TOUNICODE + case I2CS_CNS5: /* CNS 11643 plane 5 */ + c = CNS5TOUNICODE (co,c,ku,ten); + break; +#endif +#ifdef CNS6TOUNICODE + case I2CS_CNS6: /* CNS 11643 plane 6 */ + c = CNS6TOUNICODE (co,c,ku,ten); + break; +#endif +#ifdef CNS7TOUNICODE + case I2CS_CNS7: /* CNS 11643 plane 7 */ + c = CNS7TOUNICODE (co,c,ku,ten); + break; +#endif + default: /* unknown multibyte, treat as UCS-2 */ + c |= (co << 8); /* wrong, but nothing else to do */ + break; + } + } + else c = co; /* unknown single byte, treat as 8859-1 */ + } + /* convert if second pass */ + if (pass) UTF8_WRITE_BMP (s,c,cv,de) + else UTF8_COUNT_BMP (ret->size,c,cv,de); + } + } + } + if (!pass) (s = ret->data = (unsigned char *) + fs_get (ret->size + 1))[ret->size] = NIL; + else if (((unsigned long) (s - ret->data)) != ret->size) + fatal ("ISO-2022 to UTF-8 botch"); + } +} + +/* Convert UTF-7 sized text to UTF-8 + * Accepts: source sized text + * pointer to returned sized text + * canonicalization function + */ + +void utf8_text_utf7 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de) +{ + unsigned long i; + unsigned char *s; + unsigned int c,c1,d,uc,pass,e,e1,state,surrh; + for (pass = 0,s = NIL,ret->size = 0; pass <= 1; pass++) { + c1 = d = uc = e = e1 = 0; + for (i = 0,state = NIL; i < text->size;) { + c = text->data[i++]; /* get next byte */ + switch (state) { + case U7_PLUS: /* previous character was + */ + if (c == '-') { /* +- means textual + */ + c = '+'; + state = U7_ASCII; /* revert to ASCII */ + break; + } + state = U7_UNICODE; /* enter Unicode state */ + e = e1 = 0; /* initialize Unicode quantum position */ + case U7_UNICODE: /* Unicode state */ + if (c == '-') state = U7_MINUS; + else { /* decode Unicode */ + /* don't use isupper/islower since this is ASCII only */ + if ((c >= 'A') && (c <= 'Z')) c -= 'A'; + else if ((c >= 'a') && (c <= 'z')) c -= 'a' - 26; + else if (isdigit (c)) c -= '0' - 52; + else if (c == '+') c = 62; + else if (c == '/') c = 63; + else state = U7_ASCII;/* end of modified BASE64 */ + } + break; + case U7_MINUS: /* previous character was absorbed - */ + state = U7_ASCII; /* revert to ASCII */ + case U7_ASCII: /* ASCII state */ + if (c == '+') state = U7_PLUS; + break; + } + + switch (state) { /* store character if in character mode */ + case U7_UNICODE: /* Unicode */ + switch (e++) { /* install based on BASE64 state */ + case 0: + c1 = c << 2; /* byte 1: high 6 bits */ + break; + case 1: + d = c1 | (c >> 4); /* byte 1: low 2 bits */ + c1 = c << 4; /* byte 2: high 4 bits */ + break; + case 2: + d = c1 | (c >> 2); /* byte 2: low 4 bits */ + c1 = c << 6; /* byte 3: high 2 bits */ + break; + case 3: + d = c | c1; /* byte 3: low 6 bits */ + e = 0; /* reinitialize mechanism */ + break; + } + if (e == 1) break; /* done if first BASE64 state */ + if (!e1) { /* first byte of UCS-2 character */ + uc = (d & 0xff) << 8; /* note first byte */ + e1 = T; /* enter second UCS-2 state */ + break; /* done */ + } + c = uc | (d & 0xff); /* build UCS-2 character */ + e1 = NIL; /* back to first UCS-2 state, drop in */ + /* surrogate pair? */ + if ((c >= UTF16_SURR) && (c <= UTF16_MAXSURR)) { + /* save high surrogate for later */ + if (c < UTF16_SURRL) surrh = c; + else c = UTF16_BASE + ((surrh & UTF16_MASK) << UTF16_SHIFT) + + (c & UTF16_MASK); + break; /* either way with surrogates, we're done */ + } + case U7_ASCII: /* just install if ASCII */ + /* convert if second pass */ + if (pass) UTF8_WRITE_BMP (s,c,cv,de) + else UTF8_COUNT_BMP (ret->size,c,cv,de); + } + } + if (!pass) (s = ret->data = (unsigned char *) + fs_get (ret->size + 1))[ret->size] = NIL; + else if (((unsigned long) (s - ret->data)) != ret->size) + fatal ("UTF-7 to UTF-8 botch"); + } +} + + +/* Convert UTF-8 sized text to UTF-8 + * Accepts: source sized text + * pointer to returned sized text + * canonicalization function + */ + +void utf8_text_utf8 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de) +{ + unsigned long i,c; + unsigned char *s,*t; + for (ret->size = 0, t = text->data, i = text->size; i;) { + if ((c = utf8_get (&t,&i)) & U8G_ERROR) { + ret->data = text->data; /* conversion failed */ + ret->size = text->size; + return; + } + UTF8_COUNT (ret->size,c,cv,de) + } + (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] =NIL; + for (t = text->data, i = text->size; i;) { + c = utf8_get (&t,&i); + UTF8_WRITE (s,c,cv,de) /* convert UCS-4 to UTF-8 */ + } + if (((unsigned long) (s - ret->data)) != ret->size) + fatal ("UTF-8 to UTF-8 botch"); +} + +/* Convert UCS-2 sized text to UTF-8 + * Accepts: source sized text + * pointer to returned sized text + * canonicalization function + */ + +void utf8_text_ucs2 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de) +{ + unsigned long i; + unsigned char *s,*t; + unsigned int c; + for (ret->size = 0, t = text->data, i = text->size / 2; i; --i) { + c = *t++ << 8; + c |= *t++; + UTF8_COUNT_BMP (ret->size,c,cv,de); + } + (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] = NIL; + for (t = text->data, i = text->size / 2; i; --i) { + c = *t++ << 8; + c |= *t++; + UTF8_WRITE_BMP (s,c,cv,de) /* convert UCS-2 to UTF-8 */ + } + if (((unsigned long) (s - ret->data)) != ret->size) + fatal ("UCS-2 to UTF-8 botch"); +} + + +/* Convert UCS-4 sized text to UTF-8 + * Accepts: source sized text + * pointer to returned sized text + * canonicalization function + */ + +void utf8_text_ucs4 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de) +{ + unsigned long i; + unsigned char *s,*t; + unsigned long c; + for (ret->size = 0, t = text->data, i = text->size / 4; i; --i) { + c = *t++ << 24; c |= *t++ << 16; c |= *t++ << 8; c |= *t++; + UTF8_COUNT (ret->size,c,cv,de); + } + (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] = NIL; + for (t = text->data, i = text->size / 2; i; --i) { + c = *t++ << 24; c |= *t++ << 16; c |= *t++ << 8; c |= *t++; + UTF8_WRITE (s,c,cv,de) /* convert UCS-4 to UTF-8 */ + } + if (((unsigned long) (s - ret->data)) != ret->size) + fatal ("UCS-4 to UTF-8 botch"); +} + +/* Convert UTF-16 sized text to UTF-8 + * Accepts: source sized text + * pointer to returned sized text + * canonicalization function + */ + +void utf8_text_utf16 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de) +{ + unsigned long i; + unsigned char *s,*t; + unsigned long c,d; + for (ret->size = 0, t = text->data, i = text->size / 2; i; --i) { + c = *t++ << 8; + c |= *t++; + /* possible surrogate? */ + if ((c >= UTF16_SURR) && (c <= UTF16_MAXSURR)) { + /* invalid first surrogate */ + if ((c > UTF16_SURRHEND) || !i) c = UBOGON; + else { /* get second surrogate */ + d = *t++ << 8; + d |= *t++; + --i; /* swallowed another 16-bits */ + /* invalid second surrogate */ + if ((d < UTF16_SURRL) || (d > UTF16_SURRLEND)) c = UBOGON; + else c = UTF16_BASE + ((c & UTF16_MASK) << UTF16_SHIFT) + + (d & UTF16_MASK); + } + } + UTF8_COUNT (ret->size,c,cv,de); + } + (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] = NIL; + for (t = text->data, i = text->size / 2; i; --i) { + c = *t++ << 8; + c |= *t++; + /* possible surrogate? */ + if ((c >= UTF16_SURR) && (c <= UTF16_MAXSURR)) { + /* invalid first surrogate */ + if ((c > UTF16_SURRHEND) || !i) c = UBOGON; + else { /* get second surrogate */ + d = *t++ << 8; + d |= *t++; + --i; /* swallowed another 16-bits */ + /* invalid second surrogate */ + if ((d < UTF16_SURRL) || (d > UTF16_SURRLEND)) c = UBOGON; + else c = UTF16_BASE + ((c & UTF16_MASK) << UTF16_SHIFT) + + (d & UTF16_MASK); + } + } + UTF8_WRITE (s,c,cv,de) /* convert UCS-4 to UTF-8 */ + } + if (((unsigned long) (s - ret->data)) != ret->size) + fatal ("UTF-16 to UTF-8 botch"); +} + +/* Size of UCS-4 character, possibly not in BMP, as UTF-8 octets + * Accepts: character + * Returns: size (0 means bogon) + * + * Use UTF8_SIZE macro if known to be in the BMP + */ + +unsigned long utf8_size (unsigned long c) +{ + if (c < 0x80) return 1; + else if (c < 0x800) return 2; + else if (c < 0x10000) return 3; + else if (c < 0x200000) return 4; + else if (c < 0x4000000) return 5; + else if (c < 0x80000000) return 6; + return 0; +} + + +/* Put UCS-4 character, possibly not in BMP, as UTF-8 octets + * Accepts: destination string pointer + * character + * Returns: updated destination pointer + * + * Use UTF8_PUT_BMP macro if known to be in the BMP + */ + +unsigned char *utf8_put (unsigned char *s,unsigned long c) +{ + unsigned char mark[6] = {0x00,0xc0,0xe0,0xf0,0xf8,0xfc}; + unsigned long size = utf8_size (c); + switch (size) { + case 6: + s[5] = 0x80 | (unsigned char) (c & 0x3f); + c >>= 6; + case 5: + s[4] = 0x80 | (unsigned char) (c & 0x3f); + c >>= 6; + case 4: + s[3] = 0x80 | (unsigned char) (c & 0x3f); + c >>= 6; + case 3: + s[2] = 0x80 | (unsigned char) (c & 0x3f); + c >>= 6; + case 2: + s[1] = 0x80 | (unsigned char) (c & 0x3f); + c >>= 6; + case 1: + *s = mark[size-1] | (unsigned char) (c & 0x7f); + break; + } + return s + size; +} + +/* Return title case of a fixed-width UCS-4 character + * Accepts: character + * Returns: title case of character + */ + +unsigned long ucs4_titlecase (unsigned long c) +{ + if (c <= UCS4_TMAPMAX) return ucs4_tmaptab[c]; + if (c < UCS4_TMAPHIMIN) return c; + if (c <= UCS4_TMAPHIMAX) return c - UCS4_TMAPHIMAP; + if (c < UCS4_TMAPDESERETMIN) return c; + if (c <= UCS4_TMAPDESERETMAX) return c - UCS4_TMAPDESERETMAP; + return c; +} + + +/* Return width of a fixed-width UCS-4 character in planes 0-2 + * Accepts: character + * Returns: width (0, 1, 2) or negative error condition if not valid + */ + +long ucs4_width (unsigned long c) +{ + long ret; + /* out of range, not-a-char, or surrogates */ + if ((c > UCS4_MAXUNICODE) || ((c & 0xfffe) == 0xfffe) || + ((c >= UTF16_SURR) && (c <= UTF16_MAXSURR))) ret = U4W_NOTUNCD; + /* private-use */ + else if (c >= UCS4_PVTBASE) ret = U4W_PRIVATE; + /* SSP are not printing characters */ + else if (c >= UCS4_SSPBASE) ret = U4W_SSPCHAR; + /* unassigned planes */ + else if (c >= UCS4_UNABASE) ret = U4W_UNASSGN; + /* SIP and reserved plane 3 are wide */ + else if (c >= UCS4_SIPBASE) ret = 2; +#if (UCS4_WIDLEN != UCS4_SIPBASE) +#error "UCS4_WIDLEN != UCS4_SIPBASE" +#endif + /* C0/C1 controls */ + else if ((c <= UCS2_C0CONTROLEND) || + ((c >= UCS2_C1CONTROL) && (c <= UCS2_C1CONTROLEND))) + ret = U4W_CONTROL; + /* BMP and SMP get value from table */ + else switch (ret = (ucs4_widthtab[(c >> 2)] >> ((3 - (c & 0x3)) << 1)) &0x3){ + case 0: /* zero-width */ + if (c == 0x00ad) ret = 1; /* force U+00ad (SOFT HYPHEN) to width 1 */ + case 1: /* single-width */ + case 2: /* double-width */ + break; + case 3: /* ambiguous width */ + ret = (c >= 0x2100) ? 2 : 1;/* need to do something better than this */ + break; + } + return ret; +} + +/* Return screen width of UTF-8 string + * Accepts: string + * Returns: width or negative if not valid UTF-8 + */ + +long utf8_strwidth (unsigned char *s) +{ + unsigned long c,i,ret; + /* go through string */ + for (ret = 0; *s; ret += ucs4_width (c)) { + /* It's alright to give a fake value for the byte count to utf8_get() + * since the null of a null-terminated string will stop processing anyway. + */ + i = 6; /* fake value */ + if ((c = utf8_get (&s,&i)) & U8G_ERROR) return -1; + } + return ret; +} + + +/* Return screen width of UTF-8 text + * Accepts: SIZEDTEXT to string + * Returns: width or negative if not valid UTF-8 + */ + +long utf8_textwidth (SIZEDTEXT *utf8) +{ + unsigned long c; + unsigned char *s = utf8->data; + unsigned long i = utf8->size; + unsigned long ret = 0; + while (i) { /* while there's a string to process */ + if ((c = utf8_get (&s,&i)) & U8G_ERROR) return -1; + ret += ucs4_width (c); + } + return ret; +} + +/* Decomposition (phew!) */ + +#define MORESINGLE 1 /* single UCS-4 tail value */ +#define MOREMULTIPLE 2 /* multiple UCS-2 tail values */ + +struct decomposemore { + short type; /* type of more */ + union { + unsigned long single; /* single decomposed value */ + struct { /* multiple BMP values */ + unsigned short *next; + unsigned long count; + } multiple; + } data; +}; + +#define RECURSIVEMORE struct recursivemore + +RECURSIVEMORE { + struct decomposemore *more; + RECURSIVEMORE *next; +}; + + +/* Return decomposition of a UCS-4 character + * Accepts: character or U8G_ERROR to return next from "more" + * pointer to returned more + * Returns: [next] decomposed value, more set if still more decomposition + */ + +unsigned long ucs4_decompose (unsigned long c,void **more) +{ + unsigned long i,ix,ret; + struct decomposemore *m; + if (c & U8G_ERROR) { /* want to chase more? */ + /* do sanity check */ + if (m = (struct decomposemore *) *more) switch (m->type) { + case MORESINGLE: /* single value */ + ret = m->data.single; + fs_give (more); /* no more decomposition */ + break; + case MOREMULTIPLE: /* multiple value */ + ret = *m->data.multiple.next++; + if (!--m->data.multiple.count) fs_give (more); + break; + default: /* uh-oh */ + fatal ("invalid more block argument to ucs4_decompose!"); + } + else fatal ("no more block provided to ucs4_decompose!"); + } + + else { /* start decomposition */ + *more = NIL; /* initially set no more */ + /* BMP low decompositions */ + if (c < UCS4_BMPLOMIN) ret = c; + /* fix this someday */ + else if (c == UCS4_BMPLOMIN) ret = ucs4_dbmplotab[0]; + else if (c <= UCS4_BMPLOMAX) { + /* within range - have a decomposition? */ + if (i = ucs4_dbmploixtab[c - UCS4_BMPLOMIN]) { + /* get first value of decomposition */ + ret = ucs4_dbmplotab[ix = i & UCS4_BMPLOIXMASK]; + /* has continuation? */ + if (i & UCS4_BMPLOSIZEMASK) { + m = (struct decomposemore *) + (*more = memset (fs_get (sizeof (struct decomposemore)),0, + sizeof (struct decomposemore))); + m->type = MOREMULTIPLE; + m->data.multiple.next = &ucs4_dbmplotab[++ix]; + m->data.multiple.count = i >> UCS4_BMPLOSIZESHIFT; + } + } + else ret = c; /* in range but doesn't decompose */ + } + /* BMP CJK compatibility */ + else if (c < UCS4_BMPCJKMIN) ret = c; + else if (c <= UCS4_BMPCJKMAX) { + if (!(ret = ucs4_bmpcjk1decomptab[c - UCS4_BMPCJKMIN])) ret = c; + } + /* BMP CJK compatibility - some not in BMP */ +#if UCS4_BMPCJK2MIN - (UCS4_BMPCJKMAX + 1) + else if (c < UCS4_BMPCJK2MIN) ret = c; +#endif + else if (c <= UCS4_BMPCJK2MAX) + ret = ucs4_bmpcjk2decomptab[c - UCS4_BMPCJK2MIN]; + /* BMP high decompositions */ + else if (c < UCS4_BMPHIMIN) ret = c; + else if (c <= UCS4_BMPHIMAX) { + /* within range - have a decomposition? */ + if (i = ucs4_dbmphiixtab[c - UCS4_BMPHIMIN]) { + /* get first value of decomposition */ + ret = ucs4_dbmphitab[ix = i & UCS4_BMPHIIXMASK]; + /* has continuation? */ + if (i & UCS4_BMPHISIZEMASK) { + m = (struct decomposemore *) + (*more = memset (fs_get (sizeof (struct decomposemore)),0, + sizeof (struct decomposemore))); + m->type = MOREMULTIPLE; + m->data.multiple.next = &ucs4_dbmphitab[++ix]; + m->data.multiple.count = i >> UCS4_BMPHISIZESHIFT; + } + } + else ret = c; /* in range but doesn't decompose */ + } + + /* BMP half and full width forms */ + else if (c < UCS4_BMPHALFFULLMIN) ret = c; + else if (c <= UCS4_BMPHALFFULLMAX) { + if (!(ret = ucs4_bmphalffulldecomptab[c - UCS4_BMPHALFFULLMIN])) ret = c; + } + /* SMP music */ + else if (c < UCS4_SMPMUSIC1MIN) ret = c; + else if (c <= UCS4_SMPMUSIC1MAX) { + ret = ucs4_smpmusic1decomptab[c -= UCS4_SMPMUSIC1MIN][0]; + m = (struct decomposemore *) + (*more = memset (fs_get (sizeof (struct decomposemore)),0, + sizeof (struct decomposemore))); + m->type = MORESINGLE; + m->data.single = ucs4_smpmusic1decomptab[c][1]; + } + else if (c < UCS4_SMPMUSIC2MIN) ret = c; + else if (c <= UCS4_SMPMUSIC2MAX) { + ret = ucs4_smpmusic2decomptab[c -= UCS4_SMPMUSIC2MIN][0]; + m = (struct decomposemore *) + (*more = memset (fs_get (sizeof (struct decomposemore)),0, + sizeof (struct decomposemore))); + m->type = MORESINGLE; + m->data.single = ucs4_smpmusic2decomptab[c][1]; + } + /* SMP mathematical forms */ + else if (c < UCS4_SMPMATHMIN) ret = c; + else if (c <= UCS4_SMPMATHMAX) { + if (!(ret = ucs4_smpmathdecomptab[c - UCS4_SMPMATHMIN])) ret = c; + } + /* CJK compatibility ideographs in SIP */ + else if (!(ret = ((c >= UCS4_SIPMIN) && (c <= UCS4_SIPMAX)) ? + ucs4_sipdecomptab[c - UCS4_SIPMIN] : c)) ret = c; + } + return ret; +} + +/* Return recursive decomposition of a UCS-4 character + * Accepts: character or U8G_ERROR to return next from "more" + * pointer to returned more + * Returns: [next] decomposed value, more set if still more decomposition + */ + +unsigned long ucs4_decompose_recursive (unsigned long c,void **more) +{ + unsigned long c1; + void *m,*mn; + RECURSIVEMORE *mr; + if (c & U8G_ERROR) { /* want to chase more? */ + mn = NIL; + if (mr = (RECURSIVEMORE *) *more) switch (mr->more->type) { + case MORESINGLE: /* decompose single value */ + c = ucs4_decompose_recursive (mr->more->data.single,&mn); + *more = mr->next; /* done with this more, remove it */ + fs_give ((void **) &mr->more); + fs_give ((void **) &mr); + break; + case MOREMULTIPLE: /* decompose current value in multiple */ + c = ucs4_decompose_recursive (*mr->more->data.multiple.next++,&mn); + /* if done with this multiple decomposition */ + if (!--mr->more->data.multiple.count) { + *more = mr->next; /* done with this more, remove it */ + fs_give ((void **) &mr->more); + fs_give ((void **) &mr); + } + break; + default: /* uh-oh */ + fatal ("invalid more block argument to ucs4_decompose_recursive!"); + } + else fatal ("no more block provided to ucs4_decompose_recursive!"); + if (mr = mn) { /* did this value recurse on us? */ + mr->next = *more; /* yes, insert new more at head */ + *more = mr; + } + } + else { /* start decomposition */ + *more = NIL; /* initially set no more */ + mr = NIL; + do { /* repeatedly decompose this codepoint */ + c = ucs4_decompose (c1 = c,&m); + if (m) { /* multi-byte decomposition */ + if (c1 == c) fatal ("endless multiple decomposition!"); + /* create a block to stash this more */ + mr = memset (fs_get (sizeof (RECURSIVEMORE)),0,sizeof (RECURSIVEMORE)); + mr->more = m; /* note the expansion */ + mr->next = *more; /* old list is the tail */ + *more = mr; /* and this is the new head */ + } + } while (c1 != c); /* until nothing more to decompose */ + } + return c; +} diff --git a/imap/src/c-client/utf8.h b/imap/src/c-client/utf8.h new file mode 100644 index 00000000..105f856d --- /dev/null +++ b/imap/src/c-client/utf8.h @@ -0,0 +1,584 @@ +/* ======================================================================== + * Copyright 1988-2008 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: UTF-8 routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 11 June 1997 + * Last Edited: 17 January 2008 + */ + +/* UTF-8 size and conversion routines from UCS-2 values (thus in the BMP). + * Don't use these if UTF-16 data (surrogate pairs) are an issue. + * For UCS-4 values, use the utf8_size() and utf8_put() functions. + */ + +#define UTF8_SIZE_BMP(c) ((c & 0xff80) ? ((c & 0xf800) ? 3 : 2) : 1) + +#define UTF8_PUT_BMP(b,c) { \ + if (c & 0xff80) { /* non-ASCII? */ \ + if (c & 0xf800) { /* three byte code */ \ + *b++ = 0xe0 | (c >> 12); \ + *b++ = 0x80 | ((c >> 6) & 0x3f); \ + } \ + else *b++ = 0xc0 | ((c >> 6) & 0x3f); \ + *b++ = 0x80 | (c & 0x3f); \ + } \ + else *b++ = c; \ +} + +/* utf8_text() flag values */ + +#define U8T_CASECANON 2 /* canonicalize case */ +#define U8T_DECOMPOSE 4 /* decompose */ + /* full canonicalization */ +#define U8T_CANONICAL (U8T_CASECANON | U8T_DECOMPOSE) + + +/* utf8_get() return values */ + + /* 0x0000 - 0xffff BMP plane */ +#define U8GM_NONBMP 0xffff0000 /* mask for non-BMP values */ + /* 0x10000 - 0x10ffff extended planes */ + /* 0x110000 - 0x7ffffff non-Unicode */ +#define U8G_ERROR 0x80000000 /* error flag */ +#define U8G_BADCONT U8G_ERROR+1 /* continuation when not in progress */ +#define U8G_INCMPLT U8G_ERROR+2 /* incomplete UTF-8 character */ +#define U8G_NOTUTF8 U8G_ERROR+3 /* not a valid UTF-8 octet */ +#define U8G_ENDSTRG U8G_ERROR+4 /* end of string */ +#define U8G_ENDSTRI U8G_ERROR+5 /* end of string w/ incomplete UTF-8 char */ +#define U8G_SURROGA U8G_ERROR+6 /* surrogate codepoint */ +#define U8G_NOTUNIC U8G_ERROR+7 /* non-Unicode codepoint */ + + +/* ucs4_width() return values */ + +#define U4W_ERROR 0x80000000 /* error flags */ +#define U4W_NOTUNCD U4W_ERROR+1 /* not a Unicode char */ +#define U4W_PRIVATE U4W_ERROR+2 /* private-space plane */ +#define U4W_SSPCHAR U4W_ERROR+3 /* Supplementary Special-purpose Plane */ +#define U4W_UNASSGN U4W_ERROR+4 /* unassigned space plane */ +#define U4W_CONTROL U4W_ERROR+5 /* C0/C1 control */ +#define U4W_CTLSRGT U4W_CONTROL /* in case legacy code references this */ + +/* ISO-2022 engine states */ + +#define I2S_CHAR 0 /* character */ +#define I2S_ESC 1 /* previous character was ESC */ +#define I2S_MUL 2 /* previous character was multi-byte code */ +#define I2S_INT 3 /* previous character was intermediate */ + + +/* ISO-2022 Gn selections */ + +#define I2C_G0 0 /* G0 */ +#define I2C_G1 1 /* G1 */ +#define I2C_G2 2 /* G2 */ +#define I2C_G3 3 /* G3 */ +#define I2C_SG2 (2 << 2) /* single shift G2 */ +#define I2C_SG3 (3 << 2) /* single shift G2 */ + + +/* ISO-2022 octet definitions */ + +#define I2C_ESC 0x1b /* ESCape */ + + /* Intermediate character */ +#define I2C_STRUCTURE 0x20 /* announce code structure */ +#define I2C_C0 0x21 /* C0 */ +#define I2C_C1 0x22 /* C1 */ +#define I2C_CONTROL 0x23 /* single control function */ +#define I2C_MULTI 0x24 /* multi-byte character set */ +#define I2C_OTHER 0x25 /* other coding system */ +#define I2C_REVISED 0x26 /* revised registration */ +#define I2C_G0_94 0x28 /* G0 94-character set */ +#define I2C_G1_94 0x29 /* G1 94-character set */ +#define I2C_G2_94 0x2A /* G2 94-character set */ +#define I2C_G3_94 0x2B /* G3 94-character set */ +#define I2C_G0_96 0x2C /* (not in ISO-2022) G0 96-character set */ +#define I2C_G1_96 0x2D /* G1 96-character set */ +#define I2C_G2_96 0x2E /* G2 96-character set */ +#define I2C_G3_96 0x2F /* G3 96-character set */ + + /* Locking shifts */ +#define I2C_SI 0x0f /* lock shift to G0 (Shift In) */ +#define I2C_SO 0x0e /* lock shift to G1 (Shift Out) */ + /* prefixed by ESC */ +#define I2C_LS2 0x6e /* lock shift to G2 */ +#define I2C_LS3 0x6f /* lock shift to G3 */ +#define I2C_LS1R 0x7e /* lock shift GR to G1 */ +#define I2C_LS2R 0x7d /* lock shift GR to G2 */ +#define I2C_LS3R 0x7c /* lock shift GR to G3 */ + + /* Single shifts */ +#define I2C_SS2_ALT 0x8e /* single shift to G2 (SS2) */ +#define I2C_SS3_ALT 0x8f /* single shift to G3 (SS3) */ +#define I2C_SS2_ALT_7 0x19 /* single shift to G2 (SS2) */ +#define I2C_SS3_ALT_7 0x1d /* single shift to G3 (SS3) */ + /* prefixed by ESC */ +#define I2C_SS2 0x4e /* single shift to G2 (SS2) */ +#define I2C_SS3 0x4f /* single shift to G3 (SS3) */ + +/* 94 character sets */ + + /* 4/0 ISO 646 IRV */ +#define I2CS_94_BRITISH 0x41 /* 4/1 ISO 646 British */ +#define I2CS_94_ASCII 0x42 /* 4/2 ISO 646 USA (ASCII) */ + /* 4/3 NATS Finland/Sweden (primary) */ + /* 4/4 NATS Finland/Sweden (secondary) */ + /* 4/5 NATS Denmark/Norway (primary) */ + /* 4/6 NATS Denmark/Norway (secondary) */ + /* 4/7 ISO 646 Swedish SEN 850200 */ + /* 4/8 ISO 646 Swedish names */ +#define I2CS_94_JIS_BUGROM 0x48 /* 4/8 some buggy software does this */ +#define I2CS_94_JIS_KANA 0x49 /* 4/9 JIS X 0201-1976 right half */ +#define I2CS_94_JIS_ROMAN 0x4a /* 4/a JIS X 0201-1976 left half */ + /* 4/b ISO 646 German */ + /* 4/c ISO 646 Portuguese (Olivetti) */ + /* 4/d ISO 6438 African */ + /* 4/e ISO 5427 Cyrillic (Honeywell-Bull) */ + /* 4/f DIN 31624 extended bibliography */ + /* 5/0 ISO 5426-1980 Bibliography */ + /* 5/1 ISO 5427-1981 Cyrillic*/ + /* 5/2 ISO 646 French (withdrawn) */ + /* 5/3 ISO 5428-1980 Greek bibliography */ + /* 5/4 GB 1988-80 Chinese */ + /* 5/5 Latin-Greek (Honeywell-Bull) */ + /* 5/6 UK Viewdata/Teletext */ + /* 5/7 INIS (IRV subset) */ + /* 5/8 ISO 5428 Greek Bibliography */ + /* 5/9 ISO 646 Italian (Olivetti) */ + /* 5/a ISO 646 Spanish (Olivetti) */ + /* 5/b Greek (Olivetti) */ + /* 5/c Latin-Greek (Olivetti) */ + /* 5/d INIS non-standard extension */ + /* 5/e INIS Cyrillic extension */ + /* 5/f Arabic CODAR-U IERA */ + /* 6/0 ISO 646 Norwegian */ + /* 6/1 Norwegian version 2 (withdrawn) */ + /* 6/2 Videotex supplementary */ + /* 6/3 Videotex supplementary #2 */ + /* 6/4 Videotex supplementary #3 */ + /* 6/5 APL */ + /* 6/6 ISO 646 French */ + /* 6/7 ISO 646 Portuguese (IBM) */ + /* 6/8 ISO 646 Spanish (IBM) */ + /* 6/9 ISO 646 Hungarian */ + /* 6/a Greek ELOT (withdrawn) */ + /* 6/b ISO 9036 Arabic 7-bit */ + /* 6/c ISO 646 IRV supplementary set */ + /* 6/d JIS C6229-1984 OCR-A */ + /* 6/e JIS C6229-1984 OCR-B */ + /* 6/f JIS C6229-1984 OCR-B additional */ + /* 7/0 JIS C6229-1984 hand-printed */ + /* 7/1 JIS C6229-1984 additional hand-printd */ + /* 7/2 JIS C6229-1984 katakana hand-printed */ + /* 7/3 E13B Japanese graphic */ + /* 7/4 Supplementary Videotex (withdrawn) */ + /* 7/5 Teletex primary CCITT T.61 */ + /* 7/6 Teletex secondary CCITT T.61 */ + /* 7/7 CSA Z 243.4-1985 Alternate primary #1 */ + /* 7/8 CSA Z 243.4-1985 Alternate primary #2 */ + /* 7/9 Mosaic CCITT T.101 */ + /* 7/a Serbocroatian/Slovenian Latin */ + /* 7/b Serbocroatian Cyrillic */ + /* 7/c Supplementary CCITT T.101 */ + /* 7/d Macedonian Cyrillic */ + +/* 94 character sets - second intermediate byte */ + + /* 4/0 Greek primary CCITT */ + /* 4/1 Cuba */ + /* 4/2 ISO/IEC 646 invariant */ + /* 4/3 Irish Gaelic 7-bit */ + /* 4/4 Turkmen */ + + +/* 94x94 character sets */ + +#define I2CS_94x94_JIS_OLD 0x40 /* 4/0 JIS X 0208-1978 */ +#define I2CS_94x94_GB 0x41 /* 4/1 GB 2312 */ +#define I2CS_94x94_JIS_NEW 0x42 /* 4/2 JIS X 0208-1983 */ +#define I2CS_94x94_KSC 0x43 /* 4/3 KSC 5601 */ +#define I2CS_94x94_JIS_EXT 0x44 /* 4/4 JIS X 0212-1990 */ + /* 4/5 CCITT Chinese */ + /* 4/6 Blisssymbol Graphic */ +#define I2CS_94x94_CNS1 0x47 /* 4/7 CNS 11643 plane 1 */ +#define I2CS_94x94_CNS2 0x48 /* 4/8 CNS 11643 plane 2 */ +#define I2CS_94x94_CNS3 0x49 /* 4/9 CNS 11643 plane 3 */ +#define I2CS_94x94_CNS4 0x4a /* 4/a CNS 11643 plane 4 */ +#define I2CS_94x94_CNS5 0x4b /* 4/b CNS 11643 plane 5 */ +#define I2CS_94x94_CNS6 0x4c /* 4/c CNS 11643 plane 6 */ +#define I2CS_94x94_CNS7 0x4d /* 4/d CNS 11643 plane 7 */ + /* 4/e DPRK (North Korea) KGCII */ + /* 4/f JGCII plane 1 */ + /* 5/0 JGCII plane 2 */ + +/* 96 character sets */ + +#define I2CS_96_ISO8859_1 0x41 /* 4/1 Latin-1 (Western Europe) */ +#define I2CS_96_ISO8859_2 0x42 /* 4/2 Latin-2 (Czech, Slovak) */ +#define I2CS_96_ISO8859_3 0x43 /* 4/3 Latin-3 (Dutch, Turkish) */ +#define I2CS_96_ISO8859_4 0x44 /* 4/4 Latin-4 (Scandinavian) */ + /* 4/5 CSA Z 243.4-1985 */ +#define I2CS_96_ISO8859_7 0x46 /* 4/6 Greek */ +#define I2CS_96_ISO8859_6 0x47 /* 4/7 Arabic */ +#define I2CS_96_ISO8859_8 0x48 /* 4/8 Hebrew */ + /* 4/9 Czechoslovak CSN 369103 */ + /* 4/a Supplementary Latin and non-alpha */ + /* 4/b Technical */ +#define I2CS_96_ISO8859_5 0x4c /* 4/c Cyrillic */ +#define I2CS_96_ISO8859_9 0x4d /* 4/d Latin-5 (Finnish, Portuguese) */ + /* 4/e ISO 6937-2 residual */ + /* 4/f Basic Cyrillic */ + /* 5/0 Supplementary Latin 1, 2 and 5 */ + /* 5/1 Basic Box */ + /* 5/2 Supplementary ISO/IEC 6937 : 1992 */ + /* 5/3 CCITT Hebrew supplementary */ +#define I2CS_96_TIS620 0x54 /* 5/4 TIS 620 */ + /* 5/5 Arabic/French/German */ +#define I2CS_96_ISO8859_10 0x56 /* 5/6 Latin-6 (Northern Europe) */ + /* 5/7 ??? */ + /* 5/8 Sami (Lappish) supplementary */ +#define I2CS_96_ISO8859_13 0x59 /* 5/9 Latin-7 (Baltic) */ +#define I2CS_96_VSCII 0x5a /* 5/a Vietnamese */ + /* 5/b Technical #1 IEC 1289 */ +#define I2CS_96_ISO8859_14 0x5c /* 5/c Latin-8 (Celtic) */ + /* 5/d Sami supplementary Latin */ + /* 5/e Latin/Hebrew */ + /* 5/f Celtic supplementary Latin */ + /* 6/0 Uralic supplementary Cyrillic */ + /* 6/1 Volgaic supplementary Cyrillic */ +#define I2CS_96_ISO8859_15 0x62 /* 6/2 Latin-9 (Euro) */ + /* 6/3 Latin-1 with Euro */ + /* 6/4 Latin-4 with Euro */ + /* 6/5 Latin-7 with Euro */ +#define I2CS_96_ISO8859_16 0x66 /* 6/6 Latin-10 (Balkan) */ + /* 6/7 Ogham */ + /* 6/8 Sami supplementary Latin #2 */ + /* 7/d Supplementary Mosaic for CCITT 101 */ + +/* 96x96 character sets */ + +/* Types of character sets */ + +#define I2CS_94 0x000 /* 94 character set */ +#define I2CS_96 0x100 /* 96 character set */ +#define I2CS_MUL 0x200 /* multi-byte */ +#define I2CS_94x94 (I2CS_MUL | I2CS_94) +#define I2CS_96x96 (I2CS_MUL | I2CS_96) + + +/* Character set identifiers stored in Gn */ + +#define I2CS_BRITISH (I2CS_94 | I2CS_94_BRITISH) +#define I2CS_ASCII (I2CS_94 | I2CS_94_ASCII) +#define I2CS_JIS_BUGROM (I2CS_94 | I2CS_94_JIS_BUGROM) +#define I2CS_JIS_KANA (I2CS_94 | I2CS_94_JIS_KANA) +#define I2CS_JIS_ROMAN (I2CS_94 | I2CS_94_JIS_ROMAN) +#define I2CS_JIS_OLD (I2CS_94x94 | I2CS_94x94_JIS_OLD) +#define I2CS_GB (I2CS_94x94 | I2CS_94x94_GB) +#define I2CS_JIS_NEW (I2CS_94x94 | I2CS_94x94_JIS_NEW) +#define I2CS_KSC (I2CS_94x94 | I2CS_94x94_KSC) +#define I2CS_JIS_EXT (I2CS_94x94 | I2CS_94x94_JIS_EXT) +#define I2CS_CNS1 (I2CS_94x94 | I2CS_94x94_CNS1) +#define I2CS_CNS2 (I2CS_94x94 | I2CS_94x94_CNS2) +#define I2CS_CNS3 (I2CS_94x94 | I2CS_94x94_CNS3) +#define I2CS_CNS4 (I2CS_94x94 | I2CS_94x94_CNS4) +#define I2CS_CNS5 (I2CS_94x94 | I2CS_94x94_CNS5) +#define I2CS_CNS6 (I2CS_94x94 | I2CS_94x94_CNS6) +#define I2CS_CNS7 (I2CS_94x94 | I2CS_94x94_CNS7) +#define I2CS_ISO8859_1 (I2CS_96 | I2CS_96_ISO8859_1) +#define I2CS_ISO8859_2 (I2CS_96 | I2CS_96_ISO8859_2) +#define I2CS_ISO8859_3 (I2CS_96 | I2CS_96_ISO8859_3) +#define I2CS_ISO8859_4 (I2CS_96 | I2CS_96_ISO8859_4) +#define I2CS_ISO8859_7 (I2CS_96 | I2CS_96_ISO8859_7) +#define I2CS_ISO8859_6 (I2CS_96 | I2CS_96_ISO8859_6) +#define I2CS_ISO8859_8 (I2CS_96 | I2CS_96_ISO8859_8) +#define I2CS_ISO8859_5 (I2CS_96 | I2CS_96_ISO8859_5) +#define I2CS_ISO8859_9 (I2CS_96 | I2CS_96_ISO8859_9) +#define I2CS_TIS620 (I2CS_96 | I2CS_96_TIS620) +#define I2CS_ISO8859_10 (I2CS_96 | I2CS_96_ISO8859_10) +#define I2CS_ISO8859_13 (I2CS_96 | I2CS_96_ISO8859_13) +#define I2CS_VSCII (I2CS_96 | I2CS_96_VSCII) +#define I2CS_ISO8859_14 (I2CS_96 | I2CS_96_ISO8859_14) +#define I2CS_ISO8859_15 (I2CS_96 | I2CS_96_ISO8859_15) +#define I2CS_ISO8859_16 (I2CS_96 | I2CS_96_ISO8859_16) + + +/* Miscellaneous ISO 2022 definitions */ + +#define EUC_CS2 0x8e /* single shift CS2 */ +#define EUC_CS3 0x8f /* single shift CS3 */ + +#define BITS7 0x7f /* 7-bit value mask */ +#define BIT8 0x80 /* 8th bit mask */ + +/* The following saves us from having to have yet more charset tables */ + +/* Unicode codepoints */ + +#define UCS2_C0CONTROL 0x00 /* first C0 control */ +#define UCS2_C0CONTROLEND 0x1F /* last C0 control */ +#define UCS2_C1CONTROL 0x80 /* first C1 control */ +#define UCS2_C1CONTROLEND 0x9F /* last C1 control */ + + /* ISO 646 substituted Unicode codepoints */ +#define UCS2_POUNDSTERLING 0x00a3 +#define UCS2_YEN 0x00a5 +#define UCS2_OVERLINE 0x203e +#define UCS2_EURO 0x20ac +#define UCS2_KATAKANA 0xff61 /* first katakana codepoint */ +#define UCS2_BOM 0xfeff /* byte order mark */ +#define UCS2_BOGON 0xfffd /* replacement character */ + /* next two codepoints are not Unicode chars */ +#define UCS2_BOMCHECK 0xfffe /* used to check byte order with UCS2_BOM */ +#define UCS2_NOTCHAR 0xffff /* not a character */ + +#define UCS4_BMPBASE 0x0000 /* Basic Multilingual Plane */ +#define UCS4_SMPBASE 0x10000 /* Supplementary Multilinugual Plane */ +#define UCS4_SIPBASE 0x20000 /* Supplementary Ideographic Plane */ + /* EastAsianWidth says plane 3 is wide */ +#define UCS4_UNABASE 0x40000 /* unassigned space */ +#define UCS4_SSPBASE 0xe0000 /* Supplementary Special-purpose Plane */ +#define UCS4_PVTBASE 0xf0000 /* private-space (two planes) */ +#define UCS4_MAXUNICODE 0x10ffff/* highest Unicode codepoint */ + +#define UTF16_BASE 0x10000 /* base of codepoints needing surrogates */ +#define UTF16_SHIFT 10 /* surrogate shift */ +#define UTF16_MASK 0x3ff /* surrogate mask */ +#define UTF16_SURR 0xd800 /* UTF-16 surrogate area */ +#define UTF16_SURRH 0xd800 /* UTF-16 first high surrogate */ +#define UTF16_SURRHEND 0xdbff /* UTF-16 last high surrogate */ +#define UTF16_SURRL 0xdc00 /* UTF-16 first low surrogate */ +#define UTF16_SURRLEND 0xdfff /* UTF-16 last low surrogate */ +#define UTF16_MAXSURR 0xdfff /* end of UTF-16 surrogates */ + + +/* UBOGON is used to represent a codepoint in a character set which does not + * map to Unicode. It is also used for mapping failures, e.g. incomplete + * shift sequences. This name has the same text width as 0x????, for + * convenience in the mapping tables. + * + * NOCHAR is used to represent a codepoint in Unicode which does not map to + * the target character set in a reverse mapping table. This name has the + * same text width as 0x???? in case we ever add static reverse mapping tables. + */ + +#define UBOGON UCS2_BOGON +#define NOCHAR UCS2_NOTCHAR + +/* Codepoints in non-Unicode character sets */ + +/* Codepoints in ISO 646 character sets */ + +/* British ASCII codepoints */ + +#define BRITISH_POUNDSTERLING 0x23 + +/* JIS Roman codepoints */ + +#define JISROMAN_YEN 0x5c +#define JISROMAN_OVERLINE 0x7e + + +/* Hankaku katakana codepoints & parameters + * + * In earlier versions, MAX_KANA_7 and MAX_KANA_8 were the maximum codepoint + * values. Although this made sense, it was confusing with the "max ku" and + * "max ten" values used in the double-byte tables; there are 1-origin, but + * the calculated values used for "ku" and "ten" are 0-origin (derived by + * substracting the "base"). What this all meant is that for double byte + * characters the limit test is of the form (value < max_ku), but for single + * byte characters (which used the same cell to hold the max ku) the limit + * test was (value <= max_ku). + * + * By making MAX_KANA_[78] be maximum+1, the same (value < max_ku) limit test + * is used throughout. - 6/15/2006 + */ + +#define MIN_KANA_7 0x21 +#define MAX_KANA_7 0x60 /* maximum value + 1 */ +#define KANA_7 (UCS2_KATAKANA - MIN_KANA_7) +#define MIN_KANA_8 (MIN_KANA_7 | BIT8) +#define MAX_KANA_8 (MAX_KANA_7 | BIT8) +#define KANA_8 (UCS2_KATAKANA - MIN_KANA_8) + +/* Charset scripts */ + +/* The term "script" is used here in a very loose sense, enough to make + * purists cringe. Basically, the idea is to give the main program some + * idea of how it should treat the characters of text in a charset with + * respect to font, drawing routines, etc. + * + * In some cases, "script" is associated with a charset; in other cases, + * it's more closely tied to a language. + */ + +#define SC_UNICODE 0x1 /* Unicode */ +#define SC_LATIN_1 0x10 /* Western Europe */ +#define SC_LATIN_2 0x20 /* Eastern Europe */ +#define SC_LATIN_3 0x40 /* Southern Europe */ +#define SC_LATIN_4 0x80 /* Northern Europe */ +#define SC_LATIN_5 0x100 /* Turkish */ +#define SC_LATIN_6 0x200 /* Nordic */ +#define SC_LATIN_7 0x400 /* Baltic */ +#define SC_LATIN_8 0x800 /* Celtic */ +#define SC_LATIN_9 0x1000 /* Euro */ +#define SC_LATIN_0 SC_LATIN_9 /* colloquial name for Latin-9 */ +#define SC_ARABIC 0x2000 +#define SC_CYRILLIC 0x4000 +#define SC_GREEK 0x8000 +#define SC_HEBREW 0x10000 +#define SC_THAI 0x20000 +#define SC_UKRANIAN 0x40000 +#define SC_LATIN_10 0x80000 /* Balkan */ +#define SC_VIETNAMESE 0x100000 +#define SC_CHINESE_SIMPLIFIED 0x1000000 +#define SC_CHINESE_TRADITIONAL 0x2000000 +#define SC_JAPANESE 0x4000000 +#define SC_KOREAN 0x8000000 + + +/* Script table */ + +typedef struct utf8_scent { + char *name; /* script name */ + char *description; /* script description */ + unsigned long script; /* script bitmask */ +} SCRIPT; + +/* Character set table support */ + +typedef struct utf8_csent { + char *name; /* charset name */ + unsigned short type; /* type of charset */ + unsigned short flags; /* charset flags */ + void *tab; /* additional data */ + unsigned long script; /* script(s) implemented by this charset */ + char *preferred; /* preferred charset over this one */ +} CHARSET; + + +struct utf8_eucparam { + unsigned int base_ku : 8; /* base row */ + unsigned int base_ten : 8; /* base column */ + unsigned int max_ku : 8; /* maximum row */ + unsigned int max_ten : 8; /* maximum column */ + void *tab; /* conversion table */ +}; + + +/* Charset types */ + +#define CT_UNKNOWN 0 /* unknown 8-bit */ +#define CT_ASCII 1 /* 7-bit ASCII no table */ +#define CT_UCS2 2 /* 2 byte 16-bit Unicode no table */ +#define CT_UCS4 3 /* 4 byte 32-bit Unicode no table */ +#define CT_1BYTE0 10 /* 1 byte ISO 8859-1 no table */ +#define CT_1BYTE 11 /* 1 byte ASCII + table 0x80-0xff */ +#define CT_1BYTE8 12 /* 1 byte table 0x00 - 0xff */ +#define CT_EUC 100 /* 2 byte ASCII + utf8_eucparam base/CS2/CS3 */ +#define CT_DBYTE 101 /* 2 byte ASCII + utf8_eucparam */ +#define CT_DBYTE2 102 /* 2 byte ASCII + utf8_eucparam plane1/2 */ +#define CT_UTF16 1000 /* variable UTF-16 encoded Unicode no table */ +#define CT_UTF8 1001 /* variable UTF-8 encoded Unicode no table */ +#define CT_UTF7 1002 /* variable UTF-7 encoded Unicode no table */ +#define CT_2022 10000 /* variable ISO-2022 encoded no table */ +#define CT_SJIS 10001 /* 2 byte Shift-JIS encoded JIS no table */ + + +/* Character set flags */ + +#define CF_PRIMARY 0x1 /* primary name for this charset */ +#define CF_DISPLAY 0x2 /* charset used in displays */ +#define CF_POSTING 0x4 /* charset used in email posting */ +#define CF_UNSUPRT 0x8 /* charset unsupported (can't convert to it) */ +#define CF_NOEMAIL 0x10 /* charset not used in email */ + + +/* UTF-7 engine states */ + +#define U7_ASCII 0 /* ASCII character */ +#define U7_PLUS 1 /* plus seen */ +#define U7_UNICODE 2 /* Unicode characters */ +#define U7_MINUS 3 /* absorbed minus seen */ + +/* Function prototypes */ + +typedef unsigned long (*ucs4cn_t) (unsigned long c); +typedef unsigned long (*ucs4de_t) (unsigned long c,void **more); + +SCRIPT *utf8_script (char *script); +const CHARSET *utf8_charset (char *charset); +char *utf8_badcharset (char *charset); +long utf8_text (SIZEDTEXT *text,char *charset,SIZEDTEXT *ret,long flags); +long utf8_text_cs (SIZEDTEXT *text,const CHARSET *cs,SIZEDTEXT *ret, + ucs4cn_t cv,ucs4de_t de); +long utf8_cstext (SIZEDTEXT *text,char *charset,SIZEDTEXT *ret, + unsigned long errch); +long utf8_cstocstext (SIZEDTEXT *text,char *sc,SIZEDTEXT *ret,char *dc, + unsigned long errch); +unsigned short *utf8_rmap (char *charset); +unsigned short *utf8_rmap_cs (const CHARSET *cs); +unsigned short *utf8_rmap_gen (const CHARSET *cs,unsigned short *oldmap); +long utf8_rmaptext (SIZEDTEXT *text,unsigned short *rmap,SIZEDTEXT *ret, + unsigned long errch,long iso2022jp); +unsigned long utf8_rmapsize (SIZEDTEXT *text,unsigned short *rmap, + unsigned long errch,long iso2022jp); +long ucs4_rmaptext (unsigned long *ucs4,unsigned long len,unsigned short *rmap, + SIZEDTEXT *ret,unsigned long errch); +long ucs4_rmaplen (unsigned long *ucs4,unsigned long len,unsigned short *rmap, + unsigned long errch); +long ucs4_rmapbuf (unsigned char *t,unsigned long *ucs4,unsigned long len, + unsigned short *rmap,unsigned long errch); +unsigned long utf8_get (unsigned char **s,unsigned long *i); +unsigned long utf8_get_raw (unsigned char **s,unsigned long *i); +unsigned long ucs4_cs_get (CHARSET *cs,unsigned char **s,unsigned long *i); +unsigned long *utf8_csvalidmap (char *charsets[]); +const CHARSET *utf8_infercharset (SIZEDTEXT *src); +long utf8_validate (unsigned char *s,unsigned long i); +void utf8_text_1byte0 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de); +void utf8_text_1byte (SIZEDTEXT *text,SIZEDTEXT *ret,void *tab,ucs4cn_t cv, + ucs4de_t de); +void utf8_text_1byte8 (SIZEDTEXT *text,SIZEDTEXT *ret,void *tab,ucs4cn_t cv, + ucs4de_t de); +void utf8_text_euc (SIZEDTEXT *text,SIZEDTEXT *ret,void *tab,ucs4cn_t cv, + ucs4de_t de); +void utf8_text_dbyte (SIZEDTEXT *text,SIZEDTEXT *ret,void *tab,ucs4cn_t cv, + ucs4de_t de); +void utf8_text_dbyte2 (SIZEDTEXT *text,SIZEDTEXT *ret,void *tab,ucs4cn_t cv, + ucs4de_t de); +void utf8_text_sjis (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de); +void utf8_text_2022 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de); +void utf8_text_utf7 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de); +void utf8_text_utf8 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de); +void utf8_text_ucs2 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de); +void utf8_text_ucs4 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de); +void utf8_text_utf16 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de); +unsigned long utf8_size (unsigned long c); +unsigned char *utf8_put (unsigned char *s,unsigned long c); +unsigned long ucs4_titlecase (unsigned long c); +long ucs4_width (unsigned long c); +long utf8_strwidth (unsigned char *s); +long utf8_textwidth (SIZEDTEXT *utf8); +unsigned long ucs4_decompose (unsigned long c,void **more); +unsigned long ucs4_decompose_recursive (unsigned long c,void **more); diff --git a/imap/src/c-client/utf8aux.c b/imap/src/c-client/utf8aux.c new file mode 100644 index 00000000..5138987b --- /dev/null +++ b/imap/src/c-client/utf8aux.c @@ -0,0 +1,449 @@ +/* ======================================================================== + * Copyright 1988-2007 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: UTF-8 auxillary routines (c-client and MIME2 support) + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 11 June 1997 + * Last Edited: 12 October 2007 + */ + + +#include <stdio.h> +#include <ctype.h> +#include "c-client.h" + +/* Convert charset labelled stringlist to UTF-8 in place + * Accepts: string list + * charset + */ + +static void utf8_stringlist (STRINGLIST *st,char *charset) +{ + SIZEDTEXT txt; + /* convert entire stringstruct */ + if (st) do if (utf8_text (&st->text,charset,&txt,U8T_CANONICAL)) { + fs_give ((void **) &st->text.data); + st->text.data = txt.data; /* transfer this text */ + st->text.size = txt.size; + } while (st = st->next); +} + + +/* Convert charset labelled searchpgm to UTF-8 in place + * Accepts: search program + * charset + */ + +void utf8_searchpgm (SEARCHPGM *pgm,char *charset) +{ + SIZEDTEXT txt; + SEARCHHEADER *hl; + SEARCHOR *ol; + SEARCHPGMLIST *pl; + if (pgm) { /* must have a search program */ + utf8_stringlist (pgm->bcc,charset); + utf8_stringlist (pgm->cc,charset); + utf8_stringlist (pgm->from,charset); + utf8_stringlist (pgm->to,charset); + utf8_stringlist (pgm->subject,charset); + for (hl = pgm->header; hl; hl = hl->next) { + if (utf8_text (&hl->line,charset,&txt,U8T_CANONICAL)) { + fs_give ((void **) &hl->line.data); + hl->line.data = txt.data; + hl->line.size = txt.size; + } + if (utf8_text (&hl->text,charset,&txt,U8T_CANONICAL)) { + fs_give ((void **) &hl->text.data); + hl->text.data = txt.data; + hl->text.size = txt.size; + } + } + utf8_stringlist (pgm->body,charset); + utf8_stringlist (pgm->text,charset); + for (ol = pgm->or; ol; ol = ol->next) { + utf8_searchpgm (ol->first,charset); + utf8_searchpgm (ol->second,charset); + } + for (pl = pgm->not; pl; pl = pl->next) utf8_searchpgm (pl->pgm,charset); + utf8_stringlist (pgm->return_path,charset); + utf8_stringlist (pgm->sender,charset); + utf8_stringlist (pgm->reply_to,charset); + utf8_stringlist (pgm->in_reply_to,charset); + utf8_stringlist (pgm->message_id,charset); + utf8_stringlist (pgm->newsgroups,charset); + utf8_stringlist (pgm->followup_to,charset); + utf8_stringlist (pgm->references,charset); + } +} + +/* Convert MIME-2 sized text to UTF-8 + * Accepts: source sized text + * charset + * flags (same as utf8_text()) + * Returns: T if successful, NIL if failure + */ + +#define MINENCWORD 9 +#define MAXENCWORD 75 + +/* This resizing algorithm is stupid, but hopefully it should never be triggered + * except for a pathological header. The main concern is that we don't get a + * buffer overflow. + */ + +#define DSIZE 65536 /* real headers should never be this big */ +#define FUZZ 10 /* paranoia fuzz */ + +long utf8_mime2text (SIZEDTEXT *src,SIZEDTEXT *dst,long flags) +{ + unsigned char *s,*se,*e,*ee,*t,*te; + char *cs,*ce,*ls; + SIZEDTEXT txt,rtxt; + unsigned long i; + size_t dsize = min (DSIZE,((src->size / 4) + 1) * 9); + /* always create buffer if canonicalizing */ + dst->data = (flags & U8T_CANONICAL) ? + (unsigned char *) fs_get ((size_t) dsize) : NIL; + dst->size = 0; /* nothing written yet */ + /* look for encoded words */ + for (s = src->data, se = src->data + src->size; s < se; s++) { + if (((se - s) > MINENCWORD) && (*s == '=') && (s[1] == '?') && + (cs = (char *) mime2_token (s+2,se,(unsigned char **) &ce)) && + (e = mime2_token ((unsigned char *) ce+1,se,&ee)) && + (te = mime2_text (t = e+2,se)) && (ee == e + 1) && + ((te - s) < MAXENCWORD)) { + if (mime2_decode (e,t,te,&txt)) { + *ce = '\0'; /* temporarily tie off charset */ + if (ls = strchr (cs,'*')) *ls = '\0'; + /* convert to UTF-8 as best we can */ + if (!utf8_text (&txt,cs,&rtxt,flags)) utf8_text (&txt,NIL,&rtxt,flags); + if (dst->data) { /* make sure existing buffer fits */ + while (dsize <= (dst->size + rtxt.size + FUZZ)) { + dsize += DSIZE; /* kick it up */ + fs_resize ((void **) &dst->data,dsize); + } + } + else { /* make a new buffer */ + while (dsize <= (dst->size + rtxt.size)) dsize += DSIZE; + memcpy (dst->data = (unsigned char *) fs_get (dsize),src->data, + dst->size = s - src->data); + } + for (i = 0; i < rtxt.size; i++) dst->data[dst->size++] = rtxt.data[i]; + + /* all done with converted text */ + if (rtxt.data != txt.data) fs_give ((void **) &rtxt.data); + if (ls) *ls = '*'; /* restore language tag delimiter */ + *ce = '?'; /* restore charset delimiter */ + /* all done with decoded text */ + fs_give ((void **) &txt.data); + s = te+1; /* continue scan after encoded word */ + /* skip leading whitespace */ + for (t = s + 1; (t < se) && ((*t == ' ') || (*t == '\t')); t++); + /* see if likely continuation encoded word */ + if (t < (se - MINENCWORD)) switch (*t) { + case '=': /* possible encoded word? */ + if (t[1] == '?') s = t - 1; + break; + case '\015': /* CR, eat a following LF */ + if (t[1] == '\012') t++; + case '\012': /* possible end of logical line */ + if ((t[1] == ' ') || (t[1] == '\t')) { + do t++; + while ((t < (se - MINENCWORD)) && ((t[1] == ' ')||(t[1] == '\t'))); + if ((t < (se - MINENCWORD)) && (t[1] == '=') && (t[2] == '?')) + s = t; /* definitely looks like continuation */ + } + } + } + else { /* restore original text */ + if (dst->data) fs_give ((void **) &dst->data); + dst->data = src->data; + dst->size = src->size; + return NIL; /* syntax error: MIME-2 decoding failure */ + } + } + else do if (dst->data) { /* stash ASCII characters until LWSP */ + if (dsize < (dst->size + FUZZ)) { + dsize += DSIZE; /* kick it up */ + fs_resize ((void **) &dst->data,dsize); + } + /* kludge: assumes ASCII doesn't decompose and titlecases to one byte */ + dst->data[dst->size++] = (flags & U8T_CASECANON) ? + (unsigned char) ucs4_titlecase (*s) : *s; + } + while ((*s != ' ') && (*s != '\t') && (*s != '\015') && (*s != '\012') && + (++s < se)); + } + if (dst->data) dst->data[dst->size] = '\0'; + else { /* nothing converted, return identity */ + dst->data = src->data; + dst->size = src->size; + } + return T; /* success */ +} + +/* Decode MIME-2 text + * Accepts: Encoding + * text + * text end + * destination sized text + * Returns: T if successful, else NIL + */ + +long mime2_decode (unsigned char *e,unsigned char *t,unsigned char *te, + SIZEDTEXT *txt) +{ + unsigned char *q; + txt->data = NIL; /* initially no returned data */ + switch (*e) { /* dispatch based upon encoding */ + case 'Q': case 'q': /* sort-of QUOTED-PRINTABLE */ + txt->data = (unsigned char *) fs_get ((size_t) (te - t) + 1); + for (q = t,txt->size = 0; q < te; q++) switch (*q) { + case '=': /* quoted character */ + /* both must be hex */ + if (!isxdigit (q[1]) || !isxdigit (q[2])) { + fs_give ((void **) &txt->data); + return NIL; /* syntax error: bad quoted character */ + } + /* assemble character */ + txt->data[txt->size++] = hex2byte (q[1],q[2]); + q += 2; /* advance past quoted character */ + break; + case '_': /* convert to space */ + txt->data[txt->size++] = ' '; + break; + default: /* ordinary character */ + txt->data[txt->size++] = *q; + break; + } + txt->data[txt->size] = '\0'; + break; + case 'B': case 'b': /* BASE64 */ + if (txt->data = (unsigned char *) rfc822_base64 (t,te - t,&txt->size)) + break; + default: /* any other encoding is unknown */ + return NIL; /* syntax error: unknown encoding */ + } + return T; +} + +/* Get MIME-2 token from encoded word + * Accepts: current text pointer + * text limit pointer + * pointer to returned end pointer + * Returns: current text pointer & end pointer if success, else NIL + */ + +unsigned char *mime2_token (unsigned char *s,unsigned char *se, + unsigned char **t) +{ + for (*t = s; **t != '?'; ++*t) { + if ((*t < se) && isgraph (**t)) switch (**t) { + case '(': case ')': case '<': case '>': case '@': case ',': case ';': + case ':': case '\\': case '"': case '/': case '[': case ']': case '.': + case '=': + return NIL; /* none of these are valid in tokens */ + } + else return NIL; /* out of text or CTL or space */ + } + return s; +} + + +/* Get MIME-2 text from encoded word + * Accepts: current text pointer + * text limit pointer + * pointer to returned end pointer + * Returns: end pointer if success, else NIL + */ + +unsigned char *mime2_text (unsigned char *s,unsigned char *se) +{ + unsigned char *t = se - 1; + /* search for closing ?, make sure valid */ + while ((s < t) && (*s != '?') && isgraph (*s++)); + return ((s < t) && (*s == '?') && (s[1] == '=') && + ((se == (s + 2)) || (s[2] == ' ') || (s[2] == '\t') || + (s[2] == '\015') || (s[2] == '\012'))) ? s : NIL; +} + +/* Convert UTF-16 string to Modified Base64 + * Accepts: destination pointer + * source string + * source length in octets + * Returns: updated destination pointer + */ + +static unsigned char *utf16_to_mbase64 (unsigned char *t,unsigned char *s, + size_t i) +{ + char *v = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"; + *t++ = '&'; /* write shift-in */ + while (i >= 3) { /* process tuplets */ + *t++ = v[s[0] >> 2]; /* byte 1: high 6 bits (1) */ + /* byte 2: low 2 bits (1), high 4 bits (2) */ + *t++ = v[((s[0] << 4) + (s[1] >> 4)) & 0x3f]; + /* byte 3: low 4 bits (2), high 2 bits (3) */ + *t++ = v[((s[1] << 2) + (s[2] >> 6)) & 0x3f]; + *t++ = v[s[2] & 0x3f]; /* byte 4: low 6 bits (3) */ + s += 3; + i -= 3; + } + if (i) { + *t++ = v[s[0] >> 2]; /* byte 1: high 6 bits (1) */ + /* byte 2: low 2 bits (1), high 4 bits (2) */ + *t++ = v[((s[0] << 4) + (--i ? (s[1] >> 4) : 0)) & 0x3f]; + /* byte 3: low 4 bits (2) */ + if (i) *t++ = v[(s[1] << 2) & 0x3f]; + } + *t++ = '-'; /* write shift-out */ + return t; +} + + +/* Poot a UTF-16 value to a buffer + * Accepts: buffer pointer + * value + * Returns: updated pointer + */ + +static unsigned char *utf16_poot (unsigned char *s,unsigned long c) +{ + *s++ = (unsigned char) (c >> 8); + *s++ = (unsigned char) (c & 0xff); + return s; +} + +/* Convert UTF-8 to Modified UTF-7 + * Accepts: UTF-8 string + * Returns: Modified UTF-7 string on success, NIL if invalid UTF-8 + */ + +#define MAXUNIUTF8 4 /* maximum length of Unicode UTF-8 sequence */ + +unsigned char *utf8_to_mutf7 (unsigned char *src) +{ + unsigned char *u16buf,*utf16; + unsigned char *ret,*t; + unsigned long j,c; + unsigned char *s = src; + unsigned long i = 0; + int nonascii = 0; + while (*s) { /* pass one: count destination octets */ + if (*s & 0x80) { /* non-ASCII character? */ + j = MAXUNIUTF8; /* get single UCS-4 codepoint */ + if ((c = utf8_get (&s,&j)) & U8G_ERROR) return NIL; + /* tally number of UTF-16 octets */ + nonascii += (c & U8GM_NONBMP) ? 4 : 2; + } + else { /* ASCII character */ + if (nonascii) { /* add pending Modified BASE64 size + shifts */ + i += ((nonascii / 3) * 4) + ((j = nonascii % 3) ? j + 1 : 0) + 2; + nonascii = 0; /* back to ASCII */ + } + if (*s == '&') i += 2; /* two octets if the escape */ + else ++i; /* otherwise just count another octet */ + ++s; /* advance to next source octet */ + } + } + if (nonascii) /* add pending Modified BASE64 size + shifts */ + i += ((nonascii / 3) * 4) + ((j = nonascii % 3) ? j + 1 : 0) + 2; + + /* create return buffer */ + t = ret = (unsigned char *) fs_get (i + 1); + /* and scratch buffer */ + utf16 = u16buf = (unsigned char *) fs_get (i + 1); + for (s = src; *s;) { /* pass two: copy destination octets */ + if (*s & 0x80) { /* non-ASCII character? */ + j = MAXUNIUTF8; /* get single UCS-4 codepoint */ + if ((c = utf8_get (&s,&j)) & U8G_ERROR) return NIL; + if (c & U8GM_NONBMP) { /* non-BMP? */ + c -= UTF16_BASE; /* yes, convert to surrogate */ + utf16 = utf16_poot (utf16_poot (utf16,(c >> UTF16_SHIFT)+UTF16_SURRH), + (c & UTF16_MASK) + UTF16_SURRL); + } + else utf16 = utf16_poot (utf16,c); + } + else { /* ASCII character */ + if (utf16 != u16buf) { /* add pending Modified BASE64 size + shifts */ + t = utf16_to_mbase64 (t,u16buf,utf16 - u16buf); + utf16 = u16buf; /* reset buffer */ + } + *t++ = *s; /* copy the character */ + if (*s == '&') *t++ = '-';/* special sequence if the escape */ + ++s; /* advance to next source octet */ + } + } + /* add pending Modified BASE64 size + shifts */ + if (utf16 != u16buf) t = utf16_to_mbase64 (t,u16buf,utf16 - u16buf); + *t = '\0'; /* tie off destination */ + if (i != (t - ret)) fatal ("utf8_to_mutf7 botch"); + fs_give ((void **) &u16buf); + return ret; +} + +/* Convert Modified UTF-7 to UTF-8 + * Accepts: Modified UTF-7 string + * Returns: UTF-8 string on success, NIL if invalid Modified UTF-7 + */ + +unsigned char *utf8_from_mutf7 (unsigned char *src) +{ + SIZEDTEXT utf8,utf7; + unsigned char *s; + int mbase64 = 0; + /* disallow bogus strings */ + if (mail_utf7_valid (src)) return NIL; + /* initialize SIZEDTEXTs */ + memset (&utf7,0,sizeof (SIZEDTEXT)); + memset (&utf8,0,sizeof (SIZEDTEXT)); + /* make copy of source */ + for (s = cpytxt (&utf7,src,strlen (src)); *s; ++s) switch (*s) { + case '&': /* Modified UTF-7 uses & instead of + */ + *s = '+'; + mbase64 = T; /* note that we are in Modified BASE64 */ + break; + case '+': /* temporarily swap text + to & */ + if (!mbase64) *s = '&'; + break; + case '-': /* shift back to ASCII */ + mbase64 = NIL; + break; + case ',': /* Modified UTF-7 uses , instead of / ... */ + if (mbase64) *s = '/'; /* ...in Modified BASE64 */ + break; + } + /* do the conversion */ + utf8_text_utf7 (&utf7,&utf8,NIL,NIL); + /* no longer need copy of source */ + fs_give ((void **) &utf7.data); + /* post-process: switch & and + */ + for (s = utf8.data; *s; ++s) switch (*s) { + case '&': + *s = '+'; + break; + case '+': + *s = '&'; + break; + } + return utf8.data; +} diff --git a/imap/src/c-client/utf8aux.h b/imap/src/c-client/utf8aux.h new file mode 100644 index 00000000..beb55057 --- /dev/null +++ b/imap/src/c-client/utf8aux.h @@ -0,0 +1,44 @@ +/* ======================================================================== + * Copyright 1988-2007 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: UTF-8 auxillary routines (c-client and MIME2 support) + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 11 June 1997 + * Last Edited: 9 October 2007 + */ + + +/* Following routines are in utf8aux.c as these depend upon c-client. + * Splitting these routines out makes it possible for pico to link with utf8.o + * and a few rump routines (e.g., fs_get()) but not all the rest of c-client + * (which pico does not need). + */ + +void utf8_searchpgm (SEARCHPGM *pgm,char *charset); +long utf8_mime2text (SIZEDTEXT *src,SIZEDTEXT *dst,long flags); +unsigned char *mime2_token (unsigned char *s,unsigned char *se, + unsigned char **t); +unsigned char *mime2_text (unsigned char *s,unsigned char *se); +long mime2_decode (unsigned char *e,unsigned char *t,unsigned char *te, + SIZEDTEXT *txt); +unsigned char *utf8_to_mutf7 (unsigned char *src); +unsigned char *utf8_from_mutf7 (unsigned char *src); |