/* * pop3d - IP/TCP/POP3 server for UNIX 4.3BSD * Post Office Protocol - Version 3 (RFC1225) * * (C) Copyright 1991 Regents of the University of California * * Permission to use, copy, modify, and distribute this program * for any purpose and without fee is hereby granted, provided * that this copyright and permission notice appear on all copies * and supporting documentation, the name of University of California * not be used in advertising or publicity pertaining to distribution * of the program without specific prior permission, and notice be * given in supporting documentation that copying and distribution is * by permission of the University of California. * The University of California makes no representations about * the suitability of this software for any purpose. It is provided * "as is" without express or implied warranty. * * Katie Stevens * dkstevens@ucdavis.edu * Information Technology -- Campus Access Point * University of California, Davis * ************************************** * * main.c * * REVISIONS: * 02-27-90 [ks] original implementation * 1.000 03-04-90 [ks] * 1.001 06-24-90 [ks] implement optional TOP command * 1.002 07-22-91 [ks] -- reset index counter after folder rewind * in fld_release (Thanks to John Briggs, * vaxs09@vitro.com, Vitro Corporation, * Silver Spring, MD for finding this bug!) * -- set umask() value explicitly (Thanks to * Vikas Aggarwal, JvNCnet, Princeton, NJ * for suggesting this) * 1.003 03-92 [ks] close mailbox before return from main() * 1.004 11-13-91 [ks] leave original mailbox intact during POP * session (Thanks to Dave Cooley, * dwcooley@colby.edu for suggesting this) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pop3.h" #include "paths.h" static bool debug=false; #define VERSION "1.008" #define REVDATE "August, 1999" char *svr_hostname; /* Hostname of POP3 server */ char svr_buf[SVR_BUFSIZ+2]; /* Buffer for server I/O */ char cli_user[CLI_BUFSIZ]; /* Save client username */ static char *svr_invalid = "-ERR Invalid command; valid commands:"; #ifdef DEBUG FILE *logfp = NULL; /* File for recording session */ #endif static void vpop3d_sethostname (const char *domain) { free (svr_hostname); svr_hostname = strdup (domain); if (svr_hostname == NULL) fail(FAIL_OUT_OF_MEMORY); } /* Prepare to shutdown POP3 server */ static int svr_shutdown() { fld_release(); sprintf(svr_buf,"+OK %s POP3 Server (Version %s) shutdown.\r\n", svr_hostname,VERSION); return(SVR_DONE_STATE); } /* Server Folder State; need to open another folder */ static int svr_fold(int state, char *inbuf) { if (strncmp(inbuf,"quit",4) == 0) return(svr_shutdown()); if (strncmp(inbuf,"host",4) == 0) { inbuf += 4; EATSPACE(inbuf); state = fld_bsmtp(inbuf); #if 0 } else if (strncmp(inbuf,"mbox",4) == 0) { inbuf += 4; EATSPACE(inbuf); state = fld_fromsp(inbuf); #endif } else if (strncmp(inbuf,"noop",4) == 0) { strcpy(svr_buf,"+OK\r\n"); } else { sprintf(svr_buf,"%s HOST, MBOX, NOOP or QUIT\r\n",svr_invalid); } return(state); } /* Timeout while waiting for next client command */ static void int_progerr(int) { fld_release(); /* Release mailbox folder */ fail(FAIL_PROGERR); /* Exit POP3 server */ } /* Timeout while waiting for next client command */ static void svr_timeout(int ) { fld_release(); /* Release mailbox folder */ fail(FAIL_LOST_CLIENT); /* Exit POP3 server */ } /* Timeout while waiting for next client command */ static void int_hangup(int ) { fld_release(); /* Release mailbox folder */ fail(FAIL_HANGUP); /* Exit POP3 server */ } /**************************************************************************/ /* Initialize POP3 server */ static void initialize() { /* File permissions for owner only */ umask(077); /* [1.002] */ #ifdef DEBUG /* Prepare log file */ logfp = fopen(LOGFILE,"w"); fprintf(logfp,"POP3 server startup; version %s (%s)\n", VERSION,REVDATE); #endif /* Handle process signals ourself */ signal(SIGALRM, svr_timeout); /* timer expiration */ signal(SIGHUP, int_hangup); /* socket signals */ signal(SIGURG, int_hangup); signal(SIGTERM, int_hangup); #ifdef LINUX # ifdef SIGBUS signal(SIGBUS, int_progerr); /* fatal program errors */ # endif #endif signal(SIGSEGV, int_progerr); signal(SIGILL, int_progerr); signal(SIGIOT, int_progerr); } /**************************************************************************/ /* Server Authentification State; process client USER command */ static int svr_auth( int state, char *inbuf) { char *p; for (p = inbuf; *p; p++) *p = tolower(*p); if (strncmp(inbuf,"quit",4) == 0) return(svr_shutdown()); /* Expecting USER command */ if (strncmp(inbuf,"user",4) == 0) { inbuf += 4; EATSPACE(inbuf); strcpy(cli_user,inbuf); strcpy(svr_buf,"+OK please send PASS command\r\n"); state = SVR_PASS_STATE; } else { sprintf(svr_buf,"%s USER, QUIT\r\n",svr_invalid); } return(state); } struct VPOP3D_CTX{ char maildir[PATH_MAX]; // Spool directory for incoming folders char pwd_path[PATH_MAX]; char shadow_path[PATH_MAX]; char *pwdfile; // Pointer to pwd_path or NULL // when managing the default domain char domaint[PATH_MAX]; // Domain title char domain[PATH_MAX]; }; static void vpop3d_setvirtual ( struct VPOP3D_CTX *ctx, const char *domain) { strcpy (ctx->domain,domain); sprintf (ctx->pwd_path,"%s/passwd.%s",ETC_VMAIL,domain); sprintf (ctx->shadow_path,"%s/shadow.%s",ETC_VMAIL,domain); sprintf (ctx->domaint,"Virtual %s",domain); sprintf (ctx->maildir,"%s/%s",VAR_SPOOL_VMAIL,domain); ctx->pwdfile = ctx->pwd_path; vpop3d_sethostname (domain); } /* Tell if mail retrieval is locked */ static bool vpop3d_domain_islocked(const char *domain) { bool ret = false; FILE *fin = fopen (ETC_CONF_LINUXCONF,"r"); if (fin != NULL){ char keylock[200]; snprintf (keylock,sizeof(keylock)-1,"vdomain_retrievelock.%s",domain); char buf[1000]; while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ char v1[1000],v2[1000]; if (sscanf (buf,"%s %s",v1,v2)==2){ if (strcmp(keylock,v1)==0){ ret = v2[0] != '0'; break; } } } fclose (fin); } return ret; } static void vpop3d_setcontext (struct VPOP3D_CTX *ctx) { char svr_name[PATH_MAX]; ctx->pwdfile = NULL; ctx->maildir[0] = '\0'; ctx->pwd_path[0] = '\0'; ctx->domaint[0] = '\0'; ctx->shadow_path[0] = '\0'; if (vmail_getourname(svr_name,sizeof(svr_name))==-1){ strcpy(ctx->maildir,DEF_MAIL_DIR); strcpy (ctx->domaint,"main domain"); }else{ /* #Specification: virtual domain / server to domain vpop3d needs a way to identify a domain from the target of a POP request. In most organisation, one server is dedicated to email and is part of the domain which is managed. When creating a virtual email system, it is expected that one will create a virtual host like this for domain virtual.com mailserv.virtual.com (mailserv is just an example). When vpop3d gets a connection, it uses getsockname() to find out the target of the request and then do a gethostbyaddr() to get the name associated with this target. Say it is getting mailserv.virtual.com. It will check if /var/spool/vmail/mailserv.virtual.com exist. if this is the case, then it will use /etc/vmail/passwd.virtual.com. If it does not exist, it will strip the host part and try /var/spool/vmail/virtual.com. If it exists, it will use the corresponding /etc/vmail/passwd.virtual.com. If it does not exist, vpop3d will fall back to standard non-virtual operation using /var/spool/mail and /etc/passwd. */ int i; const char *ptserv = svr_name; for (i=0; i<2; i++){ struct stat st; sprintf (ctx->maildir,"%s/%s",VAR_SPOOL_VMAIL,ptserv); if (stat(ctx->maildir,&st)==-1){ if (i == 1){ strcpy(ctx->maildir,DEF_MAIL_DIR); }else if((ptserv=strchr(svr_name,'.'))!=NULL){ ptserv++; }else{ strcpy(ctx->maildir,DEF_MAIL_DIR); break; } }else{ vpop3d_setvirtual (ctx,ptserv); break; } } } } /* Server Password State; process client PASS command */ static int svr_pass( int state, char *inbuf, struct VPOP3D_CTX *ctx) { if (strncmp(inbuf,"quit",4) == 0) return(svr_shutdown()); /* Expecting PASS command */ if (strncmp(inbuf,"pass",4) != 0) { sprintf(svr_buf,"%s PASS, QUIT\r\n",svr_invalid); return(state); } /* Verify usercode/password pair */ inbuf += 4; EATSPACE(inbuf); if (verify_user(cli_user,inbuf,ctx->pwdfile,ctx->shadow_path) == -1) { strcpy(svr_buf,"-ERR invalid usercode or password, please try again\r\n"); syslog (LOG_INFO,"login failed for user %s in domain %s",cli_user ,ctx->domaint); return(SVR_AUTH_STATE); } sprintf (svr_buf,"%s/%s",ctx->maildir,cli_user); struct sockaddr_in adr; unsigned int len = sizeof(adr); if (getpeername(0,(struct sockaddr*)&adr, &len) == 0){ openlog("vpop3d", LOG_NDELAY, LOG_LOCAL0); long a = ntohl (*(long*)(&adr.sin_addr)); syslog(LOG_INFO, "(%s,%lu.%lu.%lu.%lu) Authenticated.", cli_user, (a>>24)&0xff,(a>>16)&0xff,(a>>8)&0xff,a&0xff); closelog(); openlog ("vpop3d",LOG_PID,LOG_MAIL); char cmd[256]; sprintf(cmd, "/usr/sbin/vhost --poprelay %lu.%lu.%lu.%lu", (a>>24)&0xff,(a>>16)&0xff,(a>>8)&0xff,a&0xff); system(cmd); } syslog (LOG_INFO,"user %s logged in, domain %s",cli_user,ctx->domaint); return(fld_fromsp(svr_buf)); } /* Server Transaction State; process client mailbox command */ static int svr_trans( int state, char *inbuf) { if (debug){ static FILE *fout = NULL; if (fout == NULL){ char path[PATH_MAX]; snprintf (path,sizeof(path)-1,"/tmp/vpop3d.log.%s",cli_user); fout = fopen (path,"a"); } if (fout != NULL){ fprintf (fout,"%d; %s\n",state,inbuf); } } if (strncmp(inbuf,"quit",4) == 0) return(svr_shutdown()); /* Expecting mailbox command */ if (strncmp(inbuf,"dele",4) == 0) { inbuf += 4; EATSPACE(inbuf); if (*inbuf == NULL_CHAR) sprintf(svr_buf,"-ERR message number required (e.g. DELE 1)\r\n"); else fld_delete(atoi(inbuf)); } else if (strncmp(inbuf,"host",4) == 0) { inbuf += 4; EATSPACE(inbuf); if (*inbuf == NULL_CHAR) sprintf(svr_buf,"-ERR must specify hostname\r\n"); else state = fld_bsmtp(inbuf); } else if (strncmp(inbuf,"last",4) == 0) { fld_last(); } else if (strncmp(inbuf,"list",4) == 0) { inbuf += 4; EATSPACE(inbuf); if (*inbuf == NULL_CHAR) fld_list(-1); else fld_list(atoi(inbuf)); #if 0 } else if (strncmp(inbuf,"mbox",4) == 0) { inbuf += 4; EATSPACE(inbuf); if (*inbuf == NULL_CHAR) sprintf(svr_buf,"-ERR must specify mailbox filename\r\n"); else state = fld_fromsp(inbuf); #endif } else if (strncmp(inbuf,"noop",4) == 0) { strcpy(svr_buf,"+OK\r\n"); } else if (strncmp(inbuf,"retr",4) == 0) { inbuf += 4; EATSPACE(inbuf); if (*inbuf == NULL_CHAR) { sprintf(svr_buf,"-ERR message number required (e.g. RETR 1)\r\n"); } else fld_retr(atoi(inbuf),-1); } else if (strncmp(inbuf,"rset",4) == 0) { fld_reset(); } else if (strncmp(inbuf,"stat",4) == 0) { fld_stat(); } else if (strncmp(inbuf,"top",3) == 0) { inbuf += 3; EATSPACE(inbuf); if (*inbuf == NULL_CHAR) { sprintf(svr_buf,"-ERR message number and line count required (e.g. TOP 1 7)\r\n"); } else { int msgnum = atoi(inbuf); while (!isspace(*inbuf)) ++inbuf; EATSPACE(inbuf); if (*inbuf == NULL_CHAR) sprintf(svr_buf,"-ERR line count required (e.g. TOP 1 7)\r\n"); else fld_retr(msgnum,atoi(inbuf)); } } else if (strncmp(inbuf,"uidl",4) == 0) { inbuf += 4; EATSPACE(inbuf); if (*inbuf == NULL_CHAR) { fld_uidl(-1); }else{ fld_uidl(atoi(inbuf)); } } else { sprintf(svr_buf, "%s DELE, HOST, LAST, LIST, MBOX, NOOP, RETR, RSET, STAT, TOP, UIDL or QUIT\r\n", svr_invalid); } return(state); } /**************************************/ void svr_data_out(char *buf) { /* Send out response to client */ alarm(SVR_TIMEOUT_SEND); fputs(buf,stdout); fflush(stdout); alarm(0); } /* Set the group ID of the process to mail so it can write into the /var/spool/mail and /var/spool/vmail/domain folder */ static int setgidmail() { int ret = -1; struct group *gr = getgrnam ("mail"); if (gr != NULL){ setgid (gr->gr_gid); ret = 0; }else{ syslog (LOG_ERR,"no group mail defined ???"); } return ret; } /**************************************************************************/ int main(int argc, char *argv[]) { struct VPOP3D_CTX ctx; int svr_state = SVR_LISTEN_STATE; /* State of POP3 server */ char cli_buf[CLI_BUFSIZ]; /* Buffer for client cmds */ { char buf[MAXHOSTNAMELEN+1]; /* Get our hostname */ gethostname(buf,MAXHOSTNAMELEN); vpop3d_sethostname (buf); } openlog ("vpop3d",LOG_PID,LOG_MAIL); setgidmail(); vpop3d_setcontext (&ctx); int lastarg = 1; if (argc >= 2 && strcmp(argv[1],"-d")==0){ debug = true; lastarg = 2; } if (ctx.pwdfile == NULL && argc > lastarg){ /* We jump to another (standard) POP3D daemon */ const char *other = argv[lastarg]; execvp (other,argv+lastarg); // we should not be here syslog (LOG_ERR,"Can't exec main POP server %s, trying to handle main domain" ,other); } initialize(); fprintf(stdout,"+OK %s POP3 Server (Version %s) ready.\r\n", ctx.domaint,VERSION); fflush(stdout); svr_state = SVR_AUTH_STATE; for ( ; ; ) { /* Wait for command from client */ alarm(SVR_TIMEOUT_CLI); if (fgetl(cli_buf,CLI_BUFSIZ,stdin) == NULL) break; alarm(0); /* Take action on client command */ cmd_prepare(cli_buf); #ifdef DEBUG if ((cli_buf[0] == 'p')||(cli_buf[0] == 'P')) fprintf(logfp,"CLI: PASS\n",cli_buf); else fprintf(logfp,"CLI: %s\n",cli_buf); #endif switch(svr_state) { case SVR_AUTH_STATE: /* Expecting USER command */ { char *pt; svr_state = svr_auth(svr_state,cli_buf); if (svr_state == SVR_PASS_STATE){ pt = strchr(cli_user,'/'); if (pt == NULL) pt = strchr(cli_user,'@'); if (pt != NULL){ *pt++ = '\0'; vpop3d_setvirtual (&ctx,pt); } if (vpop3d_domain_islocked(ctx.domain)){ syslog (LOG_INFO,"domain %s, user %s rejected" ,ctx.domain,cli_user); sprintf (svr_buf,"-ERR Domain is locked\n"); svr_state = SVR_DONE_STATE; } } } break; case SVR_PASS_STATE: /* Expecting PASS command */ svr_state = svr_pass(svr_state,cli_buf,&ctx); break; case SVR_TRANS_STATE: /* Expecting mailbox command */ svr_state = svr_trans(svr_state,cli_buf); break; case SVR_FOLD_STATE: /* Need to open another mailbox */ svr_state = svr_fold(svr_state,cli_buf); break; default: fail(FAIL_CONFUSION); /* Wont return */ break; } #ifdef DEBUG fprintf(logfp,"SVR: %s",svr_buf); #endif /* Send out response to client */ alarm(SVR_TIMEOUT_SEND); fputs(svr_buf,stdout); fflush(stdout); alarm(0); if (ferror(stdout)) break; /* Exit when server has sent goodbye */ if (svr_state == SVR_DONE_STATE) break; } fld_release(); /* [1.003] Make sure folder is released */ exit(0); }