Browse Source

initial commit

git-svn-id: file:///home/ps/projects/distributor/trunk@1 4c72226d-0938-4c61-bee9-4ad40ed711b9
ps 10 years ago
commit
7db1f7a975
4 changed files with 434 additions and 0 deletions
  1. 8 0
      Makefile
  2. 155 0
      dist.c
  3. 203 0
      utils.c
  4. 68 0
      utils.h

+ 8 - 0
Makefile

@@ -0,0 +1,8 @@
+CFLAGS=-DHOSTNAME=\"`hostname -f`\" -g
+
+all: dist
+
+dist: dist.o utils.o
+
+clean:
+	rm -f dist *.o

+ 155 - 0
dist.c

@@ -0,0 +1,155 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+
+#include "utils.h"
+
+extern char m_buffer[4096];
+
+#define BANNER "220 " HOSTNAME " SMTP"
+#define HELOANSWER "250 " HOSTNAME
+#define CMDUNKNOWN "502 command unimplemented"
+#define DOMAINUNKNOWN "553 unknown domain"
+#define TEMPERROR "421 delivery error, please retry later"
+#define DEFERROR "521 permanent delivery error, do not retry"
+#define PORT 25
+
+#define fail() do { perror(__FILE__ ":"); exit(-1); } while(0);
+struct serverinfo
+{
+  char *address;
+  unsigned port;
+};
+
+char *domains[]={"fakehermes.org","iteisa.net",NULL};
+struct serverinfo servers[]={
+  {"mail.gutierrezdequevedo.com",2525},
+  {"94.23.193.98",25}
+};
+
+unsigned long connid=0;
+
+struct serverinfo *get_server(char *domain)
+{
+  int i;
+  for(i=0;domains[i];i++)
+    if(!cmp(domain,domains[i]))
+      return &servers[i];
+
+  return NULL;
+}
+
+char *get_domain(char *line)
+{
+  char *strtmp=NULL;
+  char *buf=NULL;
+
+  safestrdup(buf,strchr(line,'@')+1);
+  safestrdup(strtmp,strtok(buf," \"><"));
+
+  condfree(buf);
+  return strtmp;
+}
+
+void handle_new_connection(unsigned long p_connid,int fd)
+{
+  char *buf=NULL;
+  char *from=NULL;
+  char *to=NULL;
+  char *domain=NULL;
+  char *serveraddress=NULL;
+  int fdin=-1;
+  int fds[2];
+  char databuffer[8192];
+  int bytesread;
+  int fdread,fdwrite;
+  struct serverinfo *server;
+
+  connid=p_connid;
+
+  write_string(fd,BANNER);
+
+  while(!socketeof(fd))
+  {
+    free_and_read_string(buf,fd);
+    if(!cmp(buf,"HELO")||!cmp(buf,"EHLO"))
+      write_string(fd,HELOANSWER);
+    else if(!cmp(buf,"MAIL FROM"))
+    {
+      safestrdup(from,buf);
+      write_string(fd,"250 Ok");
+    }
+    else if(!cmp(buf,"RCPT TO"))
+    {
+      safestrdup(to,buf);
+      domain=get_domain(to);
+      if(NULL==(server=get_server(domain)))
+      {
+        write_string(fd,DOMAINUNKNOWN);
+        exit(-1);
+      }
+      condfree(domain);
+      break;
+    }
+    else
+      write_string(fd,CMDUNKNOWN);
+  }
+  if(-1==(fdin=create_connected_socket(server->address,server->port)))
+  {
+    write_string(fd,TEMPERROR);
+    exit(-1);
+  }
+  do /* manage errors as we would do an exception, all in one point */
+  {
+    /* reproduce initial conversation on the new server */
+    free_and_read_string(buf,fdin); /* read banner */
+    write_string(fdin,"HELO " HOSTNAME); do { free_and_read_string(buf,fdin); } while('-'==buf[3]);
+    write_string(fdin,from); do { free_and_read_string(buf,fdin); } while('-'==buf[3]);
+    if(cmp(buf,"250")) { write_string(fd,DEFERROR); break; }
+    write_string(fdin,to);
+    /* now just copy data from one server to the other */
+    while(1)
+    {
+      fds[0]=fd; fds[1]=fdin;
+      can_read(fds,2,-1);
+      if(can_read(&fd,1,0)) { fdread=fd; fdwrite=fdin; }
+      else { fdread=fdin; fdwrite=fd; }
+      bytesread=read(fdread,databuffer,sizeof(databuffer));
+      if(bytesread<=0||write(fdwrite,databuffer,bytesread)<=0);
+      {
+        closeskt(fd);
+	closeskt(fdin);
+        break;
+      }
+    }
+  }
+  while(0);
+  condfree(buf); condfree(to); condfree(from); closeskt(fd); closeskt(fdin);
+  exit(0);
+}
+
+int main(int argc, char **argv)
+{
+  int fd_server=-1,fd=-1;
+  unsigned long conn=0;
+
+  /* ignore dying child */
+  signal(SIGCHLD,SIG_IGN);
+  openlog("dist",0,LOG_MAIL);
+  if(-1==(fd_server=create_listening_socket(PORT))) fail();
+  while(-1!=(fd=accept(fd_server,NULL,0)))
+  {
+    debug("New connection!");
+    ++conn;
+    switch(fork())
+    {
+      case 0: closeskt(fd_server); handle_new_connection(conn,fd);
+      case -1: fail();
+      default: closeskt(fd);
+    }
+  }
+  close(fd_server);
+  closelog();
+
+  exit(0);
+}

