Initial version

This commit is contained in:
Gergely Polonkai 2016-05-31 14:33:51 +02:00
commit 5c790813df
7 changed files with 759 additions and 0 deletions

3
client/Makefile Normal file
View File

@ -0,0 +1,3 @@
all:
gcc -g -Wall -o client client.c

222
client/client.c Normal file
View File

@ -0,0 +1,222 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include "config.h"
int
main(void)
{
int sockfd;
struct addrinfo hints;
struct addrinfo *servinfo;
struct addrinfo *p;
int rv;
int connected;
// Set the hints
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
// Get address info (AKA resolv hostname)
if ((rv = getaddrinfo(SERVER_ADDRESS, PORT, &hints, &servinfo)) != 0)
{
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}
// Cycle OUTER
while (1)
{
int con;
// Here we assume that we are not connected.
connected = 0;
printf("Connecting...\n");
// Create a socket
for (p = servinfo; p != NULL; p = p->ai_next)
{
if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
{
perror("client: socket");
continue;
}
break;
}
// If p is NULL, we didn't succeed to create a socket. This should never happen
if (p == NULL)
{
fprintf(stderr, "client: failed to connect\n");
return 2;
}
// Set the socket to non-blocking so we can control the timeout value
fcntl(sockfd, F_SETFL, O_NONBLOCK);
// Start the connection (it usually won't succeed yet)
con = connect(sockfd, p->ai_addr, p->ai_addrlen);
// If connect() returned an error saying the connection is in progress
if ((con < 0) && (errno == EINPROGRESS))
{
// Cycle INNER1
while (1)
{
struct timeval tv;
int t;
fd_set master;
// Create an empty descriptor set, and add our socket to it
FD_ZERO(&master);
FD_SET(sockfd, &master);
// Set the timeout value to SENDING_FREQ seconds
tv.tv_sec = SENDING_FREQ;
tv.tv_usec = 0;
// Run the select()
t = select(sockfd + 1, NULL, &master, NULL, &tv);
if ((t < 0) && (errno != EINTR))
{
// Some serious error happened, let's exit
perror("select");
exit(1);
}
else if (t < 0)
{
// select() was interrupted, lets restart the INNER1 cycle
printf("select() interrupted, continue\n");
continue;
}
else if (t > 0)
{
size_t lon;
int valopt;
lon = sizeof(int);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &lon) < 0)
{
fprintf(stderr, "Error in getsockopt() %d - %s\n", errno, strerror(errno));
connected = 0;
sleep(SENDING_FREQ);
break;
}
// Check the value returned...
if (valopt)
{
fprintf(stderr, "Error in delayed connection() %d - %s\n", valopt, strerror(valopt));
connected = 0;
sleep(SENDING_FREQ);
break;
}
printf("Connected with select().\n");
connected = 1;
break;
}
else if (t == 0)
{
// Connection timed out, let's break out from INNER1
printf("connect() timed out.\n");
connected = 0;
close(sockfd);
break;
}
}
}
else if (con < 0)
{
// connect() error. Wait SENDING_FREQ seconds and try again (restarting the OUTER cycle)
// XXX error handling?
printf("connect() error, retry\n");
sleep(SENDING_FREQ);
connected = 0;
continue;
}
else if (con == 0)
{
// We are now connected. Usually we won't get here, but in the select() loop above instead
printf("Connected without select().\n");
connected = 1;
}
if (connected)
{
// If we are connected, let's jump in the INNER2 cycle
while (1)
{
fd_set read_fds;
int t;
struct timeval tv;
char buf[MAXDATASIZE];
// Create and empty descriptor set and add our socket
FD_ZERO(&read_fds);
FD_SET(sockfd, &read_fds);
// Set the timeout value to SENDING_FREQ seconds
tv.tv_sec = SENDING_FREQ;
tv.tv_usec = 0;
// Let's run the select()
t = select(sockfd + 1, &read_fds, NULL, NULL, &tv);
if ((t < 0) && (errno != EINTR))
{
// select() ran into an error, this is bad. Let's exit
perror("select");
exit(1);
}
else if (t < 0)
{
// select() interrupted, try again by restarting the INNER2 cycle
printf("select() interrupted\n");
continue;
}
else if (t > 0)
{
// We got some data from the server
size_t len;
printf("Data from server.\n");
len = recv(sockfd, &buf, MAXDATASIZE, 0);
if (len <= 0)
{
// If the received data length is at most 0, we are disconnected, so break out from INNER2
printf("Closing connection.\n");
connected = 0;
close(sockfd);
break;
}
}
// If we arrive here, select() ran into timeout, so we should send some data to the server.
printf("Sending data to server\n");
send(sockfd, "!\n", 2, 0);
}
}
}
// This should never happen, but if so, let's clean up after ourselved
freeaddrinfo(servinfo);
close(sockfd);
return 0;
}

