saop.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. /**
  2. * saop: smtp authentication over pop3
  3. * Copyright (C) 2007 Juan José Gutiérrez de Quevedo <juanjo@iteisa.com>
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; version 2 of the License
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License along
  15. * with this program; if not, write to the Free Software Foundation, Inc.,
  16. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  17. *
  18. * @author Juan José Gutiérrez de Quevedo <juanjo@iteisa.com>
  19. */
  20. #include <stdio.h>
  21. #include <stdlib.h>
  22. #include <errno.h>
  23. #define __USE_GNU
  24. #include <string.h>
  25. #include <pthread.h>
  26. #include <unistd.h>
  27. #ifndef WIN32
  28. #include <pwd.h>
  29. #include <grp.h>
  30. #include <signal.h>
  31. #endif /* WIN32 */
  32. #include "base64.h"
  33. #include "utils.h"
  34. #include "auth.h"
  35. #include "config.h"
  36. #ifndef WIN32
  37. #define CONFIGFILE "/etc/saop/saoprc"
  38. #else
  39. #define CONFIGFILE "saop.cfg"
  40. #endif /* WIN32 */
  41. int quit=0;
  42. void *handle_connection(void *);
  43. void read_localdomains(char *);
  44. void free_localdomains();
  45. void init_winsock();
  46. void free_winsock();
  47. #ifndef WIN32
  48. void handle_quit(int);
  49. #endif /* WIN32 */
  50. struct config_t
  51. #ifdef WIN32
  52. config={25,"localhost",2525,"localhost",110,"saop_users","localdomains","pop3"}; /* win32 default config */
  53. #else
  54. config={1,1,"nobody","nobody","/etc/saop/chroot",25,"localhost",2525,"localhost",110,"/etc/saop/users","/etc/saop/localdomains","pop3"}; /* unix default config */
  55. #endif /* WIN32 */
  56. /* global vars */
  57. char **localdomains;
  58. #ifdef DEBUG
  59. FILE *fsaopdebug;
  60. #endif /* DEBUG */
  61. int
  62. #ifdef WIN32
  63. saop_main
  64. #else
  65. main
  66. #endif /* WIN32 */
  67. (int argc,char **argv)
  68. {
  69. int fd_server,fd;
  70. pthread_t thread;
  71. pthread_attr_t thread_attr;
  72. #ifndef WIN32
  73. struct passwd *pwd;
  74. struct group *grp;
  75. #endif /* WIN32 */
  76. #ifdef WIN32
  77. char *tmppath=NULL,*tmppath2=NULL;
  78. #endif /* WIN32 */
  79. /* configure detach mode for pthreads */
  80. pthread_attr_init(&thread_attr);
  81. pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);
  82. #ifdef WIN32
  83. /* first chdir to our binary's dir */
  84. condfree(tmppath);
  85. if(NULL==(tmppath=(char *)malloc(2048))) { perror("Error reserving memory"); exit(-1); }
  86. if(0==GetModuleFileName(NULL,tmppath,2048)) { condfree(tmppath); perror("Error getting module path"); exit(-1); }
  87. if(NULL==(tmppath2=strrchr(tmppath,'\\'))) { condfree(tmppath); perror("Something is wrong with our path"); exit(-1); }
  88. *tmppath2='\0';
  89. if(-1==chdir(tmppath)) { condfree(tmppath); perror("Error changing to exe's directory"); exit(-1); }
  90. condfree(tmppath);
  91. #endif /* WIN32 */
  92. #ifndef WIN32
  93. signal(SIGINT,handle_quit);
  94. signal(SIGTERM,handle_quit);
  95. #endif /* WIN32 */
  96. debug("Reading config");
  97. read_config(CONFIGFILE,&config);
  98. debug("Reading localdomains");
  99. read_localdomains(config.local_domains);
  100. #ifndef WIN32
  101. if(1==config.background)
  102. {
  103. switch(fork())
  104. {
  105. case -1: perror(_("Error forking")); exit(-1);
  106. case 0: break;
  107. default: exit(0);
  108. }
  109. }
  110. /*
  111. * we have to retrieve the user info before chrooting, but we have to drop privileges after chrooting
  112. * because chrooting requires us to be root, so we first read user & group info, then chroot, then drop
  113. * the privileges
  114. */
  115. if(1==config.drop_privileges)
  116. {
  117. if(NULL==(pwd=getpwnam(config.user))) { perror("Couldn't get user information"); exit(-1); }
  118. if(NULL==(grp=getgrnam(config.group))) { perror("Couldn't get group information"); exit(-1); }
  119. }
  120. if('\0'!=config.chroot[0])
  121. {
  122. gethostbyname("iteisa.com");
  123. if(-1==chdir(config.chroot)) { perror(_("Couldn't chdir to the chroot directory")); exit(-1); }
  124. if(-1==chroot(config.chroot)) { perror(_("Couldn't chroot to the chroot directory")); exit(-1); }
  125. if(-1==chdir("/")) { perror(_("Couldn't chdir to the root directory once inside the chroot")); exit(-1); }
  126. }
  127. if(1==config.drop_privileges)
  128. {
  129. setgroups(0,NULL);
  130. setgid(grp->gr_gid);
  131. setuid(pwd->pw_uid);
  132. setuid(pwd->pw_uid);
  133. }
  134. #endif /* WIN32 */
  135. #ifdef WIN32
  136. debug("Initializing winsock");
  137. init_winsock();
  138. #endif /* WIN32 */
  139. debug("Creating listening socket");
  140. if(-1==(fd_server=create_listening_socket(config.listen_port))) exit(-1);
  141. debug("Entering main loop");
  142. while(0==quit)
  143. {
  144. if(can_read(fd_server,1))
  145. if(-1!=(fd=accept(fd_server,NULL,0)))
  146. {
  147. debug("Accepting new connection and creating new thread");
  148. if(pthread_create(&thread,&thread_attr,handle_connection,(void *)fd)) { perror("Error creating new thread"); exit(-1); }
  149. }
  150. }
  151. debug("Closing socket");
  152. closeskt(fd_server);
  153. debug("Freeing localdomains");
  154. free_localdomains();
  155. #ifdef WIN32
  156. debug("Freeing winsock");
  157. free_winsock();
  158. #endif /* WIN32 */
  159. debug("Ending saop");
  160. return 0;
  161. }
  162. void *handle_connection(void *p_outside)
  163. {
  164. int inside,outside;
  165. char *line=NULL;
  166. int authenticated=0,doing_hello=0;
  167. char *user=NULL,*pass=NULL,*tmpchar=NULL;
  168. unsigned long count=0,recvcount;
  169. size_t b64len;
  170. struct base64_decode_context b64ctx;
  171. char *buffer=NULL;
  172. char eom[6]={0};
  173. outside=(int)p_outside;
  174. debug("Creating connection to remote server");
  175. if(-1==(inside=create_connected_socket(config.smtp_server,config.smtp_port))) { return NULL; }
  176. while(1)
  177. {
  178. if(can_read(outside,0.01))
  179. {
  180. debug("Reading from outside");
  181. free_and_read_string(line,outside);
  182. if(NULL==line) break; /* reached end of message */
  183. if(!cmp(line,"AUTH LOGIN"))
  184. {
  185. debug("AUTH");
  186. /* perform authentication */
  187. /* get username */
  188. write_string(outside,"334 VXNlcm5hbWU6"); /* Username: in base64 */
  189. condfree(line);
  190. if(NULL==(line=read_string(outside))) break; /* someone dropped the connection */
  191. condfree(user);
  192. base64_decode_ctx_init(&b64ctx);
  193. base64_decode_alloc(&b64ctx,line,strlen(line),&user,&b64len);
  194. user[b64len]='\0';
  195. condfree(line);
  196. /* get password */
  197. write_string(outside,"334 UGFzc3dvcmQ6"); /* Password: in base64 */
  198. condfree(line);
  199. if(NULL==(line=read_string(outside))) break; /* someone dropped the connection */
  200. condfree(pass);
  201. base64_decode_ctx_init(&b64ctx);
  202. base64_decode_alloc(&b64ctx,line,strlen(line),&pass,&b64len);
  203. pass[b64len]='\0';
  204. condfree(line);
  205. if(
  206. (!cmp(config.auth_method,"pop3")&&authenticate_with_pop3(user,pass,config.auth_server,config.auth_port))||
  207. (!cmp(config.auth_method,"imap4")&&authenticate_with_imap4(user,pass,config.auth_server,config.auth_port))||
  208. (!cmp(config.auth_method,"file")&&authenticate_with_file(user,pass,config.auth_file))
  209. )
  210. {
  211. write_string(outside,"235 Authentication Successful");
  212. authenticated=1;
  213. }
  214. else
  215. write_string(outside,"535 Authentication Failed");
  216. condfree(user);
  217. condfree(pass);
  218. }
  219. else if(!cmp(line,"rcpt to:"))
  220. {
  221. if(NULL==localdomains)
  222. debug("No localdomains defined, can't receive emails");
  223. else if(!authenticated)
  224. {
  225. tmpchar=strchr(line,'@');
  226. if(NULL!=tmpchar)
  227. {
  228. tmpchar++;
  229. for(count=0;NULL!=localdomains[count];count++)
  230. {
  231. if(!cmp(tmpchar,localdomains[count]))
  232. {
  233. authenticated=1;
  234. break;
  235. }
  236. }
  237. }
  238. }
  239. }
  240. else if(!cmp(line,"data"))
  241. {
  242. if(!authenticated)
  243. {
  244. write_string(inside,"QUIT");
  245. closeskt(inside);
  246. write_string(outside,"451 Not authenticated");
  247. closeskt(outside);
  248. break;
  249. }
  250. }
  251. else if(!cmp(line,"starttls"))
  252. {
  253. condfree(line);
  254. write_string(outside,"454 TLS temporarily not available");
  255. }
  256. else if(!cmp(line,"ehlo")||!cmp(line,"helo"))
  257. doing_hello=1;
  258. else
  259. doing_hello=0;
  260. if(line&&*line)
  261. write_string(inside,line);
  262. }
  263. if(can_read(inside,0.01))
  264. {
  265. debug("Reading from inside");
  266. free_and_read_string(line,inside);
  267. if(NULL==line) break; /* reached end of message */
  268. /*
  269. * we have to process some lines of the ehlo response:
  270. * -remove PIPELINING, TLS and STARTTLS
  271. * -change all auth lines to AUTH LOGIN to only support login
  272. */
  273. if(doing_hello)
  274. {
  275. if(!cmp(line,"250")&&(!cmp(line+4,"AUTH")||!cmp(line+4,"PIPELINING")||!cmp(line+4,"TLS")||!cmp(line+4,"STARTTLS")))
  276. {
  277. if('-'==line[3])
  278. condfree(line);
  279. else
  280. {
  281. condfree(line);
  282. safestrdup(line,"250 AUTH LOGIN");
  283. }
  284. }
  285. else if(!cmp(line,"250 "))
  286. write_string(outside,"250-AUTH LOGIN");
  287. }
  288. if(!cmp(line,"354 ")) /* we have received authorization to send the message */
  289. {
  290. debug("Receiving message");
  291. condfree(buffer);
  292. write_string(outside,line);
  293. condfree(line);
  294. if(NULL==(buffer=(char *)malloc(4096))) { perror(_("Error reserving memory for body buffer")); break; }
  295. while(cmp(eom,"\r\n.\r\n")&&(recvcount=recv(outside,buffer,4096,MSG_NOSIGNAL))>0)
  296. {
  297. send(inside,buffer,recvcount,MSG_NOSIGNAL);
  298. if(recvcount>=5) /* if recvcount>=5 then eom = last 5 bytes of buffer */
  299. memcpy(eom,buffer+recvcount-5,5);
  300. else /* else, move the last 5-recvcount bytes to the beginning, then copy the buffer to the end */
  301. {
  302. memmove(eom,eom+recvcount,5-recvcount);
  303. memcpy(eom+(5-recvcount),buffer,recvcount);
  304. }
  305. }
  306. }
  307. if(line&&*line)
  308. write_string(outside,line);
  309. }
  310. }
  311. debug("Closing sockets");
  312. closeskt(outside);
  313. closeskt(inside);
  314. debug("Ending thread");
  315. return NULL;
  316. }
  317. void read_localdomains(char *file)
  318. {
  319. FILE *f;
  320. unsigned long count,i;
  321. char line[512]={0};
  322. char **tmpld;
  323. localdomains=NULL;
  324. debug("Opening file to localdomains");
  325. if(NULL==(f=fopen(file,"r")))
  326. {
  327. debug("No localdomains file, continuing with an empty locals. This will probably render this program completely useless.");
  328. return;
  329. }
  330. debug("Looping over entries");
  331. for(count=0;!feof(f);count++)
  332. {
  333. fgets(line,sizeof(line),f);
  334. if(!feof(f)&&'#'!=line[0])
  335. {
  336. for(i=strlen(line)-1;i&&('\r'==line[i]||'\n'==line[i]);line[i]='\0',i--);
  337. tmpld=(char **)realloc(localdomains,(count+2)*sizeof(char *));
  338. if(NULL==tmpld)
  339. {
  340. condfree(localdomains);
  341. perror("Error reserving memory for localdomains");
  342. exit(-1);
  343. }
  344. else
  345. localdomains=tmpld;
  346. safestrdup(localdomains[count],line);
  347. }
  348. }
  349. localdomains[count-1]=NULL;
  350. debug("Closing files");
  351. fclose(f);
  352. }
  353. void free_localdomains(char *file)
  354. {
  355. unsigned long i;
  356. debug("Freeing localdomains");
  357. for(i=0;NULL!=localdomains[i];i++)
  358. condfree(localdomains[i]);
  359. debug("Freeing memory");
  360. condfree(localdomains);
  361. }
  362. #ifdef WIN32
  363. /* windows-specific functions */
  364. void init_winsock()
  365. {
  366. /* fuck windows, it needs this sh*t before allowing sockets to work */
  367. WSADATA wsaData;
  368. int wsa=WSAStartup(0xff,&wsaData);
  369. if(wsa)
  370. {
  371. perror("Windows not working, call microsoft");
  372. exit(-1);
  373. }
  374. debug("Winsock initialization correct");
  375. }
  376. void free_winsock()
  377. {
  378. debug("Winsock cleanup");
  379. WSACleanup();
  380. }
  381. #endif /* WIN32 */
  382. #ifndef WIN32
  383. void handle_quit(int signum)
  384. {
  385. quit=1;
  386. }
  387. #endif /* WIN32 */