summaryrefslogtreecommitdiff
path: root/imap/src/c-client
diff options
context:
space:
mode:
authorEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
committerEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
commit094ca96844842928810f14844413109fc6cdd890 (patch)
treee60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /imap/src/c-client
downloadalpine-094ca96844842928810f14844413109fc6cdd890.tar.xz
Initial Alpine Version
Diffstat (limited to 'imap/src/c-client')
-rw-r--r--imap/src/c-client/auth_ext.c96
-rw-r--r--imap/src/c-client/auth_gss.c423
-rw-r--r--imap/src/c-client/auth_log.c117
-rw-r--r--imap/src/c-client/auth_md5.c495
-rw-r--r--imap/src/c-client/auth_pla.c133
-rw-r--r--imap/src/c-client/c-client.h55
-rw-r--r--imap/src/c-client/env.h45
-rw-r--r--imap/src/c-client/flstring.c91
-rw-r--r--imap/src/c-client/flstring.h30
-rw-r--r--imap/src/c-client/fs.h34
-rw-r--r--imap/src/c-client/ftl.h32
-rw-r--r--imap/src/c-client/imap4r1.c5672
-rw-r--r--imap/src/c-client/imap4r1.h281
-rw-r--r--imap/src/c-client/mail.c6337
-rw-r--r--imap/src/c-client/mail.h1838
-rw-r--r--imap/src/c-client/misc.c475
-rw-r--r--imap/src/c-client/misc.h110
-rw-r--r--imap/src/c-client/netmsg.c104
-rw-r--r--imap/src/c-client/netmsg.h32
-rw-r--r--imap/src/c-client/newsrc.c510
-rw-r--r--imap/src/c-client/newsrc.h43
-rw-r--r--imap/src/c-client/nl.h34
-rw-r--r--imap/src/c-client/nntp.c2224
-rw-r--r--imap/src/c-client/nntp.h56
-rw-r--r--imap/src/c-client/pop3.c1091
-rw-r--r--imap/src/c-client/rfc822.c2373
-rw-r--r--imap/src/c-client/rfc822.h127
-rw-r--r--imap/src/c-client/smanager.c129
-rw-r--r--imap/src/c-client/smtp.c793
-rw-r--r--imap/src/c-client/smtp.h76
-rw-r--r--imap/src/c-client/sslio.h70
-rw-r--r--imap/src/c-client/tcp.h59
-rw-r--r--imap/src/c-client/utf8.c2554
-rw-r--r--imap/src/c-client/utf8.h584
-rw-r--r--imap/src/c-client/utf8aux.c449
-rw-r--r--imap/src/c-client/utf8aux.h44
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;
+ 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 **) &section,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);