5
client/config.h Normal file
View File

@ -0,0 +1,5 @@
#define PORT "2884"
#define MAXDATASIZE 128
#define SENDING_FREQ 10
#define SERVER_ADDRESS "127.0.0.1"

3
server/Makefile Normal file
View File

@ -0,0 +1,3 @@
all:
gcc -g -Wall -o server server.c

8
server/config.h Normal file
View File

@ -0,0 +1,8 @@
// TODO: Put these values into a config file
#define PORT "2884" // Port number to listen on. It must be a string!
#define DROP_AFTER 10 // Drop clients who don't send any packets in this many seconds
#define CURRENT_LOG_LEVEL LOG_LEVEL_DEBUG // Logging level. See LOG_LEVEL_* defines in server.h
#define LOGFILE "auth.log" // Place of the log file
#define CLIENT_CONNECT_SCRIPT "/usr/local/sbin/ip_allow" // Script to run when a client connects
#define CLIENT_DISCONNECT_SCRIPT "/usr/local/sbin/ip_block" // Script to run when a client disconnects
#define BACKLOG 10

499
server/server.c Normal file
View File

@ -0,0 +1,499 @@
/* Define this to get vasprintf() */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/time.h>
#include <time.h>
#include <stdarg.h>
#include "config.h"
#include "server.h"
/* FILE handle for the log file */
FILE *log_fd;
/* This will point to the beginning of the client list */
client_t *client_list;
/* This will hold all the available file descriptors */
fd_set master;
/*
* log_message(level, format, ...)
* Puts a message in the log file. If level is less than CURRENT_LOG_LEVEL (less means more importance), the message gets logged.
* The log message should not end with a newline character.
*/
static void
log_message(int level, const char *format, ...)
{
if (CURRENT_LOG_LEVEL >= level)
{
va_list ap;
char *message;
char date[100];
time_t t;
struct tm *tmp;
t = time(NULL);
tmp = localtime(&t);
/* This should never happen */
if (tmp == NULL)
{
// Cannot log the error here, as we caught error in the logger. Simply exit with an error status.
exit(1);
}
// Start handling the variable-length parameters
va_start(ap, format);
/* XXX: This is a bit unportable, maybe it should be changed to something else. */
// Create the formatted message string
vasprintf(&message, format, ap);
// End handling the variable-length parameters
va_end(ap);
// Zero-fill date's placeholder
memset(&date, 0, 100);
// Put the current time stamp into date
strftime((char *)&date, 100, "%Y-%m-%d %H:%M:%S", tmp);
// Put the timestamp and the message into the logfile
fprintf(log_fd, "[%s] %s\n", (char *)&date, message);
fflush(log_fd);
}
}
/*
* sigchld_handler(signal)
* This is the signal handler for the SIGCHLD signal. It gets called every time a child finishes its work (even with failure)
*/
void
sigchld_handler(int s)
{
// Log a debug message about the finished child
log_message(LOG_LEVEL_DEBUG, "Found a hung child.");
// Clean up all the dead children (otherwise they turn into zombie processes)
while (waitpid(-1, NULL, WNOHANG) > 0);
}
/*
* sigterm_handler(signal)
* This is the signal handler for the SIGTERM signal. It gets called if someone kills the process with SIGTERM.
*/
void
sigterm_handler(int s)
{
// Log this event as an information (it's not a real error, and shouldn't be a debug-only message)
log_message(LOG_LEVEL_INFO, "Got SIGTERM, shutting down.");
// TODO: clean shutdown! (Close client sockets, etc.)
// Exit after the shutdown.
exit(1);
}
/*
* execute(command, parameter)
* Fork and execute the given program with exactly one parameter
*/
void
execute(char *command, char *parameter)
{
pid_t pid;
/* Do the fork() */
pid = fork();
if (pid == 0)
{
// If fork() returns zero, we are the child
// Try to execute the script. This will give control to the executed script. exec() returns only when an error occurs (e.g the command cannot be found).
log_message(LOG_LEVEL_DEBUG, "Executing '%s \"%s\"'...", command, parameter);
execl(command, command, parameter, (char *)NULL);
// If we get here, we got an error, which should be logged
log_message(LOG_LEVEL_ERROR, "execl: %s", strerror(errno));
// Close the log file
fclose(log_fd);
// TODO: Do a clean shutdown
exit(1);
}
}
/*
* client_new(socket, remote_addr)
* Create a new client structure with the given data, and fully reset timer
*/
void
client_new(int socket, struct sockaddr_in *remote_addr)
{
client_t *client_data;
client_t *temp;
char *tmp_addr;
// Allocate memory for the new client's data
client_data = malloc(sizeof(client_t));
if (client_data == NULL)
{
// Log an error message if allocation fails and exit with failure code
log_message(LOG_LEVEL_ERROR, "malloc: %s", strerror(errno));
exit(1);
}
// Allocate memory for the new client's IP address
tmp_addr = malloc(16);
if (tmp_addr == NULL)
{
// Log an error message if allocation fails and exit with failure code. Here we also free the previously allocated client_data
log_message(LOG_LEVEL_ERROR, "malloc: %s", strerror(errno));
free(client_data);
exit(1);
}
// Zero-fill the address location, so the string will be surely nul-terminated
memset(tmp_addr, 0, 16);
// Get the numeric hostname (IP address) of the remote side
getnameinfo((const struct sockaddr *)remote_addr, sizeof(struct sockaddr_in), (char *)tmp_addr, 15, NULL, 0, NI_NUMERICHOST);
// Log the connection
log_message(LOG_LEVEL_INFO, "New connection: %d (IP: %s)", socket, tmp_addr);
// Fill the client_data struct
client_data->socket = socket;
client_data->ip = tmp_addr;
client_data->last_reset = time(NULL);
client_data->previous = NULL;
client_data->next = NULL;
// If the client list is empty (this is the first client)
if (!client_list)
{
// The client_list should point to the newly allocated struct
client_list = client_data;
}
// Otherwise (this is not the first client
else
{
// Set temp to the last element of the client list
for (temp = client_list; temp->next; temp = temp->next);
// Set the last element's next pointer to point to the newly allocated struct
temp->next = client_data;
// Set the newly allocated struct's previous pointer to the (till here) last client's struct
client_data->previous = temp;
}
// Execute the connect script
execute(CLIENT_CONNECT_SCRIPT, client_data->ip);
}
/*
* client_remove(socket)
* Remove a client identified by its local socket number
*/
void
client_remove(int socket)
{
client_t *temp;
// If we don't have an allocated client_list, we simply return, as we won't find the named client. However, this should never happen
if (!client_list)
return;
// Walk through the client list
for (temp = client_list; temp; temp = temp->next)
{
// If this element's socket number is the one we are looking for
if (temp->socket == socket)
{
// Logging a message about the disconnection
log_message(LOG_LEVEL_INFO, "Connection lost: %d (IP: %s)", temp->socket, temp->ip);
// Remove this client from the linked list
if (temp->previous)
temp->previous->next = temp->next;
if (temp->next)
temp->next->previous = temp->previous;
// If this is the first client, set the client_list to point to the second item (or to NULL if this is also the last client)
if (temp == client_list)
client_list = temp->next;
// If we don't have a previous, nor a next client in the list, then this was the last client, so client_list should be NULL
// This is only a paranoia check, this should already happen in the previous instruction
if ((!temp->previous) && (!temp->next))
client_list = NULL;
// Execute the disconnect script
execute(CLIENT_DISCONNECT_SCRIPT, temp->ip);
// Free the IP address' memory
free(temp->ip);
// Free the whols struct's memory
free(temp);
// Close the socket itself
close(socket);
// Remove this socket from the watched sockets' list
FD_CLR(socket, &master);
}
}
}
/*
* client_reset_timer(socket)
* Reset a client's timer, identified by the local socket number
*/
void
client_reset_timer(int socket)
{
client_t *temp;
// Walk through the client list
for (temp = client_list; temp; temp = temp->next)
// If the current client is the one we are looking for
if (temp->socket == socket)
// Set its last reset time to the current timestamp
temp->last_reset = time(NULL);
}
/*
* check_timers()
* Check all clients if they have sent data in the near past, and disconnect (thus, deauthenticate) them if not
*/
void
check_timers(void)
{
client_t *temp;
// Walk through the client list
for (temp = client_list; temp; temp = temp->next)
// If this client hasn't sent data in DROP_AFTER seconds
if (time(NULL) - temp->last_reset > DROP_AFTER)
{
// Log the timeout event
log_message(LOG_LEVEL_INFO, "Client timeout, dropping connection %d (IP: %s).", temp->socket, temp->ip);
// And remove the client from the client list
client_remove(temp->socket);
}
}
/*
* main()
* The main function of the server program
*/
int
main(void)
{
int sock_listen;
struct addrinfo hints;
struct addrinfo *servinfo;
struct addrinfo *p;
int yes = 1;
int rv;
fd_set read_fds;
int fdmax;
struct sigaction sa;
// Initially set the client list to empty
client_list = NULL;
// Set the SIGCHLD handler (which will purge zombie children)
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, NULL) < 0)
{
perror("sigaction");
return 1;
}
// Set the SIGTERM handler
sa.sa_handler = sigterm_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGTERM, &sa, NULL) < 0)
{
perror("sigaction");
return 1;
}
// Set the hints to "any"
memset (&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // use my IP
// Check if our port number is already in use
if ((rv = getaddrinfo (NULL, PORT, &hints, &servinfo)) != 0)
{
fprintf(stderr, "getaddrinfo: %s", gai_strerror(rv));
return 1;
}
for (p = servinfo; p != NULL; p = p->ai_next)
{
if ((sock_listen = socket (p->ai_family, p->ai_socktype,
p->ai_protocol)) == -1)
{
perror("socket");
continue;
}
if (setsockopt (sock_listen, SOL_SOCKET, SO_REUSEADDR, &yes,
sizeof (int)) == -1)
{
perror("setsockopt");
exit (1);
}
if (bind (sock_listen, p->ai_addr, p->ai_addrlen) == -1)
{
close(sock_listen);
perror("bind");
continue;
}
break;
}
if (p == NULL)
{
perror("bind");
return 2;
}
freeaddrinfo(servinfo);
// Start listening on the listener socket
if (listen(sock_listen, BACKLOG) == -1)
{
perror("listen");
exit (1);
}
// Clear the list of the watched sockets
FD_ZERO(&master);
FD_ZERO(&read_fds);
// Add the listener to the watched sockets
FD_SET(sock_listen, &master);
fdmax = sock_listen;
// Try to open (or create) the log file
if ((log_fd = fopen(LOGFILE, "a")) == NULL)
{
perror("fopen");
exit(1);
}
// Try to go into the background
if (daemon(0, 0) < 0)
{
perror("daemon");
exit(1);
}
log_message(LOG_LEVEL_INFO, "Started.");
while (1)
{
struct timeval tv;
int t;
// Reset the read_fds with the full list of watched sockets
read_fds = master;
// Set the timeout value
tv.tv_sec = 1;
tv.tv_usec = 0;
// Wait for incoming connection or incoming data. "Modified" sockets (ones with incoming connections or incoming data) will be put in read_fds
t = select(fdmax + 1, &read_fds, NULL, NULL, &tv);
// If select() returns a negative, it means an error
if (t < 0)
{
// However, if select() was only interrupted by a signal, simply continue
if (errno == EINTR)
continue;
// Otherwise log an error and exit
log_message(LOG_LEVEL_ERROR, "select: %s", strerror(errno));
return 1;
}
// If select() returns a positive, it means that there are incoming connections or incoming data
else if (t != 0)
{
int sock;
// Walk through the watched sockets (the lazy way, later this can cause some microseconds of hang up, as not all sockets are really monitored in this set [0..fdmax])
for (sock = 0; sock <= fdmax; sock++)
{
// If the current socket exists in read_fds (it has data to read)
if (FD_ISSET(sock, &read_fds))
{
// If the socket we found is the listener
if (sock == sock_listen)
{
int new_socket;
struct sockaddr_in remote_addr;
socklen_t addrlen = sizeof(struct sockaddr_in);
// Accept the new connection
new_socket = accept(sock_listen, (struct sockaddr *)&remote_addr, &addrlen);
// Add the new connection to the watched sockets
FD_SET(new_socket, &master);
if (new_socket > fdmax)
fdmax = new_socket;
// Create a new client entry for the new connection
client_new(new_socket, &remote_addr);
}
else
{
// Otherwise it's an already existing socket which has data to read
size_t read_len;
char buf[128];
// Read the data from the socket (in 128-bytes chunks)
read_len = recv(sock, (char *)&buf, 128, 0);
// If recv() returns a negative value, this means an error, so we should remove this client
if (read_len < 0)
{
// In this case we also log an error
log_message(LOG_LEVEL_ERROR, "recv: %s", strerror(errno));
client_remove(sock);
}
// Otherwise if recv() returns 0, we just simply remove the client (0 means the client already closed the connection)
else if (read_len == 0)
{
client_remove(sock);
}
// If recv() returns a positive, the client sent some data, which will be discarded, but the timer of the client is reset
else
{
// Log a debugging message about the reset timer
log_message(LOG_LEVEL_DEBUG, "Connection timer reset: %d", sock);
client_reset_timer(sock);
}
}
}
}
}
// After we checked all the sockets, or there was no sockets to check (select() returned 0), we go and check if any clients timed out
check_timers();
}
fclose(log_fd);
return 0;
}

19
server/server.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef _AUTH_SERVER_H
# define _AUTH_SERVER_H
/* Logging levels. These are the possible values for CURRENT_LOG_LEVEL above */
#define LOG_LEVEL_ERROR 0
#define LOG_LEVEL_INFO 1
#define LOG_LEVEL_DEBUG 2
/* The client_t struct. With this struct full client data can be stored in a doubly-linked list */
typedef struct _client_t {
int socket;
char *ip;
time_t last_reset;
struct _client_t *previous;
struct _client_t *next;
} client_t;
#endif /* _AUTH_SERVER_H */