+ 203 - 0
utils.c

@@ -0,0 +1,203 @@
+/**
+ * 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@gutierrezdequevedo.com>
+ */
+#include "utils.h"
+
+extern unsigned long connid;
+char m_buffer[4096]={0};
+
+/**
+ * reads a string as safely as possible.
+ *
+ * it will read one byte at a time until a '\\n' is read.
+ * we store one char at a time in a static buffer, and when
+ * we find a '\\n', we do an strndup of the buffer.
+ * it might be possible in this special case to just use
+ * a static buffer, return a pointer to it and never assign 
+ * any memory with strndup or in any other way.
+ *
+ * @param fd fd to read from
+ *
+ * @return a pointer to the read string
+ */
+char *read_string(int fd)
+{
+  char chr;
+  char *buffer=NULL,*tmpbuf=NULL;
+  unsigned long strsize=0;
+  unsigned long allocsize=0;
+
+  assert(fd>=0);
+  /* read one byte at a time */
+  while(1)
+  {
+    if(1!=recv(fd,&chr,1,MSG_NOSIGNAL))
+      break;
+
+    if(0==allocsize||allocsize<strsize+2)
+    {
+      allocsize=MAX(allocsize+512,strsize+2);
+      if(NULL==(tmpbuf=realloc(buffer,allocsize)))
+      {
+        debug("Error reserving memory");
+        condfree(buffer);
+        break;
+      }
+      else
+        buffer=tmpbuf;
+    }
+    if(10==chr) break;
+    if(13!=chr)
+      buffer[strsize++]=chr;
+  }
+
+  if(NULL==buffer) return NULL;
+
+  /* finalize string */
+  buffer[strsize]='\0';
+
+  snprintf(m_buffer,sizeof(m_buffer),"r>%s",buffer);
+  debug(m_buffer);
+
+  /* return a new string with new mem */
+  return buffer;
+}
+
+/**
+* create a new listening socket.
+*
+* with following options:
+* - SO_REUSEADDR
+* - INADDR_ANY
+*
+* @param port the port to listen on
+*
+* @return the new socket fd
+*/
+int create_listening_socket(unsigned int port)
+{
+  int fd_server;
+  struct sockaddr_in address;
+  int i;
+
+  /* create socket */
+  if(-1==(fd_server=socket(PF_INET,SOCK_STREAM,0))) { perror("Error creating socket"); return -1; }
+
+  /* set option for the socket: SO_REUSEADDR (will reuse address between invocations of the same program) */
+  i=1;
+  if(-1==setsockopt(fd_server,SOL_SOCKET,SO_REUSEADDR,(void *)&i,sizeof(i))) { perror("Error configuring socket reuse"); closeskt(fd_server); return -1; }
+
+  /* bind to address :ANY */
+  address.sin_family=AF_INET;
+  address.sin_addr.s_addr=INADDR_ANY;
+  address.sin_port=htons(port);
+
+  if(-1==bind(fd_server,(struct sockaddr *)&address,sizeof(address))) { perror("Error binding to address"); closeskt(fd_server); return -1; }
+
+  /* listen on socket */
+  if(-1==listen(fd_server,10)) { perror("Error listening on socket"); closeskt(fd_server); return -1; }
+  snprintf(m_buffer,sizeof(m_buffer),"Listening on 0.0.0.0:%d",port);
+  debug(m_buffer);
+
+  return fd_server; 
+}
+
+/**
+ * create a socket that is connected to an address.
+ *
+ * @param address address to connect to
+ * @param port port to connect to
+ *
+ * @return fd of the opened connection
+ */
+int create_connected_socket(char *address,unsigned int port)
+{
+  int fd;
+  struct sockaddr_in addr;
+
+  /* create socket */
+  if(-1==(fd=socket(PF_INET,SOCK_STREAM,0))) { perror("Error creating socket"); return -1; }
+
+  /* fill address structure */
+  addr.sin_family=AF_INET;
+  addr.sin_addr.s_addr=resolve(address);
+  addr.sin_port=htons(port);
+
+  /* connect to remote host */
+  snprintf(m_buffer,sizeof(m_buffer),"Connecting to %s:%d",address,port);
+  debug(m_buffer);
+  if(-1==connect(fd,(struct sockaddr *)&addr,sizeof(addr))) { perror("Error connecting to host"); return -1; }
+
+  return fd;
+}
+
+unsigned long resolve(char *address)
+{
+  struct hostent *resolved;
+  struct in_addr addr;
+
+  if(NULL==(resolved=gethostbyname(address))) { perror("Error resolving"); return 0; }
+
+  memcpy(&addr,resolved->h_addr,sizeof(addr));
+
+  return addr.s_addr;
+}
+
+/**
+ * whether a fd does have data waiting or not.
+ *
+ * @param fd array of fd to ask
+ * @param nfd number of fds
+ * @param time time to wait for data
+ *
+ * @return 1 on data waiting to be read, else return 0
+ */
+unsigned char can_read(int *fd,int nfd,float time)
+{
+  fd_set rfd;
+  struct timeval timeout;
+  int maxfd=0,i;
+
+  assert(fd>=0);
+
+  /* configure timeout */
+  timeout.tv_sec=(int)time;
+  timeout.tv_usec=(time-(int)time)*1000000;
+
+  /* configure rfd */
+  FD_ZERO(&rfd);
+  for(i=0;i<nfd;i++)
+  {
+    FD_SET(fd[i],&rfd);
+    maxfd=MAX(maxfd,fd[i]);
+  }
+
+  return (select(maxfd+1,&rfd,NULL,NULL,time<0?NULL:&timeout)>0)?1:0;
+}
+
+/**
+ * check whether fd is at eof
+ * @param fd socket to check
+ *
+ * @return true on eof, false otherwise
+ */
+unsigned char socketeof(int fd)
+{
+  unsigned char buffer;
+  return 0==recv(fd,&buffer,1,MSG_PEEK|MSG_DONTWAIT);
+}

