/************************************************************************** MTSAgent.c - Mail Transport System SMTP Relay Agent Implementation Copyright (C) 2000 Craig Morrison, All Rights Reserved Portions are: Copyright by Bob Quinn, 1997 http://www.sockets.com This being said, what is written here is so vastly different from Bob's code the above copyright is strictly for historical reason's. Credit where credit is due and he deserves his bit of mention. Notes: This source code is subject to limited distribution. You may use it to learn about SMTP, you may reuse portions of it in your own code. However, you may NOT call it your own, or redistribute it WITHOUT MY PERMISSION. If you make changes to the code and want them to be part of the distribution, please send them to me at: craig@2cah.com The above copyright notice must appear in all derivatives of this work. In short all I am asking is that you give me credit for the work that I have done in getting you this far. :-) This code works in conjunction with MTSSmtp.c, which places all mail coming in via SMTP in an INBOX. MTSAgent takes the mail in that inbox and relays it to the proper destination, whether it be local or foreign. RFC 821 Source Routing..We don't handle it here. RFC 1123 (as do many others) discourage its use. The worst that can happen is a bounce from the receiver at the end of the route. I'm not going to write the code to handle it. To Do: 1) Forwarding 2) Mailing Lists -- in progress **************************************************************************/ #define STRICT #include #include #include #include #include #include #include #include #include #include "wresolv.h" int DNS_Query(char *, char *, unsigned short, unsigned short, char *); MXDATA *mxLookup(char *achBufIn); void SMTPAgent(void); MXDATA *dnsMXLookup(char *dName); void closeMailFile(FILE * f, char *mf); char *getShortTime(void); // Some defines that are local.. #define VERSION "0.02 beta" #define banner "\nMTSAgent Copyright (C) 2000, Craig Morrison\n" \ "Mail Transport System Agent v%s\n\n" #define usage "\tUsage: MTSAgent \n" #define OKAY '+' #define RETRYIT '-' #define NOUSER '!' #define SMTP_PORT 25 #define SERVER_ALIVE 220 #define S_OKAY 250 #define SEND_DATA 354 // Globals needed by all threads.. char ezMailPath[256] = { 0 }; char ezINI[256] = { 0 }; char userINI[256] = { 0 }; char aliasINI[256] = { 0 }; char listINI[256] = { 0 }; char inboxPath[256] = { 0 }; char logFile[256] = { 0 }; char server[256] = { 0 }; char server2[256] = { 0 }; char domains[MAX_DOMAINS][256]; MXDATA *omx; void GetSMTPConfig(void) { char buf[256]; GetPrivateProfileString(ALL_SEC, MAIL_PATH, ".\\", ezMailPath, 256, ezINI); if (ezMailPath[strlen(ezMailPath) - 1] != '\\') strcat(ezMailPath, "\\"); buf[0] = 0; GetPrivateProfileString(ALL_SEC, USERS_SEC, "", buf, 256, ezINI); strcpy(userINI, ezMailPath); strcat(userINI, buf); buf[0] = 0; GetPrivateProfileString(ALL_SEC, INBOX_SEC, "", buf, 256, ezINI); strcpy(inboxPath, ezMailPath); strcat(inboxPath, buf); if (inboxPath[strlen(inboxPath) - 1] != '\\') strcat(inboxPath, "\\"); domains[0][0] = 0; GetPrivateProfileString(ALL_SEC, DOMAIN_0, "", domains[0], 256, ezINI); server[0] = 0; GetPrivateProfileString(AGENT_SEC, DNS1, "", server, 256, ezINI); server2[0] = 0; GetPrivateProfileString(AGENT_SEC, DNS2, "", server2, 256, ezINI); buf[0] = 0; GetPrivateProfileString(AGENT_SEC, ALIAS_SEC, "", buf, 256, ezINI); strcpy(aliasINI, ezMailPath); strcat(aliasINI, buf); buf[0] = 0; GetPrivateProfileString(AGENT_SEC, MLIST_SEC, "", buf, 256, ezINI); strcpy(listINI, ezMailPath); strcat(listINI, buf); buf[0] = 0; GetPrivateProfileString(AGENT_SEC, LOG_SEC, "", buf, 256, ezINI); if (strlen(buf)) { strcpy(logFile, ezMailPath); strcat(logFile, buf); } } void sigStop(int s) { return; } int main(int argc, char *argv[]) { WSADATA stWSAData; unsigned short wWSAVersion = 0x0101; int nRC, retInt; printf(banner, VERSION); if (argc == 2) strcpy(ezINI, argv[1]); else { printf(usage); return 1; } GetSMTPConfig(); if (server[0] == '\0') { printf("\nAGENT: No server in configuration.\n"); return 2; } if (domains[0][0] == '\0') { printf("\nAGENT: No domains in configuration.\n"); return 3; } nRC = WSAStartup(wWSAVersion, &stWSAData); if (nRC) { printf("WSAStartup(%d.%d) failed: %d\n", HIBYTE(wWSAVersion), LOBYTE(wWSAVersion), nRC); return 4; } printf("\nAGENT: Servicing queue %s ... ", inboxPath); signal(SIGINT, &sigStop); signal(SIGBREAK, &sigStop); signal(SIGTERM, &sigStop); retInt = GetPrivateProfileInt(AGENT_SEC, RETINT_SEC, 120, ezINI); if (retInt) printf("CTRL+C to terminate.\n\n"); else printf("Single-shot mode..\n\n"); // Lookup our own domain first.. omx = fixMX(dnsMXLookup(domains[0]), domains[0]); while (1) { SMTPAgent(); // lets play mail carrier.. if (retInt) { Sleep(retInt * 1000); // pause for retry interval.. } else break; } WSACleanup(); return 0; } MXDATA * dnsMXLookup(char *dName) { char Qbuf[BUFSIZE] = { 0 }; int nRC; nRC = DNS_Query(dName, server, IPPORT_DNS, T_MX, Qbuf); if (!nRC) { printf("AGENT: DNS Query to server %s for %s failed. nRC=[%d]", server, dName, nRC); // Primary DNS failed, try secondary.. if (strlen(server2)) { nRC = DNS_Query(dName, server2, IPPORT_DNS, T_MX, Qbuf); if (!nRC) printf ("AGENT: DNS Query to server %s for %s failed. nRC=[%d]", server2, dName, nRC); } } if (nRC > 0) return mxLookup(Qbuf); else return (MXDATA *) NULL; } int DNS_Query(char *pszQuery, char *pszServer, unsigned short usPort, unsigned short rr_type, char *achBufIn) { HEADER *pDNShdr; unsigned s; struct sockaddr_in stSockAddr; int nAddrLen = sizeof(struct sockaddr_in); struct hostent *pHostEnt; struct servent *pServEnt; char achBufOut[BUFSIZE] = { 0 }; int nQueryLen, nRC, nWSAErr; memset(&stSockAddr, 0, sizeof(stSockAddr)); stSockAddr.sin_family = AF_INET; if (usPort > 0) { stSockAddr.sin_port = htons(usPort); } else { pServEnt = getservbyname("nameserver", "UDP"); if (pServEnt) { stSockAddr.sin_port = pServEnt->s_port; } else { stSockAddr.sin_port = htons(53U); } } stSockAddr.sin_addr.s_addr = inet_addr(pszServer); if (stSockAddr.sin_addr.s_addr == INADDR_NONE) { pHostEnt = gethostbyname(pszServer); if (pHostEnt) { stSockAddr.sin_addr.s_addr = *((u_long *) pHostEnt->h_addr_list[0]); } else { return -2; } } s = socket(AF_INET, SOCK_DGRAM, 0); if (s == INVALID_SOCKET) { printf("socket() failed: %d\n", WSAGetLastError()); return -3; } // This is the work-horse, here we form the DNS query.. pDNShdr = (HEADER *) & (achBufOut[0]); pDNShdr->id = htons(0x11DF); // request ID pDNShdr->rd = 1; // recursion desired bit, makes life a LOT // easier :-) pDNShdr->qdcount = htons(1); // number of queries pDNShdr->ancount = 0; // answer entries=0 this is a request pDNShdr->nscount = 0; // authority entries=0 '' '' '' pDNShdr->arcount = 0; // resource entries=0 '' '' '' // put query name into QNAME for query // also, set the initial length of the query nQueryLen = PutQName(pszQuery, &(achBufOut[sizeof(HEADER)])); // add DNS header size to query length nQueryLen += sizeof(HEADER); // QNAME entries are null terminated if we don't use // compression, which we don't here // add null to query length (nQueryLen++) achBufOut[nQueryLen++] = 0; // set query type (QTYPE); T_A, T_PTR, T_MX, etc.. *(unsigned short *) &achBufOut[nQueryLen] = htons(rr_type); // set class type, for Internet use this will always be DNS_RRCLASS_IN *(unsigned short *) &achBufOut[nQueryLen + 2] = htons(DNS_RRCLASS_IN); // update query length nQueryLen += 5; // send that puppy! nRC = sendto(s, achBufOut, nQueryLen, 0, (struct sockaddr *) &stSockAddr, sizeof(struct sockaddr_in)); if (nRC == SOCKET_ERROR) { printf("AGENT: sendto() failed, err: %d\n", WSAGetLastError()); closesocket(s); return -4; } // wait for the puppy to fetch the paper.. nRC = recvfrom(s, achBufIn, BUFSIZE, 0, (LPSOCKADDR) & stSockAddr, &nAddrLen); if (nRC == SOCKET_ERROR) { nWSAErr = WSAGetLastError(); if (nWSAErr != WSAETIMEDOUT) { printf("AGENT: recvfrom() failed, err: %d\n", nWSAErr); closesocket(s); return -5; } else { closesocket(s); return (0); } } closesocket(s); return nRC; } MXDATA * mxLookup(char *achBufIn) { DNS_RDATA_MX *mrx; MXDATA *mx; struct in_addr i_addr; HEADER *dr; DNS_RR_HDR *q; char work[256], buf[256] = { 0 }; char *s, *d; int i, c, cbRRs = 0; int cbAN = 0; int cbNS = 0; int cbAR = 0; int ANs = 0; dr = (HEADER *) achBufIn; cbAN = ntohs(dr->ancount); // this is for future implementations cbNS = ntohs(dr->nscount); // '' '' '' '' '' cbAR = ntohs(dr->arcount); // '' '' '' '' '' cbRRs = cbAN + cbNS + cbAR; // add up total count of RRs to display. d = buf; s = (char *) achBufIn + sizeof(HEADER); c = 0; while (*s) { // loop thru QNAME until we hit a NULL i = (int) *s; s++; if (i) { for (; i; i--) { *d = *s; d++; s++; c++; } *d = '.'; d++; *d = '\0'; c++; } } if (strlen(buf)) { buf[strlen(buf) - 1] = '\0'; } // if we get here the fun begins s = achBufIn + (sizeof(HEADER) + c + 7); q = (DNS_RR_HDR *) s; // this is a kludge to handle CNAMEs and additional RRs if (ntohs(q->rr_type) == T_CNAME) { cbAN--; if (cbAN) dr->ancount = htons((unsigned short) cbAN); } if (!ntohs(dr->ancount)) { // no answer(s) to request, just bail.. printf("AGENT: No MX records for %s..\n", buf); return (MXDATA *) NULL; } if (!(mx = allocMX(cbAN))) { printf("AGENT: mxLookup memory allocation error..\n"); return (MXDATA *) NULL; } while (cbRRs) { work[0] = 0; switch (ntohs(q->rr_type)) { case T_A: i_addr.s_addr = *(long *) &q->rr_rdata; if (ANs < ntohs(dr->ancount)) { strcpy(mx[ANs].hIP, inet_ntoa(i_addr)); ANs++; } break; case T_MX: mrx = (DNS_RDATA_MX *) & q->rr_rdata; GetQName(work, (char *) &mrx->mx_xchange, achBufIn); if (cbAN) { strcpy(mx[ANs].hName, work); mx[ANs].priority = ntohs(mrx->mx_pref); ANs++; cbAN--; if (!cbAN) ANs = 0; } break; case T_CNAME: GetQName(buf, (char *) &q->rr_rdata, achBufIn); break; case T_NS: break; default: cbRRs = 1; // this forces us to break out of the loop break; } cbRRs--; if (cbRRs) { s = (char *) q; s += 10; s += ntohs(q->rr_rdlength) + 2; if (*s) // yet another kludge, this one handles // malformed QNAMEs while (*s) s++; q = (DNS_RR_HDR *) s; } } sortMX(mx); return (mx); } // moves to next path in a path1\0path2\0\0 string char * nextPath(char *s) { while (*s) s++; s++; return (s); } // transform , into \0\0\0 void splitFPath(char *fp) { char *s; while (*fp) { s = strchr(fp, ','); if (s) { *s = 0; fp = s; fp++; } else { while (*fp) fp++; fp++; *fp = 0; } } } // extracts a domain name from string // copies from 's' to 'd' void extractDomain(char *s, char *d) { *d = 0; s = strrchr(s, '@'); if (s) { s++; while ((*s != '>') && (*s)) { *d = *s; d++; s++; *d = 0; } } } // extracts a user name from string // copies from 's' to 'd' void extractUser(char *s, char *d) { *d = 0; s++; while ((*s != '@') && (*s)) { *d = *s; d++; s++; *d = 0; } } #define NOT_ATTRIB (_A_HIDDEN || _A_SUBDIR || _A_SYSTEM) // returns a string containing a comma-delimited list of filenames. char * getMailFiles(void) { struct _finddata_t fi; unsigned long hFile, cb = 1024; char mPath[1024] = { 0 }; char *mfp, *t; sprintf(mPath, "%s*.Mail", inboxPath); t = getShortTime(); printf("AGENT: %s-Collecting file list from %s..\n", t, mPath); free(t); hFile = _findfirst(mPath, &fi); if (hFile == -1L) return (char *) NULL; else { mfp = (char *) malloc(cb); if (!mfp) { _findclose(hFile); return (mfp); } else { if (!(fi.attrib & NOT_ATTRIB)) { strcpy(mfp, fi.name); } else mfp[0] = 0; while (_findnext(hFile, &fi) == 0) { if (!(fi.attrib & NOT_ATTRIB)) { if (strlen(mfp) + strlen(fi.name) > cb) { cb += 1024; t = realloc(mfp, cb); if (!t) break; else mfp = t; } if (mfp[0] != 0) strcat(mfp, ","); strcat(mfp, fi.name); } } _findclose(hFile); } } if (mfp[0] == 0) { printf("Discarding list..\n"); free(mfp); mfp = (char *) NULL; } return (mfp); } // connects to the SMTP server at 'ip' and waits for // a '220 SMTP service ready' reply. SOCKET ConnectToSMTP(char *ip) { SOCKET sc = INVALID_SOCKET; struct sockaddr_in sin; int ack; printf("AGENT: Attempting connection to %s..\n", ip); sc = socket(PF_INET, SOCK_STREAM, 0); if (sc != INVALID_SOCKET) { printf("AGENT: Socket [%ld] allocated..\n", sc); sin.sin_family = AF_INET; sin.sin_port = htons(SMTP_PORT); sin.sin_addr.s_addr = inet_addr(ip); if (sin.sin_addr.s_addr == INADDR_NONE) { printf("AGENT: Error translating IP %s..\n", ip); closesocket(sc); sc = INVALID_SOCKET; } else { if (!connect(sc, (LPSOCKADDR) & sin, sizeof(sin))) { printf("AGENT: Connected to %s on socket [%ld]..\n", ip, sc); ack = AckSend(sc); if (ack == SOCKET_ERROR) { printf ("AGENT: SOCKET_ERROR connecting to %s on socket [%ld]..\n", ip, sc); closesocket(sc); sc = INVALID_SOCKET; } else if (ack != SERVER_ALIVE) { printf ("AGENT: Error (%d) connecting to %s on socket [%ld]..\n", ack, ip, sc); closesocket(sc); sc = INVALID_SOCKET; } } else { printf("AGENT: Error connecting to %s on socket [%ld]..\n", ip, sc); closesocket(sc); sc = INVALID_SOCKET; } } } return (sc); } // pd should be in the format int SendMailFrom(SOCKET s, char *pd) { char buf[1024]; printf("AGENT: Sending MAIL FROM:%s to remote on socket [%ld]..\n", pd, s); sprintf(buf, "MAIL FROM:%s\r\n", pd); return (sendSMTP(s, buf)); } // pd should be in the format char SendRCPTTo(SOCKET s, char *pd) { char buf[1024], c; int nRC; printf("AGENT: Sending RCPT TO:%s to remote on socket [%ld]..\n", pd, s); sprintf(buf, "RCPT TO:%s\r\n", pd); nRC = sendSMTP(s, buf); switch (nRC) { case SOCKET_ERROR: c = 0; break; case S_OKAY: c = OKAY; break; case 550: case 251: case 551: c = NOUSER; break; default: c = RETRYIT; break; } return (c); } int SendHELO(SOCKET s) { char buf[512]; sprintf(buf, "HELO %s\r\n", domains[0]); return (sendSMTP(s, buf)); } int SendQUIT(SOCKET s) { char buf[512]; printf("AGENT: Sending QUIT command to remote on socket [%ld]..\n", s); sprintf(buf, "QUIT\r\n"); return (sendSMTP(s, buf)); } // sends mail file 'f' to socket 's', RFC 821 transparency is // supported. int sendMailTCP(SOCKET s, FILE * f) { char obuf[1024], buf[1024], *t; long cfp; int nRC = 0; printf("AGENT: Sending DATA command to remote on socket [%ld]..\n", s); strcpy(obuf, "DATA\r\n"); if ((nRC = sendSMTP(s, obuf)) == SEND_DATA) { cfp = ftell(f); printf("AGENT: Sending mail data to remote on socket [%ld]..\n", s); fgets(buf, 1024, f); // strip reverse path, only last receiver // adds it while (fgets(buf, 1023, f) != NULL) { t = strchr(buf, '\n'); if (t) *t = 0; if (buf[0] == '.') sprintf(obuf, ".%s\r\n", buf); else sprintf(obuf, "%s\r\n", buf); nRC = sendTCP(s, obuf, strlen(obuf)); if (nRC) break; } if (!nRC) { sprintf(buf, ".\r\n"); nRC = sendSMTP(s, buf); } fseek(f, cfp, SEEK_SET); } return (nRC); } // sends mail file to a local user // fp is in format.. int sendMailLocal(FILE * f, char *fp) { char uName[256], uidMail[512], buf[1024]; FILE *o; long cfp; int r; cfp = ftell(f); extractUser(fp, uName); printf("AGENT: sending mail to local user %s..\n", uName); /* * construct path: x:\mailpath\user\34352035.mail */ sprintf(uidMail, "%s%s\\%08X.Mail", ezMailPath, uName, time(NULL)); o = fopen(uidMail, "w"); if (o) { // we don't strip reverse path here, user is last receiver while (fgets(buf, 1024, f) != NULL) { if (fputs(buf, o) == EOF) { printf("AGENT: Error writing mail file %s\n", uidMail); break; } } fclose(o); r = 0; } else { printf("AGENT: Error creating user mail file %s\n", uidMail); r = 1; } fseek(f, cfp, SEEK_SET); return (r); } // prepare forward path to be written back to mail file.. void fixForwardPath(char *fp, char *d) { char *t = d; *d = 0; while (*fp) { if ((*fp == OKAY) || (*fp == NOUSER)) fp = nextPath(fp); else { *d = '<'; d++; fp++; while (*fp) { *d = *fp; d++; fp++; *d = 0; } *d = ','; d++; *d = 0; fp++; } } if (*t) { d--; *d = 0; } } char * getTime(void) { char tmpbuf[128], timeZone[256]; struct tm *today; int h, m; time_t ltime; _tzset(); time(<ime); today = localtime(<ime); strftime(tmpbuf, 128, "%a, %d %b %y %H:%M:%S", today); h = _timezone / 3600; m = (_timezone % 3600) / 60; sprintf(timeZone, "%s %c%0.2ld:%0.2ld", tmpbuf, h < 0 ? '+' : '-', abs(h), abs(m)); return (strdup(timeZone)); } char * getShortTime(void) { char tmpbuf[128]; struct tm *today; time_t ltime; _tzset(); time(<ime); today = localtime(<ime); strftime(tmpbuf, 128, "%d/%m/%Y %H:%M:%S", today); return (strdup(tmpbuf)); } int bounceMail(FILE * f, char *fp, char *mf) { char obuf[1024], buf[1024], mName[512], *p; FILE *o; printf("AGENT: Bouncing %s for delivery failure (no such user)..\n", mf); fgets(obuf, 1024, f); // get return path p = strchr(obuf, '<'); if (!p) p = obuf; if (!strncmp(p, "<>", 2)) { // we can't bounce a bounce message :-) printf("AGENT: can't bounce [%s] error reply..\n", mf); *fp = OKAY; return 0; } sprintf(mName, "%s%08X.Mail", inboxPath, time(NULL)); o = fopen(mName, "w"); if (o) { // p is already set above.. sprintf(buf, "Forward-Path: %s", p); // rewrite for bounce fputs(buf, o); sprintf(buf, "Return-Path: <>\n"); fputs(buf, o); // write new header fields p = getTime(); sprintf(buf, "Date: %s\n", p); free(p); fputs(buf, o); // write Date: sprintf(buf, "From: Postmaster@%s\n", domains[0]); fputs(buf, o); // write From: sprintf(buf, "Subject: Failed Delivery Notification\n", domains[0]); fputs(buf, o); // write Subject: strcpy(buf, "To: "); p = strchr(obuf, '<'); if (!p) p = obuf; p++; strcat(buf, p); p = strrchr(buf, '>'); if (!p) p = buf; *p = 0; strcat(buf, "\n\n"); fputs(buf, o); // write To: sprintf(buf, "Mail transport error, EzMTS SMTP Relay Agent could not " \ "deliver the following message.\n\nReason: 550 No such user\n\n"); fputs(buf, o); sprintf(buf, " --======-- Original Message Follows --======-- \n\n"); fputs(buf, o); while (fgets(buf, 1024, f)) fputs(buf, o); fclose(o); return 0; } else { printf("AGENT: Could not open %s for bounce..\n", mf); *fp = RETRYIT; } return 1; } // rewrites a mail file with updated 'fp' forward path information // NOTE: the original mail file is CLOSED by this function! int rewriteMail(FILE * f, char *fp, char *mf) { char obuf[1024], buf[1024], mName[512], *p; int rt, crt; FILE *o; p = strrchr(mf, '\\'); if (!p) p = mf; rt = GetPrivateProfileInt(RETRY_SEC, p, 0, ezINI); crt = GetPrivateProfileInt(AGENT_SEC, RETRY_SEC, 0, ezINI); rt++; sprintf(buf, "%d", rt); WritePrivateProfileString(RETRY_SEC, p, buf, ezINI); sprintf(mName, "%s.tmp", mf); o = fopen(mName, "w"); if (o) { if (rt <= crt) { printf("AGENT: Rewriting %s after partial processing..\n", mf); sprintf(buf, "Forward-Path: %s\n", fp); fputs(buf, o); } else { printf ("AGENT: Bouncing %s for delivery failure (retries exceeded)..\n", mf); fgets(obuf, 1024, f); // get return path p = strchr(obuf, '<'); if (!p) p = obuf; if (!strncmp(p, "<>", 2)) { // can't bounce a bounce message printf("AGENT: can't bounce [%s] error reply..\n", mf); fclose(o); // close temp message file _unlink(mName); // delete it.. closeMailFile(f, mf); // now close the original.. return 0; } sprintf(buf, "Forward-Path: %s", p); // rewrite for bounce fputs(buf, o); sprintf(buf, "Return-Path: <>\n"); fputs(buf, o); // write new header fields p = getTime(); sprintf(buf, "Date: %s\n", p); free(p); fputs(buf, o); // write Date: sprintf(buf, "From: Postmaster@%s\n", domains[0]); fputs(buf, o); // write From: sprintf(buf, "Subject: Failed Delivery Notification\n", domains[0]); fputs(buf, o); // write Subject: strcpy(buf, "To: "); p = strchr(obuf, '<'); if (!p) p = obuf; p++; strcat(buf, p); p = strrchr(buf, '>'); if (!p) p = buf; *p = 0; strcat(buf, "\n\n"); fputs(buf, o); // write To: sprintf(buf, "Mail transport error, EzMTS SMTP Agent could not " \ "deliver the following message.\n\nReason: Delivery Retries Exceeded.\n\n"); fputs(buf, o); fputs(buf, o); sprintf(buf, " --======-- Original Message Follows --======-- \n\n"); fputs(buf, o); } while (fgets(buf, 1024, f)) fputs(buf, o); fclose(o); fclose(f); _unlink(mf); rename(mName, mf); return 0; } else printf("AGENT: Could not open %s for rewrite..\n", mf); return 1; } // closes out and deletes a mail file.. void closeMailFile(FILE * f, char *mf) { char *p; p = strrchr(mf, '\\'); if (!p) p = mf; WritePrivateProfileString(RETRY_SEC, p, NULL, ezINI); printf("AGENT: Closing and removing %s..\n", mf); fclose(f); _unlink(mf); } // takes and tells us if the user is // one of ours.. int isReceiverLocal(char *rcvr) { char buf[1024], *p; char uBuf[256], name[256]; int i, c, nRC = 0; memset(buf, 0, sizeof(buf)); strncpy(buf, rcvr, 1024); p = strrchr(buf, '>'); if (p) *p = 0; p = strrchr(buf, '@'); if (p) { *p = '\0'; // truncate username at '@' character for // comparison p++; // test domain first if (!strnicmp(p, domains[0], strlen(domains[0]))) { c = GetPrivateProfileInt(USER_SEC, COUNT_SEC, 0, userINI); p = buf + 1; // point just past '<' char for user name for (i = 0; i < c; i++) { sprintf(uBuf, "User%d", i); name[0] = '\0'; GetPrivateProfileString(USER_SEC, uBuf, "...", name, 255, userINI); // test username if (!stricmp(p, name)) { nRC = 1; break; } } } } return (nRC); } MXDATA * fixMXDomain(MXDATA * om, MXDATA * m, char *tDomain) { int i, j, k = m[0].cbMX; // first remove ourselves from the list of MXs for (i = 0; i < om[0].cbMX; i++) { for (j = 0; j < m[0].cbMX; j++) { if ((m[j].priority != 0xFFFF) && (!stricmp(om[i].hName, m[j].hName))) { k--; m[j].priority = 0xFFFF; } } } // did we remove anything? if (k < m[0].cbMX) { // now remove MXs with priorities equal to/greater than our own for (i = 0; i < om[0].cbMX; i++) { for (j = 0; j < m[0].cbMX; j++) { if ((m[j].priority != 0xFFFF) && (m[i].hName >= om[j].hName)) m[j].priority = 0xFFFF; } } } if ((k) && (k < m[0].cbMX)) { sortMX(m); for (j = m[0].cbMX, i = 0; i < j; i++) m[i].cbMX = k; } else if (!k) { free(m); m = (MXDATA *) NULL; m = fixMX(m, tDomain); } return (m); } char * getAlias(char *s, char *d) { char uName[256], uDomain[256], aName[256]; extractUser(s, uName); extractDomain(s, uDomain); if (!stricmp(uDomain, domains[0])) { aName[0] = 0; GetPrivateProfileString(ALIAS_SEC, uName, "", aName, 256, aliasINI); if (strlen(aName)) strcpy(uName, aName); } sprintf(d, "<%s@%s>", uName, uDomain); while (*d) d++; d++; *d = 0; return (d); } void processAlias(char *mf) { FILE *f, *of; char buf[1024], work[1024], *t, *p; char nFile[512]; printf("AGENT: Checking aliases for mail file %s..\n", mf); f = fopen(mf, "r"); if (f) { fgets(work, 1024, f); // get forward path.. fclose(f); if (work[strlen(work) - 1] == '\n') work[strlen(work) - 1] = 0; t = work + 14; // forward path pointer strcpy(work, t); // remove Forward-Path: splitFPath(work); // split the paths out buf[0] = 0; p = buf; t = work; while (*t) { p = getAlias(t, p); t = nextPath(t); } p = buf; // new forward path while (*p) { while (*p) p++; if (*(p + 1) != 0) *p = ','; } t = work; // old forward path while (*t) { while (*t) t++; if (*(t + 1) != 0) *t = ','; } if (strcmp(work, buf)) { printf("AGENT: Rewriting mail file %s with aliases..\n", mf); sprintf(nFile, "%s.tmp", mf); of = fopen(nFile, "w"); if (of) { f = fopen(mf, "r"); if (f) { sprintf(work, "Forward-Path: %s\n", buf); fputs(work, of); fgets(work, 1024, f); // swallow old forward path while (fgets(work, 1024, f) != NULL) fputs(work, of); fclose(f); fclose(of); _unlink(mf); rename(nFile, mf); } else { fclose(of); _unlink(nFile); } } } } } /* * the agent itself.. */ void SMTPAgent(void) { char *mf, rPath[512], tDomain[256], buf[1024], *t; char mFile[512], nPath[1024]; FILE *f; MXDATA *mx; SOCKET skt; long cfp; int i; char *fList; fList = getMailFiles(); if (fList) { splitFPath(fList); mf = fList; while (*mf) { sprintf(mFile, "%s%s", inboxPath, mf); printf("AGENT: Processing mail file %s..\n", mFile); processAlias(mFile); // process aliases first.. f = fopen(mFile, "r"); // we'll close thru either // closeMailFile() // or rewriteMail().. if (f) { buf[0] = 0; fgets(buf, 1024, f); // get Forward-Path if (buf[strlen(buf) - 1] == '\n') buf[strlen(buf) - 1] = 0; // sanity check, Forward-Path should be first line if ((strlen(buf)) && (!strncmp("Forward-Path: ", buf, 14))) { t = buf + 14; strcpy(buf, t); splitFPath(buf); cfp = ftell(f); // where are we? fgets(rPath, 512, f); // get 'Return-Path: ' (13) if (rPath[strlen(rPath) - 1] == '\n') rPath[strlen(rPath) - 1] = 0; t = rPath + 13; strcpy(rPath, t); // thus begins the trek down the email path.. t = buf; // t is our forward path pointer while (*t) { fseek(f, cfp, SEEK_SET); // rewind extractDomain(t, tDomain); if (isReceiverLocal(t)) { if (!sendMailLocal(f, t)) *t = OKAY; else *t = NOUSER; } // user wasn't local see if it is our domain else if (!stricmp(domains[0], tDomain)) *t = NOUSER; // it is, user doesn't exist.. else { // process foreign mail mx = fixMX(dnsMXLookup(tDomain), tDomain); mx = fixMXDomain(omx, mx, tDomain); for (i = 0; i < mx[0].cbMX; i++) { if (*t != '<') // not first time thru, reset // it *t = '<'; skt = ConnectToSMTP(mx[i].hIP); if (skt != INVALID_SOCKET) { // we // connected! // :-) if (SendHELO(skt) == S_OKAY) { if (SendMailFrom(skt, rPath) == S_OKAY) { *t = SendRCPTTo(skt, t); if (*t == OKAY) { if (sendMailTCP(skt, f) != S_OKAY) *t = RETRYIT; } } // SendMailFrom else *t = RETRYIT; SendQUIT(skt); } // SendHELO else *t = RETRYIT; stopTCP(skt); } else *t = RETRYIT; if (*t == OKAY) break; } // for free(mx); } if (*t == NOUSER) // no such user, bounce it back // to sender bounceMail(f, t, mFile); t = nextPath(t); } // while(*t) fixForwardPath(buf, nPath); // rewrite forward path if (strlen(nPath)) rewriteMail(f, nPath, mFile); // not all sent // rewrite mail else closeMailFile(f, mFile); // all sent, close and // delete file } // if forward-path exists else { fclose(f); printf("AGENT: error parsing %s..\n", mFile); } } // if (f) mf = nextPath(mf); } // while (*mf) free(fList); } }