From: John Hodge Date: Tue, 27 Dec 2011 09:34:32 +0000 (+0800) Subject: Adding new config system, template modbus coke, AUTHIDENT in client X-Git-Url: https://git.ucc.asn.au/?a=commitdiff_plain;h=da639e3f13ee62627f4e7b29eccfcac70aad681d;p=tpg%2Fopendispense2.git Adding new config system, template modbus coke, AUTHIDENT in client --- diff --git a/.gitignore b/.gitignore index 69b5969..6e59002 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ *.[od] *.so *~ +*.swp dispsrv dispense +cokebank.log diff --git a/src/client/main.c b/src/client/main.c index ce9a9af..595d3e6 100644 --- a/src/client/main.c +++ b/src/client/main.c @@ -1314,28 +1314,53 @@ int OpenConnection(const char *Host, int Port) return sock; } -/** - * \brief Authenticate with the server - * \return Boolean Failure - */ -int Authenticate(int Socket) +int Authenticate_AutoAuth(int Socket, const char *Username) { - struct passwd *pwd; char *buf; int responseCode; - #if ATTEMPT_PASSWORD_AUTH - char salt[32]; - int i; - regmatch_t matches[4]; - #endif + int ret = -1; - if( gbIsAuthenticated ) return 0; + // Attempt automatic authentication + sendf(Socket, "AUTOAUTH %s\n", Username); - // Get user name - pwd = getpwuid( getuid() ); + // Check if it worked + buf = ReadLine(Socket); + + responseCode = atoi(buf); + switch( responseCode ) + { + case 200: // Autoauth succeeded, return + ret = 0; + break; + + case 401: // Untrusted + fprintf(stderr, "Untrusted host, AUTOAUTH unavaliable\n"); + ret = RV_PERMISSIONS; + break; + case 404: // Bad Username + fprintf(stderr, "Bad Username '%s'\n", Username); + ret = RV_INVALID_USER; + break; + + default: + fprintf(stderr, "Unkown response code %i from server\n", responseCode); + printf("%s\n", buf); + ret = RV_UNKNOWN_ERROR; + break;; + } + + free(buf); + return ret; +} + +int Authenticate_AuthIdent(int Socket) +{ + char *buf; + int responseCode; + int ret = -1; // Attempt automatic authentication - sendf(Socket, "AUTOAUTH %s\n", pwd->pw_name); + sendf(Socket, "AUTHIDENT\n"); // Check if it worked buf = ReadLine(Socket); @@ -1344,98 +1369,131 @@ int Authenticate(int Socket) switch( responseCode ) { case 200: // Autoauth succeeded, return - free(buf); + ret = 0; break; - case 401: // Untrusted, attempt password authentication - free(buf); - - #if ATTEMPT_PASSWORD_AUTH - sendf(Socket, "USER %s\n", pwd->pw_name); - printf("Using username %s\n", pwd->pw_name); - - buf = ReadLine(Socket); - - // TODO: Get Salt - // Expected format: 100 SALT ... - // OR : 100 User Set - RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response"); - responseCode = atoi(buf); - if( responseCode != 100 ) { - fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf); - free(buf); - return RV_UNKNOWN_ERROR; // ERROR - } - - // Check for salt - if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) { - // Store it for later - memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so ); - salt[ matches[3].rm_eo - matches[3].rm_so ] = 0; - } - free(buf); - - // Give three attempts - for( i = 0; i < 3; i ++ ) - { - int ofs = strlen(pwd->pw_name)+strlen(salt); - char tmpBuf[42]; - char tmp[ofs+20]; - char *pass = getpass("Password: "); - uint8_t h[20]; - - // Create hash string - // - strcpy(tmp, pwd->pw_name); - strcat(tmp, salt); - SHA1( (unsigned char*)pass, strlen(pass), h ); - memcpy(tmp+ofs, h, 20); - - // Hash all that - SHA1( (unsigned char*)tmp, ofs+20, h ); - sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9], - h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19] - ); - - // Send password - sendf(Socket, "PASS %s\n", tmpBuf); - buf = ReadLine(Socket); - - responseCode = atoi(buf); - // Auth OK? - if( responseCode == 200 ) break; - // Bad username/password - if( responseCode == 401 ) continue; - - fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf); - free(buf); - return RV_UNKNOWN_ERROR; - } - free(buf); - if( i == 3 ) - return RV_INVALID_USER; // 2 = Bad Password - - #else + case 401: // Untrusted fprintf(stderr, "Untrusted host, AUTOAUTH unavaliable\n"); - return RV_INVALID_USER; - #endif + ret = RV_PERMISSIONS; break; - case 404: // Bad Username - fprintf(stderr, "Bad Username '%s'\n", pwd->pw_name); - free(buf); - return RV_INVALID_USER; - default: fprintf(stderr, "Unkown response code %i from server\n", responseCode); printf("%s\n", buf); + ret = RV_UNKNOWN_RESPONSE; + break; + } + + free(buf); + + return ret; +} + +int Authenticate_Password(int Socket, const char *Username) +{ + #if USE_PASSWORD_AUTH + char *buf; + int responseCode; + char salt[32]; + int i; + regmatch_t matches[4]; + + sendf(Socket, "USER %s\n", Username); + printf("Using username %s\n", Username); + + buf = ReadLine(Socket); + + // TODO: Get Salt + // Expected format: 100 SALT ... + // OR : 100 User Set + RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response"); + responseCode = atoi(buf); + if( responseCode != 100 ) { + fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf); free(buf); - return RV_UNKNOWN_ERROR; + return RV_UNKNOWN_ERROR; // ERROR + } + + // Check for salt + if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) { + // Store it for later + memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so ); + salt[ matches[3].rm_eo - matches[3].rm_so ] = 0; + } + free(buf); + + // Give three attempts + for( i = 0; i < 3; i ++ ) + { + int ofs = strlen(Username)+strlen(salt); + char tmpBuf[42]; + char tmp[ofs+20]; + char *pass = getpass("Password: "); + uint8_t h[20]; + + // Create hash string + // + strcpy(tmp, Username); + strcat(tmp, salt); + SHA1( (unsigned char*)pass, strlen(pass), h ); + memcpy(tmp+ofs, h, 20); + + // Hash all that + SHA1( (unsigned char*)tmp, ofs+20, h ); + sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9], + h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19] + ); + + // Send password + sendf(Socket, "PASS %s\n", tmpBuf); + buf = ReadLine(Socket); + + responseCode = atoi(buf); + // Auth OK? + if( responseCode == 200 ) break; + // Bad username/password + if( responseCode == 401 ) continue; + + fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf); + free(buf); + return -1; } + free(buf); + if( i == 3 ) + return RV_INVALID_USER; // 2 = Bad Password + + return 0; + #else + return RV_INVALID_USER; + #endif +} + +/** + * \brief Authenticate with the server + * \return Boolean Failure + */ +int Authenticate(int Socket) +{ + struct passwd *pwd; + + if( gbIsAuthenticated ) return 0; + // Get user name + pwd = getpwuid( getuid() ); + + // Attempt AUTOAUTH + if( Authenticate_AutoAuth(Socket, pwd->pw_name) == 0 ) + ; + else if( Authenticate_AuthIdent(Socket) == 0 ) + ; + else if( Authenticate_Password(Socket, pwd->pw_name) == 0 ) + return RV_INVALID_USER; + // Set effective user if( gsEffectiveUser ) { + char *buf; + int responseCode; sendf(Socket, "SETEUSER %s\n", gsEffectiveUser); buf = ReadLine(Socket); diff --git a/src/server/Makefile b/src/server/Makefile index 6acb88c..300145d 100644 --- a/src/server/Makefile +++ b/src/server/Makefile @@ -1,16 +1,18 @@ # OpenDispense 2 # -OBJ := main.o server.o logging.o +INSTALLDIR := /usr/local/opendispense2 + +OBJ := main.o server.o logging.o config.o OBJ += dispense.o itemdb.o OBJ += handler_coke.o handler_snack.o handler_door.o BIN := ../../dispsrv DEPFILES := $(OBJ:%.o=%.d) -LINKFLAGS := -g ../../cokebank.so -lutil -lident -Wl,-rpath,. -Wl,-rpath,/usr/local/sbin +LINKFLAGS := -g ../../cokebank.so -lutil -lident -lmodbus -Wl,-rpath,. -Wl,-rpath,$(INSTALLDIR) CPPFLAGS := -CFLAGS := -Wall -Wextra -Werror -g +CFLAGS := -Wall -Wextra -Werror -g -std=gnu99 .PHONY: all clean @@ -20,7 +22,7 @@ clean: $(RM) $(BIN) $(OBJ) $(BIN): $(OBJ) - $(CC) -o $(BIN) $(LINKFLAGS) $(OBJ) + $(CC) -o $(BIN) $(OBJ) $(LINKFLAGS) %.o: %.c $(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS) diff --git a/src/server/common.h b/src/server/common.h index 19337e5..004fbf8 100644 --- a/src/server/common.h +++ b/src/server/common.h @@ -22,6 +22,8 @@ #define UNUSED(var) unused__##var __attribute__((__unused__)) +#define ASSERT(cnd) do{if(!(cnd)){fprintf(stderr, "ASSERT failed at "__FILE__":"EXPSTR(__LINE__)" - "EXPSTR(cnd)"\n");exit(-1);}}while(0) + // === STRUCTURES === typedef struct sItem tItem; typedef struct sUser tUser; @@ -96,4 +98,12 @@ extern int DispenseUpdateItem(int User, tItem *Item, const char *NewName, int Ne extern void Log_Error(const char *Format, ...); extern void Log_Info(const char *Format, ...); +// --- Config Database --- +extern void Config_ParseFile(const char *Filename); +extern void Config_AddValue(const char *Key, const char *Value); +extern int Config_GetValueCount(const char *KeyName); +extern const char *Config_GetValue(const char *KeyName, int Index); +extern int Config_GetValue_Bool(const char *KeyName, int Index); +extern int Config_GetValue_Int(const char *KeyName, int Index); + #endif diff --git a/src/server/config.c b/src/server/config.c new file mode 100644 index 0000000..4630b58 --- /dev/null +++ b/src/server/config.c @@ -0,0 +1,242 @@ +/* + * OpenDispense 2 + * UCC (University [of WA] Computer Club) Electronic Accounting System + * + * config.c - Configuration file parser + * + * 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 + +#define MAX_LINE_LEN 128 + +// === TYPES === +typedef struct sConfigValue tConfigValue; +typedef struct sConfigKey tConfigKey; + +// === STRUCTURES === +struct sConfigValue +{ + tConfigValue *Next; + char Data[]; +}; + +struct sConfigKey +{ + tConfigKey *NextKey; + tConfigValue *FirstValue; + tConfigValue *LastValue; + int ValueCount; + char KeyName[]; +}; + +// === PROTOTYPES === +void Config_ParseFile(const char *Filename); +void Config_AddValue(const char *Key, const char *Value); +void Config_int_AddValueToKey(tConfigKey *Key, const char *Value); +tConfigKey *Config_int_GetKey(const char *KeyName, int bCreate); + int Config_GetValueCount(const char *KeyName); +const char *Config_GetValue(const char *KeyName, int Index); + +// === GLOBALS === +tConfigKey *gConfig; + +// === CODE === +void Config_ParseFile(const char *Filename) +{ + FILE *fp; + char line[MAX_LINE_LEN]; + regex_t regexp_option; + regex_t regexp_empty; + + CompileRegex(®exp_option, "^\\s*([^ \t]+)\\s+(.+)$", REG_EXTENDED); // + CompileRegex(®exp_empty, "^\\s*$", REG_EXTENDED); // + + fp = fopen(Filename, "r"); + if(!fp) { + fprintf(stderr, "Unable to open config file '%s'\n", Filename); + perror("Config_ParseFile"); + exit(-1); + } + + while( fgets(line, sizeof(line), fp) ) + { + regmatch_t matches[3]; + + // Trim and clean up + { + int i; + for( i = 0; line[i]; i ++ ) + { + if( line[i] == '#' || line[i] == ';' ) { + line[i] = '\0'; + break; + } + } + + while( i --, isspace(line[i]) ) + line[i] = 0; + } + + + if( regexec(®exp_empty, line, 1, matches, 0) == 0 ) + continue ; + + if( RunRegex(®exp_option, line, 3, matches, "Parsing configuration file") ) + { + fprintf(stderr, "Syntax error\n- %s", line); + continue ; + } + + line[matches[1].rm_eo] = 0; + line[matches[2].rm_eo] = 0; + + Config_AddValue(line + matches[1].rm_so, line + matches[2].rm_so); + } + + fclose(fp); + regfree(®exp_option); + regfree(®exp_empty); +} + +void Config_AddValue(const char *Key, const char *Value) +{ + tConfigKey *key; + + // Find key (creating if needed) + key = Config_int_GetKey(Key, 1); + + Config_int_AddValueToKey(key, Value); +} + +void Config_int_AddValueToKey(tConfigKey *Key, const char *Value) +{ + tConfigValue *newVal; + // Create value + newVal = malloc(sizeof(tConfigValue) + strlen(Value) + 1); + newVal->Next = NULL; + strcpy(newVal->Data, Value); + + #if 1 + // Add to the end of the key's list + if(Key->LastValue) + Key->LastValue->Next = newVal; + else + Key->FirstValue = newVal; + Key->LastValue = newVal; + #else + // Add to the start of the key's list + if(Key->LastValue == NULL) + Key->LastValue = newVal; + newVal->Next = Key->FirstValue; + Key->FirstValue = newVal; + #endif + Key->ValueCount ++; +} + +/** + * \brief + */ +tConfigKey *Config_int_GetKey(const char *KeyName, int bCreate) +{ + tConfigKey *key, *prev = NULL; + + // Search the sorted list of keys + for( key = gConfig; key; prev = key, key = key->NextKey ) + { + int cmp = strcmp(key->KeyName, KeyName); + if(cmp == 0) return key; // Equal, return + if(cmp > 0) break; // Greater, fast exit + } + + if( bCreate ) + { + // Create new key + key = malloc(sizeof(tConfigKey) + strlen(KeyName) + 1); + key->FirstValue = NULL; + key->LastValue = NULL; + key->ValueCount = 0; + strcpy(key->KeyName, KeyName); + + // Append + if(prev) { + key->NextKey = prev->NextKey; + prev->NextKey = key; + } + else { + key->NextKey = gConfig; + gConfig = key; + } + } + else + { + key = NULL; + } + + return key; +} + +int Config_GetValueCount(const char *KeyName) +{ + tConfigKey *key = Config_int_GetKey(KeyName, 0); + if(!key) return 0; + + return key->ValueCount; +} + +const char *Config_GetValue(const char *KeyName, int Index) +{ + tConfigKey *key; + tConfigValue *val; + + key = Config_int_GetKey(KeyName, 0); + if(!key) { + fprintf(stderr, "Unknown key '%s'\n", KeyName); + exit(1); + return NULL; + } + + if(Index < 0 || Index >= key->ValueCount) return NULL; + + for( val = key->FirstValue; Index && val; val = val->Next, Index -- ); + + ASSERT(val != NULL); + + return val->Data; +} + +int Config_GetValue_Bool(const char *KeyName, int Index) +{ + const char *val = Config_GetValue(KeyName, Index); + if(!val) return -1; + + if( atoi(val) == 1 ) return 1; + if( val[0] == '0' && val[1] == '\0' ) return 0; + + if( strcasecmp(val, "true") == 0 ) return 1; + if( strcasecmp(val, "false") == 0 ) return 0; + + if( strcasecmp(val, "yes") == 0 ) return 1; + if( strcasecmp(val, "no") == 0 ) return 0; + + return -1; +} + +int Config_GetValue_Int(const char *KeyName, int Index) +{ + int tmp; + const char *val = Config_GetValue(KeyName, Index); + if(!val) return -1; + + if( (tmp = atoi(val)) ) return tmp; + if( val[0] == '0' && val[1] == '\0' ) return 0; + + return -1; +} + diff --git a/src/server/handler_coke.c b/src/server/handler_coke.c index fffa7b6..21d61f6 100644 --- a/src/server/handler_coke.c +++ b/src/server/handler_coke.c @@ -13,33 +13,24 @@ #include "common.h" #include #include -#include -#include -#include -#include #include #include +#include +#include -#define READ_TIMEOUT 2 // 2 seconds for ReadChar -#define TRACE_COKE 0 +#define MIN_DISPENSE_PERIOD 5 -#if TRACE_COKE -# define TRACE(v...) do{printf("%s: ",__func__);printf(v);}while(0) -#else -# define TRACE(...) -#endif +// === CONSTANTS === +const int ciCoke_MinPeriod = 5; +const int ciCoke_DropBitBase = 0; +const int ciCoke_StatusBitBase = 0; // === IMPORTS === // === PROTOTYPES === int Coke_InitHandler(); - int Coke_int_GetSlotStatus(char *Buffer, int Slot); -void Coke_int_UpdateSlotStatuses(void); int Coke_CanDispense(int User, int Item); int Coke_DoDispense(int User, int Item); - int Writef(const char *Format, ...); - int WaitForColon(); - int ReadLine(int len, char *output); // === GLOBALS === tHandler gCoke_Handler = { @@ -48,152 +39,58 @@ tHandler gCoke_Handler = { Coke_CanDispense, Coke_DoDispense }; -char *gsCoke_SerialPort = "/dev/ttyS0"; - int giCoke_SerialFD; -regex_t gCoke_StatusRegex; - int gaCoke_CachedStatus[7]; -pthread_mutex_t gCoke_Mutex = PTHREAD_MUTEX_INITIALIZER; +const char *gsCoke_ModbusAddress = "130.95.13.73"; +modbus_t *gCoke_Modbus; time_t gtCoke_LastDispenseTime; + int gbCoke_DummyMode = 1; // == CODE === int Coke_InitHandler() { - CompileRegex(&gCoke_StatusRegex, "^slot\\s+([0-9]+)\\s+([^:]+):([a-zA-Z]+)\\s*", REG_EXTENDED); - - printf("Connecting to coke machine on '%s'\n", gsCoke_SerialPort); - - giCoke_SerialFD = InitSerial(gsCoke_SerialPort, 9600); - if( giCoke_SerialFD == -1 ) { - fprintf(stderr, "ERROR: Unable to open coke serial port ('%s')\n", gsCoke_SerialPort); - } - else { - int i; - for( i = 0; i < 7; i ++ ) - gaCoke_CachedStatus[i] = -1; - // Reset the slot names. - // - Dunno why this is needed, but the machine plays silly - // sometimes. - Writef("\r\n\r\n"); - WaitForColon(); - Writef("n0 Slot0\r\n"); - if( !WaitForColon() ) - { - Writef("n1 Slot1\r\n"); - WaitForColon(); - Writef("n2 Slot2\r\n"); - WaitForColon(); - Writef("n3 Slot3\r\n"); - WaitForColon(); - Writef("n4 Slot4\r\n"); - WaitForColon(); - Writef("n5 Slot5\r\n"); - WaitForColon(); - Writef("n6 Coke\r\n"); - WaitForColon(); - - Coke_int_UpdateSlotStatuses(); - } - else - fprintf(stderr, "Coke machine timed out.\n"); - } - - AddPeriodicFunction(Coke_int_UpdateSlotStatuses); - - return 0; -} + printf("Connecting to coke machine on '%s'\n", gsCoke_ModbusAddress); -int Coke_int_GetSlotStatus(char *Buffer, int Slot) -{ - regmatch_t matches[4]; - int ret; - char *status; - - // Parse status response - ret = RunRegex(&gCoke_StatusRegex, Buffer, sizeof(matches)/sizeof(matches[0]), matches, "Bad Response"); - if( ret ) { - return -1; + // Configuable dummy/blank mode (all dispenses succeed) + // TODO: Find a better way of handling missing/invalid options + if( Config_GetValueCount("coke_dummy_mode") > 0 ) + { + gbCoke_DummyMode = Config_GetValue_Bool("coke_dummy_mode", 0); + if(gbCoke_DummyMode == -1) gbCoke_DummyMode = 0; } - // Get slot status - Buffer[ matches[3].rm_eo ] = '\0'; - status = &Buffer[ matches[3].rm_so ]; - - TRACE("Machine responded slot %i status '%s'\n", Slot, status); - - if( strcmp(status, "full") == 0 ) { - gaCoke_CachedStatus[Slot] = 0; // 0: Avaliiable - return 0; - } - else { - gaCoke_CachedStatus[Slot] = 1; // 1: Empty - return 1; + // Open modbus + modbus_new_tcp(gsCoke_ModbusAddress, 502); + if( !gCoke_Modbus ) + { + perror("coke - modbus_new_tcp"); } -} - -/** - * \brief Update the status of all coke slots - * \note Uses goto to reduce the chance of the lock being kept - */ -void Coke_int_UpdateSlotStatuses(void) -{ - int i, len; - char tmp[40]; - - if( giCoke_SerialFD == -1 ) return ; - - pthread_mutex_lock(&gCoke_Mutex); - - while( ReadLine(sizeof tmp, tmp) >= 0 ) ; - TRACE("send d7\n"); - Writef("d7\r\n"); // Update slot statuses - if( WaitForColon() ) goto ret; - TRACE("send s\n"); - Writef("s\r\n"); - do { - i = ReadLine(sizeof tmp, tmp); // Read back what we just said - if( i == -1 ) { - TRACE("Eat read failed"); - goto ret; - } - } while(tmp[0] != ':' && tmp[1] != 's'); - - for( i = 0; i <= 6; i ++ ) + else { - // Read until non-blank line - while( (len = ReadLine(sizeof tmp, tmp)) == 0 ) ; - if( len == -1 ) { - TRACE("Read failed on slot %i\n", i); - goto ret; // I give up :( + if( modbus_connect(gCoke_Modbus) ) + { + perror("coke - modbus_connect"); } - TRACE("tmp = '%s'\n", tmp); - Coke_int_GetSlotStatus(tmp, i); - } - // Eat blank line - len = ReadLine(sizeof tmp, tmp); - if( len == -1 ) { - TRACE("Read failed on blank line\n"); } - TRACE("Updated\n"); - -ret: - pthread_mutex_unlock(&gCoke_Mutex); + return 0; } int Coke_CanDispense(int UNUSED(User), int Item) { + uint8_t status; // Sanity please - if( Item < 0 || Item > 6 ) return -1; // -EYOURBAD + if( Item < 0 || Item > 6 ) return -1; + + // Check for 'dummy' mode + if( gbCoke_DummyMode ) + return 0; // Can't dispense if the machine is not connected - if( giCoke_SerialFD == -1 ) + if( !gCoke_Modbus ) return -2; - // HACK! - // 2011-10-21: The sensors in slot 1 and 3 a bad, just ignore the sensor result - if( Item == 1 || Item == 3 ) return 0; + modbus_read_bits(gCoke_Modbus, ciCoke_StatusBitBase + Item, 1, &status); - return gaCoke_CachedStatus[Item]; + return status == 0; } /** @@ -201,195 +98,29 @@ int Coke_CanDispense(int UNUSED(User), int Item) */ int Coke_DoDispense(int UNUSED(User), int Item) { - char tmp[32]; - int ret, len; - // Sanity please if( Item < 0 || Item > 6 ) return -1; + // Check for 'dummy' mode + if( gbCoke_DummyMode ) + return 0; + // Can't dispense if the machine is not connected - if( giCoke_SerialFD == -1 ) + if( !gCoke_Modbus ) return -2; // Make sure there are not two dispenses within n seconds - if( time(NULL) - gtCoke_LastDispenseTime < 10 ) + if( time(NULL) - gtCoke_LastDispenseTime < ciCoke_MinPeriod ) { - printf("Wait %li seconds?\n", 10 - (time(NULL) - gtCoke_LastDispenseTime)); - sleep( 10 - (time(NULL) - gtCoke_LastDispenseTime) ); + int delay = ciCoke_MinPeriod - (time(NULL) - gtCoke_LastDispenseTime); + printf("Wait %i seconds?\n", delay); + sleep( delay ); printf("wait done\n"); } - // LOCK - pthread_mutex_lock(&gCoke_Mutex); - - TRACE("flushing input\n"); - - - // Wait for prompt - ret = 0; - while( WaitForColon() && ret < 3 ) - { - // Flush the input buffer - char tmpbuf[512]; - read(giCoke_SerialFD, tmpbuf, sizeof(tmpbuf)); - TRACE("sending 'd7'\n"); - Writef("d7\r\n"); - ret ++; - } - if( ret == 3 ) - { - TRACE("timed out\n"); - pthread_mutex_unlock(&gCoke_Mutex); - return -1; - } - - TRACE("sending 'd%i'\n", Item); - // Dispense - Writef("d%i\r\n", Item); - - // Read empty lines and echo-backs - do { - ret = ReadLine(sizeof(tmp)-1, tmp); - if( ret == -1 ) { - pthread_mutex_unlock(&gCoke_Mutex); - return -1; - } - TRACE("read %i '%s'\n", ret, tmp); - } while( ret == 0 || tmp[0] == ':' || tmp[0] == 'd' ); - - WaitForColon(); // Eat up rest of response - - TRACE("done\n"); - - // TODO: Regex instead? - if( strcmp(tmp, "ok") == 0 ) { - // We think dispense worked - // - The machine returns 'ok' if an empty slot is dispensed, even if - // it doesn't actually try to dispense (no sound) - ret = 0; - } - else { - printf("Coke_DoDispense: Machine returned unknown value '%s'\n", tmp); - ret = -1; - } - - TRACE("Updating slot status\n"); - - // Update status - WaitForColon(); - Writef("s%i\r\n", Item); - len = ReadLine(sizeof tmp, tmp); - if(len == -1) gaCoke_CachedStatus[Item] = -1; - Coke_int_GetSlotStatus(tmp, Item); - { - char buf[512]; - read(giCoke_SerialFD, buf, 512); // Flush - } - - gtCoke_LastDispenseTime = time(NULL); - - // Release and return - pthread_mutex_unlock(&gCoke_Mutex); - - //return ret; - // HACK!!! - return 0; -} - -char ReadChar() -{ - fd_set readfs; - char ch = 0; - int ret; - struct timeval timeout; - - timeout.tv_sec = READ_TIMEOUT; - timeout.tv_usec = 0; - - FD_ZERO(&readfs); - FD_SET(giCoke_SerialFD, &readfs); - - ret = select(giCoke_SerialFD+1, &readfs, NULL, NULL, &timeout); - if( ret == 0 ) { - fprintf(stderr, "ReadChar: Timeout of %is expired\n", READ_TIMEOUT); - return 0; // Timeout - } - if( ret != 1 ) { - printf("ReadChar: select return %i\n", ret); - return 0; - } - - ret = read(giCoke_SerialFD, &ch, 1); - if( ret != 1 ) { - printf("ReadChar: ret != 1 (%i)\n", ret); - return 0; - } - - return ch; -} - -int Writef(const char *Format, ...) -{ - va_list args; - int len; - - va_start(args, Format); - len = vsnprintf(NULL, 0, Format, args); - va_end(args); - - { - char buf[len+1]; - va_start(args, Format); - vsnprintf(buf, len+1, Format, args); - va_end(args); - - #if DEBUG - printf("Writef: %s", buf); - #endif - - return write(giCoke_SerialFD, buf, len); - } - -} - -int WaitForColon() -{ - fd_set readfs; - char ch = 0; - - FD_SET(giCoke_SerialFD, &readfs); - - while( (ch = ReadChar()) != ':' && ch != 0); - - if( ch == 0 ) return -1; // Timeout + // Dispense (with locking) + modbus_write_bit(gCoke_Modbus, ciCoke_DropBitBase + Item, 1); return 0; } -int ReadLine(int len, char *output) -{ - char ch; - int i = 0; - - for(;;) - { - ch = ReadChar(); - - if( i < len ) - output[i++] = ch; - - if( ch == '\0' ) { - break; - } - if( ch == '\n' || ch == '\r' ) { - if( i < len ) - output[--i] = '\0'; - break; - } - } - - if( !ch ) return -1; - return i; -} - - diff --git a/src/server/main.c b/src/server/main.c index 590e197..c2ef55c 100644 --- a/src/server/main.c +++ b/src/server/main.c @@ -27,17 +27,16 @@ extern void Load_Itemlist(void); extern void Server_Start(void); extern int gbServer_RunInBackground; extern int giServer_Port; -extern char *gsItemListFile; -extern char *gsCoke_SerialPort; -extern char *gsSnack_SerialPort; -extern char *gsDoor_SerialPort; +extern const char *gsItemListFile; +extern const char *gsCoke_ModbusAddress; +extern const char *gsDoor_SerialPort; // === PROTOTYPES === void *Periodic_Thread(void *Unused); // === GLOBALS === int giDebugLevel = 0; -char *gsCokebankPath = "cokebank.db"; +const char *gsCokebankPath = "cokebank.db"; // - Functions called every 20s (or so) #define ciMaxPeriodics 10 struct sPeriodicCall { @@ -54,24 +53,16 @@ void sigint_handler() void PrintUsage(const char *progname) { fprintf(stderr, "Usage: %s\n", progname); - fprintf(stderr, " -p Set server port (default 11020)\n"); fprintf(stderr, " -d Set debug level (0 - 2, default 0)\n"); - fprintf(stderr, " --itemsfile\n"); - fprintf(stderr, " Set debug level (0 - 2, default 0)\n"); - fprintf(stderr, " --cokeport\n"); - fprintf(stderr, " Coke machine serial port (Default \"/dev/ttyS0\")\n"); - fprintf(stderr, " --doorport\n"); - fprintf(stderr, " Door modem/relay serial port (Default \"/dev/ttyS3\")\n"); - fprintf(stderr, " --cokebank\n"); - fprintf(stderr, " Coke bank database file (Default \"cokebank.db\")\n"); fprintf(stderr, " --[dont-]daemonise\n"); - fprintf(stderr, " Run (or explicitly don't) the server disconnected from the terminal\n"); + fprintf(stderr, " Run (or explicitly don't run) the server disconnected from the terminal\n"); } int main(int argc, char *argv[]) { int i; - + const char *config_file = "dispsrv.conf"; + // Parse Arguments for( i = 1; i < argc; i++ ) { @@ -80,62 +71,56 @@ int main(int argc, char *argv[]) { switch(arg[1]) { - case 'p': + case 'f': if( i + 1 >= argc ) return -1; - giServer_Port = atoi(argv[++i]); + config_file = argv[++i]; break; case 'd': if( i + 1 >= argc ) return -1; - giDebugLevel = atoi(argv[++i]); + Config_AddValue("debug_level", argv[++i]); + giDebugLevel = atoi(argv[i]); break; - case 'D': - gbServer_RunInBackground = 1; - return -1; default: - // Usage Error? + // Usage Error PrintUsage(argv[0]); return -1; } } - else if( arg[0] == '-' && arg[1] == '-' ) { - if( strcmp(arg, "--itemsfile") == 0 ) { - if( i + 1 >= argc ) return -1; - gsItemListFile = argv[++i]; - } - else if( strcmp(arg, "--cokeport") == 0 ) { - if( i + 1 >= argc ) return -1; - gsCoke_SerialPort = argv[++i]; - } - else if( strcmp(arg, "--snackport") == 0 ) { - if( i + 1 >= argc ) return -1; - gsSnack_SerialPort = argv[++i]; - } - else if( strcmp(arg, "--doorport") == 0 ) { - if( i + 1 >= argc ) return -1; - gsDoor_SerialPort = argv[++i]; - } - else if( strcmp(arg, "--cokebank") == 0 ) { + else if( arg[0] == '-' && arg[1] == '-' ) + { + if( strcmp(arg, "--configfile") == 0 ) { if( i + 1 >= argc ) return -1; - gsCokebankPath = argv[++i]; + config_file = argv[++i]; } else if( strcmp(arg, "--daemonise") == 0 ) { - gbServer_RunInBackground = 1; + Config_AddValue("daemonise", "true"); } else if( strcmp(arg, "--dont-daemonise") == 0 ) { - gbServer_RunInBackground = 0; + Config_AddValue("daemonise", "false"); } else { - // Usage error? + // Usage error PrintUsage(argv[0]); return -1; } } - else { - // Usage Error? + else + { + // Usage Error PrintUsage(argv[0]); return -1; } } + + Config_ParseFile( config_file ); + + // Parse config values + gbServer_RunInBackground = Config_GetValue_Bool("daemonise", 0); + gsCokebankPath = Config_GetValue("cokebank_database", 0); + gsDoor_SerialPort = Config_GetValue("door_serial_port", 0); + gsCoke_ModbusAddress = Config_GetValue("coke_modbus_address", 0); + giServer_Port = Config_GetValue_Int("server_port", 0); + gsItemListFile = Config_GetValue("items_file", 0); signal(SIGINT, sigint_handler); signal(SIGTERM, sigint_handler); @@ -157,10 +142,9 @@ int main(int argc, char *argv[]) return 0; } -void *Periodic_Thread(void *Unused) +void *Periodic_Thread(void *Unused __attribute__((unused))) { int i; - Unused = NULL; // quiet, gcc for( ;; ) { @@ -220,7 +204,7 @@ void CompileRegex(regex_t *regex, const char *pattern, int flags) size_t len = regerror(ret, regex, NULL, 0); char errorStr[len]; regerror(ret, regex, errorStr, len); - fprintf(stderr, "Regex compilation failed - %s\n", errorStr); + fprintf(stderr, "Regex compilation failed - %s\n%s\n", errorStr, pattern); exit(-1); } } diff --git a/src/server/server.c b/src/server/server.c index b7ca068..05ee547 100644 --- a/src/server/server.c +++ b/src/server/server.c @@ -44,7 +44,8 @@ typedef struct sClient int Socket; // Client socket ID int ID; // Client ID - int bIsTrusted; // Is the connection from a trusted host/port + int bTrustedHost; + int bCanAutoAuth; // Is the connection from a trusted host/port char *Username; char Salt[9]; @@ -57,7 +58,7 @@ typedef struct sClient // === PROTOTYPES === void Server_Start(void); void Server_Cleanup(void); -void Server_HandleClient(int Socket, int bTrusted); +void Server_HandleClient(int Socket, int bTrustedHost, int bRootPort); void Server_ParseClientCommand(tClient *Client, char *CommandString); // --- Commands --- void Server_Cmd_USER(tClient *Client, char *Args); @@ -118,6 +119,8 @@ const struct sClientCommand { int gbServer_RunInBackground = 0; char *gsServer_LogFile = "/var/log/dispsrv.log"; char *gsServer_ErrorLog = "/var/log/dispsrv.err"; + int giServer_NumTrustedHosts; +struct in_addr *gaServer_TrustedHosts; // - State variables int giServer_Socket; // Server socket int giServer_NextClientID = 1; // Debug client ID @@ -132,6 +135,19 @@ void Server_Start(void) int client_socket; struct sockaddr_in server_addr, client_addr; + // Parse trusted hosts list + giServer_NumTrustedHosts = Config_GetValueCount("trusted_host"); + gaServer_TrustedHosts = malloc(giServer_NumTrustedHosts * sizeof(*gaServer_TrustedHosts)); + for( int i = 0; i < giServer_NumTrustedHosts; i ++ ) + { + const char *addr = Config_GetValue("trusted_host", i); + + if( inet_aton(addr, &gaServer_TrustedHosts[i]) == 0 ) { + fprintf(stderr, "Invalid IP address '%s'\n", addr); + continue ; + } + } + atexit(Server_Cleanup); // Ignore SIGPIPE (stops crashes when the client exits early) signal(SIGPIPE, SIG_IGN); @@ -210,6 +226,7 @@ void Server_Start(void) { uint len = sizeof(client_addr); int bTrusted = 0; + int bRootPort = 0; // Accept a connection client_socket = accept(giServer_Socket, (struct sockaddr *) &client_addr, &len); @@ -241,9 +258,22 @@ void Server_Start(void) // Doesn't matter what, localhost is trusted if( ntohl( client_addr.sin_addr.s_addr ) == 0x7F000001 ) bTrusted = 1; - - // Trusted Connections + + // Check if the host is on the trusted list + for( int i = 0; i < giServer_NumTrustedHosts; i ++ ) + { + if( memcmp(&client_addr.sin_addr, &gaServer_TrustedHosts[i], sizeof(struct in_addr)) == 0 ) + { + bTrusted = 1; + break; + } + } + + // Root port (can AUTOAUTH if also a trusted machine if( ntohs(client_addr.sin_port) < 1024 ) + bRootPort = 1; + + #if 0 { // TODO: Make this runtime configurable switch( ntohl( client_addr.sin_addr.s_addr ) ) @@ -264,9 +294,10 @@ void Server_Start(void) break; } } + #endif // TODO: Multithread this? - Server_HandleClient(client_socket, bTrusted); + Server_HandleClient(client_socket, bTrusted, bRootPort); close(client_socket); } @@ -284,7 +315,7 @@ void Server_Cleanup(void) * \param Socket Client socket number/handle * \param bTrusted Is the client trusted? */ -void Server_HandleClient(int Socket, int bTrusted) +void Server_HandleClient(int Socket, int bTrusted, int bRootPort) { char inbuf[INPUT_BUFFER_SIZE]; char *buf = inbuf; @@ -297,7 +328,8 @@ void Server_HandleClient(int Socket, int bTrusted) // Initialise Client info clientInfo.Socket = Socket; clientInfo.ID = giServer_NextClientID ++; - clientInfo.bIsTrusted = bTrusted; + clientInfo.bTrustedHost = bTrusted; + clientInfo.bCanAutoAuth = bTrusted && bRootPort; clientInfo.EffectiveUID = -1; // Read from client @@ -492,7 +524,7 @@ void Server_Cmd_AUTOAUTH(tClient *Client, char *Args) } // Check if trusted - if( !Client->bIsTrusted ) { + if( !Client->bCanAutoAuth ) { if(giDebugLevel) Debug(Client, "Untrusted client attempting to AUTOAUTH"); sendf(Client->Socket, "401 Untrusted\n"); @@ -548,35 +580,20 @@ void Server_Cmd_AUTHIDENT(tClient *Client, char *Args) char *username; int userflags; const int ident_timeout = 5; - socklen_t len; - struct sockaddr_in client_addr; - uint32_t client_ip; if( Args != NULL && strlen(Args) ) { sendf(Client->Socket, "407 AUTHIDENT takes no arguments\n"); return ; } - // Check if trusted (only works with INET sockets at present) - len = sizeof(client_addr); - if( getpeername(Client->Socket, (struct sockaddr*)&client_addr, &len) == -1 ) { - Debug(Client, "500 getpeername() failed\n"); - perror("Getting AUTHIDENT peer name"); - sendf(Client->Socket, "500 getpeername() failed\n"); + // Check if trusted + if( !Client->bTrustedHost ) { + if(giDebugLevel) + Debug(Client, "Untrusted client attempting to AUTHIDENT"); + sendf(Client->Socket, "401 Untrusted\n"); return ; } - client_ip = client_addr.sin_addr.s_addr; - if(giDebugLevel >= 2) { - Debug(Client, "client_ip = %x, ntohl(client_ip) = %x", client_ip, ntohl(client_ip)); - } - if( ntohl(client_ip) != 0x7F000001 && (ntohl(client_ip) & IDENT_TRUSTED_NETMASK) != IDENT_TRUSTED_NETWORK ) { - if(giDebugLevel) - Debug(Client, "Untrusted client attempting to AUTHIDENT"); - sendf(Client->Socket, "401 Untrusted\n"); - return ; - } - // Get username via IDENT username = ident_id(Client->Socket, ident_timeout); if( !username ) {