/* nullpop: A NOOP POP3 Server. Based on: virtualmail-pop3d - a POP3 server with virtual domains support This code is licensed under the GPL; it has several authors. vm-pop3d is based on: GNU POP3 - a small, fast, and efficient POP3 daemon Copyright (C) 1999 Jakob 'sparky' Kaivo This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* A command that doesn't exist */ #define BAD_COMMAND "Invalid command" /* Incorrect number of arguments passed to a command */ #define BAD_ARGS "Invalid arguments" /* An action on a message that doesn't exist */ #define NO_MESG "No such message" /* A command that is known but not implemented */ #define NOT_IMPL "Not implemented" /* The command argument was > 40 characters */ #define TOO_LONG "Argument too long" /* Longest legal POP command */ #define POP_MAXCMDLEN 255 /* Maximum length of a hostname (is this defined somewhere else?) */ #ifndef MAXHOSTNAMELEN #define MAXHOSTNAMELEN 64 #endif #define OK 0 #define ERR_BAD_ARGS 2 #define ERR_NO_MESG 4 #define ERR_NOT_IMPL 5 #define ERR_BAD_CMD 6 #define ERR_TOO_LONG 8 #define ERR_NO_MEM 9 #define ERR_DEAD_SOCK 10 #define ERR_SIGNAL 11 #define ERR_FILE 12 #define ERR_NO_OFILE 13 #define ERR_TIMEOUT 14 unsigned int timeout; int ifile; FILE *ofile; /* Prints out usage information and exits the program */ void pop3_usage(char *argv0) { printf("Usage: %s [OPTIONS]\n", argv0); printf("Runs the nullpop POP3 daemon.\n\n"); printf(" -t sets idle timeout to TIMEOUT seconds\n"); exit(2); } /* This is called if it needs to quit without going to the UPDATE stage. This is used for conditions such as out of memory, a broken socket, or being killed on a signal */ int pop3_abquit(int reason) { switch (reason) { case ERR_NO_MEM: if (NULL != ofile) fprintf(ofile, "-ERR Out of memory, quitting\r\n"); syslog(LOG_ERR, "Out of memory"); break; case ERR_DEAD_SOCK: if (NULL != ofile) /* should this even print to socket? */ fprintf(ofile, "-ERR Socket closed, quitting\r\n"); syslog(LOG_ERR, "Socket closed"); break; case ERR_SIGNAL: if (NULL != ofile) fprintf(ofile, "-ERR Quitting on signal\r\n"); break; case ERR_TIMEOUT: if (NULL != ofile) fprintf(ofile, "-ERR Session timed out\r\n"); else syslog(LOG_INFO, "Session timed out for no user"); break; case ERR_NO_OFILE: syslog(LOG_ERR, "Quitting - couldn't open stream"); break; default: if (NULL != ofile) fprintf(ofile, "-ERR Quitting (reason unknown)\r\n"); syslog(LOG_ERR, "Unknown quit"); break; } fflush(ofile); exit(1); } /* Default signal handler to call the pop3_abquit() function */ void pop3_signal(int signal) { syslog(LOG_ERR, "Quitting on signal: %d", signal); pop3_abquit(ERR_SIGNAL); } /* Gets a line of input from the client */ char * pop3_readline(int fd) { fd_set rfds; struct timeval tv; char buf[1024], *ret = NULL; int available; FD_ZERO(&rfds); FD_SET(fd, &rfds); tv.tv_sec = timeout; tv.tv_usec = 0; memset(buf, '\0', 1024); while (strchr(buf, '\n') == NULL) { if (timeout > 0) { available = select(fd + 1, &rfds, NULL, NULL, &tv); if (!available) pop3_abquit(ERR_TIMEOUT); } if (read(fd, buf, 1024) < 1) pop3_abquit(ERR_DEAD_SOCK); if (ret == NULL) { ret = malloc((strlen(buf) + 1) * sizeof(char)); strcpy(ret, buf); } else { ret = realloc(ret, (strlen(ret) + strlen(buf) + 1) * sizeof(char)); strcat(ret, buf); } } return ret; } /* Takes a string as input and returns either the remainder of the string after the first space, or a zero length string if no space */ char * pop3_args(const char *cmd) { int space = -1, i = 0, len; char *buf; len = strlen(cmd) + 1; buf = malloc(len * sizeof(char)); if (buf == NULL) pop3_abquit(ERR_NO_MEM); while (space < 0 && i < len) { if (cmd[i] == ' ') space = i + 1; else if (cmd[i] == '\0' || cmd[i] == '\r' || cmd[i] == '\n') len = i; i++; } if (space < 0) buf[0] = '\0'; else { for (i = space; i < len; i++) if (cmd[i] == '\0' || cmd[i] == '\r' || cmd[i] == '\n') buf[i - space] = '\0'; else buf[i - space] = cmd[i]; } return buf; } /* This takes a string and returns the string up to the first space or end of the string, whichever occurs first */ char * pop3_cmd(const char *cmd) { char *buf; int i = 0, len; len = strlen(cmd) + 1; buf = malloc(len * sizeof(char)); if (buf == NULL) pop3_abquit(ERR_NO_MEM); for (i = 0; i < len; i++) { if (cmd[i] == ' ' || cmd[i] == '\0' || cmd[i] == '\r' || cmd[i] == '\n') len = i; else buf[i] = cmd[i]; } buf[i - 1] = '\0'; return buf; } /* The main part of the daemon. This function reads input from the client and executes the proper functions. Also handles the bulk of error reporting. */ int pop3_mainloop(int infile, int outfile) { int status = OK; char *buf, *arg, *cmd; struct sockaddr_in name; int namelen = sizeof(name); char *temp_domain; ifile = infile; ofile = fdopen(outfile, "w+"); if (ofile == NULL) pop3_abquit(ERR_NO_OFILE); if (getpeername(infile, (struct sockaddr *) & name, (socklen_t *) & namelen) == 0) { if ((temp_domain = (char *) inet_ntoa(name.sin_addr))) syslog(LOG_INFO,"Connect from %s", temp_domain); else syslog(LOG_INFO, "Connection opened"); } else syslog(LOG_INFO, "Incoming connection opened"); fflush(ofile); fprintf(ofile, "+OK POP3 \r\n"); for (;;) { fflush(ofile); status = OK; buf = pop3_readline(ifile); cmd = pop3_cmd(buf); arg = pop3_args(buf); if (strlen(arg) > POP_MAXCMDLEN || strlen(cmd) > POP_MAXCMDLEN) status = ERR_TOO_LONG; else if (strlen(cmd) > 4) status = ERR_BAD_CMD; else if (strncasecmp(cmd, "USER", 4) == 0) { fprintf(ofile, "+OK\r\n"); status = OK; } else if (strncasecmp(cmd, "PASS", 4) == 0) { fprintf(ofile, "+OK\r\n"); status = OK; } else if (strncasecmp(cmd, "QUIT", 4) == 0) { fprintf(ofile, "+OK\r\n"); fflush(ofile); break; } else if (strncasecmp(cmd, "AUTH", 4) == 0) { status = ERR_NOT_IMPL; } else if (strncasecmp(cmd, "STAT", 4) == 0) { fprintf(ofile, "+OK %d %d\r\n", 0, 0); status = OK; } else if (strncasecmp(cmd, "LIST", 4) == 0) { if (strchr(arg, ' ') != NULL) { status = ERR_BAD_ARGS; } else if (strlen(arg) == 0) { fprintf(ofile, "+OK\r\n"); fprintf(ofile, ".\r\n"); status = OK; } else { status = ERR_NO_MESG; } } else if (strncasecmp(cmd, "RETR", 4) == 0) { if ((strlen(arg) == 0) || (strchr(arg, ' ') != NULL)) status = ERR_BAD_ARGS; else status = ERR_NO_MESG; } else if (strncasecmp(cmd, "DELE", 4) == 0) { if ((strlen(arg) == 0) || (strchr(arg, ' ') != NULL)) status = ERR_BAD_ARGS; else status = ERR_NO_MESG; } else if (strncasecmp(cmd, "NOOP", 4) == 0) { if (strlen(arg) != 0) status = ERR_BAD_ARGS; else { fprintf(ofile, "+OK\r\n"); status = OK; } } else if (strncasecmp(cmd, "RSET", 4) == 0) { if (strlen(arg) != 0) status = ERR_BAD_ARGS; else { fprintf(ofile, "+OK\r\n"); status = OK; } } else if ((strncasecmp(cmd, "TOP", 3) == 0) && (strlen(cmd) == 3)) { status = ERR_NO_MESG; } else if (strncasecmp(cmd, "UIDL", 4) == 0) { if (strchr(arg, ' ') != NULL) status = ERR_BAD_ARGS; else status = ERR_NO_MESG; } else if (strncasecmp(cmd, "CAPA", 4) == 0) { if (strlen(arg) != 0) status = ERR_BAD_ARGS; else { fprintf(ofile, "+OK Capability list follows\r\n"); fprintf(ofile, "TOP\r\n"); fprintf(ofile, "USER\r\n"); fprintf(ofile, "RESP-CODES\r\n"); fprintf(ofile, "UIDL\r\n"); fprintf(ofile, ".\r\n"); status = OK; } } else { status = ERR_BAD_CMD; } if (status == OK) fflush(ofile); else if (status == ERR_BAD_ARGS) fprintf(ofile, "-ERR " BAD_ARGS "\r\n"); else if (status == ERR_NO_MESG) fprintf(ofile, "-ERR " NO_MESG "\r\n"); else if (status == ERR_NOT_IMPL) fprintf(ofile, "-ERR " NOT_IMPL "\r\n"); else if (status == ERR_BAD_CMD) fprintf(ofile, "-ERR " BAD_COMMAND "\r\n"); else if (status == ERR_TOO_LONG) fprintf(ofile, "-ERR " TOO_LONG "\r\n"); free(buf); free(cmd); free(arg); } fflush(ofile); return OK; } int main(int argc, char **argv) { int c = 0; timeout = 0; /* Timeout turned off */ /* Set the signal handlers */ signal(SIGINT, pop3_signal); signal(SIGQUIT, pop3_signal); signal(SIGTERM, pop3_signal); /* Don't die when a process goes away unexpectedly. Ignore write on a pipe with no reader. */ signal(SIGPIPE, SIG_IGN); while ((c = getopt(argc, argv, "t:") && c)) { switch (c) { case 't': timeout = atoi(optarg); break; default: pop3_usage(argv[0]); break; } } /* Set up for syslog */ openlog("nullpop", LOG_PID, LOG_MAIL); pop3_mainloop(fileno(stdin), fileno(stdout)); /* Close the syslog connection and exit */ closelog(); return OK; }