#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
int get_policy_data(const char *, void **);
void generate_error(int, const char *, const char *);
void process_reload_signal(int);
int create_socket(unsigned short);
void parse_requests(int, void *, int, const char *, const char *);
int create_fdset(fd_set *, int *, unsigned, int);
int * array_add(int *, unsigned, int);
int * array_remove(int *, unsigned, int);
void send_policy_info(int, void *, unsigned);
volatile sig_atomic_t reload_next;
int main (int argc, char ** argv) {
if (argc != 2) generate_error(1, *argv, argv[1]);
void * policydata;
int policylength = get_policy_data(argv[1], &policydata);
if (policylength < 0) generate_error(-policylength, *argv, argv[1]);
reload_next = 0;
int socketnumber = create_socket(843);
if (socketnumber < 0) generate_error(2, *argv, argv[1]);
if (signal(SIGHUP, &process_reload_signal) == SIG_ERR)
fprintf(stderr, "%s: could not install SIGHUP handler\n", *argv);
parse_requests(socketnumber, policydata, policylength, *argv, argv[1]);
close(socketnumber);
return 0;
}
int get_policy_data (const char * filename, void ** data) {
int fd = open(filename, 0);
if (fd < 0)
switch (errno) {
case EACCES: return -3;
case ENOENT: case ENOTDIR: return -4;
default: return -5;
}
struct stat statdata;
if(fstat(fd, &statdata)) {
close(fd);
return -5;
}
int remainder = statdata.st_size;
int size = remainder;
*data = malloc(size + 1);
char * readp = *data;
int readsize;
while (remainder > 0) {
readsize = read(fd, readp, remainder);
if (readsize > 0) {
remainder -= readsize;
readp += readsize;
continue;
}
if (!readsize) {
size = readp - ((char *) (*data));
break;
}
if ((errno == EINTR) || (errno = EAGAIN)) continue;
close(fd);
if (errno == EISDIR) return -6;
return -5;
}
close(fd);
((char *) (*data))[size] = 0;
return size + 1;
}
void generate_error (int error, const char * programname, const char * filename) {
switch (error) {
case 1:
fprintf(stderr, "usage: %s policyfile\n", programname);
break;
case 2:
fprintf(stderr, "%s: could not create listening socket on port 843\n", programname);
break;
case 3:
fprintf(stderr, "%s: permission denied when accessing policy file %s\n", programname, filename);
break;
case 4:
fprintf(stderr, "%s: policy file %s does not exist\n", programname, filename);
break;
case 5:
fprintf(stderr, "%s: unknown error accessing policy file %s\n", programname, filename);
break;
case 6:
fprintf(stderr, "%s: policy file %s is actually a directory\n", programname, filename);
break;
}
exit(error);
}
void process_reload_signal (int signalnumber) {
reload_next = 1;
}
int create_socket (unsigned short port) {
int number = socket(AF_INET, SOCK_STREAM || SOCK_NONBLOCK, 0);
if (number < 0) return -1;
struct sockaddr_in sa;
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = INADDR_ANY;
if (bind(number, (struct sockaddr *) &sa, sizeof sa)) {
close(number);
return -1;
}
if (listen(number, 128)) {
close(number);
return -1;
}
return number;
}
void parse_requests (int socketnumber, void * policydata, int policylength, const char * programname, const char * filename) {
struct timeval tv = {.tv_sec = 5, .tv_usec = 0};
struct timeval tvs;
int cont = 1;
int result;
fd_set fdset;
int * clients = NULL;
unsigned clientcount = 0;
int * copy;
unsigned current, limit;
while (cont) {
result = create_fdset(&fdset, clients, clientcount, socketnumber);
tvs = tv;
result = select(result, &fdset, NULL, NULL, &tvs);
if (reload_next) {
void * newdata;
int status = get_policy_data(filename, &newdata);
if (status < 0)
fprintf(stderr, "%s: could not reload policy data from %s\n", programname, filename);
else {
free(policydata);
policydata = newdata;
policylength = status;
}
reload_next = 0;
}
if (result <= 0) continue;
copy = malloc(clientcount * sizeof(int));
memcpy(copy, clients, clientcount * sizeof(int));
limit = clientcount;
if (FD_ISSET(socketnumber, &fdset)) {
int newsocket = accept(socketnumber, NULL, NULL);
if (newsocket > 0) clients = array_add(clients, clientcount ++, newsocket);
}
for (current = 0; current < limit; current ++)
if (FD_ISSET(copy[current], &fdset)) {
char buffer[1024];
int reception = recv(copy[current], buffer, 1024, MSG_DONTWAIT);
if (!reception) {
close(copy[current]);
clients = array_remove(clients, clientcount --, copy[current]);
}
if (reception <= 0) continue;
unsigned short pos;
for (pos = 0; pos < reception; pos ++)
if (!buffer[pos])
send_policy_info(copy[current], policydata, policylength);
}
free(copy);
}
}
int create_fdset (fd_set * fdset, int * fds, unsigned count, int listener) {
int * max = fds + count;
int * current;
int maxfd = listener + 1;
FD_ZERO(fdset);
FD_SET(listener, fdset);
for (current = fds; current < max; current ++) {
if (*current >= maxfd) maxfd = *current + 1;
FD_SET(*current, fdset);
}
return maxfd;
}
int * array_add (int * array, unsigned length, int new) {
array = realloc(array, sizeof(int) * (length + 1));
array[length] = new;
return array;
}
int * array_remove (int * array, unsigned length, int item) {
if (!length) {
free(array);
return NULL;
}
unsigned position;
for (position = 0; position < length; position ++)
if (array[position] == item) break;
if (position >= length) return array;
array[position] = array[-- length];
return realloc(array, sizeof(int) * length);
}
void send_policy_info (int destsocket, void * data, unsigned length) {
char * pending = data;
unsigned size = length;
int status, retries = 0;
while (size) {
status = send(destsocket, pending, size, MSG_NOSIGNAL);
if (status >= 0) {
retries = 0;
size -= status;
pending += status;
continue;
}
if (errno == EPIPE) return;
retries ++;
if (retries >= 3) return;
}
}
Comments