*/
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#else
#include <sys_socket.h>
#endif
// Application headers
#include "sendmail.h"
static const int kLinefeedChr = '\012';
static const int kCrChr = '\015';
static StatusCallbackFunc gStatusFunc;
static ErrorCallbackFunc gErrorFunc;
/* This function reads from a socket, until
it recieves a linefeed
character. It fills the buffer "str" up to the maximum
size "count".
This function will return -1 if the socket is closed during
the read
operation.
Note that if a single line exceeds the length of count,
the extra data
will be read and discarded! You have been warned.
この関数は、改行文字を受けるまで、ソケットから文字を読み出して、
"count"を最大文字数として、"str"で示されるバッファに取り込む。
読み出し中にソケットがクローズされた場合、この関数は-1を返却する
に注意。
*/
static int sock_gets(int sockfd, char *str, size_t
count)
{
int bytes_read;
int total_count = 0;
char *current_position;
char last_read = 0;
const char kLinefeed = 10;
const char kCR = 13;
current_position = str;
while (last_read != kLinefeed) {
bytes_read = read(sockfd, &last_read, 1);
if (bytes_read <= 0) {
/* The other side may have closed unexpectedly
*/
return -1; /* Is this effective on other
platforms than linux? */
}
if ( (total_count < count) && (last_read
!= kLinefeed) &&
(last_read !=kCR) ) {
current_position[0] = last_read;
current_position++;
total_count++;
}
}
if (count > 0)
current_position[0] = 0;
return total_count;
}
#define IsDigit(c) ((c) >= '0' && (c) <= '9')
// reads lines until we get a non-continuation
line
static int ReadReply(int fd, char *s, unsigned
int sLen)
{
int numBytes;
do {
numBytes = sock_gets(fd, s, sLen);
} while (numBytes >= 0 && !(strlen(s) >= 4 &&
s[3] == ' ' &&
IsDigit(s[0]) && IsDigit(s[1]) && IsDigit(s[2])));
if (numBytes < 0)
return numBytes;
else
return 0;
}
/* Take a service name, and a service type, and
return a port number. he number
returned is byte ordered for the network. */
static int atoport(char *service, char *proto)
{
int port;
struct servent *serv;
/* First try to read it from /etc/services */
serv = getservbyname(service, proto);
if (serv != NULL)
port = serv->s_port;
else {
return -1; /* Invalid port address */
}
return port;
}
/* Converts ascii text to in_addr struct.
NULL is returned if the address
can not be found. */
static struct in_addr *atoaddr(char *address)
{
struct hostent *host;
static struct in_addr saddr;
/* First try it as aaa.bbb.ccc.ddd. */
saddr.s_addr = inet_addr(address);
if (saddr.s_addr != -1) {
return &saddr;
}
host = gethostbyname(address);
if (host != NULL) {
return (struct in_addr *) *host->h_addr_list;
}
return NULL;
}
/* This is a generic function to make
a connection to a given server/port.
service is the port name/number,
type is either SOCK_STREAM or SOCK_DGRAM, and
netaddress is the host name to connect to.
The function returns the socket, ready for action.*/
static int make_connection(char *service, int
type, char *netaddress)
{
/* First convert service from a string, to a number... */
int port = -1;
struct in_addr *addr;
int sock, connected;
struct sockaddr_in address;
if (type == SOCK_STREAM)
port = atoport(service, "tcp");
if (type == SOCK_DGRAM)
port = atoport(service, "udp");
if (port == -1) {
(*gErrorFunc)("make_connection: Invalid socket type.\n",
NULL);
return -1;
}
addr = atoaddr(netaddress);
if (addr == NULL) {
(*gErrorFunc)("make_connection: Invalid network
address.\n", NULL);
return -1;
}
memset((char *) &address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = (port);
address.sin_addr.s_addr = addr->s_addr;
sock = socket(AF_INET, type, 0);
if (type == SOCK_STREAM) {
connected = connect(sock, (struct sockaddr *) &address,
sizeof(address));
if (connected < 0) {
(*gErrorFunc)("connect", NULL);
return -1;
}
return sock;
}
/* Otherwise, must be for udp, so bind to address. */
if (bind(sock, (struct sockaddr *) &address, sizeof(address))
< 0) {
(*gErrorFunc)("bind", NULL);
return -1;
}
return sock;
}
static unsigned int nwrite(int
fd, char *ptr, unsigned int nbytes)
{
unsigned int nleft;
int chunk;
int nwritten;
nleft = nbytes;
while (nleft > 0) {
if (nleft > 0x7000) chunk = 0x7000;
else chunk = nleft;
nwritten = write(fd, ptr, chunk);
if (nwritten <= 0)
return(nwritten); /* error */
nleft -= nwritten;
ptr += nwritten;
}
return(nbytes - nleft);
}
#define kMaxReplySize 512
static int GotReply(int
fd, char expectedLeadingChar)
{
int err;
char reply[kMaxReplySize];
err = ReadReply(fd, reply, sizeof(reply));
if (err != 0) {
(*gErrorFunc)("Read error", NULL);
return 0;
}
if (*reply != expectedLeadingChar) {
(*gErrorFunc)("Protocol error", reply);
return 0;
}
return 1;
}
// sends s1 followed by s2 followed by s3 followed
by CRLF
static int Send(int fd, char *s1, char *s2, char
*s3)
{
if (s1 && nwrite(fd, s1, strlen(s1)) < 0)
goto error;
if (s2 && nwrite(fd, s2, strlen(s2)) < 0)
goto error;
if (s3 && nwrite(fd, s3, strlen(s3)) < 0)
goto error;
if (nwrite(fd, "\015\012", 2) < 0)
goto error;
return 1;
error:
(*gErrorFunc)("Write error", NULL);
return 0;
}
// sends aLine which is length chars
long
static int SendSingleBodyLine(int fd, char *aLine,
int length)
{
if (*aLine == '.') // double-up on '.' lines
if (nwrite(fd, ".", 1) < 0)
goto error;
if (nwrite(fd, aLine, length) < 0)
goto error;
if (nwrite(fd, "\015\012", 2) < 0)
goto error;
return 1;
error:
(*gErrorFunc)("Write error", NULL);
return 0;
}
static int SendBody(int
fd, char *body)
{
char *lineStart = body;
int result = 0;
// send all the newline-terminated lines
while (*body != '\0' && result == 0) {
if (*body == '\n') {
result = SendSingleBodyLine(fd, lineStart,
body - lineStart);
lineStart = body + 1;
}
body++;
}
// send the last partial line
if (lineStart < body && result == 0)
result = SendSingleBodyLine(fd, lineStart,
body - lineStart);
return result;
}
#define kOK '2'
#define kWantMore '3'
int sendmail(char *smtpHost,
char *from, char *to, char *subject,
char *data, StatusCallbackFunc statusFunc,
ErrorCallbackFunc errorFunc)
{
int success = 0;
int fd = -1; // socket file descriptor
gErrorFunc = errorFunc;
gStatusFunc = statusFunc;
// open connection to the server
if ((fd = make_connection("smtp", SOCK_STREAM, smtpHost)) <
0 )
{
(*errorFunc)("Couldn't open connection", NULL);
goto _Exit;
}
// send & receive the data
if (!GotReply(fd, kOK))
goto _Exit;
if (!Send(fd, "HELO [", "127.0.0.1", "]"))
goto _Exit;
if (!GotReply(fd, kOK))
goto _Exit;
if (!Send(fd, "MAIL from:<", from, ">"))
goto _Exit;
if (!GotReply(fd, kOK))
goto _Exit;
if (!Send(fd, "RCPT to:<", to, ">"))
goto _Exit;
if (!GotReply(fd, kOK))
goto _Exit;
if (!Send(fd, "DATA", NULL, NULL))
goto _Exit;
if (!GotReply(fd, kWantMore))
goto _Exit;
if (!Send(fd, "Subject: ", subject, NULL))
goto _Exit;
// need empty line between headers and data
if (!Send(fd, NULL, NULL, NULL))
goto _Exit;
if (!SendBody(fd,data))
goto _Exit;
if (!Send(fd, ".", NULL, NULL))
goto _Exit;
if (!GotReply(fd, kOK))
goto _Exit;
if (!Send(fd, "QUIT", NULL, NULL))
goto _Exit;
success = 1;
// cleanup the mess...
_Exit:
if ( fd >= 0 ) close( fd );
return success;
}