From: John Hodge Date: Sat, 20 Nov 2010 15:09:24 +0000 (+0800) Subject: Restructured code into src/ X-Git-Url: https://git.ucc.asn.au/?p=tpg%2Fopendispense2.git;a=commitdiff_plain;h=a189b749e46348379d2271eb7c51ca1f7334cbda Restructured code into src/ - Added cokebank library > Also changed API slightly - Implemented more of the protocol in server.c - Coke handler started - Other misc changes --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fec2672 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.[od] +*~ +dispsrv diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..55bf2dd --- /dev/null +++ b/src/Makefile @@ -0,0 +1,11 @@ + +.PHONY: all clean + +all: + @make -C cokebank all + @make -C server all + +clean: + @make -C cokebank clean + @make -C server clean + diff --git a/src/cokebank/Makefile b/src/cokebank/Makefile new file mode 100644 index 0000000..8e6ea60 --- /dev/null +++ b/src/cokebank/Makefile @@ -0,0 +1,20 @@ + +BIN := ../../cokebank.so +OBJ := main.o + +CPPFLAGS := +CFLAGS := -Wall -Werror -g -fPIC +LDFLAGS := -shared -Wl,-soname,cokebank.so + +.PHONY: all clean + +all: $(BIN) + +clean: + $(RM) $(BIN) $(OBJ) + +$(BIN): $(OBJ) + $(CC) $(LDFLAGS) -o $(BIN) $(OBJ) + +%.o: %.c + $(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS) diff --git a/src/cokebank/main.c b/src/cokebank/main.c new file mode 100644 index 0000000..d83958f --- /dev/null +++ b/src/cokebank/main.c @@ -0,0 +1,77 @@ +/* + * OpenDispense 2 + * UCC (University [of WA] Computer Club) Electronic Accounting System + * + * cokebank.c - Coke-Bank management + * + * This file is licenced under the 3-clause BSD Licence. See the file COPYING + * for full details. + * + * TODO: Make this a Dynamic Library and load it at runtime + */ +#include +#include + +// === PROTOTYPES === +void Init_Cokebank(void); + int Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason); + int GetBalance(int User); +char *GetUserName(int User); + int GetUserID(const char *Username); + int GetUserAuth(const char *Username, const char *Password); + +// === CODE === +/** + * \brief Load the cokebank database + */ +void Init_Cokebank(void) +{ + +} + +/** + * \brief Transfers money from one user to another + * \param SourceUser Source user + * \param DestUser Destination user + * \param Ammount Ammount of cents to move from \a SourceUser to \a DestUser + * \param Reason Reason for the transfer (essentially a comment) + * \return Boolean failure + */ +int Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason); +{ + return 0; +} + +/** + * \brief Get the balance of the passed user + */ +int GetBalance(int User) +{ + return 0; +} + +/** + * \brief Return the name the passed user + */ +char *GetUserName(int User) +{ + return NULL; +} + +/** + * \brief Get the User ID of the named user + */ +int GetUserID(const char *Username) +{ + return -1; +} + +/** + * \brief Authenticate a user + * \return User ID, or -1 if authentication failed + */ +int GetUserAuth(const char *Username, const char *Password) +{ + return -1; +} + diff --git a/src/server/Makefile b/src/server/Makefile new file mode 100644 index 0000000..34e55f6 --- /dev/null +++ b/src/server/Makefile @@ -0,0 +1,22 @@ +# OpenDispense 2 +# + +OBJ := main.o server.o logging.o +OBJ += dispense.o itemdb.o +OBJ += handler_coke.o +BIN := ../../dispsrv + +LINKFLAGS := -g ../../cokebank.so +CPPFLAGS := +CFLAGS := -Wall -g + +.PHONY: all clean + +all: $(BIN) + +clean: + $(RM) $(BIN) $(OBJ) + +$(BIN): $(OBJ) + $(CC) -o $(BIN) $(LINKFLAGS) $(OBJ) + diff --git a/src/server/common.h b/src/server/common.h new file mode 100644 index 0000000..9105db1 --- /dev/null +++ b/src/server/common.h @@ -0,0 +1,76 @@ +/* + * OpenDispense2 + * + * This code is published under the terms of the Acess licence. + * See the file COPYING for details. + * + * common.h - Core Header + */ +#ifndef _COMMON_H_ +#define _COMMON_H_ + +// === CONSTANTS === +#define DEFAULT_CONFIG_FILE "/etc/opendispense/main.cfg" +#define DEFAULT_ITEM_FILE "/etc/opendispense/items.cfg" + +// === HELPER MACROS === +#define _EXPSTR(x) #x +#define EXPSTR(x) _EXPSTR(x) + +// === STRUCTURES === +typedef struct sItem tItem; +typedef struct sUser tUser; +typedef struct sConfigItem tConfigItem; +typedef struct sHandler tHandler; + +struct sItem +{ + char *Name; //!< Display Name + int Price; //!< Price + + tHandler *Handler; //!< Handler for the item + short ID; //!< Item ID +}; + +struct sUser +{ + int ID; //!< User ID (LDAP ID) + int Balance; //!< Balance in cents + int Bytes; //!< Traffic Usage + char Name[]; //!< Username +}; + +struct sConfigItem +{ + char *Name; + char *Value; +}; + +struct sHandler +{ + char *Name; + int (*Init)(int NConfig, tConfigItem *Config); + int (*CanDispense)(int User, int ID); + int (*DoDispense)(int User, int ID); +}; + +// === GLOBALS === +extern tItem *gaItems; +extern int giNumItems; +extern tHandler *gaHandlers[]; +extern int giNumHandlers; +extern int giDebugLevel; + +// === FUNCTIONS === +// --- Logging --- +extern void Log_Error(const char *Format, ...); +extern void Log_Info(const char *Format, ...); + +// --- Cokebank Functions --- +extern int Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason); +extern int GetBalance(int User); +extern char *GetUserName(int User); +extern int GetUserID(const char *Username); +extern int GetUserAuth(const char *Username, const char *Password); + +#endif diff --git a/src/server/dispense.c b/src/server/dispense.c new file mode 100644 index 0000000..32e6647 --- /dev/null +++ b/src/server/dispense.c @@ -0,0 +1,58 @@ +/** + */ +#include "common.h" +#include + +// === CODE === +/** + * \brief Dispense an item for a user + * + * The core of the dispense system, I kinda like it :) + */ +int DispenseItem(int User, int Item) +{ + int ret; + tItem *item; + tHandler *handler; + char *username; + + // Sanity check please? + if(Item < 0 || Item >= giNumItems) + return -1; + + // Get item pointers + item = &gaItems[Item]; + handler = item->Handler; + + // Check if the dispense is possible + ret = handler->CanDispense( User, item->ID ); + if(!ret) return ret; + + // Subtract the balance + ret = AlterBalance( User, -item->Price ); + // What value should I use for this error? + // AlterBalance should return the final user balance + if(ret == 0) return 1; + + // Get username for debugging + username = GetUserName(User); + + // Actually do the dispense + ret = handler->DoDispense( User, item->ID ); + if(ret) { + Log_Error("Dispense failed after deducting cost (%s dispensing %s - %ic)", + username, item->Name, item->Price); + AlterBalance( User, item->Price ); + free( username ); + return 1; + } + + // And log that it happened + Log_Info("Dispensed %s (%i:%i) for %s [cost %i, balance %i cents]", + item->Name, handler->Name, item->ID, + username, item->Price, GetBalance(User) + ); + + free( username ); + return 0; +} diff --git a/src/server/handler_coke.c b/src/server/handler_coke.c new file mode 100644 index 0000000..f8ed168 --- /dev/null +++ b/src/server/handler_coke.c @@ -0,0 +1,87 @@ +/* + * OpenDispense 2 + * UCC (University [of WA] Computer Club) Electronic Accounting System + * + * handler_coke.c - Coke controller code + * + * This file is licenced under the 3-clause BSD Licence. See the file + * COPYING for full details. + */ +#include "common.h" +#include +#include +#include +#include +#include + +// === IMPORTS === + +// === PROTOTYPES === + int Coke_InitHandler(); + int Coke_CanDispense(int User, int Item); + int Coke_DoDispense(int User, int Item); + +// === GLOBALS === +tHandler gCoke_Handler = { + "coke", + Coke_InitHandler, + Coke_CanDispense, + Coke_DoDispense +}; +char *gsCoke_SerialPort = "/dev/ttyS0"; + int giCoke_SerialFD; +regex_t gCoke_StatusRegex; + +// == CODE === +int Coke_InitHandler() +{ + giCoke_SerialFD = open(gsCoke_SerialPort, O_RDWR); + regexc(&gCoke_StatusRegex, "^$", REG_EXTENDED); + return 0; +} + +int Coke_CanDispense(int User, int Item) +{ + char tmp[32]; + regmatch_t matches[4]; + + // Sanity please + if( Item < 0 || Item > 6 ) return -1; + + // Ask the coke machine + sprintf(tmp, "s%i", Item); + write(giCoke_SerialFD, tmp, 2); + + // Read the response + read(giCoke_SerialFD, tmp, sizeof(tmp)-1); + regexec(&gCoke_StatusRegex, tmp, sizeof(matches)/sizeof(matches[0]), matches); + + printf("s%i response '%s'\n", Item, tmp); + + return 0; +} + +/** + * \brief Actually do a dispense from the coke machine + */ +int Coke_DoDispense(int User, int Item) +{ + char tmp[32]; + + // Sanity please + if( Item < 0 || Item > 6 ) return -1; + + // Dispense + sprintf(tmp, "d%i", Item); + write(giCoke_SerialFD, tmp, 2); + + // Get status + read(giCoke_SerialFD, tmp, sizeof(tmp)-1); + regexec(&gCoke_StatusRegex, tmp, sizeof(matches)/sizeof(matches[0]), matches); + + printf("d%i response '%s'\n", Item, tmp); + + return 0; +} + + diff --git a/src/server/itemdb.c b/src/server/itemdb.c new file mode 100644 index 0000000..0cb932d --- /dev/null +++ b/src/server/itemdb.c @@ -0,0 +1,152 @@ +/* + * OpenDispense 2 + * UCC (University [of WA] Computer Club) Electronic Accounting System + * + * itemdb.c - Dispense Item Databse + * + * This file is licenced under the 3-clause BSD Licence. See the file COPYING + * for full details. + */ +#include +#include +#include +#include +#include "common.h" +#include + +// === IMPORTS === +extern tHandler gCoke_Handler; + +// === PROTOTYPES === +void Load_Itemlist(void); +char *trim(char *__str); + +// === GLOBALS === + int giNumItems = 0; +tItem *gaItems = NULL; +tHandler gPseudo_Handler = {Name:"pseudo"}; +tHandler *gaHandlers[] = {&gPseudo_Handler, &gCoke_Handler}; + int giNumHandlers = sizeof(gaHandlers)/sizeof(gaHandlers[0]); +char *gsItemListFile = DEFAULT_ITEM_FILE; + +// === CODE === +/** + * \brief Read the item list from disk + */ +void Load_Itemlist(void) +{ + FILE *fp = fopen(gsItemListFile, "r"); + char buffer[BUFSIZ]; + char *line; + int lineNum = 0; + int i; + regex_t regex; + regmatch_t matches[5]; + + i = regcomp(®ex, "^([a-zA-Z][a-zA-Z0-9]*)\\s+([0-9]+)\\s+([0-9]+)\\s+(.*)", REG_EXTENDED); + if( i ) + { + size_t len = regerror(i, ®ex, NULL, 0); + char *errorStr = malloc(len); + regerror(i, ®ex, errorStr, len); + fprintf(stderr, "Rexex compilation failed - %s\n", errorStr); + free(errorStr); + exit(-1); + } + + // Error check + if(!fp) { + fprintf(stderr, "Unable to open item file '%s'\n", gsItemListFile); + perror("Unable to open item file"); + } + + while( fgets(buffer, BUFSIZ, fp) ) + { + char *tmp; + char *type, *desc; + int num, price; + tHandler *handler; + + lineNum ++; + + // Remove comments + tmp = strchr(buffer, '#'); + if(tmp) *tmp = '\0'; + tmp = strchr(buffer, ';'); + if(tmp) *tmp = '\0'; + + // Trim whitespace + line = trim(buffer); + + if(strlen(line) == 0) continue; + + // Pass regex over line + if( (i = regexec(®ex, line, 5, matches, 0)) ) { + size_t len = regerror(i, ®ex, NULL, 0); + char *errorStr = malloc(len); + regerror(i, ®ex, errorStr, len); + fprintf(stderr, "Syntax error on line %i of item file '%s'\n%s", lineNum, gsItemListFile, errorStr); + free(errorStr); + exit(-1); + } + + // Read line data + type = line + matches[1].rm_so; line[ matches[1].rm_eo ] = '\0'; + num = atoi( line + matches[2].rm_so ); + price = atoi( line + matches[3].rm_so ); + desc = line + matches[4].rm_so; + + printf("Item '%s' - %i cents, %s:%i\n", desc, price, type, num); + + handler = NULL; + for( i = 0; i < giNumHandlers; i ++ ) + { + if( strcmp(type, gaHandlers[i]->Name) == 0 ) { + handler = gaHandlers[i]; + break; + } + } + + if( !handler ) { + fprintf(stderr, "Unknow item type '%s' on line %i (%s)\n", type, lineNum, desc); + continue ; + } + + for( i = 0; i < giNumItems; i ++ ) + { + if( gaItems[i].Handler != handler ) continue; + if( gaItems[i].ID != num ) continue; + + printf("Redefinition of %s:%i, updated\n", handler->Name, num); + gaItems[i].Price = price; + free(gaItems[i].Name); + gaItems[i].Name = strdup(desc); + break; + } + if( i < giNumItems ) continue; + + gaItems = realloc( gaItems, (giNumItems + 1)*sizeof(gaItems[0]) ); + gaItems[giNumItems].Handler = handler; + gaItems[giNumItems].ID = num; + gaItems[giNumItems].Price = price; + gaItems[giNumItems].Name = strdup(desc); + giNumItems ++; + } +} + +char *trim(char *__str) +{ + char *ret; + int i; + + while( isspace(*__str) ) + __str++; + ret = __str; + + i = strlen(ret); + while( i-- && isspace(__str[i]) ) { + __str[i] = '\0'; + } + + return ret; +} diff --git a/src/server/logging.c b/src/server/logging.c new file mode 100644 index 0000000..51cc8d2 --- /dev/null +++ b/src/server/logging.c @@ -0,0 +1,19 @@ +/* + * OpenDispense2 + * + * logging.c - Debug/Logging Routines + */ +#include +#include +#include "common.h" + +// === CODE == +void Log_Error(const char *Format, ...) +{ + +} + +void Log_Info(const char *Format, ...) +{ +} + diff --git a/src/server/main.c b/src/server/main.c new file mode 100644 index 0000000..e789b13 --- /dev/null +++ b/src/server/main.c @@ -0,0 +1,74 @@ +/* + * OpenDispense 2 + * UCC (University [of WA] Computer Club) Electronic Accounting System + * + * main.c - Initialisation Code + * + * This file is licenced under the 3-clause BSD Licence. See the file + * COPYING for full details. + */ +#include +#include +#include +#include "common.h" + +// === IMPORTS === +extern void Init_Cokebank(void); // cokebank.c +extern void Load_Itemlist(void); +extern void Server_Start(void); +extern int giServer_Port; +extern char* gsItemListFile; +extern char* gsCoke_SerialPort; + +// === GLOBALS === + int giDebugLevel = 0; + +// === CODE === +int main(int argc, char *argv[]) +{ + int i; + + // Parse Arguments + for( i = 1; i < argc; i++ ) + { + char *arg = argv[i]; + if( arg[0] == '-' && arg[1] != '-') + { + switch(arg[1]) + { + case 'p': + giServer_Port = atoi(argv[++i]); + break; + case 'd': + giDebugLevel = atoi(argv[++i]); + break; + default: + // Usage Error? + break; + } + } + else if( arg[0] == '-' && arg[1] == '-' ) { + if( strcmp(arg, "--itemsfile") == 0 ) { + gsItemListFile = argv[++i]; + } + if( strcmp(arg, "--cokeport") == 0 ) { + gsCoke_SerialPort = argv[++i]; + } + else { + // Usage error? + } + } + else { + // Usage Error? + } + } + + Init_Cokebank(); + + Load_Itemlist(); + + Server_Start(); + + return 0; +} + diff --git a/src/server/server.c b/src/server/server.c new file mode 100644 index 0000000..c9ab63e --- /dev/null +++ b/src/server/server.c @@ -0,0 +1,508 @@ +/* + * OpenDispense 2 + * UCC (University [of WA] Computer Club) Electronic Accounting System + * + * server.c - Client Server Code + * + * This file is licenced under the 3-clause BSD Licence. See the file + * COPYING for full details. + */ +#include +#include +#include "common.h" +#include +#include +#include +#include +#include + +#define MAX_CONNECTION_QUEUE 5 +#define INPUT_BUFFER_SIZE 256 + +#define HASH_TYPE SHA512 +#define HASH_LENGTH 64 + +#define MSG_STR_TOO_LONG "499 Command too long (limit "EXPSTR(INPUT_BUFFER_SIZE)")\n" + +// === TYPES === +typedef struct sClient +{ + int ID; // Client ID + + int bIsTrusted; // Is the connection from a trusted host/port + + char *Username; + char Salt[9]; + + int UID; + int bIsAuthed; +} tClient; + +// === PROTOTYPES === +void Server_Start(void); +void Server_HandleClient(int Socket, int bTrusted); +char *Server_ParseClientCommand(tClient *Client, char *CommandString); +// --- Commands --- +char *Server_Cmd_USER(tClient *Client, char *Args); +char *Server_Cmd_PASS(tClient *Client, char *Args); +char *Server_Cmd_AUTOAUTH(tClient *Client, char *Args); +char *Server_Cmd_ENUMITEMS(tClient *Client, char *Args); +char *Server_Cmd_ITEMINFO(tClient *Client, char *Args); +// --- Helpers --- +void HexBin(uint8_t *Dest, char *Src, int BufSize); + +// === GLOBALS === + int giServer_Port = 1020; + int giServer_NextClientID = 1; +// - Commands +struct sClientCommand { + char *Name; + char *(*Function)(tClient *Client, char *Arguments); +} gaServer_Commands[] = { + {"USER", Server_Cmd_USER}, + {"PASS", Server_Cmd_PASS}, + {"AUTOAUTH", Server_Cmd_AUTOAUTH}, + {"ENUM_ITEMS", Server_Cmd_ENUMITEMS}, + {"ITEM_INFO", Server_Cmd_ITEMINFO} +}; +#define NUM_COMMANDS (sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0])) + +// === CODE === +/** + * \brief Open listenting socket and serve connections + */ +void Server_Start(void) +{ + int server_socket, client_socket; + struct sockaddr_in server_addr, client_addr; + + // Create Server + server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if( server_socket < 0 ) { + fprintf(stderr, "ERROR: Unable to create server socket\n"); + return ; + } + + // Make listen address + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; // Internet Socket + server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Listen on all interfaces + server_addr.sin_port = htons(giServer_Port); // Port + + // Bind + if( bind(server_socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0 ) { + fprintf(stderr, "ERROR: Unable to bind to 0.0.0.0:%i\n", giServer_Port); + return ; + } + + // Listen + if( listen(server_socket, MAX_CONNECTION_QUEUE) < 0 ) { + fprintf(stderr, "ERROR: Unable to listen to socket\n"); + return ; + } + + printf("Listening on 0.0.0.0:%i\n", giServer_Port); + + for(;;) + { + uint len = sizeof(client_addr); + int bTrusted = 0; + + client_socket = accept(server_socket, (struct sockaddr *) &client_addr, &len); + if(client_socket < 0) { + fprintf(stderr, "ERROR: Unable to accept client connection\n"); + return ; + } + + if(giDebugLevel >= 2) { + char ipstr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &client_addr.sin_addr, ipstr, INET_ADDRSTRLEN); + printf("Client connection from %s:%i\n", + ipstr, ntohs(client_addr.sin_port)); + } + + // Trusted Connections + if( ntohs(client_addr.sin_port) < 1024 ) + { + // TODO: Make this runtime configurable + switch( ntohl( client_addr.sin_addr.s_addr ) ) + { + case 0x7F000001: // 127.0.0.1 localhost + //case 0x825E0D00: // 130.95.13.0 + case 0x825E0D12: // 130.95.13.18 mussel + case 0x825E0D17: // 130.95.13.23 martello + bTrusted = 1; + break; + default: + break; + } + } + + // TODO: Multithread this? + Server_HandleClient(client_socket, bTrusted); + + close(client_socket); + } +} + +/** + * \brief Reads from a client socket and parses the command strings + * \param Socket Client socket number/handle + * \param bTrusted Is the client trusted? + */ +void Server_HandleClient(int Socket, int bTrusted) +{ + char inbuf[INPUT_BUFFER_SIZE]; + char *buf = inbuf; + int remspace = INPUT_BUFFER_SIZE-1; + int bytes = -1; + tClient clientInfo = {0}; + + // Initialise Client info + clientInfo.ID = giServer_NextClientID ++; + clientInfo.bIsTrusted = bTrusted; + + // Read from client + /* + * Notes: + * - The `buf` and `remspace` variables allow a line to span several + * calls to recv(), if a line is not completed in one recv() call + * it is saved to the beginning of `inbuf` and `buf` is updated to + * the end of it. + */ + while( (bytes = recv(Socket, buf, remspace, 0)) > 0 ) + { + char *eol, *start; + buf[bytes] = '\0'; // Allow us to use stdlib string functions on it + + // Split by lines + start = inbuf; + while( (eol = strchr(start, '\n')) ) + { + char *ret; + *eol = '\0'; + ret = Server_ParseClientCommand(&clientInfo, start); + // `ret` is a string on the heap + send(Socket, ret, strlen(ret), 0); + free(ret); + start = eol + 1; + } + + // Check if there was an incomplete line + if( *start != '\0' ) { + int tailBytes = bytes - (start-buf); + // Roll back in buffer + memcpy(inbuf, start, tailBytes); + remspace -= tailBytes; + if(remspace == 0) { + send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0); + buf = inbuf; + remspace = INPUT_BUFFER_SIZE - 1; + } + } + else { + buf = inbuf; + remspace = INPUT_BUFFER_SIZE - 1; + } + } + + // Check for errors + if( bytes < 0 ) { + fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket); + return ; + } + + if(giDebugLevel >= 2) { + printf("Client %i: Disconnected\n", clientInfo.ID); + } +} + +/** + * \brief Parses a client command and calls the required helper function + * \param Client Pointer to client state structure + * \param CommandString Command from client (single line of the command) + * \return Heap String to return to the client + */ +char *Server_ParseClientCommand(tClient *Client, char *CommandString) +{ + char *space, *args; + int i; + + // Split at first space + space = strchr(CommandString, ' '); + if(space == NULL) { + args = NULL; + } + else { + *space = '\0'; + args = space + 1; + } + + // Find command + for( i = 0; i < NUM_COMMANDS; i++ ) + { + if(strcmp(CommandString, gaServer_Commands[i].Name) == 0) + return gaServer_Commands[i].Function(Client, args); + } + + return strdup("400 Unknown Command\n"); +} + +// --- +// Commands +// --- +/** + * \brief Set client username + * + * Usage: USER + */ +char *Server_Cmd_USER(tClient *Client, char *Args) +{ + char *ret; + + // Debug! + if( giDebugLevel ) + printf("Client %i authenticating as '%s'\n", Client->ID, Args); + + // Save username + if(Client->Username) + free(Client->Username); + Client->Username = strdup(Args); + + #if USE_SALT + // Create a salt (that changes if the username is changed) + // Yes, I know, I'm a little paranoid, but who isn't? + Client->Salt[0] = 0x21 + (rand()&0x3F); + Client->Salt[1] = 0x21 + (rand()&0x3F); + Client->Salt[2] = 0x21 + (rand()&0x3F); + Client->Salt[3] = 0x21 + (rand()&0x3F); + Client->Salt[4] = 0x21 + (rand()&0x3F); + Client->Salt[5] = 0x21 + (rand()&0x3F); + Client->Salt[6] = 0x21 + (rand()&0x3F); + Client->Salt[7] = 0x21 + (rand()&0x3F); + + // "100 Salt xxxxXXXX\n" + ret = strdup("100 SALT xxxxXXXX\n"); + sprintf(ret, "100 SALT %s\n", Client->Salt); + #else + ret = strdup("100 User Set\n"); + #endif + return ret; +} + +/** + * \brief Authenticate as a user + * + * Usage: PASS + */ +char *Server_Cmd_PASS(tClient *Client, char *Args) +{ + uint8_t clienthash[HASH_LENGTH] = {0}; + + // Read user's hash + HexBin(clienthash, Args, HASH_LENGTH); + + if( giDebugLevel ) { + int i; + printf("Client %i: Password hash ", Client->ID); + for(i=0;i + */ +char *Server_Cmd_AUTOAUTH(tClient *Client, char *Args) +{ + char *spos = strchr(Args, ' '); + if(spos) *spos = '\0'; // Remove characters after the ' ' + + // Check if trusted + if( !Client->bIsTrusted ) { + if(giDebugLevel) + printf("Client %i: Untrusted client attempting to AUTOAUTH\n", Client->ID); + return strdup("401 Untrusted\n"); + } + + // Get UID + Client->UID = GetUserID( Args ); + if( Client->UID < 0 ) { + if(giDebugLevel) + printf("Client %i: Unknown user '%s'\n", Client->ID, Args); + return strdup("401 Auth Failure\n"); + } + + if(giDebugLevel) + printf("Client %i: Authenticated as '%s' (%i)\n", Client->ID, Args, Client->UID); + + return strdup("200 Auth OK\n"); +} + +/** + * \brief Enumerate the items that the server knows about + */ +char *Server_Cmd_ENUMITEMS(tClient *Client, char *Args) +{ +// int nItems = giNumItems; + int retLen; + int i; + char *ret; + + retLen = snprintf(NULL, 0, "201 Items %i", giNumItems); + + for( i = 0; i < giNumItems; i ++ ) + { + retLen += snprintf(NULL, 0, " %s:%i", gaItems[i].Handler->Name, gaItems[i].ID); + } + + ret = malloc(retLen+1); + retLen = 0; + retLen += sprintf(ret+retLen, "201 Items %i", giNumItems); + + for( i = 0; i < giNumItems; i ++ ) { + retLen += sprintf(ret+retLen, " %s:%i", gaItems[i].Handler->Name, gaItems[i].ID); + } + + strcat(ret, "\n"); + + return ret; +} + +/** + * \brief Fetch information on a specific item + */ +char *Server_Cmd_ITEMINFO(tClient *Client, char *Args) +{ + int retLen = 0; + char *ret; + tItem *item; + tHandler *handler; + char *type = Args; + char *colon = strchr(Args, ':'); + int num, i; + + if( !colon ) { + return strdup("406 Bad Item ID\n"); + } + + num = atoi(colon+1); + *colon = '\0'; + + // Find handler + handler = NULL; + for( i = 0; i < giNumHandlers; i ++ ) + { + if( strcmp(gaHandlers[i]->Name, type) == 0) { + handler = gaHandlers[i]; + break; + } + } + if( !handler ) { + return strdup("406 Bad Item ID\n"); + } + + // Find item + for( i = 0; i < giNumItems; i ++ ) + { + if( gaItems[i].Handler != handler ) continue; + if( gaItems[i].ID != num ) continue; + item = &gaItems[i]; + break; + } + if( !item ) { + return strdup("406 Bad Item ID\n"); + } + + // Create return + retLen = snprintf(NULL, 0, "202 Item %s:%i %i %s\n", + handler->Name, item->ID, item->Price, item->Name); + ret = malloc(retLen+1); + sprintf(ret, "202 Item %s:%i %i %s\n", + handler->Name, item->ID, item->Price, item->Name); + + return ret; +} + +// --- INTERNAL HELPERS --- +// TODO: Move to another file +void HexBin(uint8_t *Dest, char *Src, int BufSize) +{ + int i; + for( i = 0; i < BufSize; i ++ ) + { + uint8_t val = 0; + + if('0' <= *Src && *Src <= '9') + val |= (*Src-'0') << 4; + else if('A' <= *Src && *Src <= 'F') + val |= (*Src-'A'+10) << 4; + else if('a' <= *Src && *Src <= 'f') + val |= (*Src-'a'+10) << 4; + else + break; + Src ++; + + if('0' <= *Src && *Src <= '9') + val |= (*Src-'0'); + else if('A' <= *Src && *Src <= 'F') + val |= (*Src-'A'+10); + else if('a' <= *Src && *Src <= 'f') + val |= (*Src-'a'+10); + else + break; + Src ++; + + Dest[i] = val; + } + for( ; i < BufSize; i++ ) + Dest[i] = 0; +} + +/** + * \brief Decode a Base64 value + */ +int UnBase64(uint8_t *Dest, char *Src, int BufSize) +{ + uint32_t val; + int i, j; + char *start_src = Src; + + for( i = 0; i+2 < BufSize; i += 3 ) + { + val = 0; + for( j = 0; j < 4; j++, Src ++ ) { + if('A' <= *Src && *Src <= 'Z') + val |= (*Src - 'A') << ((3-j)*6); + else if('a' <= *Src && *Src <= 'z') + val |= (*Src - 'a' + 26) << ((3-j)*6); + else if('0' <= *Src && *Src <= '9') + val |= (*Src - '0' + 52) << ((3-j)*6); + else if(*Src == '+') + val |= 62 << ((3-j)*6); + else if(*Src == '/') + val |= 63 << ((3-j)*6); + else if(!*Src) + break; + else if(*Src != '=') + j --; // Ignore invalid characters + } + Dest[i ] = (val >> 16) & 0xFF; + Dest[i+1] = (val >> 8) & 0xFF; + Dest[i+2] = val & 0xFF; + if(j != 4) break; + } + + // Finish things off + if(i < BufSize) + Dest[i] = (val >> 16) & 0xFF; + if(i+1 < BufSize) + Dest[i+1] = (val >> 8) & 0xFF; + + return Src - start_src; +}