I have done some work on opening a socket and waiting for a connection. This can be read line by line and I have started a request struct that it will accept. Also started on some docs. Not much is yet working. I am going to start learning µnit for unit tests: https://nemequ.github.io/munit/master
parent
4e17e706fa
commit
f48a110429
15 changed files with 599 additions and 1 deletions
@ -0,0 +1,28 @@ |
||||
# This was stolen from here: https://avikdas.com/2019/12/16/makefiles-for-c-cpp-projects.html
|
||||
|
||||
CFILES = $(wildcard src/*.c)
|
||||
OBJFILES = $(CFILES:.c=.o)
|
||||
OUT = proxy
|
||||
CFLAGS = -Wall
|
||||
LDLIBS = -lsqlite3
|
||||
CC = gcc
|
||||
|
||||
|
||||
.PHONY: default |
||||
default: $(OUT) |
||||
|
||||
|
||||
.PHONY: run |
||||
run: $(OUT) |
||||
./$(OUT)
|
||||
|
||||
$(OUT): $(OBJFILES) |
||||
$(CC) -o $@ $^ $(LDLIBS)
|
||||
|
||||
%.o: %.c |
||||
$(CC) $(CFLAGS) -c -o $@ $^
|
||||
|
||||
|
||||
.PHONY: clean |
||||
clean: |
||||
rm -f $(OBJFILES) $(OUT)
|
@ -1,6 +1,7 @@ |
||||
# Intercepting Proxy in C |
||||
# YAIP |
||||
|
||||
This will hopefully one day be an intercepting proxy written in c. I am using it |
||||
as a way to learn rather than as a tool I expect other people to use. However, |
||||
if you wish to, you are welcome to use this. |
||||
|
||||
Check laws in your local area, this tool should only be used for legal purposes. |
||||
|
@ -0,0 +1,79 @@ |
||||
# Config |
||||
|
||||
There will be 2 config files available. One at |
||||
|
||||
$XDG_CONFIG_HOME/yaip/proxy.conf and one at proxy.conf in the directory that the |
||||
tool is run from. Either can be overwritten with command line flags. |
||||
|
||||
The config file is (currently), very simple. Empty lines are ignored, as are |
||||
lines that start with a # symbol. This is useful if you wish to make comments |
||||
about configuration choices. |
||||
|
||||
The file in $XDG_CONFIG_HOME is parsed first, followed by the file in the |
||||
current working directory meaning that the config file in the current directory |
||||
will take precedence. |
||||
|
||||
To set a configuration option, the line should have the configuration option |
||||
followed by a colon, an optional space and the value it should be set to. |
||||
|
||||
For example: |
||||
|
||||
``` |
||||
port: 8080 |
||||
``` |
||||
|
||||
All options may also be set via a command line switch -c or --config followed by the name |
||||
and the value. |
||||
|
||||
For example: |
||||
|
||||
``` |
||||
yaip --config port 8080 |
||||
``` |
||||
|
||||
|
||||
## Available Options |
||||
|
||||
### port |
||||
|
||||
The listening port. |
||||
|
||||
Default: |
||||
|
||||
``` |
||||
port: 8080 |
||||
``` |
||||
|
||||
### database |
||||
|
||||
The database that requests and responses should be stored in. |
||||
|
||||
Default: |
||||
|
||||
``` |
||||
database: proxy.sqlite |
||||
``` |
||||
|
||||
### localConfig |
||||
|
||||
The name of the local config file: |
||||
|
||||
Default: |
||||
|
||||
``` |
||||
localConfig: proxy.conf |
||||
``` |
||||
|
||||
### userConfig |
||||
|
||||
The name of the user config file: |
||||
|
||||
Default: |
||||
|
||||
``` |
||||
$XDG_CONFIG_HOME/yaip/proxy.conf |
||||
``` |
||||
|
||||
**Note:** It only makes sense to change this as a command line argument as it is |
||||
the first config file that is sourced. |
||||
|
@ -0,0 +1,28 @@ |
||||
# Plugins |
||||
|
||||
**Note:** This is not implemented yet - it is mearly my plan. |
||||
|
||||
The tool should be able to load plugins. These should be stand alone scripts / |
||||
executables that YAIP can call using `system()` or similar. |
||||
|
||||
I will use a hook / filter type system. There will be different things that |
||||
plugins can do. The order in which plugins run will be dictated in the config |
||||
file or via an API if already running. The API still needs to be thought out. |
||||
|
||||
## Intercept Plugins |
||||
|
||||
Plugins will be able to intercept requests / responses. This will be blocking. |
||||
I haven't decided yet if these plugins will run on stdin or a temporary file. |
||||
|
||||
I would prefer stdin / stdout although I think that would make it difficult for |
||||
a plugin to launch vim and have it be interactive. I'll have to do some trial |
||||
and error. |
||||
|
||||
## Analysis Plugins |
||||
|
||||
These plugins will hopefully be able to run asynchronously without intercepting |
||||
requests / responses. The idea for this is that the plugins could check for |
||||
misconfigured headers or sql statements or whatever. These won't be able to |
||||
change what the server / browser receives. |
||||
|
||||
|
@ -0,0 +1,64 @@ |
||||
#include "config.h" |
||||
|
||||
/*
|
||||
* Checks if the given path exists by calling stat(). |
||||
* |
||||
*/ |
||||
static bool path_exists(const char *path) { |
||||
struct stat buf; |
||||
return (stat(path, &buf) == 0); |
||||
} |
||||
|
||||
/*
|
||||
* This function resolves ~ in pathnames. |
||||
*/ |
||||
static char* resolveTilde(const char *path) { |
||||
static glob_t globbuf; |
||||
|
||||
int res = glob(path, GLOB_TILDE, NULL, &globbuf); |
||||
/* no match, or many wildcard matches are bad */ |
||||
if (res == GLOB_NOMATCH) |
||||
return strdup(path); |
||||
else if (res != 0) { |
||||
printf("glob() failed\n"); |
||||
return NULL; |
||||
} else { |
||||
return strdup(globbuf.gl_pathv[0]); |
||||
} |
||||
globfree(&globbuf); |
||||
} |
||||
|
||||
/*
|
||||
* Returns a pointer to a config object containing defaults |
||||
*/ |
||||
Config* configDefaults(){ |
||||
Config *conf = malloc( sizeof( Config ) ); |
||||
conf->database = "proxy.sqlite"; |
||||
conf->port = 8080; |
||||
conf->localConfig = "proxy.conf"; |
||||
conf->userConfig = getDefaultUserConfigLoc(); |
||||
|
||||
return conf; |
||||
} |
||||
|
||||
char* getConfigDir(){ |
||||
char *xdg_config_home; |
||||
if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) |
||||
xdg_config_home = resolveTilde("~/.config"); |
||||
|
||||
return xdg_config_home; |
||||
} |
||||
|
||||
char* getDefaultUserConfigLoc(){ |
||||
char *configDir = getConfigDir(); |
||||
char *configFile[strlen(configDir) + 11]; |
||||
|
||||
memset(configFile, '\0', strlen(configDir) + 11); |
||||
|
||||
strcpy( configFile, configDir ); |
||||
|
||||
strcat( configFile, configDir ); |
||||
|
||||
return configFile; |
||||
} |
||||
|
@ -0,0 +1,24 @@ |
||||
#ifndef CONFIG_H |
||||
#define CONFIG_H |
||||
|
||||
#include <glob.h> |
||||
#include <stdbool.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <sys/stat.h> |
||||
|
||||
typedef struct { |
||||
char *database; |
||||
char *localConfig; // Project Specific
|
||||
char *userConfig; // User Specific
|
||||
unsigned int port; |
||||
} Config; |
||||
|
||||
static bool path_exists(const char *path); |
||||
static char* resolveTilde(const char *path); |
||||
Config* configDefaults(); |
||||
char* getConfigDir(); |
||||
char* getDefaultUserConfigLoc(); |
||||
|
||||
#endif /* ifndef CONFIG_H */ |
@ -0,0 +1,46 @@ |
||||
#include "database.h" |
||||
|
||||
sqlite3 *db_open(char *file){ |
||||
sqlite3 *db; |
||||
/* Open database */ |
||||
int rc = sqlite3_open(file, &db); |
||||
|
||||
if( rc ) { |
||||
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); |
||||
return NULL; |
||||
} else { |
||||
fprintf(stderr, "Opened database successfully\n"); |
||||
return db; |
||||
} |
||||
} |
||||
|
||||
bool db_file_exists(char *file){ |
||||
FILE *fp; |
||||
|
||||
if ( (fp = fopen(file, "r")) != NULL) { |
||||
fclose(fp); |
||||
return true; |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
bool db_create(char *file){ |
||||
FILE *fp = fopen(file, "a"); |
||||
if(fp == NULL) { |
||||
printf("file can't be opened\n"); |
||||
return false; |
||||
} |
||||
fclose(fp); |
||||
|
||||
sqlite3 *db = db_open(file); |
||||
|
||||
// Do stuff here to create the tables.
|
||||
// Will probably need Requests and Responses - maybe others
|
||||
|
||||
|
||||
sqlite3_close(db); |
||||
|
||||
return true; |
||||
} |
||||
|
@ -0,0 +1,13 @@ |
||||
#ifndef DATABASE_H |
||||
#define DATABASE_H |
||||
|
||||
#include <stdio.h> |
||||
#include <sqlite3.h> |
||||
#include <stdbool.h> |
||||
|
||||
sqlite3 *db_open(char *file); |
||||
bool db_file_exists(char *file); |
||||
//Returns true if the file was successfully created, false otherwise
|
||||
bool db_create(char *file); |
||||
|
||||
#endif /* ifndef LOG_H */ |
@ -0,0 +1,76 @@ |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
|
||||
#include "database.h" |
||||
#include "proxy.h" |
||||
#include "config.h" |
||||
|
||||
#define PACKAGE_NAME "WICTP" |
||||
#define DEFAULT_DATABASE "data.sqlite" |
||||
#define DEFAULT_CONFIG "config.json" |
||||
#define DEFAULT_PORT 8080 |
||||
|
||||
unsigned int validatePortNumber(unsigned int portNumber){ |
||||
// Given that we are dealing with unsined ints, we don't need to worry about
|
||||
// checking for values less than 0
|
||||
if ( portNumber > 65535 ){ |
||||
printf( "Port %i is invalid, using default of %i", portNumber, DEFAULT_PORT ); |
||||
return DEFAULT_PORT; |
||||
} |
||||
return portNumber; |
||||
} |
||||
|
||||
void printHelp(){ |
||||
printf("Usage: %s [options]\n", PACKAGE_NAME); |
||||
printf( "Options are:\n" ); |
||||
printf( "\t --database DATABASE A sqlite3 database file (Defatult: %s)\n", DEFAULT_DATABASE ); |
||||
printf( "\t --config CONFIG A config file (Default: %s)\n", DEFAULT_CONFIG ); |
||||
printf( "\t --port PORT The listening port (Default: %i)\n", DEFAULT_PORT ); |
||||
printf( "\t --help Display version information.\n"); |
||||
} |
||||
|
||||
int main(int argc, char**argv){ |
||||
char *database = DEFAULT_DATABASE; |
||||
char *config = DEFAULT_CONFIG; |
||||
unsigned int port = DEFAULT_PORT; |
||||
|
||||
Config *defaultconfig = configDefaults(); |
||||
|
||||
|
||||
for ( unsigned int i = 1; i < argc; i++ ){ |
||||
if ( strcmp( argv[i], "--database" ) == 0 ){ |
||||
if ( i + 1 < argc ){ |
||||
database = argv[++i]; |
||||
} else { |
||||
printf("--database requires a positional argument\n"); |
||||
} |
||||
} else if ( strcmp( argv[i], "--config" ) == 0 ){ |
||||
if ( i + 1 < argc ){ |
||||
config = argv[++i]; |
||||
} else { |
||||
printf("--config requires a positional argument\n"); |
||||
} |
||||
} else if ( strcmp( argv[i], "--port" ) == 0 ){ |
||||
if ( i + 1 < argc ){ |
||||
port = validatePortNumber( atoi(argv[++i]) ); |
||||
} else { |
||||
printf("--port requires a positional argument\n"); |
||||
} |
||||
} else if ( strcmp( argv[i], "--help" ) == 0 ){ |
||||
printHelp(); |
||||
return 0; |
||||
} else { |
||||
printf("Unknown option %s\n", argv[i]); |
||||
} |
||||
} |
||||
|
||||
if ( !db_file_exists(database) ){ |
||||
printf("Creating DB"); |
||||
db_create(database); |
||||
} |
||||
|
||||
proxy_startListener(port); |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,42 @@ |
||||
#include "proxy.h" |
||||
|
||||
void proxy_startListener(unsigned int port){ |
||||
// https://www.geeksforgeeks.org/socket-programming-cc/
|
||||
int server_fd, new_socket; |
||||
struct sockaddr_in address; |
||||
int opt = 1; |
||||
int addrlen = sizeof(address); |
||||
char line[1024] = {0}; |
||||
|
||||
// Creating socket file descriptor
|
||||
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { |
||||
perror("socket failed"); |
||||
return ; |
||||
} |
||||
|
||||
address.sin_family = AF_INET; |
||||
address.sin_addr.s_addr = htonl(INADDR_ANY); |
||||
address.sin_port = htons( port ); |
||||
|
||||
// Forcefully attaching socket to the port 8080
|
||||
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) { |
||||
perror("bind failed"); |
||||
return ; |
||||
} |
||||
|
||||
if (listen(server_fd, 3) < 0) { |
||||
perror("listen"); |
||||
return ; |
||||
} |
||||
while ( true ){ |
||||
printf("Listening\n"); |
||||
if ((new_socket = accept(server_fd, (struct sockaddr *)&address,
|
||||
(socklen_t*)&addrlen))<0) { |
||||
perror("accept"); |
||||
return ; |
||||
} |
||||
|
||||
Request *request = newRequestFromSocket(new_socket); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,16 @@ |
||||
#ifndef PROXY_H |
||||
#define PROXY_H |
||||
|
||||
#include <unistd.h> |
||||
#include <stdio.h> |
||||
#include <sys/socket.h> |
||||
#include <stdlib.h> |
||||
#include <netinet/in.h> |
||||
#include <string.h> |
||||
#include <stdbool.h> |
||||
|
||||
#include "request.h" |
||||
|
||||
void proxy_startListener( unsigned int port ); |
||||
|
||||
#endif /* ifndef PROXY_H */ |
@ -0,0 +1,50 @@ |
||||
// https://man7.org/tlpi/code/online/dist/sockets/read_line.c.html
|
||||
#include "readline.h" |
||||
|
||||
/* Read characters from 'fd' until a newline is encountered. If a newline
|
||||
character is not encountered in the first (n - 1) bytes, then the excess |
||||
characters are discarded. The returned string placed in 'buf' is |
||||
null-terminated and includes the newline character if it was read in the |
||||
first (n - 1) bytes. The function return value is the number of bytes |
||||
placed in buffer (which includes the newline character if encountered, |
||||
but excludes the terminating null byte). */ |
||||
ssize_t fdReadLine(int fd, void *buffer, size_t n) { |
||||
ssize_t numRead; /* # of bytes fetched by last read() */ |
||||
size_t totRead; /* Total bytes read so far */ |
||||
char *buf; |
||||
char ch; |
||||
if (n <= 0 || buffer == NULL) { |
||||
errno = EINVAL; |
||||
return -1; |
||||
} |
||||
buf = buffer; /* No pointer arithmetic on "void *" */ |
||||
totRead = 0; |
||||
for (;;) { |
||||
numRead = read(fd, &ch, 1); |
||||
if (numRead == -1) { |
||||
if (errno == EINTR) /* Interrupted --> restart read() */ |
||||
continue; |
||||
else |
||||
return -1; /* Some other error */ |
||||
|
||||
} else if (numRead == 0) { /* EOF */ |
||||
if (totRead == 0) /* No bytes read; return 0 */ |
||||
return 0; |
||||
else /* Some bytes read; add '\0' */ |
||||
break; |
||||
|
||||
} else { /* 'numRead' must be 1 if we get here */ |
||||
if (totRead < n - 1) { /* Discard > (n - 1) bytes */ |
||||
totRead++; |
||||
*buf++ = ch; |
||||
} |
||||
|
||||
if (ch == '\n') |
||||
break; |
||||
} |
||||
} |
||||
|
||||
*buf = '\0'; |
||||
return totRead; |
||||
} |
||||
|
@ -0,0 +1,9 @@ |
||||
#ifndef READLINE_H |
||||
#define READLINE_H |
||||
|
||||
#include <unistd.h> |
||||
#include <errno.h> |
||||
|
||||
ssize_t fdReadLine(int fd, void *buffer, size_t n); |
||||
|
||||
#endif /* ifndef READLINE_H */ |
@ -0,0 +1,69 @@ |
||||
#include "request.h" |
||||
|
||||
Header* newHeader(char *str){ |
||||
Header *header = malloc(sizeof(Header)); |
||||
memset(header, 0, sizeof(Header)); |
||||
|
||||
int position = -1; |
||||
|
||||
|
||||
for ( unsigned int i = 0; i < strlen(str); i++ ){ |
||||
if ( str[i] == ':' ){ |
||||
position = i; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if ( position == -1 ){ |
||||
printf("Header without colon. Not sure what to do\n"); |
||||
return NULL; |
||||
} |
||||
|
||||
|
||||
// We want to allocate 1 more than the length so we can have a \0 at the end
|
||||
header->name = malloc( sizeof(char) * ( position + 1 ) ); |
||||
memset(header->name, '\0', position+1); |
||||
strncpy( header->name, str, position ); |
||||
|
||||
for ( unsigned int i = position+1; i < strlen(str); i++ ){ |
||||
if ( str[i] == '\t' || str[i] == ' ' ) continue; |
||||
position = i; |
||||
break; |
||||
} |
||||
|
||||
|
||||
//Anything left is the value
|
||||
header->value = malloc( sizeof(char) * ( strlen(str) - position ) ); |
||||
memset(header->value, '\0', ( strlen(str) - position ) ); |
||||
strcpy( header->value, str + position ); |
||||
|
||||
|
||||
return header; |
||||
} |
||||
|
||||
Request* newRequest(){ |
||||
Request *request = malloc(sizeof(Request)); |
||||
memset(request, 0, sizeof(Request)); |
||||
return request; |
||||
} |
||||
|
||||
|
||||
Request* newRequestFromSocket(int socket){ |
||||
Request *req = newRequest(); |
||||
int valread; |
||||
char line[1024] = {0}; |
||||
valread = fdReadLine( socket , line, 1024); |
||||
char hello[] = "this is a test"; |
||||
|
||||
// The first line will give us some important information
|
||||
|
||||
//a length of 2 will indicate an empty line which will split the headers
|
||||
//from the body (if there is a body)
|
||||
while ( valread > 2 ){ |
||||
printf("%s",line ); |
||||
valread = fdReadLine( socket , line, 1024); |
||||
} |
||||
send(socket , hello , strlen(hello) , 0 ); |
||||
printf("Hello message sent\n"); |
||||
return req; |
||||
} |
@ -0,0 +1,53 @@ |
||||
#ifndef REQUEST_H |
||||
#define REQUEST_H |
||||
|
||||
#include <stdlib.h> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include "readline.h" |
||||
#include <netinet/in.h> |
||||
|
||||
typedef enum {HTTP,HTTPS} HTTPProtocol; |
||||
typedef enum {GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH} HTTPMethod; |
||||
|
||||
/*
|
||||
* A struct reperesenting an http header |
||||
*/ |
||||
typedef struct { |
||||
char *name; |
||||
// The spec doesn't specify a max size for headers, but nginx doesn't accept headers bigger than 4096 so that's probably ok for us
|
||||
char *value; |
||||
} Header; |
||||
|
||||
/*
|
||||
* A linked list wrapper around the headers |
||||
*/ |
||||
typedef struct HeaderList HeaderList; |
||||
struct HeaderList { |
||||
Header header; |
||||
HeaderList *next; |
||||
}; |
||||
|
||||
/*
|
||||
* A struct reperesenting an http request |
||||
*/ |
||||
typedef struct { |
||||
// Common versions are: 0.9, 1.0, 1.1, 2.0
|
||||
double version; |
||||
HTTPMethod method; |
||||
HTTPProtocol protocol; |
||||
char *host; |
||||
char *path; |
||||
HeaderList *headers; |
||||
char *queryString; |
||||
char *body; |
||||
} Request; |
||||
|
||||
Header* newHeader(); |
||||
Request* newRequest(); |
||||
Request* newRequestFromSocket(int socket); |
||||
void* addHeader(Request *req); |
||||
|
||||
|
||||
|
||||
#endif /* ifndef REQUEST_H */ |
Loading…
Reference in new issue