/************************************************************************** MTSPop3.c - Mail Transport System POP3 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 POP3, 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: This is a standard POP3 server, which functions in accordance with RFC 1939. There is one slight deviation from that standard in the handling of the NOOP command, I treat it as a 'global' command instead of being valid only in the transaction state. No harm done as long as the client is written according to the RFC. Here's what we support: USER PASS STAT LIST RETR DELE NOOP RSET QUIT TOP UIDL TOP and UIDL are optional, however, some clients seem to expect them. **************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wresolv.h" // forward references.. void doPOP(void *p); long spawnPOP(void *p); int abortProcess = 0; int getMailTCP(SOCKET s, char *mf, char *rt, char *fd); char *getShortTime(void); // local defines #define VERSION "0.02 beta" #define banner "\nMTSPop3 Copyright (C) 2000, Craig Morrison\n" \ "Mail Transport System POP3 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 passINI[256] = { 0 }; char inboxPath[256] = { 0 }; char domains[MAX_DOMAINS][256]; typedef struct fdata { unsigned short cbData; char name[256]; unsigned long size; int deleted; } FDATA , *PFDATA; void GetPOP3Config(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, "\\"); buf[0] = 0; GetPrivateProfileString(POP_SEC, PASS_SEC, "", buf, 256, ezINI); strcpy(passINI, ezMailPath); strcat(passINI, buf); domains[0][0] = 0; GetPrivateProfileString(ALL_SEC, DOMAIN_0, "", domains[0], 256, ezINI); } 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 POP3 server processing agent.\n\n" \ " Serves email via standard POP3 (Post Office Protocol Version 3)\n" \ " Please see the documentation for more details..\n\n" \ " Usage:\tMTSPop3 \n"); return 0; } strcpy(ezINI, argv[1]); GetPOP3Config(); printf(" Servicing domain: %s\n\n", domains[0]); port = GetPrivateProfileInt(POP_SEC, PORT_SEC, 110, ezINI); signal(SIGINT, &sigStop); signal(SIGBREAK, &sigStop); signal(SIGTERM, &sigStop); nRC = WSAStartup(wWSAVersion, &stWSAData); if (nRC) { printf("POP: WSAStartup(%d.%d) failed: %d\n", HIBYTE(wWSAVersion), LOBYTE(wWSAVersion), nRC); return -1; } else printf("POP: WinSock Startup complete..\n"); lsock = socket(PF_INET, SOCK_STREAM, 0); if (lsock == INVALID_SOCKET) { printf("POP: Error creating listening socket.. Error %d\n", WSAGetLastError()); WSACleanup(); return -2; } else printf("POP: 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("POP: Error binding to port (%d).. Error %d\n", port, WSAGetLastError()); WSACleanup(); return -3; } else printf("POP: Port (%d) bound to socket [%ld] for service..\n", port, lsock); if (listen(lsock, OURMAXCONN)) { printf("POP: Error listening on socket.. Error %d\n", WSAGetLastError()); WSACleanup(); return -4; } else printf ("POP: Now listening on socket [%ld] for incoming connections..\n", lsock); printf("\nPOP: Servicing POP3 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("POP: Interrupt detected, Closing.. Result (%d)\n", nRC); break; } } else { pc = (SOCKET *) malloc(sizeof(SOCKET)); if (pc) { memcpy(pc, &client, sizeof(SOCKET)); spawnPOP((void *) pc); } else { // if no memory, we need to inform client of the problem printf ("POP: Error out of memory, rejecting connection..\n"); sprintf(buf, "-ERR %s POP3 Server memory error, please try again later..\r\n", domains[0]); sendTCP(client, buf, strlen(buf)); stopTCP(client); } } } printf ("POP: 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("POP: Server process terminated.\n"); return 0; } long spawnPOP(void *p) { SOCKET *s = (SOCKET *) p; long thid = _beginthread(doPOP, 0, p); char buf[1024], *t; if (thid < 0) { printf("POP: Error creating thread, rejecting connection..\n"); sprintf(buf, "-ERR %s POP3 Server busy please try again later..\r\n", domains[0]); sendTCP(*s, buf, strlen(buf)); stopTCP(*s); free(p); } else { t = getShortTime(); printf("POP: %s-Spawning server thread for socket [%ld]..\n", t, *s); free(t); } return thid; } #define NOT_ATTRIB (_A_HIDDEN || _A_SUBDIR || _A_SYSTEM) FDATA * getFileList(char *mPath, int *count) { struct _finddata_t fi; unsigned long hFile, cb = 10, cbFiles = 0; FDATA *mfp, *t; printf("POP: Collecting file list from %s..\n", mPath); hFile = _findfirst(mPath, &fi); if (hFile == -1L) return (FDATA *) NULL; else { mfp = (FDATA *) malloc(cb * sizeof(FDATA)); if (!mfp) { _findclose(hFile); return (mfp); } else { if (!(fi.attrib & NOT_ATTRIB)) { strcpy(mfp[cbFiles].name, fi.name); mfp[cbFiles].size = fi.size; mfp[cbFiles].deleted = 0; cbFiles++; } while (_findnext(hFile, &fi) == 0) { if (!(fi.attrib & NOT_ATTRIB)) { if (cbFiles >= cb) { cb += 10; t = realloc(mfp, cb * sizeof(FDATA)); if (!t) break; else mfp = t; } strcpy(mfp[cbFiles].name, fi.name); mfp[cbFiles].size = fi.size; mfp[cbFiles].deleted = 0; cbFiles++; } } _findclose(hFile); } } if (!cbFiles) { free(mfp); *count = 0; mfp = (FDATA *) NULL; } else *count = cbFiles; return (mfp); } int isPOPUserLocal(char *rcvr, char *u) { char *p, uBuf[256], name[256], uname[256]; int i, c, nRC = 0; memset(name, 0, sizeof(name)); strncpy(name, rcvr, 255); p = strchr(name, '\r'); if (!p) p = strchr(name, '\n'); if (p) *p = 0; c = GetPrivateProfileInt(USER_SEC, COUNT_SEC, 0, userINI); for (i = 0; i < c; i++) { sprintf(uBuf, "User%d", i); GetPrivateProfileString(USER_SEC, uBuf, "...", uname, 255, userINI); // test username if (!stricmp(name, uname)) { strcpy(u, uname); nRC = 1; break; } } return (nRC); } int isPOPUserOkay(char *uName, char *pass) { char *p, uPW[256], pw[256]; int nRC = 0; memset(pw, 0, sizeof(pw)); strncpy(pw, pass, 255); p = strrchr(pw, '\r'); if (!p) p = strrchr(pw, '\n'); if (p) *p = 0; GetPrivateProfileString(PASS_SEC, uName, "...", uPW, 255, passINI); // test password if (!strcmp(pw, uPW)) nRC = 1; return (nRC); } // sends mail file 'f' to socket 's', RFC 821 transparency is // supported. int sendMailTCP(SOCKET s, char *mFile, char *uName) { char obuf[1024], buf[1024], *t; FILE *f; int nRC = 0; printf("POP: Sending +OK to start mail for remote on socket [%ld]..\n", s); sprintf(buf, "+OK mail data follows.\r\n"); if (!sendTCP(s, buf, strlen(buf))) { sprintf(obuf, "%s%s\\%s", ezMailPath, uName, mFile); f = fopen(obuf, "r"); if (f) { printf ("POP: Sending mail data [%s] to remote on socket [%ld]..\n", mFile, s); while (fgets(buf, 1023, f) != NULL) { t = strchr(buf, '\n'); if (t) *t = 0; if (buf[0] == '.') sprintf(obuf, ".%s\n", buf); else sprintf(obuf, "%s\n", buf); nRC = sendTCP(s, obuf, strlen(obuf)); if (nRC) break; } if (!nRC) { sprintf(buf, "\r\n.\r\n"); nRC = sendTCP(s, buf, strlen(buf)); printf ("POP: EOD sent to remote on socket [%ld], sendTCP() RC=%d..\n", s, nRC); } fclose(f); } } return (nRC); } // sends mail file 'f' to socket 's', RFC 821 transparency is // supported. int sendTOPMailTCP(SOCKET s, char *mFile, char *uName, int lines) { char obuf[1024], buf[1024], *t; FILE *f; int nRC = 0; printf ("POP: Sending +OK to start (TOP) mail for remote on socket [%ld]..\n", s); sprintf(buf, "+OK mail data follows.\r\n"); if (!sendTCP(s, buf, strlen(buf))) { sprintf(obuf, "%s%s\\%s", ezMailPath, uName, mFile); f = fopen(obuf, "r"); if (f) { printf ("POP: Sending mail data [%s] to remote on socket [%ld]..\n", mFile, s); while (fgets(buf, 1023, f) != NULL) { t = strchr(buf, '\n'); if (t) *t = 0; sprintf(obuf, "%s\n", buf); nRC = sendTCP(s, obuf, strlen(obuf)); if ((nRC) || (!strlen(buf))) break; } while ((lines) && (fgets(buf, 1023, f) != NULL)) { t = strchr(buf, '\n'); if (t) *t = 0; if (buf[0] == '.') sprintf(obuf, ".%s\n", buf); else sprintf(obuf, "%s\n", buf); nRC = sendTCP(s, obuf, strlen(obuf)); if (nRC) break; lines--; } if (!nRC) { sprintf(obuf, ".\r\n"); printf("POP: EOD sent to remote on socket [%ld]..\n", s); nRC = sendTCP(s, obuf, strlen(obuf)); } fclose(f); } } return (nRC); } // Server states, sanity checking for sender/receiver as per RFC 1939 #define AUTHENTICATE 1 #define TRANSACTION 2 #define UPDATE 3 // this IS the server, simple state machine to send messages via POP3.. void doPOP(void *p) { SOCKET *s = (SOCKET *) p; char buf[1024], uName[128]; char *t; int state = AUTHENTICATE; int i, fc, nRC, cbRX, cbFiles; unsigned long fs; FDATA *fp; // t = t; printf("POP: Sending 'service ready' to receiver on socket [%ld]..\n", *s); sprintf(buf, "+OK %s MTSPop3 %s POP3 service ready\r\n", domains[0], VERSION); if (!sendTCP(*s, buf, strlen(buf))) { memset(uName, 0, sizeof(uName)); fp = (FDATA *) NULL; while (1) { cbRX = 1024; if (nRC = getTCP(*s, buf, &cbRX)) { printf ("POP: Error waiting for response (%d) on socket [%ld]..\n", nRC, *s); break; } else if (!cbRX) { printf ("POP: Error receiving response, 0 length reply on socket [%ld]..\n", *s); break; } buf[cbRX] = 0; // quit is always valid, regardless of state.. if (!strnicmp(buf, "QUIT", 4)) { if ((state == TRANSACTION) && (fp)) { state = UPDATE; printf("POP: Entering UPDATE state..\n"); fc = 0; if (fp) { for (i = 0; i < cbFiles; i++) { if (fp[i].deleted) { sprintf(buf, "%s%s\\%s", ezMailPath, uName, fp[i].name); printf("POP: deleting %s..\n", buf); if (_unlink(buf)) { printf("POP: Error deleting: %s..\n", buf); fc++; } } else fc++; } } if (fc) printf("POP: Nothing to delete.. :-)\n"); printf("POP: Leaving UPDATE state..\n"); } printf("POP: (State=%d) on socket [%ld] Got QUIT\n", state, *s); sprintf(buf, "+OK %s POP3 service closing (%d msgs left), thank you.\r\n", domains[0], fc); sendTCP(*s, buf, strlen(buf)); break; } // this doesn't belong here, should be in TRANSACTION state.. else if (!strnicmp(buf, "NOOP", 4)) { printf("POP: (State=%d) on socket [%ld] Got NOOP\n", state, *s); sprintf(buf, "+OK\r\n"); if (sendTCP(*s, buf, strlen(buf))) break; } // authenticate user and open maildrop else if (state == AUTHENTICATE) { if (!strnicmp(buf, "USER", 4)) { printf("POP: (State=%d) on socket [%ld] Got %s", state, *s, buf); if (strlen(uName)) { printf("POP: sent ERR to USER on socket [%ld]..\n", *s); sprintf(buf, "-ERR Command invalid in this state.\r\n"); if (sendTCP(*s, buf, strlen(buf))) break; } else if (isPOPUserLocal(&buf[5], uName)) { printf ("POP: sent OK to USER %s on socket [%ld]..\n", uName, *s); sprintf(buf, "+OK Username %s accepted, send PASS.\r\n", uName); if (sendTCP(*s, buf, strlen(buf))) break; } else { printf("POP: sent ERR to USER on socket [%ld]..\n", *s); sprintf(buf, "-ERR No such user here.\r\n"); if (sendTCP(*s, buf, strlen(buf))) break; } } else if (!strnicmp(buf, "PASS", 4)) { printf("POP: (State=%d) on socket [%ld] Got PASS\n", state, *s); if (!strlen(uName)) { printf("POP: sent ERR to PASS on socket [%ld]..\n", *s); sprintf(buf, "-ERR Must send USER first.\r\n"); if (sendTCP(*s, buf, strlen(buf))) break; } else if (isPOPUserOkay(uName, buf + 5)) { printf("POP: sent OK to PASS on socket [%ld]..\n", *s); sprintf(buf, "+OK maildrop %s accepted.\r\n", uName); if (sendTCP(*s, buf, strlen(buf))) break; state = TRANSACTION; } else { printf("POP: sent ERR to PASS on socket [%ld]..\n", *s); sprintf(buf, "-ERR PASS not valid for USER.\r\n"); if (sendTCP(*s, buf, strlen(buf))) break; memset(uName, 0, sizeof(uName)); } } else { printf("POP: (State=%d) on socket [%ld] Got %s", state, *s, buf); sprintf(buf, "-ERR Command invalid in this state.\r\n"); if (sendTCP(*s, buf, strlen(buf))) break; } } // maildrop open, enter transaction state else if (state == TRANSACTION) { printf("POP: (State=%d) on socket [%ld] Got %s", state, *s, buf); if (!strnicmp(buf, "STAT", 4)) { sprintf(buf, "%s%s\\*.mail", ezMailPath, uName); fp = getFileList(buf, &cbFiles); if (fp) { for (fs = 0, fc = 0, i = 0; i < cbFiles; i++) if (!fp[i].deleted) { fs += fp[i].size; fc++; } } else { fs = 0; fc = 0; } sprintf(buf, "+OK %d %d\r\n", fc, fs); printf("POP: STAT result on socket[%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; } else if (!strnicmp(buf, "RSET", 4)) { if (fp) { for (i = 0; i < cbFiles; i++) fp[i].deleted = 0; } sprintf(buf, "+OK maildrop has %d message%s.\r\n", cbFiles, (cbFiles == 1) ? "" : "s"); printf("POP: RSET result on socket[%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; } else if (!strnicmp(buf, "DELE", 4)) { t = buf + 5; i = atoi(t); if ((fp) && (i <= cbFiles) && (i != 0)) { i--; if (fp[i].deleted) sprintf(buf, "-ERR message %d already deleted.\r\n", i + 1); else { fp[i].deleted = 1; sprintf(buf, "+OK message %d deleted.\r\n", i + 1); } printf("POP: DELE result on socket [%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; } else { sprintf(buf, "-ERR message %d does not exist.\r\n", i); printf("POP: DELE result on socket [%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; } } else if (!strnicmp(buf, "LIST", 4)) { t = buf + 5; i = atoi(t); if ((fp) && (i <= cbFiles)) { if (!i) { // scan listing sprintf(buf, "+OK scan listing follows.\r\n"); printf("POP: LIST result on socket [%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; for (i = 0; i < cbFiles; i++) { if (!fp[i].deleted) { sprintf(buf, "%d %d\r\n", i + 1, fp[i].size); printf ("POP: LIST result on socket [%ld]=%s", *s, buf); nRC = sendTCP(*s, buf, strlen(buf)); if (nRC) break; } } if (nRC) break; else { sprintf(buf, ".\r\n"); printf ("POP: LIST result on socket [%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; } } else { i--; if (fp[i].deleted) sprintf(buf, "-ERR message %d is deleted.\r\n", i + 1); else sprintf(buf, "+OK %d %d\r\n", i + 1, fp[i].size); printf("POP: LIST result on socket [%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; } } else { sprintf(buf, "-ERR message %d does not exist.\r\n", i); printf("POP: LIST result on socket [%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; } } else if (!strnicmp(buf, "UIDL", 4)) { t = buf + 5; i = atoi(t); if ((fp) && (i <= cbFiles)) { if (!i) { // scan listing sprintf(buf, "+OK scan listing follows.\r\n"); printf("POP: UIDL result on socket [%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; for (i = 0; i < cbFiles; i++) { if (!fp[i].deleted) { _splitpath(fp[i].name, NULL, NULL, buf, NULL); strcat(buf, "\r\n"); printf ("POP: UIDL result on socket [%ld]=%s", *s, buf); nRC = sendTCP(*s, buf, strlen(buf)); if (nRC) break; } } if (nRC) break; else { sprintf(buf, ".\r\n"); printf ("POP: UIDL result on socket [%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; } } else { i--; if (fp[i].deleted) sprintf(buf, "-ERR message %d is deleted.\r\n", i + 1); else { strcpy(buf, "+OK "); _splitpath(fp[i].name, NULL, NULL, &buf[4], NULL); strcat(buf, "\r\n"); } printf("POP: UIDL result on socket [%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; } } else { sprintf(buf, "-ERR message %d does not exist.\r\n", i); printf("POP: UIDL result on socket [%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; } } // do last, they send message data.. else if (!strnicmp(buf, "RETR", 4)) { t = buf + 5; i = atoi(t); if ((fp) && (i <= cbFiles) && (i != 0)) { i--; if (fp[i].deleted) { sprintf(buf, "-ERR message %d deleted.\r\n", i + 1); printf("POP: RETR result on socket [%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; } else { if (sendMailTCP(*s, fp[i].name, uName)) break; } } else { sprintf(buf, "-ERR message %d does not exist.\r\n", i); printf("POP: RETR result on socket [%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; } } // TOP 1 15 else if (!strnicmp(buf, "TOP", 3)) { t = buf + 4; i = atoi(t); // get message number while ((*t != ' ') && (*t)) t++; // skip first arg t++; // skip ' ' fc = atoi(t); // get number of lines to send if ((fp) && (i <= cbFiles) && (i != 0)) { i--; if (fp[i].deleted) { sprintf(buf, "-ERR message %d deleted.\r\n", i + 1); printf("POP: TOP result on socket [%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; } else { if (sendTOPMailTCP(*s, fp[i].name, uName, fc)) break; } } else { sprintf(buf, "-ERR message %d does not exist.\r\n", i); printf("POP: TOP result on socket [%ld]=%s", *s, buf); if (sendTCP(*s, buf, strlen(buf))) break; } } else { printf("POP: (State=%d) on socket [%ld] Got %s", state, *s, buf); sprintf(buf, "-ERR Invalid Command.\r\n"); if (sendTCP(*s, buf, strlen(buf))) break; } } // state = transaction } // while } if (fp) free(fp); printf("POP: Closing connection on socket [%ld]..\n", *s); stopTCP(*s); printf("POP: Exiting thread for socket [%ld]..\n", *s); free(p); return; } 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)); }