Skip to content

Instantly share code, notes, and snippets.

@How2Compute
Created March 29, 2017 16:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save How2Compute/7bb91ff7b71e31ccb000b3a18217c675 to your computer and use it in GitHub Desktop.
Save How2Compute/7bb91ff7b71e31ccb000b3a18217c675 to your computer and use it in GitHub Desktop.
/*
* Login server for WildCard
* Alpha Version 0.0.1
* Written by Alpha-V
*/
// Standard includes
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
// Networking specific includes
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <signal.h>
// MySQL
#include <mysql.h>
// Multithreading
#include <pthread.h>
#include <semaphore.h>
// Crypto
#include <openssl/sha.h>
#include <openssl/rand.h>
#include <openssl/md5.h>
// Parameters
#define CONNECTIONBACKLOG 5
#define MAXUSERS 5
// Function Prototpyes
void sigchld_handler(int s);
void *get_in_addr(struct sockaddr *sa);
int parse(char *source, char **username, char **password);
int LoginUser(MYSQL *connection, MYSQL_STMT *stmt_connection, char *username, char *password, /* OUT */char **sessionID);
void MySQLQueryFail_Handler(MYSQL *connection);
// Just here for now
int tokencounter;
int main(int argc, char *argv[])
{
// Check if the porgram was run with the correct command line arguements
if(argc != 2)
{
fprintf(stderr, "Usage: %s [port]\n", argv[0]);
return -1;
}
fprintf(stdout, "Wildcard Server Starting Up!\n");
fprintf(stdout, "Wildcard Server Version: %s running on port %s\nMySQL Version: %s\n", "0.0.1", argv[1], mysql_get_client_info());
tokencounter = 0;
struct addrinfo hints, *res;
struct sigaction sa;
struct sockaddr_storage cli_addr;
socklen_t addr_len;
char s[INET6_ADDRSTRLEN];
int sock_fd, cli_fd;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
// The MySQL *object*
MYSQL *databasecon;
MYSQL_STMT *stmt_databasecon;
/*
* MySQL Login data!
*/
char *mysqluser = "root";
char *mysqlpasswd = "mysql";
// Attempt to initialize the the MySQL object
if((databasecon = mysql_init(NULL)) == NULL)
{
fprintf(stderr, "Error initializing database connection: %s\n", mysql_error(databasecon));
return -20;
}
// Connect to the MySQL database
if(mysql_real_connect(databasecon, "localhost", mysqluser, mysqlpasswd, "Accounts", 0, NULL, 0) == NULL)
{
fprintf(stderr, "Error connecting to database: %s\n", mysql_error(databasecon));
mysql_close(databasecon);
return -21;
}
if((stmt_databasecon = mysql_stmt_init(databasecon)) == NULL)
{
fprintf(stderr, "Error creating stmt object: %s\n", mysql_error(databasecon));
mysql_close(databasecon);
return -22;
}
if(getaddrinfo(NULL, argv[1], &hints, &res) != 0)
{
perror("Error getting server address info");
return -10;
}
else
{
if((sock_fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1)
{
perror("Error creating socket");
return -11;
}
else
{
if(bind(sock_fd, res->ai_addr, res->ai_addrlen) == -1)
{
perror("Error binding socket");
close(sock_fd);
return -12;
}
else
{
if(listen(sock_fd, CONNECTIONBACKLOG) == -1)
{
perror("Error starting listener");
close(sock_fd);
return -13;
}
else
{
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if(sigaction(SIGCHLD, &sa, NULL) == -1)
{
perror("Error working with sigaction");
return -14;
}
while(1)
{
addr_len = sizeof(cli_addr);
cli_fd = accept(sock_fd, (struct sockaddr *)&cli_addr, &addr_len);
if(cli_fd == -1)
{
perror("Error accepting connection");
}
inet_ntop(cli_addr.ss_family, get_in_addr((struct sockaddr *)&cli_addr), s, sizeof(s));
fprintf(stdout, "Recieved connection from: %s\n", s);
if(!fork())
{
// TODO evaluate if this is indeed a safe/the best way to do it!
while(1)
{
int buffsize = sizeof(char) * 1024;
char *recvbuff = malloc(buffsize);
int bytesRecieved = 0;
if((bytesRecieved = recv(cli_fd, recvbuff, buffsize, 0)) == -1)
{
perror("Error recieving data");
// Close the socket; The client shall have to open a new one for a new request
break;
}
else
{
// Copy it to a \0 terminated string
char *buff = malloc((bytesRecieved + 1) * sizeof(char));
// Copy all of the read characters
for(int i = 0; i < bytesRecieved; i++)
{
buff[i] = recvbuff[i];
}
// Null terminate the string (to avoid buffer overflow)
buff[bytesRecieved] = '\0';
//printf("Recieved (%i bytes): %s\n", bytesRecieved, buff);
char *username;
char *password;
char *token;
if(parse(buff, &username, &password))
{
if(LoginUser(databasecon, stmt_databasecon, username, password, &token))
{
// The user successfully logged in! TODO: return token too
char *returnmsg = malloc(strlen("OKE_") + strlen(token) + 1);
sprintf(returnmsg, "OKE_%s", token);
//char *returnmsg = "OKE_123\0";
if(send(cli_fd, returnmsg, strlen(returnmsg), 0) == -1)
{
perror("Error sending login confimration");
}
// If it successfully sent off the message, exit the loop
break;
}
else
{
// The users credentials whre invalid
if(send(cli_fd, "NOK", sizeof("NOK"), 0) == -1)
{
perror("Error sending login error");
}
// If it successfully sent off the message, exit the loop
break;
}
}
else
{
// The parse failed so return NOK
if(send(cli_fd, "NOK", sizeof("NOK"), 0) == -1)
{
perror("Error sending parse error");
}
// If it successfully sent off the message, exit the loop
break;
}
}
}
// Close the connection to the client
close(cli_fd);
}
}
}
}
}
}
}
void sigchld_handler(int s)
{
int saved_errno = errno;
while(waitpid(-1, NULL, WNOHANG) > 0);
errno = saved_errno;
}
void *get_in_addr(struct sockaddr *sa)
{
if(sa->sa_family == AF_INET)
{
return &(((struct sockaddr_in *)sa)->sin_addr);
}
else
{
return &(((struct sockaddr_in6 *)sa)->sin6_addr);
}
}
int parse(char *source, char **username, char **password)
{
// Init a variable to 0 to keep track of where in the parse we are
int stage = 0;
// Make a buff to store part of the parse
char buff[1024];
// To avoid overflow
int buffused = 0;
for(int i = 0, n = strlen(source); i < n; i++)
{
// Should it switch stage? AKA did this part of the parse finish?
if(source[i] == '\n')
{
if(stage == 0)
{
// Allocate the right ammount of memory to username
*username = malloc((buffused + 1) * sizeof(char));
// Copy the buffer to the username (cant use strcpy as buff isn't null terminated)
for(int i = 0; i < buffused; i++)
{
(*username)[i] = buff[i];
}
// Null-terminate the string
(*username)[buffused] = '\0';
}
else if(stage == 1)
{
// Allocate the right ammount of memory to username
*password = malloc((buffused + 1) * sizeof(char));
// Copy the buffer to the password (cant use strcpy as buff isn't null terminated)
for(int i = 0; i < buffused; i++)
{
(*password)[i] = buff[i];
}
// Null-terminate the string
(*password)[buffused] = '\0';
}
else
{
// Something didn't go quite right!
stage++;
printf("Error while parsing!!!\n");
break;
}
// Reset the buffer that has been used and increase the stage #
buffused = 0;
stage++;
}
else
{
// Copy the current character to the buffer and increment the buffer size
buff[buffused] = source[i];
buffused++;
}
}
// Was the parse valid? (where there too many or too little stages? If so it failed)
if(stage != 1)
{
return 0;
}
// Does it still need to process the password?
else if(stage == 1)
{
// Allocate the right ammount of memory to username
*password = malloc((buffused + 1) * sizeof(char));
// Copy the buffer to the password (cant use strcpy as buff isn't null terminated)
for(int i = 0; i < buffused; i++)
{
(*password)[i] = buff[i];
}
// Null-terminate the string
(*password)[buffused] = '\0';
}
// Otherwise assume everything went fine
return 1;
}
void MySQLQueryFail_Handler(MYSQL *connection)
{
fprintf(stderr, "Error while executing query: %s\n", mysql_error(connection));
mysql_close(connection);
exit(-23);
}
int LoginUser(MYSQL *connection, MYSQL_STMT *stmt_connection, char *username, char *password, /* OUT */char **sessionID)
{
/* TODO fix this
// TODO make this a prepared statement!!! And use the the below escape real etc
// mysql_real_escape_string(username);
char *stmtquery = malloc(strlen("SELECT * FROM PlayerAccounts WHERE UserName = \"?\"") * sizeof(char) + strlen(username) * sizeof(char) + sizeof(char));
sprintf(stmtquery, "SELECT * FROM PlayerAccounts WHERE UserName = \"?\"");
unsigned long length = strlen(stmtquery);
if(mysql_stmt_prepare(stmt_connection, stmtquery, length) != 0)
{
fprintf(stderr, "Error preparing prepared statement: %s\n", mysql_stmt_error(stmt_connection));
}
if(mysql_stmt_param_count(stmt_connection) != 1)
{
fprintf(stderr, "Params: %lu\n", mysql_stmt_param_count(stmt_connection));
// Too many or too little parameters setup!
//fprintf(stderr, "Error preparing prepared statement: Invalid ammount of ?'s in statement!\n");
}
// Setup the bind params
MYSQL_BIND queryVars[1];
memset(&queryVars, 0, sizeof(queryVars));
unsigned long strlength = strlen(username);
queryVars[1].buffer_type = MYSQL_TYPE_STRING;
queryVars[1].buffer = username;
queryVars[1].buffer_length = strlen(username);
queryVars[1].is_null = 0;
queryVars[1].length = &strlength;
exit(-100);
*/
// TODO replace this!!! Buffer overflow!!!
char *tokenbuffer = malloc(16384);
struct tm *tm;
time_t t;
char str_time[1024];
t = time(NULL);
tm = localtime(&t);
strftime(str_time, sizeof(str_time), "%H %M %S", tm);
tokencounter++;
sprintf(tokenbuffer, "%s%s%i%s%s%s", "@)^NKVDSLfdsf@%$FS", username, tokencounter, ",.,.kjsFDASgfn6349hj", str_time, "F**DSGSG(YSY#*@(&R@&#$#");
printf("Created token!");
unsigned char digest[16];
const char* string = tokenbuffer;
MD5_CTX context;
MD5_Init(&context);
MD5_Update(&context, string, strlen(string));
MD5_Final(digest, &context);
char md5string[33];
for(int i = 0; i < 16; ++i)
sprintf(&md5string[i*2], "%02x", (unsigned int)digest[i]);
// TODO replace the * with only the required stuff
// Create a variable to store the query and format the query
char *query = malloc(strlen("SELECT * FROM PlayerAccounts WHERE UserName = \"\"`") * sizeof(char) + strlen(username) * sizeof(char) + sizeof(char));
sprintf(query, "SELECT * FROM PlayerAccounts WHERE UserName = \"%s\"", username);
// Look the user up in the database
if(mysql_query(connection, query))
{
MySQLQueryFail_Handler(connection);
}
MYSQL_RES *entries = mysql_store_result(connection);
if(entries == NULL)
{
MySQLQueryFail_Handler(connection);
}
int numFields = mysql_num_fields(entries);
MYSQL_ROW entry;
while((entry = mysql_fetch_row(entries)))
{
// Are the passwords different sizes?
if(strlen(entry[2]) < strlen(password))
{
// Then it can't be the correct password.
return 0;
}
// entry[2] is the password row. TODO make this encrypted/hashed
if(strncmp(entry[2], password, strlen(entry[2])) == 0)
{
// The user login was correct.
// Insert the random token
char *query2 = malloc(strlen("UPDATE PlayerAccounts SET SessionID=\"\" WHERE UserName = \"\"") * sizeof(char) + strlen(username) * sizeof(char) + strlen(username) * sizeof(char) + strlen(md5string) * sizeof(char) + sizeof(char));
sprintf(query2, "UPDATE PlayerAccounts SET SessionID=\"%s\" WHERE UserName = \"%s\"", md5string, username);
// Attempt to insert
// Look the user up in the database
if(mysql_query(connection, query2))
{
MySQLQueryFail_Handler(connection);
}
*sessionID = md5string;
return 1;
}
else
{
// The users password was incorrect.
// But if there are more records for this user? Continue through those.
}
}
// The username was not found!
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment