move email address related functions oven to internet_addressing
[citadel.git] / citadel / internet_addressing.c
index 8d751d844d71361a2d385044501d03dea5108c20..a1b0a37e176c84748abdb35f90d6202a5b2a5720 100644 (file)
@@ -326,8 +326,637 @@ int CtdlHostAlias(char *fqdn) {
 
 
 
+/*
+ * Determine whether a given Internet address belongs to the current user
+ */
+int CtdlIsMe(char *addr, int addr_buf_len)
+{
+       struct recptypes *recp;
+       int i;
+
+       recp = validate_recipients(addr, NULL, 0);
+       if (recp == NULL) return(0);
+
+       if (recp->num_local == 0) {
+               free_recipients(recp);
+               return(0);
+       }
+
+       for (i=0; i<recp->num_local; ++i) {
+               extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
+               if (!strcasecmp(addr, CC->user.fullname)) {
+                       free_recipients(recp);
+                       return(1);
+               }
+       }
+
+       free_recipients(recp);
+       return(0);
+}
+
+
+/*
+ * Citadel protocol command to do the same
+ */
+void cmd_isme(char *argbuf) {
+       char addr[256];
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+       extract_token(addr, argbuf, 0, '|', sizeof addr);
+
+       if (CtdlIsMe(addr, sizeof addr)) {
+               cprintf("%d %s\n", CIT_OK, addr);
+       }
+       else {
+               cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
+       }
+
+}
+
+
+/* If the last item in a list of recipients was truncated to a partial address,
+ * remove it completely in order to avoid choking libSieve
+ */
+void sanitize_truncated_recipient(char *str)
+{
+       if (!str) return;
+       if (num_tokens(str, ',') < 2) return;
+
+       int len = strlen(str);
+       if (len < 900) return;
+       if (len > 998) str[998] = 0;
+
+       char *cptr = strrchr(str, ',');
+       if (!cptr) return;
+
+       char *lptr = strchr(cptr, '<');
+       char *rptr = strchr(cptr, '>');
+
+       if ( (lptr) && (rptr) && (rptr > lptr) ) return;
+
+       *cptr = 0;
+}
+
+
+
+
+
+/*
+ * This function is self explanatory.
+ * (What can I say, I'm in a weird mood today...)
+ */
+void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
+{
+       int i;
+
+       for (i = 0; i < strlen(name); ++i) {
+               if (name[i] == '@') {
+                       while (isspace(name[i - 1]) && i > 0) {
+                               strcpy(&name[i - 1], &name[i]);
+                               --i;
+                       }
+                       while (isspace(name[i + 1])) {
+                               strcpy(&name[i + 1], &name[i + 2]);
+                       }
+               }
+       }
+}
+
+
+/*
+ * Aliasing for network mail.
+ * (Error messages have been commented out, because this is a server.)
+ */
+int alias(char *name)
+{                              /* process alias and routing info for mail */
+       struct CitContext *CCC = CC;
+       FILE *fp;
+       int a, i;
+       char aaa[SIZ], bbb[SIZ];
+       char *ignetcfg = NULL;
+       char *ignetmap = NULL;
+       int at = 0;
+       char node[64];
+       char testnode[64];
+       char buf[SIZ];
+
+       char original_name[256];
+       safestrncpy(original_name, name, sizeof original_name);
+
+       striplt(name);
+       remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
+       stripallbut(name, '<', '>');
+
+       fp = fopen(file_mail_aliases, "r");
+       if (fp == NULL) {
+               fp = fopen("/dev/null", "r");
+       }
+       if (fp == NULL) {
+               return (MES_ERROR);
+       }
+       strcpy(aaa, "");
+       strcpy(bbb, "");
+       while (fgets(aaa, sizeof aaa, fp) != NULL) {
+               while (isspace(name[0]))
+                       strcpy(name, &name[1]);
+               aaa[strlen(aaa) - 1] = 0;
+               strcpy(bbb, "");
+               for (a = 0; a < strlen(aaa); ++a) {
+                       if (aaa[a] == ',') {
+                               strcpy(bbb, &aaa[a + 1]);
+                               aaa[a] = 0;
+                       }
+               }
+               if (!strcasecmp(name, aaa))
+                       strcpy(name, bbb);
+       }
+       fclose(fp);
+
+       /* Hit the Global Address Book */
+       if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
+               strcpy(name, aaa);
+       }
+
+       if (strcasecmp(original_name, name)) {
+               MSG_syslog(LOG_INFO, "%s is being forwarded to %s\n", original_name, name);
+       }
+
+       /* Change "user @ xxx" to "user" if xxx is an alias for this host */
+       for (a=0; a<strlen(name); ++a) {
+               if (name[a] == '@') {
+                       if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
+                               name[a] = 0;
+                               MSG_syslog(LOG_INFO, "Changed to <%s>\n", name);
+                       }
+               }
+       }
+
+       /* determine local or remote type, see citadel.h */
+       at = haschar(name, '@');
+       if (at == 0) return(MES_LOCAL);         /* no @'s - local address */
+       if (at > 1) return(MES_ERROR);          /* >1 @'s - invalid address */
+       remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
+
+       /* figure out the delivery mode */
+       extract_token(node, name, 1, '@', sizeof node);
+
+       /* If there are one or more dots in the nodename, we assume that it
+        * is an FQDN and will attempt SMTP delivery to the Internet.
+        */
+       if (haschar(node, '.') > 0) {
+               return(MES_INTERNET);
+       }
+
+       /* Otherwise we look in the IGnet maps for a valid Citadel node.
+        * Try directly-connected nodes first...
+        */
+       ignetcfg = CtdlGetSysConfig(IGNETCFG);
+       for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
+               extract_token(buf, ignetcfg, i, '\n', sizeof buf);
+               extract_token(testnode, buf, 0, '|', sizeof testnode);
+               if (!strcasecmp(node, testnode)) {
+                       free(ignetcfg);
+                       return(MES_IGNET);
+               }
+       }
+       free(ignetcfg);
+
+       /*
+        * Then try nodes that are two or more hops away.
+        */
+       ignetmap = CtdlGetSysConfig(IGNETMAP);
+       for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
+               extract_token(buf, ignetmap, i, '\n', sizeof buf);
+               extract_token(testnode, buf, 0, '|', sizeof testnode);
+               if (!strcasecmp(node, testnode)) {
+                       free(ignetmap);
+                       return(MES_IGNET);
+               }
+       }
+       free(ignetmap);
+
+       /* If we get to this point it's an invalid node name */
+       return (MES_ERROR);
+}
+
+
+
+/*
+ * Validate recipients, count delivery types and errors, and handle aliasing
+ * FIXME check for dupes!!!!!
+ *
+ * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
+ * were specified, or the number of addresses found invalid.
+ *
+ * Caller needs to free the result using free_recipients()
+ */
+struct recptypes *validate_recipients(const char *supplied_recipients, 
+                                     const char *RemoteIdentifier, 
+                                     int Flags) {
+       struct CitContext *CCC = CC;
+       struct recptypes *ret;
+       char *recipients = NULL;
+       char *org_recp;
+       char this_recp[256];
+       char this_recp_cooked[256];
+       char append[SIZ];
+       long len;
+       int num_recps = 0;
+       int i, j;
+       int mailtype;
+       int invalid;
+       struct ctdluser tempUS;
+       struct ctdlroom tempQR;
+       struct ctdlroom tempQR2;
+       int err = 0;
+       char errmsg[SIZ];
+       int in_quotes = 0;
+
+       /* Initialize */
+       ret = (struct recptypes *) malloc(sizeof(struct recptypes));
+       if (ret == NULL) return(NULL);
+
+       /* Set all strings to null and numeric values to zero */
+       memset(ret, 0, sizeof(struct recptypes));
+
+       if (supplied_recipients == NULL) {
+               recipients = strdup("");
+       }
+       else {
+               recipients = strdup(supplied_recipients);
+       }
+
+       /* Allocate some memory.  Yes, this allocates 500% more memory than we will
+        * actually need, but it's healthier for the heap than doing lots of tiny
+        * realloc() calls instead.
+        */
+       len = strlen(recipients) + 1024;
+       ret->errormsg = malloc(len);
+       ret->recp_local = malloc(len);
+       ret->recp_internet = malloc(len);
+       ret->recp_ignet = malloc(len);
+       ret->recp_room = malloc(len);
+       ret->display_recp = malloc(len);
+       ret->recp_orgroom = malloc(len);
+       org_recp = malloc(len);
+
+       ret->errormsg[0] = 0;
+       ret->recp_local[0] = 0;
+       ret->recp_internet[0] = 0;
+       ret->recp_ignet[0] = 0;
+       ret->recp_room[0] = 0;
+       ret->recp_orgroom[0] = 0;
+       ret->display_recp[0] = 0;
+
+       ret->recptypes_magic = RECPTYPES_MAGIC;
+
+       /* Change all valid separator characters to commas */
+       for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
+               if ((recipients[i] == ';') || (recipients[i] == '|')) {
+                       recipients[i] = ',';
+               }
+       }
+
+       /* Now start extracting recipients... */
+
+       while (!IsEmptyStr(recipients)) {
+               for (i=0; i<=strlen(recipients); ++i) {
+                       if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
+                       if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
+                               safestrncpy(this_recp, recipients, i+1);
+                               this_recp[i] = 0;
+                               if (recipients[i] == ',') {
+                                       strcpy(recipients, &recipients[i+1]);
+                               }
+                               else {
+                                       strcpy(recipients, "");
+                               }
+                               break;
+                       }
+               }
+
+               striplt(this_recp);
+               if (IsEmptyStr(this_recp))
+                       break;
+               MSG_syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
+               ++num_recps;
+
+               strcpy(org_recp, this_recp);
+               alias(this_recp);
+               alias(this_recp);
+               mailtype = alias(this_recp);
+
+               for (j = 0; !IsEmptyStr(&this_recp[j]); ++j) {
+                       if (this_recp[j]=='_') {
+                               this_recp_cooked[j] = ' ';
+                       }
+                       else {
+                               this_recp_cooked[j] = this_recp[j];
+                       }
+               }
+               this_recp_cooked[j] = '\0';
+               invalid = 0;
+               errmsg[0] = 0;
+               switch(mailtype) {
+               case MES_LOCAL:
+                       if (!strcasecmp(this_recp, "sysop")) {
+                               ++ret->num_room;
+                               strcpy(this_recp, config.c_aideroom);
+                               if (!IsEmptyStr(ret->recp_room)) {
+                                       strcat(ret->recp_room, "|");
+                               }
+                               strcat(ret->recp_room, this_recp);
+                       }
+                       else if ( (!strncasecmp(this_recp, "room_", 5))
+                                 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
 
+                               /* Save room so we can restore it later */
+                               tempQR2 = CCC->room;
+                               CCC->room = tempQR;
+                                       
+                               /* Check permissions to send mail to this room */
+                               err = CtdlDoIHavePermissionToPostInThisRoom(
+                                       errmsg, 
+                                       sizeof errmsg, 
+                                       RemoteIdentifier,
+                                       Flags,
+                                       0                       /* 0 = not a reply */
+                                       );
+                               if (err)
+                               {
+                                       ++ret->num_error;
+                                       invalid = 1;
+                               } 
+                               else {
+                                       ++ret->num_room;
+                                       if (!IsEmptyStr(ret->recp_room)) {
+                                               strcat(ret->recp_room, "|");
+                                       }
+                                       strcat(ret->recp_room, &this_recp_cooked[5]);
 
+                                       if (!IsEmptyStr(ret->recp_orgroom)) {
+                                               strcat(ret->recp_orgroom, "|");
+                                       }
+                                       strcat(ret->recp_orgroom, org_recp);
+
+                               }
+                                       
+                               /* Restore room in case something needs it */
+                               CCC->room = tempQR2;
+
+                       }
+                       else if (CtdlGetUser(&tempUS, this_recp) == 0) {
+                               ++ret->num_local;
+                               strcpy(this_recp, tempUS.fullname);
+                               if (!IsEmptyStr(ret->recp_local)) {
+                                       strcat(ret->recp_local, "|");
+                               }
+                               strcat(ret->recp_local, this_recp);
+                       }
+                       else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
+                               ++ret->num_local;
+                               strcpy(this_recp, tempUS.fullname);
+                               if (!IsEmptyStr(ret->recp_local)) {
+                                       strcat(ret->recp_local, "|");
+                               }
+                               strcat(ret->recp_local, this_recp);
+                       }
+                       else {
+                               ++ret->num_error;
+                               invalid = 1;
+                       }
+                       break;
+               case MES_INTERNET:
+                       /* Yes, you're reading this correctly: if the target
+                        * domain points back to the local system or an attached
+                        * Citadel directory, the address is invalid.  That's
+                        * because if the address were valid, we would have
+                        * already translated it to a local address by now.
+                        */
+                       if (IsDirectory(this_recp, 0)) {
+                               ++ret->num_error;
+                               invalid = 1;
+                       }
+                       else {
+                               ++ret->num_internet;
+                               if (!IsEmptyStr(ret->recp_internet)) {
+                                       strcat(ret->recp_internet, "|");
+                               }
+                               strcat(ret->recp_internet, this_recp);
+                       }
+                       break;
+               case MES_IGNET:
+                       ++ret->num_ignet;
+                       if (!IsEmptyStr(ret->recp_ignet)) {
+                               strcat(ret->recp_ignet, "|");
+                       }
+                       strcat(ret->recp_ignet, this_recp);
+                       break;
+               case MES_ERROR:
+                       ++ret->num_error;
+                       invalid = 1;
+                       break;
+               }
+               if (invalid) {
+                       if (IsEmptyStr(errmsg)) {
+                               snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
+                       }
+                       else {
+                               snprintf(append, sizeof append, "%s", errmsg);
+                       }
+                       if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
+                               if (!IsEmptyStr(ret->errormsg)) {
+                                       strcat(ret->errormsg, "; ");
+                               }
+                               strcat(ret->errormsg, append);
+                       }
+               }
+               else {
+                       if (IsEmptyStr(ret->display_recp)) {
+                               strcpy(append, this_recp);
+                       }
+                       else {
+                               snprintf(append, sizeof append, ", %s", this_recp);
+                       }
+                       if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
+                               strcat(ret->display_recp, append);
+                       }
+               }
+       }
+       free(org_recp);
+
+       if ((ret->num_local + ret->num_internet + ret->num_ignet +
+            ret->num_room + ret->num_error) == 0) {
+               ret->num_error = (-1);
+               strcpy(ret->errormsg, "No recipients specified.");
+       }
+
+       MSGM_syslog(LOG_DEBUG, "validate_recipients()\n");
+       MSG_syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
+       MSG_syslog(LOG_DEBUG, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
+       MSG_syslog(LOG_DEBUG, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
+       MSG_syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
+       MSG_syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
+
+       free(recipients);
+       return(ret);
+}
+
+
+/*
+ * Destructor for struct recptypes
+ */
+void free_recipients(struct recptypes *valid) {
+
+       if (valid == NULL) {
+               return;
+       }
+
+       if (valid->recptypes_magic != RECPTYPES_MAGIC) {
+               struct CitContext *CCC = CC;
+               MSGM_syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
+               abort();
+       }
+
+       if (valid->errormsg != NULL)            free(valid->errormsg);
+       if (valid->recp_local != NULL)          free(valid->recp_local);
+       if (valid->recp_internet != NULL)       free(valid->recp_internet);
+       if (valid->recp_ignet != NULL)          free(valid->recp_ignet);
+       if (valid->recp_room != NULL)           free(valid->recp_room);
+       if (valid->recp_orgroom != NULL)        free(valid->recp_orgroom);
+       if (valid->display_recp != NULL)        free(valid->display_recp);
+       if (valid->bounce_to != NULL)           free(valid->bounce_to);
+       if (valid->envelope_from != NULL)       free(valid->envelope_from);
+       if (valid->sending_room != NULL)        free(valid->sending_room);
+       free(valid);
+}
+
+
+char *qp_encode_email_addrs(char *source)
+{
+       struct CitContext *CCC = CC;
+       char *user, *node, *name;
+       const char headerStr[] = "=?UTF-8?Q?";
+       char *Encoded;
+       char *EncodedName;
+       char *nPtr;
+       int need_to_encode = 0;
+       long SourceLen;
+       long EncodedMaxLen;
+       long nColons = 0;
+       long *AddrPtr;
+       long *AddrUtf8;
+       long nAddrPtrMax = 50;
+       long nmax;
+       int InQuotes = 0;
+       int i, n;
+
+       if (source == NULL) return source;
+       if (IsEmptyStr(source)) return source;
+       if (MessageDebugEnabled != 0) cit_backtrace();
+       MSG_syslog(LOG_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
+
+       AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
+       AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
+       memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
+       *AddrPtr = 0;
+       i = 0;
+       while (!IsEmptyStr (&source[i])) {
+               if (nColons >= nAddrPtrMax){
+                       long *ptr;
+
+                       ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
+                       memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
+                       free (AddrPtr), AddrPtr = ptr;
+
+                       ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
+                       memset(&ptr[nAddrPtrMax], 0, 
+                              sizeof (long) * nAddrPtrMax);
+
+                       memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
+                       free (AddrUtf8), AddrUtf8 = ptr;
+                       nAddrPtrMax *= 2;                               
+               }
+               if (((unsigned char) source[i] < 32) || 
+                   ((unsigned char) source[i] > 126)) {
+                       need_to_encode = 1;
+                       AddrUtf8[nColons] = 1;
+               }
+               if (source[i] == '"')
+                       InQuotes = !InQuotes;
+               if (!InQuotes && source[i] == ',') {
+                       AddrPtr[nColons] = i;
+                       nColons++;
+               }
+               i++;
+       }
+       if (need_to_encode == 0) {
+               free(AddrPtr);
+               free(AddrUtf8);
+               return source;
+       }
+
+       SourceLen = i;
+       EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
+       Encoded = (char*) malloc (EncodedMaxLen);
+
+       for (i = 0; i < nColons; i++)
+               source[AddrPtr[i]++] = '\0';
+       /* TODO: if libidn, this might get larger*/
+       user = malloc(SourceLen + 1);
+       node = malloc(SourceLen + 1);
+       name = malloc(SourceLen + 1);
+
+       nPtr = Encoded;
+       *nPtr = '\0';
+       for (i = 0; i < nColons && nPtr != NULL; i++) {
+               nmax = EncodedMaxLen - (nPtr - Encoded);
+               if (AddrUtf8[i]) {
+                       process_rfc822_addr(&source[AddrPtr[i]], 
+                                           user,
+                                           node,
+                                           name);
+                       /* TODO: libIDN here ! */
+                       if (IsEmptyStr(name)) {
+                               n = snprintf(nPtr, nmax, 
+                                            (i==0)?"%s@%s" : ",%s@%s",
+                                            user, node);
+                       }
+                       else {
+                               EncodedName = rfc2047encode(name, strlen(name));                        
+                               n = snprintf(nPtr, nmax, 
+                                            (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
+                                            EncodedName, user, node);
+                               free(EncodedName);
+                       }
+               }
+               else { 
+                       n = snprintf(nPtr, nmax, 
+                                    (i==0)?"%s" : ",%s",
+                                    &source[AddrPtr[i]]);
+               }
+               if (n > 0 )
+                       nPtr += n;
+               else { 
+                       char *ptr, *nnPtr;
+                       ptr = (char*) malloc(EncodedMaxLen * 2);
+                       memcpy(ptr, Encoded, EncodedMaxLen);
+                       nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
+                       free(Encoded), Encoded = ptr;
+                       EncodedMaxLen *= 2;
+                       i--; /* do it once more with properly lengthened buffer */
+               }
+       }
+       for (i = 0; i < nColons; i++)
+               source[--AddrPtr[i]] = ',';
+
+       free(user);
+       free(node);
+       free(name);
+       free(AddrUtf8);
+       free(AddrPtr);
+       return Encoded;
+}
 
 
 /*
@@ -1074,3 +1703,12 @@ char *harvest_collected_addresses(struct CtdlMessage *msg) {
        }
        return(coll);
 }
+
+
+CTDL_MODULE_INIT(internet_addressing)
+{
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
+       }
+       return "internet_addressing";
+}