123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- /**
- * saop: smtp authentication over pop3
- * Copyright (C) 2007 Juan José Gutiérrez de Quevedo <juanjo@iteisa.com>
- *
- * 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; version 2 of the License
- *
- * 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.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * @author Juan José Gutiérrez de Quevedo <juanjo@iteisa.com>
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <errno.h>
- #define __USE_GNU
- #include <string.h>
- #include <pthread.h>
- #include <unistd.h>
- #ifndef WIN32
- #include <pwd.h>
- #include <grp.h>
- #include <signal.h>
- #endif /* WIN32 */
- #include "base64.h"
- #include "utils.h"
- #include "auth.h"
- #include "config.h"
- #ifndef WIN32
- #define CONFIGFILE "/etc/saop/saoprc"
- #else
- #define CONFIGFILE "saop.cfg"
- #endif /* WIN32 */
- int quit=0;
- void *handle_connection(void *);
- void read_localdomains(char *);
- void free_localdomains();
- void init_winsock();
- void free_winsock();
- #ifndef WIN32
- void handle_quit(int);
- #endif /* WIN32 */
- struct config_t
- #ifdef WIN32
- config={25,"localhost",2525,"localhost",110,"saop_users","localdomains","pop3"}; /* win32 default config */
- #else
- config={1,1,"nobody","nobody","/etc/saop/chroot",25,"localhost",2525,"localhost",110,"/etc/saop/users","/etc/saop/localdomains","pop3"}; /* unix default config */
- #endif /* WIN32 */
- /* global vars */
- char **localdomains;
- #ifdef DEBUG
- FILE *fsaopdebug;
- #endif /* DEBUG */
- int
- #ifdef WIN32
- saop_main
- #else
- main
- #endif /* WIN32 */
- (int argc,char **argv)
- {
- int fd_server,fd;
- pthread_t thread;
- pthread_attr_t thread_attr;
- #ifndef WIN32
- struct passwd *pwd;
- struct group *grp;
- #endif /* WIN32 */
- #ifdef WIN32
- char *tmppath=NULL,*tmppath2=NULL;
- #endif /* WIN32 */
- /* configure detach mode for pthreads */
- pthread_attr_init(&thread_attr);
- pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);
- #ifdef WIN32
- /* first chdir to our binary's dir */
- condfree(tmppath);
- if(NULL==(tmppath=(char *)malloc(2048))) { perror("Error reserving memory"); exit(-1); }
- if(0==GetModuleFileName(NULL,tmppath,2048)) { condfree(tmppath); perror("Error getting module path"); exit(-1); }
- if(NULL==(tmppath2=strrchr(tmppath,'\\'))) { condfree(tmppath); perror("Something is wrong with our path"); exit(-1); }
- *tmppath2='\0';
- if(-1==chdir(tmppath)) { condfree(tmppath); perror("Error changing to exe's directory"); exit(-1); }
- condfree(tmppath);
- #endif /* WIN32 */
- #ifndef WIN32
- signal(SIGINT,handle_quit);
- signal(SIGTERM,handle_quit);
- #endif /* WIN32 */
- debug("Reading config");
- read_config(CONFIGFILE,&config);
- debug("Reading localdomains");
- read_localdomains(config.local_domains);
- #ifndef WIN32
- if(1==config.background)
- {
- switch(fork())
- {
- case -1: perror(_("Error forking")); exit(-1);
- case 0: break;
- default: exit(0);
- }
- }
- /*
- * we have to retrieve the user info before chrooting, but we have to drop privileges after chrooting
- * because chrooting requires us to be root, so we first read user & group info, then chroot, then drop
- * the privileges
- */
- if(1==config.drop_privileges)
- {
- if(NULL==(pwd=getpwnam(config.user))) { perror("Couldn't get user information"); exit(-1); }
- if(NULL==(grp=getgrnam(config.group))) { perror("Couldn't get group information"); exit(-1); }
- }
- if('\0'!=config.chroot[0])
- {
- gethostbyname("iteisa.com");
- if(-1==chdir(config.chroot)) { perror(_("Couldn't chdir to the chroot directory")); exit(-1); }
- if(-1==chroot(config.chroot)) { perror(_("Couldn't chroot to the chroot directory")); exit(-1); }
- if(-1==chdir("/")) { perror(_("Couldn't chdir to the root directory once inside the chroot")); exit(-1); }
- }
- if(1==config.drop_privileges)
- {
- setgroups(0,NULL);
- setgid(grp->gr_gid);
- setuid(pwd->pw_uid);
- setuid(pwd->pw_uid);
- }
- #endif /* WIN32 */
- #ifdef WIN32
- debug("Initializing winsock");
- init_winsock();
- #endif /* WIN32 */
- debug("Creating listening socket");
- if(-1==(fd_server=create_listening_socket(config.listen_port))) exit(-1);
- debug("Entering main loop");
- while(0==quit)
- {
- if(can_read(fd_server,1))
- if(-1!=(fd=accept(fd_server,NULL,0)))
- {
- debug("Accepting new connection and creating new thread");
- if(pthread_create(&thread,&thread_attr,handle_connection,(void *)fd)) { perror("Error creating new thread"); exit(-1); }
- }
- }
- debug("Closing socket");
- closeskt(fd_server);
- debug("Freeing localdomains");
- free_localdomains();
- #ifdef WIN32
- debug("Freeing winsock");
- free_winsock();
- #endif /* WIN32 */
- debug("Ending saop");
- return 0;
- }
- void *handle_connection(void *p_outside)
- {
- int inside,outside;
- char *line=NULL;
- int authenticated=0,doing_hello=0;
- char *user=NULL,*pass=NULL,*tmpchar=NULL;
- unsigned long count=0,recvcount;
- size_t b64len;
- struct base64_decode_context b64ctx;
- char *buffer=NULL;
- char eom[6]={0};
- outside=(int)p_outside;
- debug("Creating connection to remote server");
- if(-1==(inside=create_connected_socket(config.smtp_server,config.smtp_port))) { return NULL; }
- while(1)
- {
- if(can_read(outside,0.01))
- {
- debug("Reading from outside");
- free_and_read_string(line,outside);
- if(NULL==line) break; /* reached end of message */
- if(!cmp(line,"AUTH LOGIN"))
- {
- debug("AUTH");
- /* perform authentication */
- /* get username */
- write_string(outside,"334 VXNlcm5hbWU6"); /* Username: in base64 */
- condfree(line);
- if(NULL==(line=read_string(outside))) break; /* someone dropped the connection */
- condfree(user);
- base64_decode_ctx_init(&b64ctx);
- base64_decode_alloc(&b64ctx,line,strlen(line),&user,&b64len);
- user[b64len]='\0';
- condfree(line);
- /* get password */
- write_string(outside,"334 UGFzc3dvcmQ6"); /* Password: in base64 */
- condfree(line);
- if(NULL==(line=read_string(outside))) break; /* someone dropped the connection */
- condfree(pass);
- base64_decode_ctx_init(&b64ctx);
- base64_decode_alloc(&b64ctx,line,strlen(line),&pass,&b64len);
- pass[b64len]='\0';
- condfree(line);
- if(
- (!cmp(config.auth_method,"pop3")&&authenticate_with_pop3(user,pass,config.auth_server,config.auth_port))||
- (!cmp(config.auth_method,"imap4")&&authenticate_with_imap4(user,pass,config.auth_server,config.auth_port))||
- (!cmp(config.auth_method,"file")&&authenticate_with_file(user,pass,config.auth_file))
- )
- {
- write_string(outside,"235 Authentication Successful");
- authenticated=1;
- }
- else
- write_string(outside,"535 Authentication Failed");
- condfree(user);
- condfree(pass);
- }
- else if(!cmp(line,"rcpt to:"))
- {
- if(NULL==localdomains)
- debug("No localdomains defined, can't receive emails");
- else if(!authenticated)
- {
- tmpchar=strchr(line,'@');
- if(NULL!=tmpchar)
- {
- tmpchar++;
- for(count=0;NULL!=localdomains[count];count++)
- {
- if(!cmp(tmpchar,localdomains[count]))
- {
- authenticated=1;
- break;
- }
- }
- }
- }
- }
- else if(!cmp(line,"data"))
- {
- if(!authenticated)
- {
- write_string(inside,"QUIT");
- closeskt(inside);
- write_string(outside,"451 Not authenticated");
- closeskt(outside);
- break;
- }
- }
- else if(!cmp(line,"starttls"))
- {
- condfree(line);
- write_string(outside,"454 TLS temporarily not available");
- }
- else if(!cmp(line,"ehlo")||!cmp(line,"helo"))
- doing_hello=1;
- else
- doing_hello=0;
- if(line&&*line)
- write_string(inside,line);
- }
- if(can_read(inside,0.01))
- {
- debug("Reading from inside");
- free_and_read_string(line,inside);
- if(NULL==line) break; /* reached end of message */
- /*
- * we have to process some lines of the ehlo response:
- * -remove PIPELINING, TLS and STARTTLS
- * -change all auth lines to AUTH LOGIN to only support login
- */
- if(doing_hello)
- {
- if(!cmp(line,"250")&&(!cmp(line+4,"AUTH")||!cmp(line+4,"PIPELINING")||!cmp(line+4,"TLS")||!cmp(line+4,"STARTTLS")))
- {
- if('-'==line[3])
- condfree(line);
- else
- {
- condfree(line);
- safestrdup(line,"250 AUTH LOGIN");
- }
- }
- else if(!cmp(line,"250 "))
- write_string(outside,"250-AUTH LOGIN");
- }
- if(!cmp(line,"354 ")) /* we have received authorization to send the message */
- {
- debug("Receiving message");
- condfree(buffer);
- write_string(outside,line);
- condfree(line);
- if(NULL==(buffer=(char *)malloc(4096))) { perror(_("Error reserving memory for body buffer")); break; }
- while(cmp(eom,"\r\n.\r\n")&&(recvcount=recv(outside,buffer,4096,MSG_NOSIGNAL))>0)
- {
- send(inside,buffer,recvcount,MSG_NOSIGNAL);
- if(recvcount>=5) /* if recvcount>=5 then eom = last 5 bytes of buffer */
- memcpy(eom,buffer+recvcount-5,5);
- else /* else, move the last 5-recvcount bytes to the beginning, then copy the buffer to the end */
- {
- memmove(eom,eom+recvcount,5-recvcount);
- memcpy(eom+(5-recvcount),buffer,recvcount);
- }
- }
- }
- if(line&&*line)
- write_string(outside,line);
- }
- }
- debug("Closing sockets");
- closeskt(outside);
- closeskt(inside);
- debug("Ending thread");
- return NULL;
- }
- void read_localdomains(char *file)
- {
- FILE *f;
- unsigned long count,i;
- char line[512]={0};
- char **tmpld;
- localdomains=NULL;
- debug("Opening file to localdomains");
- if(NULL==(f=fopen(file,"r")))
- {
- debug("No localdomains file, continuing with an empty locals. This will probably render this program completely useless.");
- return;
- }
- debug("Looping over entries");
- for(count=0;!feof(f);count++)
- {
- fgets(line,sizeof(line),f);
- if(!feof(f)&&'#'!=line[0])
- {
- for(i=strlen(line)-1;i&&('\r'==line[i]||'\n'==line[i]);line[i]='\0',i--);
- tmpld=(char **)realloc(localdomains,(count+2)*sizeof(char *));
- if(NULL==tmpld)
- {
- condfree(localdomains);
- perror("Error reserving memory for localdomains");
- exit(-1);
- }
- else
- localdomains=tmpld;
- safestrdup(localdomains[count],line);
- }
- }
- localdomains[count-1]=NULL;
- debug("Closing files");
- fclose(f);
- }
- void free_localdomains(char *file)
- {
- unsigned long i;
- debug("Freeing localdomains");
- for(i=0;NULL!=localdomains[i];i++)
- condfree(localdomains[i]);
- debug("Freeing memory");
- condfree(localdomains);
- }
- #ifdef WIN32
- /* windows-specific functions */
- void init_winsock()
- {
- /* fuck windows, it needs this sh*t before allowing sockets to work */
- WSADATA wsaData;
- int wsa=WSAStartup(0xff,&wsaData);
- if(wsa)
- {
- perror("Windows not working, call microsoft");
- exit(-1);
- }
- debug("Winsock initialization correct");
- }
- void free_winsock()
- {
- debug("Winsock cleanup");
- WSACleanup();
- }
- #endif /* WIN32 */
- #ifndef WIN32
- void handle_quit(int signum)
- {
- quit=1;
- }
- #endif /* WIN32 */
|