/*
* Copyright (C) 2000-2012 Free Software Foundation, Inc.
*
* This file is part of GnuTLS.
*
* GnuTLS 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, either version 3 of the License, or
* (at your option) any later version.
*
* GnuTLS 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, see .
*/
#include
#if HAVE_SYS_SOCKET_H
#include
#elif HAVE_WS2TCPIP_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef _WIN32
#include
#include
#endif
#include
#include
#include "sockets.h"
#ifdef HAVE_LIBIDN
#include
#include
#endif
#define MAX_BUF 4096
/* Functions to manipulate sockets
*/
ssize_t
socket_recv(const socket_st * socket, void *buffer, int buffer_size)
{
int ret;
if (socket->secure) {
do {
ret =
gnutls_record_recv(socket->session, buffer,
buffer_size);
if (ret == GNUTLS_E_HEARTBEAT_PING_RECEIVED)
gnutls_heartbeat_pong(socket->session, 0);
}
while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN
|| ret == GNUTLS_E_HEARTBEAT_PING_RECEIVED);
} else
do {
ret = recv(socket->fd, buffer, buffer_size, 0);
}
while (ret == -1 && errno == EINTR);
return ret;
}
ssize_t
socket_send(const socket_st * socket, const void *buffer, int buffer_size)
{
return socket_send_range(socket, buffer, buffer_size, NULL);
}
ssize_t
socket_send_range(const socket_st * socket, const void *buffer,
int buffer_size, gnutls_range_st * range)
{
int ret;
if (socket->secure)
do {
if (range == NULL)
ret =
gnutls_record_send(socket->session,
buffer,
buffer_size);
else
ret =
gnutls_record_send_range(socket->
session,
buffer,
buffer_size,
range);
}
while (ret == GNUTLS_E_AGAIN
|| ret == GNUTLS_E_INTERRUPTED);
else
do {
ret = send(socket->fd, buffer, buffer_size, 0);
}
while (ret == -1 && errno == EINTR);
if (ret > 0 && ret != buffer_size && socket->verbose)
fprintf(stderr,
"*** Only sent %d bytes instead of %d.\n", ret,
buffer_size);
return ret;
}
static
ssize_t send_line(int fd, const char *txt)
{
int len = strlen(txt);
int ret;
ret = send(fd, txt, len, 0);
if (ret == -1) {
fprintf(stderr, "error sending %s\n", txt);
exit(1);
}
return ret;
}
static
ssize_t wait_for_text(int fd, const char *txt, unsigned txt_size)
{
char buf[512];
char *p;
int ret;
fd_set read_fds;
struct timeval tv;
do {
FD_ZERO(&read_fds);
FD_SET(fd, &read_fds);
tv.tv_sec = 10;
tv.tv_usec = 0;
ret = select(fd + 1, &read_fds, NULL, NULL, &tv);
if (ret <= 0)
ret = -1;
else
ret = recv(fd, buf, sizeof(buf)-1, 0);
if (ret == -1) {
fprintf(stderr, "error receiving %s\n", txt);
exit(1);
}
buf[ret] = 0;
p = memmem(buf, ret, txt, txt_size);
if (p != NULL && p != buf) {
p--;
if (*p == '\n')
break;
}
} while(ret < (int)txt_size || strncmp(buf, txt, txt_size) != 0);
return ret;
}
void
socket_starttls(socket_st * socket, const char *app_proto)
{
if (socket->secure)
return;
if (app_proto == NULL || strcasecmp(app_proto, "https") == 0)
return;
if (strcasecmp(app_proto, "smtp") == 0 || strcasecmp(app_proto, "submission") == 0) {
if (socket->verbose)
printf("Negotiating SMTP STARTTLS\n");
wait_for_text(socket->fd, "220 ", 4);
send_line(socket->fd, "EHLO mail.example.com\n");
wait_for_text(socket->fd, "250 ", 4);
send_line(socket->fd, "STARTTLS\n");
wait_for_text(socket->fd, "220 ", 4);
} else if (strcasecmp(app_proto, "imap") == 0 || strcasecmp(app_proto, "imap2") == 0) {
if (socket->verbose)
printf("Negotiating IMAP STARTTLS\n");
send_line(socket->fd, "a CAPABILITY\r\n");
wait_for_text(socket->fd, "a OK", 4);
send_line(socket->fd, "a STARTTLS\r\n");
wait_for_text(socket->fd, "a OK", 4);
} else if (strcasecmp(app_proto, "ftp") == 0 || strcasecmp(app_proto, "ftps") == 0) {
if (socket->verbose)
printf("Negotiating FTP STARTTLS\n");
send_line(socket->fd, "FEAT\n");
wait_for_text(socket->fd, "211 End", 7);
send_line(socket->fd, "AUTH TLS\n");
wait_for_text(socket->fd, "234", 3);
} else {
if (!c_isdigit(app_proto[0])) {
static int warned = 0;
if (warned == 0) {
fprintf(stderr, "unknown protocol %s\n", app_proto);
warned = 1;
}
}
}
return;
}
void socket_bye(socket_st * socket)
{
int ret;
if (socket->secure) {
do
ret = gnutls_bye(socket->session, GNUTLS_SHUT_WR);
while (ret == GNUTLS_E_INTERRUPTED
|| ret == GNUTLS_E_AGAIN);
if (ret < 0)
fprintf(stderr, "*** gnutls_bye() error: %s\n",
gnutls_strerror(ret));
gnutls_deinit(socket->session);
socket->session = NULL;
}
freeaddrinfo(socket->addr_info);
socket->addr_info = socket->ptr = NULL;
free(socket->ip);
free(socket->hostname);
free(socket->service);
shutdown(socket->fd, SHUT_RDWR); /* no more receptions */
close(socket->fd);
socket->fd = -1;
socket->secure = 0;
}
void
socket_open(socket_st * hd, const char *hostname, const char *service,
int udp, const char *msg)
{
struct addrinfo hints, *res, *ptr;
int sd, err = 0;
char buffer[MAX_BUF + 1];
char portname[16] = { 0 };
char *a_hostname = (char*)hostname;
#ifdef HAVE_LIBIDN
err = idna_to_ascii_8z(hostname, &a_hostname, IDNA_ALLOW_UNASSIGNED);
if (err != IDNA_SUCCESS) {
fprintf(stderr, "Cannot convert %s to IDNA: %s\n", hostname,
idna_strerror(err));
exit(1);
}
#endif
if (msg != NULL)
printf("Resolving '%s'...\n", a_hostname);
/* get server name */
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = udp ? SOCK_DGRAM : SOCK_STREAM;
if ((err = getaddrinfo(a_hostname, service, &hints, &res))) {
fprintf(stderr, "Cannot resolve %s:%s: %s\n", hostname,
service, gai_strerror(err));
exit(1);
}
sd = -1;
for (ptr = res; ptr != NULL; ptr = ptr->ai_next) {
sd = socket(ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (sd == -1)
continue;
if ((err =
getnameinfo(ptr->ai_addr, ptr->ai_addrlen, buffer,
MAX_BUF, portname, sizeof(portname),
NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
fprintf(stderr, "getnameinfo(): %s\n",
gai_strerror(err));
continue;
}
if (hints.ai_socktype == SOCK_DGRAM) {
#if defined(IP_DONTFRAG)
int yes = 1;
if (setsockopt(sd, IPPROTO_IP, IP_DONTFRAG,
(const void *) &yes,
sizeof(yes)) < 0)
perror("setsockopt(IP_DF) failed");
#elif defined(IP_MTU_DISCOVER)
int yes = IP_PMTUDISC_DO;
if (setsockopt(sd, IPPROTO_IP, IP_MTU_DISCOVER,
(const void *) &yes,
sizeof(yes)) < 0)
perror("setsockopt(IP_DF) failed");
#endif
}
if (msg)
printf("%s '%s:%s'...\n", msg, buffer, portname);
err = connect(sd, ptr->ai_addr, ptr->ai_addrlen);
if (err < 0) {
continue;
}
break;
}
if (err != 0) {
int e = errno;
fprintf(stderr, "Could not connect to %s:%s: %s\n",
buffer, portname, strerror(e));
exit(1);
}
if (sd == -1) {
fprintf(stderr, "Could not find a supported socket\n");
exit(1);
}
hd->secure = 0;
hd->fd = sd;
hd->hostname = strdup(hostname);
hd->ip = strdup(buffer);
hd->service = strdup(portname);
hd->ptr = ptr;
hd->addr_info = res;
#ifdef HAVE_LIBIDN
idn_free(a_hostname);
#endif
return;
}
void sockets_init(void)
{
#ifdef _WIN32
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(1, 1);
if (WSAStartup(wVersionRequested, &wsaData) != 0) {
perror("WSA_STARTUP_ERROR");
}
#else
signal(SIGPIPE, SIG_IGN);
#endif
}
/* converts a textual service or port to
* a service.
*/
const char *port_to_service(const char *sport, const char *proto)
{
unsigned int port;
struct servent *sr;
if (!c_isdigit(sport[0]))
return sport;
port = atoi(sport);
if (port == 0)
return sport;
port = htons(port);
sr = getservbyport(port, proto);
if (sr == NULL) {
fprintf(stderr,
"Warning: getservbyport(%s) failed. Using port number as service.\n", sport);
return sport;
}
return sr->s_name;
}
int service_to_port(const char *service, const char *proto)
{
unsigned int port;
struct servent *sr;
port = atoi(service);
if (port != 0)
return port;
sr = getservbyname(service, proto);
if (sr == NULL) {
fprintf(stderr, "Warning: getservbyname() failed.\n");
exit(1);
}
return ntohs(sr->s_port);
}