*.[od]
*.so
*~
+*.swp
dispsrv
dispense
+cokebank.log
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);
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 <something> ...
- // 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
- // <username><salt><hash>
- 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 <something> ...
+ // 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
+ // <username><salt><hash>
+ 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);
# 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
$(RM) $(BIN) $(OBJ)
$(BIN): $(OBJ)
- $(CC) -o $(BIN) $(LINKFLAGS) $(OBJ)
+ $(CC) -o $(BIN) $(OBJ) $(LINKFLAGS)
%.o: %.c
$(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS)
#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;
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
--- /dev/null
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include "common.h"
+#include <regex.h>
+#include <string.h>
+#include <ctype.h>
+
+#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;
+}
+
#include "common.h"
#include <stdio.h>
#include <string.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <regex.h>
#include <stdarg.h>
#include <pthread.h>
+#include <unistd.h>
+#include <modbus/modbus.h>
-#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 = {
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;
}
/**
*/
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;
-}
-
-
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 {
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++ )
{
{
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);
return 0;
}
-void *Periodic_Thread(void *Unused)
+void *Periodic_Thread(void *Unused __attribute__((unused)))
{
int i;
- Unused = NULL; // quiet, gcc
for( ;; )
{
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);
}
}
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];
// === 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);
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
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);
{
uint len = sizeof(client_addr);
int bTrusted = 0;
+ int bRootPort = 0;
// Accept a connection
client_socket = accept(giServer_Socket, (struct sockaddr *) &client_addr, &len);
// 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 ) )
break;
}
}
+ #endif
// TODO: Multithread this?
- Server_HandleClient(client_socket, bTrusted);
+ Server_HandleClient(client_socket, bTrusted, bRootPort);
close(client_socket);
}
* \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;
// 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
}
// Check if trusted
- if( !Client->bIsTrusted ) {
+ if( !Client->bCanAutoAuth ) {
if(giDebugLevel)
Debug(Client, "Untrusted client attempting to AUTOAUTH");
sendf(Client->Socket, "401 Untrusted\n");
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 ) {