/************************************************************************** MTSSmtp.c - Mail Transport System SMTP Server (Receiver) Implementation Copyright (C) 2000 Craig Morrison, All Rights Reserved 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. :-) Notes: We are required by RFC 821 to implement the following SMTP commands: HELO - Hello MAIL - Who from RCPT - Who to DATA - The mail itself RSET - Reset and start over NOOP - Nuttin'honey QUIT - See ya And we do so here.. There is no handling of the mail here other than to dump the incoming mail into an 'inbox'. MTSAgent handles getting the mail to where it belongs. This keeps the SMTP server implementation cleaner. This server should be used in low to medium traffic situations. It uses seperate threads to handle each incoming request. While there is a test for resources and the client is notified of the problem, in high-traffic situations you'll hear a lot of bitching about connection problems unless you have a very high-end server. This server follows a STRICT rule for relaying. The sender domain is extracted from the HELO domain.com command in the SMTP dialogue. When [SMTP] Relay=0 is set, the domain must match that of the domain this server is providing SMTP for. This is for your security, if you want anyone to be able to send email through your system set Relay=1 (this is NOT recommended.) To Do: Nothing at present. **************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wresolv.h" // forward references.. void doSMTP(void *p); long spawnSMTP(void *p); int abortProcess = 0; int getMailTCP(SOCKET s, char *mf, char *rt, char *fd); int isRCPTOkay(char *rcvr); char *getShortTime(void); // local defines #define VERSION "0.06 beta" #define banner "\nMTSSmtp Copyright (C) 2000, Craig Morrison\n" \ "Mail Transport System SMTP Server v%s\n\n" #define OURMAXCONN 25 // worthless on any MShitty products except // server versions.. // 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 domains[MAX_DOMAINS][256]; int doRelay = 0; 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); doRelay = GetPrivateProfileInt(SMTP_SEC, "Relay", 0, 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); } void sigStop(int s) { WSACleanup(); abortProcess = 1; } int main(int argc, char **argv) { WSADATA stWSAData; unsigned short wWSAVersion = 0x0101; int nRC; SOCKET lsock, client, *pc; struct sockaddr_in fromsock, sock; short port; char buf[1024]; printf(banner, VERSION); if (argc != 2) { printf("\nMail Transport System SMTP server processing agent.\n\n" \ " Receives email via standard SMTP (Simple Mail Transfer Protocol)\n" \ " Please see the documentation for more details..\n\n" \ " Usage:\tMTSSmtp \n"); return 0; } strcpy(ezINI, argv[1]); GetSMTPConfig(); printf(" Servicing domain: %s\n\n", domains[0]); port = GetPrivateProfileInt(SMTP_SEC, PORT_SEC, 25, ezINI); signal(SIGINT, &sigStop); signal(SIGBREAK, &sigStop); signal(SIGTERM, &sigStop); nRC = WSAStartup(wWSAVersion, &stWSAData); if (nRC) { printf("SMTP: WSAStartup(%d.%d) failed: %d\n", HIBYTE(wWSAVersion), LOBYTE(wWSAVersion), nRC); return -1; } else printf("SMTP: WinSock Startup complete..\n"); lsock = socket(PF_INET, SOCK_STREAM, 0); if (lsock == INVALID_SOCKET) { printf("SMTP: Error creating listening socket.. Error %d\n", WSAGetLastError()); WSACleanup(); return -2; } else printf("SMTP: Listening socket [%ld] created..\n", lsock); sock.sin_family = PF_INET; sock.sin_port = htons(port); sock.sin_addr.s_addr = 0; if (bind(lsock, (struct sockaddr *) &sock, sizeof(sock))) { printf("SMTP: Error binding to port (%d).. Error %d\n", port, WSAGetLastError()); WSACleanup(); return -3; } else printf("SMTP: Port (%d) bound to socket [%ld] for service..\n", port, lsock); if (listen(lsock, OURMAXCONN)) { printf("SMTP: Error listening on socket.. Error %d\n", WSAGetLastError()); WSACleanup(); return -4; } else printf ("SMTP: Now listening on socket [%ld] for incoming connections..\n", lsock); printf("\nSMTP: Servicing SMTP on port %d.. CTRL+C to terminate.\n", port); while (1) { nRC = sizeof(fromsock); if ((client = accept(lsock, (struct sockaddr *) &fromsock, &nRC)) == INVALID_SOCKET) { if ((nRC = WSAGetLastError()) == WSAEINPROGRESS) continue; else { printf("SMTP: Interrupt detected, Closing.. Result (%d)\n", nRC); break; } } else { pc = (SOCKET *) malloc(sizeof(SOCKET)); if (pc) { memcpy(pc, &client, sizeof(SOCKET)); spawnSMTP((void *) pc); } else { // if no memory, we need to inform client of the problem (421 // ...yada, yada...) printf ("SMTP: Error out of memory, rejecting connection..\n"); sprintf(buf, "421 %s SMTP Server memory error, please try again later..\r\n", domains[0]); sendTCP(client, buf, strlen(buf)); stopTCP(client); } } } printf ("SMTP: Process cleanup in progess, waiting for threads to terminate..\n"); if (WSACleanup() == SOCKET_ERROR) { nRC = WSAGetLastError(); if (nRC != WSANOTINITIALISED) printf("WSACleanup error: %d\n", WSAGetLastError()); } printf("SMTP: Server process terminated.\n"); return 0; } long spawnSMTP(void *p) { SOCKET *s = (SOCKET *) p; long thid = _beginthread(doSMTP, 0, p); char buf[1024], *t; if (thid < 0) { printf("SMTP: Error creating thread, rejecting connection..\n"); // need to tell remote that we can't handle the connection here.. // 421 2CAH.COM SMTP Server busy, please try later... sprintf(buf, "421 %s SMTP Server busy please try again later..\r\n", domains[0]); sendTCP(*s, buf, strlen(buf)); stopTCP(*s); free(p); } else { t = getShortTime(); printf("SMTP: %s-Spawning server thread for socket [%ld]..\n", t, *s); free(t); } return thid; } // Server states, sanity checking for sender/receiver as per RFC 821 #define HELO 1 #define MAIL 2 #define RCPT 3 #define DATA 4 #define SMTP_OK "250 Ok\r\n" // this IS the server, simple state machine to receive messages via SMTP.. void doSMTP(void *p) { SOCKET *s = (SOCKET *) p; char buf[1024], *cmd; char mailFrom[256] = { 0 }; char fromDomain[256] = { 0 }; char *rcptTo = (char *) NULL; char *t; int state = HELO; int nRC, cbRX; printf("SMTP: Sending 'service ready' to receiver on socket [%ld]..\n", *s); sprintf(buf, "220 %s MTSSmtp %s SMTP service ready\r\n", domains[0], VERSION); if (!sendTCP(*s, buf, strlen(buf))) { while (1) { cbRX = 1024; if (nRC = getTCP(*s, buf, &cbRX)) { printf ("SMTP: Error waiting for response (%d) on socket [%ld]..\n", nRC, *s); break; } buf[cbRX] = 0; // first we handle the common commands for all states.. if (!strnicmp(buf, "QUIT", 4)) { printf("SMTP: (State=%d) on socket [%ld] Got QUIT\n", state, *s); sprintf(buf, "221 %s SMTP service closing, thank you.\r\n", domains[0]); sendTCP(*s, buf, strlen(buf)); break; } else if (!strnicmp(buf, "NOOP", 4)) { printf("SMTP: (State=%d) on socket [%ld] Got NOOP\n", state, *s); strcpy(buf, SMTP_OK); if (sendTCP(*s, buf, strlen(buf))) { break; } } else if (!strnicmp(buf, "RSET", 4)) { if (state != HELO) { state = MAIL; memset(mailFrom, 0, sizeof(mailFrom)); // clear // reverse-path if (rcptTo) free(rcptTo); // clear forward-path rcptTo = (char *) NULL; } printf("SMTP: (State=%d) on socket [%ld] Got RSET\n", state, *s); strcpy(buf, SMTP_OK); if (sendTCP(*s, buf, strlen(buf))) break; // the state machine actually begins here.. } else if (state == HELO) { cmd = buf; if (!strnicmp(buf, "HELO", 4)) { printf("SMTP: (State=%d) on socket [%ld] Got HELO %s", state, *s, buf + 5); memset(fromDomain, 0, sizeof(fromDomain)); strncpy(fromDomain, buf + 5, 255); t = strchr(fromDomain, '\r'); if (t) *t = '\0'; state = MAIL; strcpy(buf, SMTP_OK); if (sendTCP(*s, buf, strlen(buf))) break; } else { // we didn't get HELO, bitch about it printf ("SMTP: (State=%d) Didn't get HELO on socket [%ld], command=%s", state, *s, buf); sprintf(buf, "502 SMTP expecting HELO, try again\r\n"); if (sendTCP(*s, buf, strlen(buf))) break; } } else if (state == MAIL) { if (!strnicmp(buf, "MAIL FROM:", 10)) { printf("SMTP: (State=%d) on socket [%ld] Got %s", state, *s, buf); state = RCPT; memset(mailFrom, 0, sizeof(mailFrom)); // clear // reverse-path if (rcptTo) free(rcptTo); // clear forward-path rcptTo = (char *) NULL; strncpy(mailFrom, buf + 10, 255); // set new // reverse-path t = strchr(mailFrom, '\r'); if (t) *t = '\0'; strcpy(buf, SMTP_OK); if (sendTCP(*s, buf, strlen(buf))) break; } else { printf ("SMTP: Sender command out of sequence on socket [%ld].. Command: %s", *s, buf); strcpy(buf, "503 SMTP expecting MAIL FROM, try again\r\n"); if (sendTCP(*s, buf, strlen(buf))) break; } } else if ((state == RCPT) || (state == DATA)) { if (!strnicmp(buf, "RCPT TO:", 8)) { printf("SMTP: (State=%d) on socket [%ld] Got %s", state, *s, buf); t = buf + 8; while (*t != '<') t++; if (isRCPTOkay(t)) { if (!rcptTo) { rcptTo = (char *) malloc(2048); if (rcptTo) memset(rcptTo, 0, 2048); } if (rcptTo) { if (strlen(rcptTo) + strlen(t) + 1 < 2048) { if (rcptTo[0] != '\0') strcat(rcptTo, ","); strcat(rcptTo, t); t = strrchr(rcptTo, '>'); if (t) { t++; *t = '\0'; } state = DATA; strcpy(buf, SMTP_OK); } else strcpy(buf, "451 server buffer overflow\r\n"); } else strcpy(buf, "452 server resource problem\r\n"); } else { if ((!doRelay) && (stricmp(fromDomain, domains[0]))) { printf ("SMTP: (State=%d) on socket [%ld] not relaying to %s", state, *s, t); strcpy(buf, "550 no such user here\r\n"); } else { // from our domain or relaying is okay // (user option) if (!rcptTo) { rcptTo = (char *) malloc(2048); if (rcptTo) memset(rcptTo, 0, 2048); } if (rcptTo) { if (strlen(rcptTo) + strlen(t) + 1 < 2048) { if (rcptTo[0] != '\0') strcat(rcptTo, ","); strcat(rcptTo, t); t = strrchr(rcptTo, '>'); if (t) { t++; *t = '\0'; } state = DATA; strcpy(buf, SMTP_OK); } else strcpy(buf, "451 server buffer overflow\r\n"); } else strcpy(buf, "452 server resource problem\r\n"); } } if (sendTCP(*s, buf, strlen(buf))) break; } else if (!strnicmp(buf, "DATA", 4)) { printf("SMTP: (State=%d) on socket [%ld] Got %s", state, *s, buf); if (state == DATA) { strcpy(buf, "354 Start mail input, end with .\r\n"); if (sendTCP(*s, buf, strlen(buf))) break; if (getMailTCP(*s, mailFrom, rcptTo, fromDomain)) break; else { state = MAIL; memset(mailFrom, 0, sizeof(mailFrom)); // clear // reverse-path if (rcptTo) free(rcptTo); // clear forward-path rcptTo = (char *) NULL; } } else { strcpy(buf, "501 Receiver list empty, please send RCPT TO\r\n"); if (sendTCP(*s, buf, strlen(buf))) break; } } else { printf ("SMTP: Sender command out of sequence on socket [%ld].. Command: %s", *s, buf); strcpy(buf, "503 SMTP expecting RCPT TO or DATA, try again\r\n"); if (sendTCP(*s, buf, strlen(buf))) break; } } } } printf("SMTP: Closing connection on socket [%ld]..\n", *s); stopTCP(*s); printf("SMTP: Exiting thread for socket [%ld]..\n", *s); free(p); return; } int isRCPTOkay(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 (!strcmp(p, name)) { nRC = 1; break; } } } if (!nRC) { name[0] = 0; GetPrivateProfileString(ALIAS_SEC, p, "", name, 255, aliasINI); if (strlen(name)) { printf("SMTP: %s is an alias for %s..\n", p, name); nRC = 1; } } if (!nRC) { name[0] = 0; GetPrivateProfileString(MLIST_SEC, p, "", name, 255, listINI); if (strlen(name)) { printf("SMTP: %s is a mailing list..\n", p); nRC = 1; } } } return (nRC); } 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)); } int getSMTPLine(SOCKET s, char *buf) { char c; int nRC, cc = 1, cb = 0; while (!(nRC = getTCP(s, &c, &cc))) { if (c == '\n') { break; } else { *buf = c; buf++; cb++; if (cb >= 1023) break; } cc = 1; } if ((cb) && (*(buf - 1) == '\r')) *(buf - 1) = 0; else *buf = 0; return (nRC); } /* * Accepts: * * s - socket mf - mail from rt - rcpt to fd * - from domain */ int getMailTCP(SOCKET s, char *mf, char *rt, char *fd) { char uidMail[512], uidMailIn[512], *t; char buf[1024]; FILE *f; int fTerm = 0, nRC = 0; sprintf(uidMail, "%s%08X%03X.Mail", inboxPath, time(NULL), s); sprintf(uidMailIn, "%s%08X%03X.Mail.In", inboxPath, time(NULL), s); f = fopen(uidMailIn, "wt"); if (f) { fprintf(f, "Forward-Path: %s\nReturn-Path: %s\n", rt, mf); t = getTime(); if (t) { fprintf(f, "Received: from %s by %s (EzMTS MTSSmtp %s) ; %s\n", fd, domains[0], VERSION, t); free(t); } else fprintf(f, "Received: from %s by %s ; 01 Oct 1981 12:00:00 +00:00\n", fd, domains[0]); // 11 Jul 2000, fixed a REALLY screwed up logic error (boundary), // Liebfraumilch and SMTP don't mix.. :-) while (!fTerm) { nRC = getSMTPLine(s, buf); if (nRC) { printf ("SMTP: Error getting mail from %s on socket [%ld]..\n", fd, s); break; } if (buf[0] == '.') { if (buf[1] == 0) fTerm = 1; else fprintf(f, "%s\n", &buf[1]); } else fprintf(f, "%s\n", buf); } if (!nRC) printf("SMTP: Got mail OK from %s into %s on socket [%ld]..\n", fd, uidMail, s); fclose(f); rename(uidMailIn, uidMail); } if (!nRC) { strcpy(buf, SMTP_OK); sendTCP(s, buf, strlen(buf)); } return (nRC); } 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)); }