+ 68 - 0
utils.h

@@ -0,0 +1,68 @@
+/**
+ * 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@gutierrezdequevedo.com>
+ */
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <syslog.h>
+#ifdef WIN32
+  #include <winsock2.h>
+  #include <ws2tcpip.h>
+#else
+  #include <sys/socket.h>
+  #include <netdb.h>
+  #include <arpa/inet.h>
+  #include <netinet/in.h>
+  #include <sys/select.h>
+#endif /* WIN32 */
+
+#define BUFLEN 512 /* max length of strings */
+
+#ifndef MSG_NOSIGNAL
+  #define MSG_NOSIGNAL 0
+#endif /* MSG_NOSIGNAL */
+
+/* utility macros */
+#define cmp(x,y) (NULL==x||NULL==y||strncasecmp(x,y,strlen(y)))
+#define write_string(x,y) do { snprintf(m_buffer,sizeof(m_buffer),"w>%s",y); debug(m_buffer); send(x,y,strlen(y),MSG_NOSIGNAL); send(x,"\r\n",2,MSG_NOSIGNAL); } while(0)
+#define condfree(x) do { if(NULL!=x) free(x); x=NULL; } while(0)
+#ifndef WIN32
+  #define safestrdup(x,y) do { assert(NULL!=y); if(NULL==(x=strndup(y,strlen(y)))) { perror("Error duplicating string"); exit(-1); } } while(0)
+  #define closeskt(x); do { if(-1!=x) { close(x); x=-1; } } while(0)
+#else
+  #define safestrdup(x,y) do { assert(NULL!=y); if(NULL==(x=strdup(y))) { perror("Error duplicating string"); exit(-1); } } while(0)
+  #define closeskt(x); do { if(-1!=x) { closesocket(x); x=-1; } } while(0)
+#endif /* WIN32 */
+#define _(x) x
+#define MAX(x,y) (((x)>(y))?(x):(y))
+#define free_and_read_string(x,y) do { condfree(x); x=read_string(y); } while(0)
+#define debug(x) do { syslog(LOG_DEBUG,"%s:%d [%ld] %s",__FILE__,__LINE__,connid,x); printf("%s:%d [%ld] %s\n",__FILE__,__LINE__,connid,x); } while(0)
+
+char *read_string(int);
+int create_listening_socket(unsigned int);
+int create_connected_socket(char *,unsigned int);
+unsigned long resolve(char *);
+unsigned char can_read(int *,int,float);
+unsigned char socketeof(int);
+
+#endif /* UTILS_H */